Django_Basic_Manufacturing/purchase/models.py
2025-08-17 21:42:40 +07:00

192 lines
6.6 KiB
Python

from django.db import models
from django.core.validators import MinValueValidator
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
class Supplier(models.Model):
"""Supplier information"""
name = models.CharField(max_length=200)
code = models.CharField(max_length=50, unique=True, help_text='Unique supplier code')
contact_person = models.CharField(max_length=100, blank=True)
email = models.EmailField(blank=True)
phone = models.CharField(max_length=20, blank=True)
address = models.TextField(blank=True)
# Business information
tax_id = models.CharField(max_length=50, blank=True)
payment_terms = models.CharField(max_length=100, blank=True, help_text='e.g., Net 30, Net 60')
credit_limit = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
# Status
is_active = models.BooleanField(default=True)
rating = models.IntegerField(
choices=[(i, i) for i in range(1, 6)],
default=3,
help_text='Supplier rating from 1-5'
)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
verbose_name = _('Supplier')
verbose_name_plural = _('Suppliers')
def __str__(self):
return f"{self.name} ({self.code})"
def get_total_purchases(self):
"""Get total purchases from this supplier"""
return self.purchase_orders.aggregate(
total=models.Sum('total_amount')
)['total'] or Decimal('0')
class PurchaseOrder(models.Model):
"""Purchase order model"""
ORDER_STATUS = [
('draft', 'Draft'),
('sent', 'Sent to Supplier'),
('confirmed', 'Confirmed by Supplier'),
('received', 'Received'),
('cancelled', 'Cancelled'),
]
order_number = models.CharField(max_length=50, unique=True, help_text='Unique purchase order number')
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, related_name='purchase_orders')
date = models.DateField()
expected_delivery_date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=20, choices=ORDER_STATUS, default='draft')
# Financial information
subtotal = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
tax_amount = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
shipping_cost = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
total_amount = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
# Additional information
notes = models.TextField(blank=True)
terms_conditions = models.TextField(blank=True)
# User tracking
created_by = models.ForeignKey('users.CustomUser', on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-date', '-created_at']
verbose_name = _('Purchase Order')
verbose_name_plural = _('Purchase Orders')
def __str__(self):
return f"PO-{self.order_number} - {self.supplier.name}"
def save(self, *args, **kwargs):
"""Override save to calculate total amount"""
self.total_amount = self.subtotal + self.tax_amount + self.shipping_cost
super().save(*args, **kwargs)
def get_item_count(self):
"""Get total number of items in this purchase order"""
return self.items.count()
def is_overdue(self):
"""Check if delivery is overdue"""
if self.expected_delivery_date and self.status not in ['received', 'cancelled']:
from django.utils import timezone
return timezone.now().date() > self.expected_delivery_date
return False
class PurchaseOrderItem(models.Model):
"""Individual items in purchase orders"""
purchase_order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name='items')
product = models.ForeignKey('inventory.Product', on_delete=models.CASCADE, related_name='purchase_order_items')
quantity = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(Decimal('0.01'))]
)
unit_price = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(Decimal('0'))]
)
total_price = models.DecimalField(
max_digits=12,
decimal_places=2,
validators=[MinValueValidator(Decimal('0'))]
)
# Additional information
description = models.CharField(max_length=200, blank=True)
notes = models.TextField(blank=True)
class Meta:
verbose_name = _('Purchase Order Item')
verbose_name_plural = _('Purchase Order Items')
def __str__(self):
return f"{self.purchase_order.order_number} - {self.product.name} ({self.quantity})"
def save(self, *args, **kwargs):
"""Override save to calculate total price"""
self.total_price = self.quantity * self.unit_price
super().save(*args, **kwargs)
# Update purchase order subtotal
self.purchase_order.subtotal = self.purchase_order.items.aggregate(
total=models.Sum('total_price')
)['total'] or Decimal('0')
self.purchase_order.save()
class PurchaseReceipt(models.Model):
"""Receipt for received purchase orders"""
receipt_number = models.CharField(max_length=50, unique=True)
purchase_order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name='receipts')
receipt_date = models.DateField()
# Receipt details
notes = models.TextField(blank=True)
received_by = models.ForeignKey('users.CustomUser', on_delete=models.SET_NULL, null=True)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-receipt_date']
verbose_name = _('Purchase Receipt')
verbose_name_plural = _('Purchase Receipts')
def __str__(self):
return f"Receipt-{self.receipt_number} for PO-{self.purchase_order.order_number}"