Django_Basic_Manufacturing_3/apps/manufacturing/models.py
2025-08-22 17:05:22 +07:00

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