189 lines
5.6 KiB
Python
189 lines
5.6 KiB
Python
from django.db import models
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from decimal import Decimal
|
|
from datetime import datetime
|
|
|
|
|
|
class Customer(models.Model):
|
|
name = models.CharField(max_length=100, default=None, null=False, blank=False)
|
|
address = models.CharField(max_length=100, null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class MeasuringUnits(models.TextChoices):
|
|
UNIT = 'UNIT', _('Unit')
|
|
|
|
|
|
class ProductCategory(models.Model):
|
|
name = models.CharField(max_length=100, unique=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Product(models.Model):
|
|
name = models.CharField(max_length=100, unique=True)
|
|
price = models.DecimalField(max_digits=9, decimal_places=2)
|
|
measuring_unit = models.CharField(
|
|
max_length=20,
|
|
choices=MeasuringUnits.choices,
|
|
default=MeasuringUnits.UNIT
|
|
)
|
|
categories = models.ManyToManyField(ProductCategory)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@classmethod
|
|
def to_list(cls):
|
|
products_list = []
|
|
all_products = cls.objects.all()
|
|
for product in all_products:
|
|
rproduct = {
|
|
"id": product.id,
|
|
"name": product.name,
|
|
"price_list": product.price,
|
|
"uom": product.measuring_unit,
|
|
"categories": [c.name for c in product.categories.all()]
|
|
}
|
|
products_list.append(rproduct)
|
|
return products_list
|
|
|
|
|
|
class Sale(models.Model):
|
|
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
|
|
date = models.DateField("Date")
|
|
phone = models.CharField(max_length=13, null=True, blank=True)
|
|
description = models.CharField(max_length=255, null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.date} {self.customer}"
|
|
|
|
def get_total(self):
|
|
lines = self.saleline_set.all()
|
|
return sum([l.quantity * l.unit_price for l in lines])
|
|
|
|
@classmethod
|
|
def sale_header_csv(cls):
|
|
sale_header_csv = [field.name for field in cls._meta.fields]
|
|
|
|
return sale_header_csv
|
|
|
|
|
|
class SaleLine(models.Model):
|
|
|
|
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|
|
product = models.ForeignKey(Product, null=False, blank=False, on_delete=models.CASCADE)
|
|
quantity = models.IntegerField(null=True)
|
|
unit_price = models.DecimalField(max_digits=9, decimal_places=2)
|
|
description = models.CharField(max_length=255, null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.sale} - {self.product}"
|
|
|
|
|
|
class PaymentMethods(models.TextChoices):
|
|
CASH = 'CASH', _('Cash')
|
|
CONFIAR = 'CONFIAR', _('Confiar')
|
|
BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia')
|
|
|
|
|
|
class ReconciliationJarSummary():
|
|
def __init__(self, payments):
|
|
self._validate_payments(payments)
|
|
self._payments = payments
|
|
|
|
def _validate_payments(self, payments):
|
|
pass
|
|
|
|
@property
|
|
def total(self):
|
|
return sum([p.amount for p in self.payments])
|
|
|
|
@property
|
|
def payments(self):
|
|
return self._payments
|
|
|
|
|
|
class ReconciliationJar(models.Model):
|
|
is_valid = models.BooleanField(default=False)
|
|
date_time = models.DateTimeField()
|
|
description = models.CharField(max_length=255, null=True, blank=True)
|
|
reconcilier = models.CharField(max_length=255, null=False, blank=False)
|
|
cash_taken = models.DecimalField(max_digits=9, decimal_places=2)
|
|
cash_discrepancy = models.DecimalField(max_digits=9, decimal_places=2)
|
|
|
|
def clean(self):
|
|
if not self.is_valid:
|
|
payments = Payment.get_reconciliation_jar_summary().payments
|
|
else:
|
|
payments = self.payment_set.all()
|
|
|
|
payments_amount = Decimal(sum([p.amount for p in payments]))
|
|
reconciliation_ammount = Decimal(sum([
|
|
self.cash_taken,
|
|
self.cash_discrepancy,
|
|
]))
|
|
|
|
equal_ammounts = reconciliation_ammount.compare(payments_amount) == Decimal('0')
|
|
if not equal_ammounts:
|
|
raise ValidationError(
|
|
{"cash_taken": _("The taken ammount has discrepancy.")}
|
|
)
|
|
|
|
def add_payments(self, payments):
|
|
for payment in payments:
|
|
self.payment_set.add(payment)
|
|
self.is_valid = True
|
|
|
|
|
|
class Payment(models.Model):
|
|
date_time = models.DateTimeField()
|
|
type_payment = models.CharField(
|
|
max_length=30,
|
|
choices=PaymentMethods.choices,
|
|
default=PaymentMethods.CASH
|
|
)
|
|
amount = models.DecimalField(max_digits=9, decimal_places=2)
|
|
reconciliation_jar = models.ForeignKey(
|
|
ReconciliationJar,
|
|
null=True,
|
|
default=None,
|
|
blank=True,
|
|
on_delete=models.RESTRICT
|
|
)
|
|
description = models.CharField(max_length=255, null=True, blank=True)
|
|
|
|
@classmethod
|
|
def get_reconciliation_jar_summary(cls):
|
|
return ReconciliationJarSummary(
|
|
cls.objects.filter(
|
|
type_payment=PaymentMethods.CASH,
|
|
reconciliation_jar=None
|
|
)
|
|
)
|
|
|
|
@classmethod
|
|
def total_payment_from_sale(cls, payment_method, sale):
|
|
payment = cls()
|
|
payment.date_time = datetime.today()
|
|
payment.type_payment = payment_method
|
|
payment.amount = sale.get_total()
|
|
payment.clean()
|
|
payment.save()
|
|
|
|
payment_sale = PaymentSale()
|
|
payment_sale.payment = payment
|
|
payment_sale.sale = sale
|
|
payment_sale.clean()
|
|
payment_sale.save()
|
|
|
|
|
|
class PaymentSale(models.Model):
|
|
payment = models.ForeignKey(Payment, on_delete=models.CASCADE)
|
|
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|