1. Home
  2. Erp
  3. Inventory

Inventory

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)

Articles

How can we help?