from django.db import models
from django.core.exceptions import ValidationError
from django.db import transaction
class ItemGroup(models.Model):
group_code = models.IntegerField(unique=True)
group_name = models.CharField(max_length=100)
def __str__(self):
return f"{self.group_code} - {self.group_name}"
def clean(self):
if self.group_code < 1:
raise ValidationError("গ্রুপ কোড ১ এর চেয়ে কম হতে পারে না।")
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
class UnitOfMeasure(models.Model):
uom_code = models.CharField(max_length=20, unique=True)
uom_name = models.CharField(max_length=50)
def __str__(self):
return f"{self.uom_code} - {self.uom_name}"
class Warehouse(models.Model):
whscode = models.CharField(max_length=8, unique=True)
whsname = models.CharField(max_length=100)
def __str__(self):
return f"{self.whscode} - {self.whsname}"
def get_total_stock_value(self):
total_value = sum(iw.get_stock_value() for iw in self.itemwarehouse_set.all())
return total_value
class TaxCode(models.Model):
code = models.CharField(max_length=8, unique=True)
name = models.CharField(max_length=50)
rate = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return f"{self.code} - {self.name}"
def calculate_tax(self, amount):
return amount * (self.rate / 100)
class Item(models.Model):
itemcode = models.CharField(max_length=50, unique=True)
itemname = models.CharField(max_length=100)
itmsgrpcod = models.ForeignKey(ItemGroup, on_delete=models.PROTECT)
invntitem = models.CharField(max_length=1, choices=[('Y', 'Yes'), ('N', 'No')])
sellitem = models.CharField(max_length=1, choices=[('Y', 'Yes'), ('N', 'No')])
prchseitem = models.CharField(max_length=1, choices=[('Y', 'Yes'), ('N', 'No')])
onhand = models.DecimalField(max_digits=18, decimal_places=6, default=0)
iscommited = models.DecimalField(max_digits=18, decimal_places=6, default=0)
onorder = models.DecimalField(max_digits=18, decimal_places=6, default=0)
price = models.DecimalField(max_digits=18, decimal_places=6, default=0)
def __str__(self):
return f"{self.itemcode} - {self.itemname}"
def clean(self):
if self.onhand < 0:
raise ValidationError("স্টক পরিমাণ ঋণাত্মক হতে পারে না।")
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
def get_available_quantity(self):
return self.onhand - self.iscommited
def get_total_value(self):
return self.onhand * self.price
class ItemWarehouse(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='warehouse_stock')
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
onhand = models.DecimalField(max_digits=18, decimal_places=6, default=0)
class Meta:
unique_together = ('item', 'warehouse')
def __str__(self):
return f"{self.item.itemcode} - {self.warehouse.whscode}"
def clean(self):
if self.onhand < 0:
raise ValidationError("গুদামে স্টক পরিমাণ ঋণাত্মক হতে পারে না।")
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
def get_stock_value(self):
return self.onhand * self.item.price
class GoodsReceipt(models.Model):
docnum = models.CharField(max_length=20, unique=True)
docdate = models.DateField()
postdate = models.DateField()
comments = models.TextField(blank=True)
def __str__(self):
return f"GR-{self.docnum}"
def get_total_amount(self):
return sum(line.linetotal for line in self.lines.all())
class GoodsReceiptLine(models.Model):
goods_receipt = models.ForeignKey(GoodsReceipt, on_delete=models.CASCADE, related_name='lines')
item = models.ForeignKey(Item, on_delete=models.PROTECT)
warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
quantity = models.DecimalField(max_digits=18, decimal_places=6)
unitprice = models.DecimalField(max_digits=18, decimal_places=6)
linetotal = models.DecimalField(max_digits=18, decimal_places=6)
def __str__(self):
return f"GR-{self.goods_receipt.docnum} - {self.item.itemcode}"
def clean(self):
if self.quantity <= 0:
raise ValidationError("পরিমাণ অবশ্যই ধনাত্মক হতে হবে।")
@transaction.atomic
def save(self, *args, **kwargs):
self.full_clean()
self.linetotal = self.quantity * self.unitprice
super().save(*args, **kwargs)
self.item.onhand += self.quantity
self.item.save()
item_warehouse, created = ItemWarehouse.objects.get_or_create(
item=self.item,
warehouse=self.warehouse,
defaults={'onhand': 0}
)
item_warehouse.onhand += self.quantity
item_warehouse.save()
class GoodsIssue(models.Model):
docnum = models.CharField(max_length=20, unique=True)
docdate = models.DateField()
postdate = models.DateField()
comments = models.TextField(blank=True)
def __str__(self):
return f"GI-{self.docnum}"
def get_total_amount(self):
return sum(line.linetotal for line in self.lines.all())
class GoodsIssueLine(models.Model):
goods_issue = models.ForeignKey(GoodsIssue, on_delete=models.CASCADE, related_name='lines')
item = models.ForeignKey(Item, on_delete=models.PROTECT)
warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
quantity = models.DecimalField(max_digits=18, decimal_places=6)
unitprice = models.DecimalField(max_digits=18, decimal_places=6)
linetotal = models.DecimalField(max_digits=18, decimal_places=6)
def __str__(self):
return f"GI-{self.goods_issue.docnum} - {self.item.itemcode}"
def clean(self):
if self.quantity <= 0:
raise ValidationError("পরিমাণ অবশ্যই ধনাত্মক হতে হবে।")
item_warehouse = ItemWarehouse.objects.get(item=self.item, warehouse=self.warehouse)
if self.quantity > item_warehouse.onhand:
raise ValidationError("পর্যাপ্ত স্টক নেই।")
@transaction.atomic
def save(self, *args, **kwargs):
self.full_clean()
self.linetotal = self.quantity * self.unitprice
super().save(*args, **kwargs)
self.item.onhand -= self.quantity
self.item.save()
item_warehouse = ItemWarehouse.objects.get(
item=self.item,
warehouse=self.warehouse
)
item_warehouse.onhand -= self.quantity
item_warehouse.save()
class InventoryTransfer(models.Model):
docnum = models.CharField(max_length=20, unique=True)
docdate = models.DateField()
from_warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT, related_name='transfers_from')
to_warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT, related_name='transfers_to')
comments = models.TextField(blank=True)
def __str__(self):
return f"IT-{self.docnum}"
def clean(self):
if self.from_warehouse == self.to_warehouse:
raise ValidationError("উৎস এবং গন্তব্য গুদাম একই হতে পারে না।")
class InventoryTransferLine(models.Model):
inventory_transfer = models.ForeignKey(InventoryTransfer, on_delete=models.CASCADE, related_name='lines')
item = models.ForeignKey(Item, on_delete=models.PROTECT)
quantity = models.DecimalField(max_digits=18, decimal_places=6)
def __str__(self):
return f"IT-{self.inventory_transfer.docnum} - {self.item.itemcode}"
def clean(self):
if self.quantity <= 0:
raise ValidationError("পরিমাণ অবশ্যই ধনাত্মক হতে হবে।")
from_item_warehouse = ItemWarehouse.objects.get(
item=self.item,
warehouse=self.inventory_transfer.from_warehouse
)
if self.quantity > from_item_warehouse.onhand:
raise ValidationError("উৎস গুদামে পর্যাপ্ত স্টক নেই।")
@transaction.atomic
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
from_item_warehouse = ItemWarehouse.objects.get(
item=self.item,
warehouse=self.inventory_transfer.from_warehouse
)
from_item_warehouse.onhand -= self.quantity
from_item_warehouse.save()
to_item_warehouse, created = ItemWarehouse.objects.get_or_create(
item=self.item,
warehouse=self.inventory_transfer.to_warehouse,
defaults={'onhand': 0}
)
to_item_warehouse.onhand += self.quantity
to_item_warehouse.save()
class CycleCount(models.Model):
count_date = models.DateField()
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
status = models.CharField(max_length=20, choices=[('Draft', 'Draft'), ('In Progress', 'In Progress'), ('Completed', 'Completed')])
def __str__(self):
return f"Cycle Count - {self.warehouse.whscode} - {self.count_date}"
def start_count(self):
if self.status == 'Draft':
self.status = 'In Progress'
self.save()
else:
raise ValidationError("সাইকেল কাউন্ট শুরু করা যাবে না। স্ট্যাটাস ড্রাফট নয়।")
def complete_count(self):
if self.status == 'In Progress':
self.status = 'Completed'
self.save()
self.adjust_inventory()
else:
raise ValidationError("সাইকেল কাউন্ট সম্পন্ন করা যাবে না। স্ট্যাটাস প্রগতিতে নয়।")
@transaction.atomic
def adjust_inventory(self):
for line in self.lines.all():
item_warehouse, created = ItemWarehouse.objects.get_or_create(
item=line.item,
warehouse=self.warehouse,
defaults={'onhand': 0}
)
difference = line.counted_quantity - item_warehouse.onhand
item_warehouse.onhand = line.counted_quantity
item_warehouse.save()
line.item.onhand += difference
line.item.save()
class CycleCountLine(models.Model):
cycle_count = models.ForeignKey(CycleCount, on_delete=models.CASCADE, related_name='lines')
item = models.ForeignKey(Item, on_delete=models.CASCADE)
expected_quantity = models.DecimalField(max_digits=18, decimal_places=6)
counted_quantity = models.DecimalField(max_digits=18, decimal_places=6, null=True, blank=True)
difference = models.DecimalField(max_digits=18, decimal_places=6, null=True, blank=True)
def __str__(self):
return f"{self.cycle_count} - {self.item.itemcode}"
def save(self, *args, **kwargs):
if self.counted_quantity is not None:
self.difference = self.counted_quantity - self.expected_quantity
super().save(*args, **kwargs)