118 lines
4.4 KiB
Python
118 lines
4.4 KiB
Python
from django.db import models
|
|
|
|
|
|
class BillOfMaterial(models.Model):
|
|
product = models.ForeignKey('inventory.Product', on_delete=models.CASCADE, related_name='boms')
|
|
bom_code = models.CharField(max_length=50, unique=True, blank=True)
|
|
version = models.CharField(max_length=20, default='1.0')
|
|
is_active = models.BooleanField(default=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.bom_code:
|
|
self.bom_code = self.generate_bom_code()
|
|
super().save(*args, **kwargs)
|
|
|
|
def generate_bom_code(self):
|
|
"""Generate a unique BOM code"""
|
|
last_bom = BillOfMaterial.objects.order_by('-id').first()
|
|
|
|
if last_bom and last_bom.bom_code and last_bom.bom_code.startswith('BOM'):
|
|
try:
|
|
last_number = int(last_bom.bom_code[3:]) # Remove 'BOM' prefix
|
|
new_number = last_number + 1
|
|
except ValueError:
|
|
new_number = 1
|
|
else:
|
|
new_number = 1
|
|
|
|
return f'BOM{new_number:04d}'
|
|
|
|
def __str__(self):
|
|
return f"{self.bom_code} - {self.product.name}"
|
|
|
|
@property
|
|
def total_cost(self):
|
|
"""Calculate total cost of all BOM items"""
|
|
return sum(item.total_cost for item in self.items.all())
|
|
|
|
|
|
class BOMItem(models.Model):
|
|
bom = models.ForeignKey(BillOfMaterial, on_delete=models.CASCADE, related_name='items')
|
|
component = models.ForeignKey('inventory.Product', on_delete=models.CASCADE, related_name='bom_components')
|
|
quantity = models.DecimalField(max_digits=10, decimal_places=2)
|
|
unit_of_measure = models.ForeignKey('inventory.UnitOfMeasure', on_delete=models.SET_NULL, null=True)
|
|
|
|
class Meta:
|
|
unique_together = ('bom', 'component')
|
|
|
|
@property
|
|
def total_cost(self):
|
|
"""Calculate total cost for this BOM item"""
|
|
return self.quantity * self.component.cost
|
|
|
|
|
|
class ManufacturingOrder(models.Model):
|
|
STATUS_CHOICES = [
|
|
('scheduled', 'Scheduled'),
|
|
('in_progress', 'In Progress'),
|
|
('completed', 'Completed'),
|
|
('cancelled', 'Cancelled'),
|
|
]
|
|
|
|
mo_number = models.CharField(max_length=50, unique=True)
|
|
bom = models.ForeignKey(BillOfMaterial, on_delete=models.CASCADE)
|
|
quantity_to_produce = models.DecimalField(max_digits=10, decimal_places=2)
|
|
scheduled_start_date = models.DateField()
|
|
scheduled_end_date = models.DateField()
|
|
actual_start_date = models.DateField(null=True, blank=True)
|
|
actual_end_date = models.DateField(null=True, blank=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='in_progress')
|
|
created_by = models.ForeignKey('accounts.User', on_delete=models.SET_NULL, null=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return self.mo_number
|
|
|
|
@property
|
|
def total_cost(self):
|
|
"""Calculate total cost of all components"""
|
|
return sum(component.total_cost for component in self.mocomponents.all())
|
|
|
|
@property
|
|
def is_overdue(self):
|
|
"""Check if the manufacturing order is overdue"""
|
|
from django.utils import timezone
|
|
if self.status in ['scheduled', 'in_progress']:
|
|
return timezone.now().date() > self.scheduled_end_date
|
|
return False
|
|
|
|
def can_start(self):
|
|
"""Check if the manufacturing order can be started"""
|
|
return self.status == 'scheduled'
|
|
|
|
def can_complete(self):
|
|
"""Check if the manufacturing order can be completed"""
|
|
return self.status == 'in_progress'
|
|
|
|
def can_cancel(self):
|
|
"""Check if the manufacturing order can be cancelled"""
|
|
return self.status in ['scheduled', 'in_progress']
|
|
|
|
|
|
class MOComponent(models.Model):
|
|
manufacturing_order = models.ForeignKey(ManufacturingOrder, on_delete=models.CASCADE, related_name='mocomponents', null=True, blank=True)
|
|
component = models.ForeignKey('inventory.Product', on_delete=models.CASCADE)
|
|
required_quantity = models.DecimalField(max_digits=10, decimal_places=2)
|
|
consumed_quantity = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
|
|
|
class Meta:
|
|
unique_together = ('manufacturing_order', 'component')
|
|
|
|
@property
|
|
def total_cost(self):
|
|
"""Calculate total cost for this component"""
|
|
return self.required_quantity * self.component.cost
|