diff --git a/tienda_ilusion/don_confiao/forms.py b/tienda_ilusion/don_confiao/forms.py index 4db321a..5836c83 100644 --- a/tienda_ilusion/don_confiao/forms.py +++ b/tienda_ilusion/don_confiao/forms.py @@ -1,7 +1,7 @@ from django import forms -from django.forms.widgets import DateInput +from django.forms.widgets import DateInput, DateTimeInput -from .models import Sale, SaleLine +from .models import Sale, SaleLine, ReconciliationJar class ImportProductsForm(forms.Form): @@ -37,3 +37,17 @@ LineaFormSet = forms.models.inlineformset_factory( extra=2, fields='__all__' ) + +class ReconciliationJarForm(forms.ModelForm): + class Meta: + model = ReconciliationJar + fields = [ + 'date_time', + 'description', + 'reconciler', + 'cash_taken', + 'cash_discrepancy', + ] + # widgets = { + # 'date_time': DateTimeInput(attrs={'type': 'datetime-local'}) + # } diff --git a/tienda_ilusion/don_confiao/migrations/0016_reconciliationjar_cash_discrepancy_and_more.py b/tienda_ilusion/don_confiao/migrations/0016_reconciliationjar_cash_discrepancy_and_more.py new file mode 100644 index 0000000..8bb7bd2 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0016_reconciliationjar_cash_discrepancy_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.0.6 on 2024-07-13 18:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0015_alter_payment_reconciliation_jar'), + ] + + operations = [ + migrations.AddField( + model_name='reconciliationjar', + name='cash_discrepancy', + field=models.DecimalField(decimal_places=2, default=0, max_digits=9), + preserve_default=False, + ), + migrations.AddField( + model_name='reconciliationjar', + name='cash_float', + field=models.DecimalField(decimal_places=2, default=0, max_digits=9), + preserve_default=False, + ), + migrations.AddField( + model_name='reconciliationjar', + name='cash_taken', + field=models.DecimalField(decimal_places=2, default=0, max_digits=9), + preserve_default=False, + ), + migrations.AddField( + model_name='reconciliationjar', + name='reconciler', + field=models.CharField(default='Jorge', max_length=255), + preserve_default=False, + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0017_reconciliationjar_draft.py b/tienda_ilusion/don_confiao/migrations/0017_reconciliationjar_draft.py new file mode 100644 index 0000000..13dc149 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0017_reconciliationjar_draft.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-13 18:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0016_reconciliationjar_cash_discrepancy_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='reconciliationjar', + name='draft', + field=models.BooleanField(default=False), + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0018_rename_draft_reconciliationjar_valid.py b/tienda_ilusion/don_confiao/migrations/0018_rename_draft_reconciliationjar_valid.py new file mode 100644 index 0000000..7228161 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0018_rename_draft_reconciliationjar_valid.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-13 18:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0017_reconciliationjar_draft'), + ] + + operations = [ + migrations.RenameField( + model_name='reconciliationjar', + old_name='draft', + new_name='valid', + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0019_rename_valid_reconciliationjar_is_valid.py b/tienda_ilusion/don_confiao/migrations/0019_rename_valid_reconciliationjar_is_valid.py new file mode 100644 index 0000000..a0d7a02 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0019_rename_valid_reconciliationjar_is_valid.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-13 19:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0018_rename_draft_reconciliationjar_valid'), + ] + + operations = [ + migrations.RenameField( + model_name='reconciliationjar', + old_name='valid', + new_name='is_valid', + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0020_remove_reconciliationjar_cash_float.py b/tienda_ilusion/don_confiao/migrations/0020_remove_reconciliationjar_cash_float.py new file mode 100644 index 0000000..dea5619 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0020_remove_reconciliationjar_cash_float.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-07-13 20:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0019_rename_valid_reconciliationjar_is_valid'), + ] + + operations = [ + migrations.RemoveField( + model_name='reconciliationjar', + name='cash_float', + ), + ] diff --git a/tienda_ilusion/don_confiao/models.py b/tienda_ilusion/don_confiao/models.py index 357e3e0..579189f 100644 --- a/tienda_ilusion/don_confiao/models.py +++ b/tienda_ilusion/don_confiao/models.py @@ -1,5 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import ValidationError + class Sale(models.Model): @@ -23,15 +25,18 @@ class SaleLine(models.Model): def __str__(self): return f"{self.sale} - {self.product}" + 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) @@ -45,11 +50,13 @@ class Product(models.Model): def __str__(self): return self.name + class PyamentMethods(models.TextChoices): CASH = 'CASH', _('Cash') CONFIAR = 'CONFIAR', _('Confiar') BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia') + class ReconciliationJarSummary(): def __init__(self, payments): self._validate_payments(payments) @@ -68,9 +75,24 @@ class ReconciliationJarSummary(): class ReconciliationJar(models.Model): + is_valid = models.BooleanField(default=False) date_time = models.DateTimeField() description = models.CharField(max_length=255, null=True, blank=True) + reconciler = 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): + payments_amount = sum([p.amount for p in self.payment_set.all()]) + reconciliation_ammount = sum([ + self.cash_taken, + self.cash_discrepancy, + ]) + if reconciliation_ammount != payments_amount: + raise ValidationError( + {"cash_take": _("The taken ammount has discrepancy.")} + ) + self.is_valid = True class Payment(models.Model): date_time = models.DateTimeField() diff --git a/tienda_ilusion/don_confiao/templates/don_confiao/reconciliate_jar.html b/tienda_ilusion/don_confiao/templates/don_confiao/reconciliate_jar.html new file mode 100644 index 0000000..2da1ac5 --- /dev/null +++ b/tienda_ilusion/don_confiao/templates/don_confiao/reconciliate_jar.html @@ -0,0 +1,2 @@ + +{{ summary.total }} diff --git a/tienda_ilusion/don_confiao/test_billing.py b/tienda_ilusion/don_confiao/test_billing.py index 4e6f212..49f8e97 100644 --- a/tienda_ilusion/don_confiao/test_billing.py +++ b/tienda_ilusion/don_confiao/test_billing.py @@ -1,22 +1,21 @@ from django.test import Client, TestCase -from .models import Payment +from django.core.exceptions import ValidationError +from .models import Payment, ReconciliationJar class TestBilling(TestCase): def test_reconciliation_jar_summary(self): - cash_payment1 = Payment() - cash_payment1.date_time = '2024-07-07 12:00:00' - cash_payment1.type_payment = 'CASH' - cash_payment1.amount = 132000 - cash_payment1.description = 'Saldo en compra' - cash_payment1.save() + cash_payment1, cash_payment2 = self._create_two_cash_payments() + jar_summary = Payment.get_reconciliation_jar_summary() + self.assertEqual(164000, jar_summary.total) + self.assertSetEqual( + {cash_payment1, cash_payment2}, + set(jar_summary.payments) + ) - cash_payment2 = Payment() - cash_payment2.date_time = '2024-07-07 13:05:00' - cash_payment2.type_payment = 'CASH' - cash_payment2.amount = 32000 - cash_payment2.save() + def test_reconciliation_jar_summary_use_only_cash(self): + cash_payment1, cash_payment2 = self._create_two_cash_payments() confiar_payment = Payment() confiar_payment.date_time = '2024-07-07 16:00:00' @@ -36,3 +35,57 @@ class TestBilling(TestCase): {cash_payment1, cash_payment2}, set(jar_summary.payments) ) + + def test_fail_validate_reconciliation_jar_with_discrepancy_values(self): + cash_payment1, cash_payment2 = self._create_two_cash_payments() + + jar_summary = Payment.get_reconciliation_jar_summary() + + reconciliation_jar = ReconciliationJar() + reconciliation_jar.date_time = '2024-07-13 13:02:00' + reconciliation_jar.description = "test reconcialiation jar" + reconciliation_jar.reconcilier = 'Jorge' + reconciliation_jar.cash_float = 0 + reconciliation_jar.cash_taken = 0 + reconciliation_jar.cash_discrepancy = 0 + reconciliation_jar.save() + + for payment in jar_summary.payments: + reconciliation_jar.payment_set.add(payment) + with self.assertRaises(ValidationError): + reconciliation_jar.clean() + + def test_validate_reconciliation_jar_with_cash_float(self): + cash_payment1, cash_payment2 = self._create_two_cash_payments() + jar_summary = Payment.get_reconciliation_jar_summary() + + reconciliation_jar = ReconciliationJar() + reconciliation_jar.date_time = '2024-07-13 13:02:00' + reconciliation_jar.description = "test reconcialiation jar" + reconciliation_jar.reconcilier = 'Jorge' + reconciliation_jar.cash_float = 10000 + reconciliation_jar.cash_taken = jar_summary.total + reconciliation_jar.cash_discrepancy = 0 + reconciliation_jar.save() + + for payment in jar_summary.payments: + reconciliation_jar.payment_set.add(payment) + reconciliation_jar.clean() + reconciliation_jar.save() + self.assertTrue(reconciliation_jar.is_valid) + + def _create_two_cash_payments(self): + cash_payment1 = Payment() + cash_payment1.date_time = '2024-07-07 12:00:00' + cash_payment1.type_payment = 'CASH' + cash_payment1.amount = 132000 + cash_payment1.description = 'Saldo en compra' + cash_payment1.save() + + cash_payment2 = Payment() + cash_payment2.date_time = '2024-07-07 13:05:00' + cash_payment2.type_payment = 'CASH' + cash_payment2.amount = 32000 + cash_payment2.save() + + return [cash_payment1, cash_payment2] diff --git a/tienda_ilusion/don_confiao/test_reconciliation_jar_client.py b/tienda_ilusion/don_confiao/test_reconciliation_jar_client.py new file mode 100644 index 0000000..04ce878 --- /dev/null +++ b/tienda_ilusion/don_confiao/test_reconciliation_jar_client.py @@ -0,0 +1,34 @@ +from django.test import Client, TestCase +from django.contrib.auth.models import AnonymousUser, User +#from django.conf import settings + +#from .views import import_products, products +from .models import Payment + + +class TestReconciliationJarClient(TestCase): + def setUp(self): + self.client = Client() + + def test_get_summary_info_on_view(self): + self._generate_two_cash_payments() + response = self.client.get("/don_confiao/cuadrar_tarro") + self.assertEqual(response.status_code, 200) + # raise Exception(response.content.decode('utf-8')) + self.assertEqual(response.context["summary"].total, 160000) + self.assertIn('160000', response.content.decode('utf-8')) + + def _generate_two_cash_payments(self): + cash_payment1 = Payment() + cash_payment1.date_time = '2024-07-07 12:00:00' + cash_payment1.type_payment = 'CASH' + cash_payment1.amount = 130000 + cash_payment1.description = 'Saldo en compra' + cash_payment1.save() + # raise Exception (cash_payment1.id) + + cash_payment2 = Payment() + cash_payment2.date_time = '2024-07-07 13:05:00' + cash_payment2.type_payment = 'CASH' + cash_payment2.amount = 30000 + cash_payment2.save() diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index a9b716f..10f9518 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -8,5 +8,6 @@ urlpatterns = [ path("comprar", views.buy, name="buy"), path("compras", views.purchases, name="purchases"), path("productos", views.products, name="products"), - path("importar_productos", views.import_products, name="import_products") + path("importar_productos", views.import_products, name="import_products"), + path("cuadrar_tarro", views.reconciliate_jar, name="reconciliate_jar"), ] diff --git a/tienda_ilusion/don_confiao/views.py b/tienda_ilusion/don_confiao/views.py index 1904ab6..5d7e9e2 100644 --- a/tienda_ilusion/don_confiao/views.py +++ b/tienda_ilusion/don_confiao/views.py @@ -2,8 +2,8 @@ from django.shortcuts import render from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.template import loader -from .models import Sale, Product, ProductCategory -from .forms import ImportProductsForm, PurchaseForm, LineaFormSet +from .models import Sale, Product, ProductCategory, Payment +from .forms import ImportProductsForm, PurchaseForm, LineaFormSet, ReconciliationJarForm import csv import io @@ -69,6 +69,21 @@ def import_products(request): {'form': form} ) + +def reconciliate_jar(request): + if request.method == 'POST': + return HttpResponseRedirect("cuadres") + + form = ReconciliationJarForm() + summary = Payment.get_reconciliation_jar_summary() + # raise Exception(Payment.get_reconciliation_jar_summary().payments) + return render( + request, + "don_confiao/reconciliate_jar.html", + {'summary': summary, 'form': form} + ) + + def _categories_from_csv_string(categories_string, separator="&"): categories = categories_string.split(separator) clean_categories = [c.strip() for c in categories]