remove: django.
This commit is contained in:
parent
27cbeca6cb
commit
88b262e9cf
@ -1,12 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
Customer, Sale, SaleLine, Product, ProductCategory, Payment,
|
||||
ReconciliationJar)
|
||||
|
||||
admin.site.register(Customer)
|
||||
admin.site.register(Sale)
|
||||
admin.site.register(SaleLine)
|
||||
admin.site.register(Product)
|
||||
admin.site.register(ProductCategory)
|
||||
admin.site.register(Payment)
|
||||
admin.site.register(ReconciliationJar)
|
@ -1,146 +0,0 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode
|
||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
|
||||
|
||||
from decimal import Decimal
|
||||
import json
|
||||
|
||||
|
||||
class Pagination(PageNumberPagination):
|
||||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
|
||||
|
||||
class SaleView(viewsets.ModelViewSet):
|
||||
queryset = Sale.objects.all()
|
||||
serializer_class = SaleSerializer
|
||||
|
||||
def create(self, request):
|
||||
data = request.data
|
||||
customer = Customer.objects.get(pk=data['customer'])
|
||||
date = data['date']
|
||||
lines = data['saleline_set']
|
||||
payment_method = data['payment_method']
|
||||
sale = Sale.objects.create(
|
||||
customer=customer,
|
||||
date=date,
|
||||
payment_method=payment_method
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
product = Product.objects.get(pk=line['product'])
|
||||
quantity = line['quantity']
|
||||
unit_price = line['unit_price']
|
||||
SaleLine.objects.create(
|
||||
sale=sale,
|
||||
product=product,
|
||||
quantity=quantity,
|
||||
unit_price=unit_price
|
||||
)
|
||||
|
||||
return Response(
|
||||
{'id': sale.id, 'message': 'Venta creada con exito'},
|
||||
status=201
|
||||
)
|
||||
|
||||
|
||||
class ProductView(viewsets.ModelViewSet):
|
||||
queryset = Product.objects.all()
|
||||
serializer_class = ProductSerializer
|
||||
|
||||
|
||||
class CustomerView(viewsets.ModelViewSet):
|
||||
queryset = Customer.objects.all()
|
||||
serializer_class = CustomerSerializer
|
||||
|
||||
|
||||
class ReconciliateJarView(APIView):
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
cash_purchases_id = data.get('cash_purchases')
|
||||
serializer = ReconciliationJarSerializer(data=data)
|
||||
if serializer.is_valid():
|
||||
cash_purchases = Sale.objects.filter(pk__in=cash_purchases_id)
|
||||
if not self._is_valid_total(cash_purchases, data.get('total_cash_purchases')):
|
||||
return Response(
|
||||
{'error': 'total_cash_purchases not equal to sum of all purchases.'},
|
||||
status=HTTP_400_BAD_REQUEST
|
||||
)
|
||||
reconciliation = serializer.save()
|
||||
other_purchases = self._get_other_purchases(data.get('other_totals'))
|
||||
|
||||
self._link_purchases(reconciliation, cash_purchases, other_purchases)
|
||||
return Response({'id': reconciliation.id})
|
||||
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
|
||||
|
||||
def get(self, request):
|
||||
reconciliations = ReconciliationJar.objects.all()
|
||||
serializer = ReconciliationJarSerializer(reconciliations, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def _is_valid_total(self, purchases, total):
|
||||
calculated_total = sum(p.get_total() for p in purchases)
|
||||
return calculated_total == Decimal(total)
|
||||
|
||||
def _get_other_purchases(self, other_totals):
|
||||
if not other_totals:
|
||||
return []
|
||||
purchases = []
|
||||
for method in other_totals:
|
||||
purchases.extend(other_totals[method]['purchases'])
|
||||
if purchases:
|
||||
return Sale.objects.filter(pk__in=purchases)
|
||||
return []
|
||||
|
||||
def _link_purchases(self, reconciliation, cash_purchases, other_purchases):
|
||||
for purchase in cash_purchases:
|
||||
purchase.reconciliation = reconciliation
|
||||
purchase.clean()
|
||||
purchase.save()
|
||||
|
||||
for purchase in other_purchases:
|
||||
purchase.reconciliation = reconciliation
|
||||
purchase.clean()
|
||||
purchase.save()
|
||||
|
||||
|
||||
class PaymentMethodView(APIView):
|
||||
def get(self, request):
|
||||
serializer = PaymentMethodSerializer(PaymentMethods.choices, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class SalesForReconciliationView(APIView):
|
||||
def get(self, request):
|
||||
sales = Sale.objects.filter(reconciliation=None)
|
||||
grouped_sales = {}
|
||||
|
||||
for sale in sales:
|
||||
if sale.payment_method not in grouped_sales.keys():
|
||||
grouped_sales[sale.payment_method] = []
|
||||
serializer = SaleForRenconciliationSerializer(sale)
|
||||
grouped_sales[sale.payment_method].append(serializer.data)
|
||||
|
||||
return Response(grouped_sales)
|
||||
|
||||
class SaleSummary(APIView):
|
||||
def get(self, request, id):
|
||||
sale = Sale.objects.get(pk=id)
|
||||
serializer = SaleSummarySerializer(sale)
|
||||
return Response(serializer.data)
|
||||
|
||||
class AdminCodeValidateView(APIView):
|
||||
def get(self, request, code):
|
||||
codes = AdminCode.objects.filter(value=code)
|
||||
return Response({'validCode': bool(codes)})
|
||||
|
||||
|
||||
class ReconciliateJarModelView(viewsets.ModelViewSet):
|
||||
queryset = ReconciliationJar.objects.all().order_by('-date_time')
|
||||
pagination_class = Pagination
|
||||
serializer_class = ReconciliationJarSerializer
|
@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DonConfiaoConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'don_confiao'
|
@ -1,4 +0,0 @@
|
||||
nombre,correo,telefono
|
||||
Alejandro Ayala,mono@disroot.org,3232321
|
||||
Mono Francisco,pablo@onecluster.org,321312312
|
||||
Pablo Bolivar,alejo@onecluster.org,3243242
|
|
@ -1,4 +0,0 @@
|
||||
"producto","unidad","precio","categorias"
|
||||
"Aceite","Unidad", 50000,"Aceites&Alimentos"
|
||||
"Café","Unidad", 14000,"Cafes&Alimentos"
|
||||
"Arroz","Unidad", 7000,"Cafes&Alimentos"
|
|
@ -1,4 +0,0 @@
|
||||
"producto","unidad","precio","categorias"
|
||||
"Aceite","Unidad", 50000,"Aceites&Alimentos"
|
||||
"Café","Unidad", 15000,"Cafes&Alimentos"
|
||||
"Arroz","Unidad", 6000,"Alimentos&Granos"
|
|
@ -1 +0,0 @@
|
||||
#!/usr/bin/env python3
|
@ -1,66 +0,0 @@
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory
|
||||
|
||||
from django.forms.widgets import DateInput, DateTimeInput
|
||||
|
||||
from .models import Sale, SaleLine, PaymentMethods
|
||||
|
||||
readonly_number_widget = forms.NumberInput(attrs={'readonly': 'readonly'})
|
||||
|
||||
|
||||
class ImportProductsForm(forms.Form):
|
||||
csv_file = forms.FileField()
|
||||
|
||||
|
||||
class ImportCustomersForm(forms.Form):
|
||||
csv_file = forms.FileField()
|
||||
|
||||
|
||||
class PurchaseForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Sale
|
||||
fields = [
|
||||
"customer",
|
||||
"date",
|
||||
"phone",
|
||||
"description",
|
||||
]
|
||||
widgets = {
|
||||
'date': DateInput(attrs={'type': 'date'})
|
||||
}
|
||||
|
||||
|
||||
class PurchaseLineForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = SaleLine
|
||||
fields = [
|
||||
"product",
|
||||
"quantity",
|
||||
"unit_price",
|
||||
"description",
|
||||
]
|
||||
|
||||
|
||||
class PurchaseSummaryForm(forms.Form):
|
||||
quantity_lines = forms.IntegerField(
|
||||
widget=readonly_number_widget
|
||||
)
|
||||
quantity_products = forms.IntegerField(
|
||||
widget=readonly_number_widget
|
||||
)
|
||||
ammount = forms.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
widget=readonly_number_widget
|
||||
)
|
||||
payment_method = forms.ChoiceField(
|
||||
choices=[(PaymentMethods.CASH, PaymentMethods.CASH)],
|
||||
)
|
||||
|
||||
|
||||
SaleLineFormSet = inlineformset_factory(
|
||||
Sale,
|
||||
SaleLine,
|
||||
extra=1,
|
||||
fields='__all__'
|
||||
)
|
@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 15:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Sale',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 15:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='saleline',
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.product'),
|
||||
),
|
||||
]
|
@ -1,37 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 15:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='customer',
|
||||
field=models.CharField(default='', max_length=100),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='date',
|
||||
field=models.DateField(default='2024-06-22', verbose_name='Date'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='description',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='phone',
|
||||
field=models.CharField(default='', max_length=13),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 15:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0002_sale_customer_sale_date_sale_description_sale_phone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SaleLine',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 16:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0003_saleline'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='saleline',
|
||||
name='quantity',
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
]
|
@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 16:05
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0004_saleline_quantity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='saleline',
|
||||
name='sale',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='don_confiao.sale'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,31 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 18:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0005_saleline_sale'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='saleline',
|
||||
name='description',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='saleline',
|
||||
name='product',
|
||||
field=models.CharField(default='', max_length=100),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='saleline',
|
||||
name='unit_price',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=9),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 19:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0006_saleline_description_saleline_product_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-22 19:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0007_alter_sale_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='phone',
|
||||
field=models.CharField(blank=True, max_length=13, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='saleline',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
@ -1,30 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-29 18:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0008_alter_sale_phone_alter_saleline_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProductCategory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=9)),
|
||||
('measuring_unit', models.CharField(choices=[('UNIT', 'Unit')], default='UNIT', max_length=20)),
|
||||
('categories', models.ManyToManyField(to='don_confiao.productcategory')),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-29 21:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0009_productcategory_product'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='productcategory',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-29 21:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0010_alter_productcategory_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='product',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True),
|
||||
),
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 14:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0011_alter_product_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Payment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date_time', models.DateTimeField()),
|
||||
('type_payment', models.CharField(choices=[('CASH', 'Cash'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30)),
|
||||
('mount', models.DecimalField(decimal_places=2, max_digits=9)),
|
||||
('description', models.CharField(blank=True, max_length=255, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 15:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0012_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='payment',
|
||||
old_name='mount',
|
||||
new_name='amount',
|
||||
),
|
||||
]
|
@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 16:35
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0013_rename_mount_payment_amount'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ReconciliationJar',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date_time', models.DateTimeField()),
|
||||
('description', models.CharField(blank=True, max_length=255, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
name='reconciliation_jar',
|
||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.RESTRICT, to='don_confiao.reconciliationjar'),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 16:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0014_reconciliationjar_payment_reconciliation_jar'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='reconciliation_jar',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.RESTRICT, to='don_confiao.reconciliationjar'),
|
||||
),
|
||||
]
|
@ -1,37 +0,0 @@
|
||||
# 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,
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# 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',
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# 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',
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# 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',
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 22:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0020_remove_reconciliationjar_cash_float'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='reconciliationjar',
|
||||
old_name='reconciler',
|
||||
new_name='reconcilier',
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-07-13 22:39
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0021_rename_reconciler_reconciliationjar_reconcilier'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='reconciliation_jar',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.RESTRICT, to='don_confiao.reconciliationjar'),
|
||||
),
|
||||
]
|
@ -1,14 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 15:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0002_alter_saleline_product'),
|
||||
('don_confiao', '0022_alter_payment_reconciliation_jar'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 15:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0023_merge_20240803_1504'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='product',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 15:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0024_alter_product_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('address', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 15:55
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0025_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='don_confiao.customer'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 16:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0026_alter_sale_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='product',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-08-17 14:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0027_alter_product_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customer',
|
||||
name='address',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-08-17 19:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0028_alter_customer_address'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customer',
|
||||
name='name',
|
||||
field=models.CharField(default=None, max_length=100),
|
||||
),
|
||||
]
|
@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-08-17 21:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0029_alter_customer_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PaymentSale',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('payment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.payment')),
|
||||
('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.sale')),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-10-26 22:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0030_paymentsale'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='customer',
|
||||
old_name='address',
|
||||
new_name='email',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customer',
|
||||
name='phone',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-10-26 22:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0031_rename_address_customer_email_customer_phone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customer',
|
||||
name='address',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-11-09 17:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0032_customer_address'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='payment_method',
|
||||
field=models.CharField(choices=[('CASH', 'Cash'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
]
|
@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-11-16 20:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0033_sale_payment_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='type_payment',
|
||||
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='date',
|
||||
field=models.DateTimeField(verbose_name='Date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='payment_method',
|
||||
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
]
|
@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-11-18 03:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0033_sale_payment_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='reconciliation',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='Sales', to='don_confiao.reconciliationjar'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='type_payment',
|
||||
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sale',
|
||||
name='payment_method',
|
||||
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-12-03 02:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0034_sale_reconciliation_alter_payment_type_payment_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='reconciliationjar',
|
||||
name='total_cash_purchases',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,14 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-12-28 22:12
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0034_alter_payment_type_payment_alter_sale_date_and_more'),
|
||||
('don_confiao', '0035_reconciliationjar_total_cash_purchases'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2025-01-11 23:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0036_merge_20241228_2212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminCode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,204 +0,0 @@
|
||||
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 PaymentMethods(models.TextChoices):
|
||||
CASH = 'CASH', _('Efectivo')
|
||||
CONFIAR = 'CONFIAR', _('Confiar')
|
||||
BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia')
|
||||
|
||||
|
||||
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)
|
||||
email = models.CharField(max_length=100, null=True, blank=True)
|
||||
phone = 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 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)
|
||||
total_cash_purchases = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
|
||||
def clean(self):
|
||||
self._validate_taken_ammount()
|
||||
|
||||
def add_payments(self, payments):
|
||||
for payment in payments:
|
||||
self.payment_set.add(payment)
|
||||
self.is_valid = True
|
||||
|
||||
def _validate_taken_ammount(self):
|
||||
ammount_cash = self.cash_taken + self.cash_discrepancy
|
||||
if not self.total_cash_purchases == ammount_cash:
|
||||
raise ValidationError(
|
||||
{"cash_taken": _("The taken ammount has discrepancy.")}
|
||||
)
|
||||
|
||||
|
||||
class Sale(models.Model):
|
||||
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
|
||||
date = models.DateTimeField("Date")
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
description = models.CharField(max_length=255, null=True, blank=True)
|
||||
payment_method = models.CharField(
|
||||
max_length=30,
|
||||
choices=PaymentMethods.choices,
|
||||
default=PaymentMethods.CASH,
|
||||
blank=False,
|
||||
null=False
|
||||
)
|
||||
reconciliation = models.ForeignKey(
|
||||
ReconciliationJar,
|
||||
on_delete=models.RESTRICT,
|
||||
related_name='Sales',
|
||||
null=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])
|
||||
|
||||
def clean(self):
|
||||
if self.payment_method not in PaymentMethods.values:
|
||||
raise ValidationError({'payment_method': "Invalid payment method"})
|
||||
|
||||
@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 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 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)
|
||||
|
||||
|
||||
class AdminCode(models.Model):
|
||||
value = models.CharField(max_length=255, null=False, blank=False)
|
@ -1,103 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Sale, SaleLine, Product, Customer, ReconciliationJar
|
||||
|
||||
|
||||
class SaleLineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SaleLine
|
||||
fields = ['id', 'sale', 'product', 'unit_price', 'quantity']
|
||||
|
||||
|
||||
class SaleSerializer(serializers.ModelSerializer):
|
||||
total = serializers.ReadOnlyField(source='get_total')
|
||||
|
||||
class Meta:
|
||||
model = Sale
|
||||
fields = ['id', 'customer', 'date', 'saleline_set',
|
||||
'total', 'payment_method']
|
||||
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['id', 'name', 'price', 'measuring_unit', 'categories']
|
||||
|
||||
|
||||
class CustomerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ['id', 'name', 'address', 'email', 'phone']
|
||||
|
||||
|
||||
class ReconciliationJarSerializer(serializers.ModelSerializer):
|
||||
Sales = SaleSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ReconciliationJar
|
||||
fields = [
|
||||
'id',
|
||||
'date_time',
|
||||
'reconcilier',
|
||||
'cash_taken',
|
||||
'cash_discrepancy',
|
||||
'total_cash_purchases',
|
||||
'Sales',
|
||||
]
|
||||
|
||||
|
||||
class PaymentMethodSerializer(serializers.Serializer):
|
||||
text = serializers.CharField()
|
||||
value = serializers.CharField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
'text': instance[1],
|
||||
'value': instance[0],
|
||||
}
|
||||
|
||||
|
||||
class SaleForRenconciliationSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
date = serializers.DateTimeField()
|
||||
payment_method = serializers.CharField()
|
||||
customer = serializers.SerializerMethodField()
|
||||
total = serializers.SerializerMethodField()
|
||||
|
||||
def get_customer(self, sale):
|
||||
return {
|
||||
'id': sale.customer.id,
|
||||
'name': sale.customer.name,
|
||||
}
|
||||
|
||||
def get_total(self, sale):
|
||||
return sale.get_total()
|
||||
|
||||
|
||||
class ListCustomerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class ListProductSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class SummarySaleLineSerializer(serializers.ModelSerializer):
|
||||
product = ListProductSerializer()
|
||||
|
||||
class Meta:
|
||||
model = SaleLine
|
||||
fields = ['product', 'quantity', 'unit_price', 'description']
|
||||
|
||||
|
||||
class SaleSummarySerializer(serializers.ModelSerializer):
|
||||
customer = ListCustomerSerializer()
|
||||
lines = SummarySaleLineSerializer(many=True, source='saleline_set')
|
||||
|
||||
class Meta:
|
||||
model = Sale
|
||||
fields = ['id', 'date', 'customer', 'payment_method', 'lines']
|
@ -1,37 +0,0 @@
|
||||
nav#main_menu a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: #178E79 ;
|
||||
padding: 10px 20px;
|
||||
min-width: 90%;
|
||||
text-align: center;
|
||||
border: solid #178E79 4px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nav#main_menu a:hover {
|
||||
transition: ease-in-out 0.2s;
|
||||
background-color: #7BDCB5;
|
||||
color: #178E79;
|
||||
}
|
||||
|
||||
nav#main_menu a:active {
|
||||
background-color: #aaa;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
nav#main_menu {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
li{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page_title {
|
||||
color: #04A1E4
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 179 KiB |
@ -1,30 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
var button = document.getElementById('add_line');
|
||||
var formContainer = document.getElementById('formset-container');
|
||||
var totalForms = document.getElementById('id_saleline_set-TOTAL_FORMS');
|
||||
button.addEventListener('click', function(){
|
||||
var newForm = formContainer.querySelector('.form-container').cloneNode(true);
|
||||
var formCount = parseInt(totalForms.value);
|
||||
var regex = new RegExp('__prefix__', 'g');
|
||||
newForm.innerHTML = newForm.innerHTML.replace(regex, formCount);
|
||||
|
||||
var fields = newForm.querySelectorAll('[id^="id_saleline_set-"], [name^="saleline_set-"]');
|
||||
fields.forEach(function(field) {
|
||||
var oldId = field.id;
|
||||
var oldName = field.name;
|
||||
|
||||
if (oldId) {
|
||||
var newId = oldId.replace(/-\d+-/, '-' + formCount + '-');
|
||||
field.id = newId;
|
||||
}
|
||||
if (oldName) {
|
||||
var newName = oldName.replace(/-\d+-/, '-' + formCount + '-');
|
||||
field.name = newName;
|
||||
}
|
||||
});
|
||||
|
||||
formContainer.appendChild(newForm);
|
||||
totalForms.value = formCount + 1;
|
||||
setPriceListeners();
|
||||
});
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
setPriceListeners();
|
||||
|
||||
function setPriceListeners() {
|
||||
document.querySelectorAll('select[id^="id_saleline_set-"][id$="-product"]').forEach((input) => {
|
||||
input.addEventListener('change', (e) => setLinePrice(e));
|
||||
});
|
||||
}
|
||||
|
||||
function setLinePrice(e) {
|
||||
let input = e.target;
|
||||
const idLine = input.id.split('-')[1];
|
||||
const productId = input.value;
|
||||
const priceInput = document.getElementById(`id_saleline_set-${idLine}-unit_price`);
|
||||
|
||||
const product = listProducts.find((product) => product.id == productId);
|
||||
if (product) {
|
||||
priceInput.value = product.price_list;
|
||||
} else {
|
||||
priceInput.value = '';
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
const quantity_lineRegexSelector = `[id^="${idPrefix}"][id$="${quantitySuffix}"]`;
|
||||
const price_lineRegexSelector = `[id^="${idPrefix}"][id$="${priceSuffix}"]`;
|
||||
|
||||
function insertSubtotalField() {
|
||||
// Selecciona la fila de precio unitario para añadir la fila del subtotal después de ella
|
||||
const unitPriceRow = document.querySelector('input[id="id_saleline_set-0-unit_price"]').closest('tr');
|
||||
|
||||
// Crear una nueva fila para el subtotal
|
||||
const subtotalRow = document.createElement('tr');
|
||||
subtotalRow.innerHTML = `
|
||||
<th><label for="id_saleline_set-0-subtotal">Subtotal:</label></th>
|
||||
<td><input type="number" name="saleline_set-0-subtotal" id="id_saleline_set-0-subtotal" readonly></td>
|
||||
`;
|
||||
|
||||
// Insertar la fila del subtotal después de la fila del precio unitario
|
||||
unitPriceRow.after(subtotalRow);
|
||||
}
|
||||
|
||||
function calculateSubtotal(id) {
|
||||
const quantityElement = document.getElementById(`id_saleline_set-${id}-quantity`);
|
||||
const unitPriceElement = document.getElementById(`id_saleline_set-${id}-unit_price`);
|
||||
const subtotalElement = document.getElementById(`id_saleline_set-${id}-subtotal`);
|
||||
|
||||
const quantity = parseFloat(quantityElement.value) || 0;
|
||||
const unitPrice = parseFloat(unitPriceElement.value) || 0;
|
||||
const subtotal = quantity * unitPrice;
|
||||
|
||||
subtotalElement.value = subtotal.toFixed(2);
|
||||
}
|
||||
|
||||
// Inserta el campo subtotal al cargar la página
|
||||
window.addEventListener('load', () => {
|
||||
insertSubtotalField();
|
||||
|
||||
complete_form.addEventListener('change', function(event){
|
||||
const quantityInputs = document.querySelectorAll(quantity_lineRegexSelector);
|
||||
const ids = Array.prototype.map.call(quantityInputs, function(input) {
|
||||
return input.id.match(/\d+/)[0];
|
||||
});
|
||||
|
||||
ids.forEach(function(id) {
|
||||
if (event.target.matches(quantity_lineRegexSelector)) {
|
||||
calculateSubtotal(id);
|
||||
}
|
||||
if (event.target.matches(price_lineRegexSelector)) {
|
||||
calculateSubtotal(id);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
const complete_form = document.getElementById('complete_form_purchase')
|
||||
const quantityLines = document.getElementById('id_quantity_lines');
|
||||
const quantityProducts = document.getElementById('id_quantity_products');
|
||||
const ammountInput = document.getElementById('id_ammount');
|
||||
const idPrefix = 'id_saleline_set-';
|
||||
const quantitySuffix = '-quantity';
|
||||
const priceSuffix = '-unit_price';
|
||||
const quantityRegexSelector = `[id^="${idPrefix}"][id$="${quantitySuffix}"]`;
|
||||
const priceRegexSelector = `[id^="${idPrefix}"][id$="${priceSuffix}"]`;
|
||||
|
||||
complete_form.addEventListener('change', function(event){
|
||||
if (event.target.matches(quantityRegexSelector)) {
|
||||
calculateSummary();
|
||||
}
|
||||
if (event.target.matches(priceRegexSelector)) {
|
||||
calculateSummary();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function calculateSummary() {
|
||||
let quantity = 0;
|
||||
let ammount = 0;
|
||||
const quantityInputs = document.querySelectorAll(quantityRegexSelector);
|
||||
const ids = Array.prototype.map.call(quantityInputs, function(input) {
|
||||
return input.id.match(/\d+/)[0];
|
||||
});
|
||||
ids.forEach(function(id) {
|
||||
let lineQuantity = document.getElementById(`${idPrefix}${id}${quantitySuffix}`)
|
||||
let linePrice = document.getElementById(`${idPrefix}${id}${priceSuffix}`)
|
||||
quantity += parseFloat(lineQuantity.value);
|
||||
ammount += parseFloat(linePrice.value) * parseFloat(lineQuantity.value);
|
||||
});
|
||||
quantityProducts.value = quantity;
|
||||
quantityLines.value = quantityInputs.length;
|
||||
ammountInput.value = ammount;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
{% load static %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full">
|
||||
<head>
|
||||
<title>Don Confiao - Tienda la Ilusión</title>
|
||||
</head>
|
||||
<body class="flex h-full w-full">
|
||||
<div id="menu" class="h-full w-2/12 border bg-green-400 max-h-screen overflow-auto">
|
||||
{% include 'don_confiao/menu.html' %}
|
||||
</div>
|
||||
<div id="content" class="w-10/12 h-screen max-h-full overflow-auto">
|
||||
{% block content %} {% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
<script src="https://cdn.tailwindcss.com/"></script>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
{% if form.is_multipart %}
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% endif %}
|
||||
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Importar">
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,13 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
{% if form.is_multipart %}
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% endif %}
|
||||
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Importar">
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,8 +0,0 @@
|
||||
<h1>Tienda la Ilusión</h1>
|
||||
<h2>Don Confiao</h2>
|
||||
<ul>
|
||||
<li><a href='./comprar'>Comprar</a></li>
|
||||
<li><a href='./productos'>Productos</a></li>
|
||||
<li><a href='./importar_productos'>Importar Productos</a></li>
|
||||
<li><a href='./importar_terceros'>Importar Terceros</a></li>
|
||||
</ul>
|
@ -1,17 +0,0 @@
|
||||
{% load static %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/main_menu.css' %}">
|
||||
|
||||
<div class="h-full flex flex-col justify-around shadow hover:shadow-lg">
|
||||
<img class="w-full px-12" src="{% static 'img/recreo_logo.png' %}" alt="Recreo">
|
||||
<nav id="main_menu">
|
||||
<ul class="flex flex-col m-0 p-0 justify-center shadow hover:shadow-lg gap-y-12 items-center drop-shadow-lg">
|
||||
<li><a href='/don_confiao/comprar' >Comprar</a></li>
|
||||
<li><a href='/don_confiao/compras'>Compras</a></li>
|
||||
<li><a href='/don_confiao/lista_productos'>Productos</a></li>
|
||||
<li><a href='/don_confiao/importar_productos'>Importar Productos</a></li>
|
||||
<li><a href='/don_confiao/importar_terceros'>Importar Terceros</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<p id="page_title" class="text-center decoration-solid font-mono font-bold text-lg page_title">Don Confiao - Tienda la Ilusión</p>
|
||||
</div>
|
||||
<script src="https://cdn.tailwindcss.com/"></script>
|
@ -1,14 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
<form action="" method="get">
|
||||
<label>Filtro por nombre:</label>
|
||||
<input type="text" name="name" value="{{ request.GET.name }}">
|
||||
<button type="submit">Filtrar</button>
|
||||
</form>
|
||||
<h1>Lista de productos</h1>
|
||||
<ul>
|
||||
{% for obj in object_list %}
|
||||
<li>{{ obj.name }} ({{ obj.id }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,39 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
<script>
|
||||
let listProducts = JSON.parse("{{ list_products|escapejs }}");
|
||||
</script>
|
||||
<div class="flex h-full">
|
||||
<div class="h-full w-10/12 flex flex-col p-5">
|
||||
<form id="complete_form_purchase" method="POST" class="h-10/12 w-full max-h-full overflow-auto">
|
||||
{% csrf_token %}
|
||||
{{ linea_formset.management_form }}
|
||||
<div id="formset-container" class="w-full">
|
||||
{% for form in linea_formset %}
|
||||
<div class="form-container flex justify-center ">
|
||||
<table class="w-3/4 my-5 shadow-inner" style="border: solid 1px #178E79;">
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="h-2/12 flex justify-center">
|
||||
<button id="add_line" type="button" class="bg-yellow-400 shadow hover:shadow-lg py-2 px-5 rounded-full font-bold hover:bg-violet-200 ease-in duration-150">Añadir Linea</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-full w-3/12 bg-green-400 p-5 shadow hover:shadow-lg flex flex-col gap-y-3 font-semibold justify-around">
|
||||
<p id="sale_resume_title" class="text-center decoration-solid font-mono font-bold text-xl page_title">Resumen de Venta</p>
|
||||
{{ sale_form }}
|
||||
{{ summary_form }}
|
||||
<button class="font-bold my-10 py-2 px-4 rounded-full bg-yellow-400 shadow hover:shadow-lg hover:bg-violet-200 ease-in duration-150" name="form" type="submit" >Comprar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com/"></script>
|
||||
<script src="{% static 'js/buy_general.js' %}"></script>
|
||||
<script src="{% static 'js/add_line.js' %}"></script>
|
||||
<script src="{% static 'js/sale_summary.js' %}"></script>
|
||||
<script src="{% static 'js/calculate_subtotal_line.js' %}"></script>
|
||||
{% endblock %}
|
@ -1,12 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Resumen de compra</h1>
|
||||
<dl>
|
||||
<dt>Date</dt> <dd>{{ purchase.date }}</dd>
|
||||
<dt>ID</dt> <dd>{{ purchase.id }}</dd>
|
||||
<dt>Customer</dt> <dd>{{ purchase.customer.name }}</dd>
|
||||
<dt>Total</dt> <dd>{{ purchase.get_total }}</dd>
|
||||
</dl>
|
||||
|
||||
{% endblock %}
|
@ -1,14 +0,0 @@
|
||||
{% extends 'don_confiao/base.html' %}
|
||||
{% block content %}
|
||||
|
||||
{% if purchases %}
|
||||
<ul>
|
||||
{% for purchase in purchases %}
|
||||
<li><a href="/don_confiao/resumen_compra/{{ purchase.id }}">{{ purchase.date }}, {{ purchase.customer }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No hay Compras</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,47 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "don_confiao.customer",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Alejandro Fernandez",
|
||||
"address": "Avenida Siempre Viva"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "don_confiao.productcategory",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Unidad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "don_confiao.product",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Papaya",
|
||||
"price": 2500,
|
||||
"measuring_unit": "Unidad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "don_confiao.sale",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"customer": 1,
|
||||
"date": "2024-08-31",
|
||||
"phone": 312201103,
|
||||
"description": "Primera Venta"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "don_confiao.saleline",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"sale": 1,
|
||||
"product": 1,
|
||||
"quantity": 10,
|
||||
"unit_price": 5000,
|
||||
"description": "Primer Sale Line"
|
||||
}
|
||||
}
|
||||
]
|
@ -1 +0,0 @@
|
||||
#!/usr/bin/env python3
|
@ -1,41 +0,0 @@
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from ..models import AdminCode
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class TestAdminCode(TestCase):
|
||||
def setUp(self):
|
||||
self.valid_code = 'some valid code'
|
||||
admin_code = AdminCode()
|
||||
admin_code.value = self.valid_code
|
||||
admin_code.clean()
|
||||
admin_code.save()
|
||||
|
||||
self.client = Client()
|
||||
|
||||
def test_validate_code(self):
|
||||
url = '/don_confiao/api/admin_code/validate/' + self.valid_code
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertTrue(content['validCode'])
|
||||
|
||||
def test_invalid_code(self):
|
||||
invalid_code = 'some invalid code'
|
||||
url = '/don_confiao/api/admin_code/validate/' + invalid_code
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertFalse(content['validCode'])
|
||||
|
||||
def test_empty_code(self):
|
||||
empty_code = ''
|
||||
url = '/don_confiao/api/admin_code/validate/' + empty_code
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
@ -1,59 +0,0 @@
|
||||
import json
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
from ..models import Sale, Product, Customer
|
||||
|
||||
|
||||
class TestAPI(APITestCase):
|
||||
def setUp(self):
|
||||
self.product = Product.objects.create(
|
||||
name='Panela',
|
||||
price=5000,
|
||||
measuring_unit='UNIT'
|
||||
)
|
||||
self.customer = Customer.objects.create(
|
||||
name='Camilo'
|
||||
)
|
||||
|
||||
def test_create_sale(self):
|
||||
response = self._create_sale()
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Sale.objects.count(), 1)
|
||||
sale = Sale.objects.all()[0]
|
||||
self.assertEqual(
|
||||
sale.customer.name,
|
||||
self.customer.name
|
||||
)
|
||||
self.assertEqual(
|
||||
sale.id,
|
||||
content['id']
|
||||
)
|
||||
|
||||
def test_get_products(self):
|
||||
url = '/don_confiao/api/products/'
|
||||
response = self.client.get(url)
|
||||
json_response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(self.product.name, json_response[0]['name'])
|
||||
|
||||
def test_get_customers(self):
|
||||
url = '/don_confiao/api/customers/'
|
||||
response = self.client.get(url)
|
||||
json_response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(self.customer.name, json_response[0]['name'])
|
||||
|
||||
def _create_sale(self):
|
||||
url = '/don_confiao/api/sales/'
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'date': '2024-09-02',
|
||||
'payment_method': 'CASH',
|
||||
'saleline_set': [
|
||||
{'product': self.product.id, 'quantity': 2, 'unit_price': 3000},
|
||||
{'product': self.product.id, 'quantity': 3, 'unit_price': 5000}
|
||||
],
|
||||
}
|
||||
return self.client.post(url, data, format='json')
|
@ -1,22 +0,0 @@
|
||||
from django.test import Client, TestCase
|
||||
from ..models import Product
|
||||
|
||||
|
||||
class TestBuyForm(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.product = Product()
|
||||
self.product.name = "Arroz"
|
||||
self.product.price = 5000
|
||||
self.product.save()
|
||||
|
||||
def test_buy_contains_products_list(self):
|
||||
response = self.client.get('/don_confiao/comprar')
|
||||
self.assertIn(
|
||||
self.product.name,
|
||||
response.context['list_products']
|
||||
)
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertIn('5000', content)
|
||||
self.assertIn('Arroz', content)
|
||||
self.assertIn(str(self.product.id), content)
|
@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from django.test import Client, TestCase
|
||||
from io import StringIO
|
||||
import csv
|
||||
|
||||
|
||||
class TestExportSales(TestCase):
|
||||
fixtures = ['sales_fixture']
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_export_sales(self):
|
||||
sales_response = self._export_sales_csv()
|
||||
filename = sales_response.headers[
|
||||
'Content-Disposition'].split('; ')[1].strip('filename=').strip("'")
|
||||
content = sales_response.content
|
||||
content_str = content.decode('utf-8')
|
||||
csv_file = StringIO(content_str)
|
||||
header = next(csv.reader(csv_file))
|
||||
|
||||
self.assertGreater(len(content), 0)
|
||||
self.assertEqual(filename, 'sales.csv')
|
||||
self.assertEqual(sales_response.headers['Content-Type'], 'text/csv')
|
||||
self.assertEqual(header, self._tryton_sale_header())
|
||||
|
||||
def _export_sales_csv(self):
|
||||
return self.client.get("/don_confiao/exportar_ventas_para_tryton")
|
||||
|
||||
def _tryton_sale_header(self):
|
||||
return [
|
||||
"Tercero",
|
||||
"Dirección de facturación",
|
||||
"Dirección de envío",
|
||||
"Descripción",
|
||||
"Referencia",
|
||||
"Fecha venta",
|
||||
"Plazo de pago",
|
||||
"Almacén",
|
||||
"Moneda",
|
||||
"Líneas/Producto",
|
||||
"Líneas/Cantidad",
|
||||
"Líneas/Precio unitario",
|
||||
"Líneas/Unidad",
|
||||
"Empresa",
|
||||
"Tienda",
|
||||
"Terminal de venta",
|
||||
"Autorecogida",
|
||||
"Comentario"
|
||||
]
|
@ -1,264 +0,0 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.core.exceptions import ValidationError
|
||||
from ..models import Sale, Product, SaleLine, Customer, ReconciliationJar
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class TestJarReconcliation(TestCase):
|
||||
def setUp(self):
|
||||
customer = Customer()
|
||||
customer.name = 'Alejo Mono'
|
||||
customer.save()
|
||||
|
||||
self.client = Client()
|
||||
|
||||
purchase = Sale()
|
||||
purchase.customer = customer
|
||||
purchase.date = "2024-07-30"
|
||||
purchase.payment_method = 'CASH'
|
||||
purchase.clean()
|
||||
purchase.save()
|
||||
|
||||
product = Product()
|
||||
product.name = "cafe"
|
||||
product.price = "72500"
|
||||
product.save()
|
||||
|
||||
line = SaleLine()
|
||||
line.sale = purchase
|
||||
line.product = product
|
||||
line.quantity = "11"
|
||||
line.unit_price = "72500"
|
||||
line.save()
|
||||
self.purchase = purchase
|
||||
|
||||
purchase2 = Sale()
|
||||
purchase2.customer = customer
|
||||
purchase2.date = "2024-07-30"
|
||||
purchase.payment_method = 'CASH'
|
||||
purchase2.clean()
|
||||
purchase2.save()
|
||||
|
||||
line2 = SaleLine()
|
||||
line2.sale = purchase2
|
||||
line2.product = product
|
||||
line2.quantity = "27"
|
||||
line2.unit_price = "72500"
|
||||
line2.save()
|
||||
self.purchase2 = purchase2
|
||||
|
||||
purchase3 = Sale()
|
||||
purchase3.customer = customer
|
||||
purchase3.date = "2024-07-30"
|
||||
purchase3.payment_method = 'CASH'
|
||||
purchase3.clean()
|
||||
purchase3.save()
|
||||
|
||||
line3 = SaleLine()
|
||||
line3.sale = purchase3
|
||||
line3.product = product
|
||||
line3.quantity = "37"
|
||||
line3.unit_price = "72500"
|
||||
line3.save()
|
||||
self.purchase3 = purchase3
|
||||
|
||||
purchase4 = Sale()
|
||||
purchase4.customer = customer
|
||||
purchase4.date = "2024-07-30"
|
||||
purchase4.payment_method = 'CONFIAR'
|
||||
purchase4.clean()
|
||||
purchase4.save()
|
||||
|
||||
line4 = SaleLine()
|
||||
line4.sale = purchase4
|
||||
line4.product = product
|
||||
line4.quantity = "47"
|
||||
line4.unit_price = "72500"
|
||||
line4.save()
|
||||
self.purchase4 = purchase4
|
||||
|
||||
def test_create_reconciliation_jar(self):
|
||||
reconciliation = self._create_simple_reconciliation()
|
||||
self.assertTrue(isinstance(reconciliation, ReconciliationJar))
|
||||
|
||||
def test_get_purchases_for_reconciliation(self):
|
||||
# link purchase to reconciliation to exclude from list
|
||||
reconciliation = self._create_simple_reconciliation()
|
||||
self.purchase3.reconciliation = reconciliation
|
||||
self.purchase3.clean()
|
||||
self.purchase3.save()
|
||||
|
||||
url = '/don_confiao/purchases/for_reconciliation'
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
rawContent = response.content.decode('utf-8')
|
||||
content = json.loads(rawContent)
|
||||
|
||||
self.assertIn('CASH', content.keys())
|
||||
self.assertIn('CONFIAR', content.keys())
|
||||
self.assertEqual(2, len(content.get('CASH')))
|
||||
self.assertEqual(1, len(content.get('CONFIAR')))
|
||||
self.assertNotIn(str(37*72500), rawContent)
|
||||
self.assertIn(str(47*72500), rawContent)
|
||||
|
||||
def test_don_create_reconcialiation_with_bad_numbers(self):
|
||||
reconciliation = ReconciliationJar()
|
||||
reconciliation.date_time = "2024-07-30"
|
||||
reconciliation.total_cash_purchases = 145000
|
||||
reconciliation.cash_taken = 143000
|
||||
reconciliation.cash_discrepancy = 1000
|
||||
with self.assertRaises(ValidationError):
|
||||
reconciliation.clean()
|
||||
reconciliation.save()
|
||||
|
||||
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(self):
|
||||
url = '/don_confiao/reconciliate_jar'
|
||||
total_purchases = (11 * 72500) + (27 * 72500)
|
||||
bad_total_purchases = total_purchases + 2
|
||||
data = {
|
||||
'date_time': '2024-12-02T21:07',
|
||||
'reconcilier': 'carlos',
|
||||
'total_cash_purchases': bad_total_purchases,
|
||||
'cash_taken': total_purchases,
|
||||
'cash_discrepancy': 0,
|
||||
'cash_purchases': [
|
||||
self.purchase.id,
|
||||
self.purchase2.id,
|
||||
self.purchase.id,
|
||||
],
|
||||
}
|
||||
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
||||
content_type='application/json')
|
||||
rawContent = response.content.decode('utf-8')
|
||||
content = json.loads(rawContent)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('error', content)
|
||||
self.assertIn('total_cash_purchases', content['error'])
|
||||
|
||||
def test_create_reconciliation_with_purchases(self):
|
||||
response = self._create_reconciliation_with_purchase()
|
||||
|
||||
rawContent = response.content.decode('utf-8')
|
||||
content = json.loads(rawContent)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('id', content)
|
||||
|
||||
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
||||
self.assertEqual(len(purchases), 2)
|
||||
|
||||
def test_create_reconciliation_with_purchases_and_other_totals(self):
|
||||
url = '/don_confiao/reconciliate_jar'
|
||||
total_purchases = (11 * 72500) + (27 * 72500)
|
||||
data = {
|
||||
'date_time': '2024-12-02T21:07',
|
||||
'reconcilier': 'carlos',
|
||||
'total_cash_purchases': total_purchases,
|
||||
'cash_taken': total_purchases,
|
||||
'cash_discrepancy': 0,
|
||||
'cash_purchases': [
|
||||
self.purchase.id,
|
||||
self.purchase2.id,
|
||||
],
|
||||
'other_totals': {
|
||||
'Confiar': {
|
||||
'total': (47 * 72500) + 1,
|
||||
'purchases': [self.purchase4.id],
|
||||
},
|
||||
},
|
||||
}
|
||||
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
||||
content_type='application/json')
|
||||
|
||||
rawContent = response.content.decode('utf-8')
|
||||
content = json.loads(rawContent)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('id', content)
|
||||
|
||||
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
||||
self.assertEqual(len(purchases), 3)
|
||||
|
||||
def test_list_reconciliations(self):
|
||||
self._create_simple_reconciliation()
|
||||
self._create_simple_reconciliation()
|
||||
|
||||
url = '/don_confiao/api/reconciliate_jar/'
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(2, content['count'])
|
||||
self.assertEqual(2, len(content['results']))
|
||||
self.assertEqual('2024-07-30T00:00:00Z',
|
||||
content['results'][0]['date_time'])
|
||||
|
||||
def test_list_reconciliations_pagination(self):
|
||||
self._create_simple_reconciliation()
|
||||
self._create_simple_reconciliation()
|
||||
|
||||
url = '/don_confiao/api/reconciliate_jar/?page=2&page_size=1'
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(1, len(content['results']))
|
||||
self.assertEqual('2024-07-30T00:00:00Z',
|
||||
content['results'][0]['date_time'])
|
||||
|
||||
def test_get_single_reconciliation(self):
|
||||
createResponse = self._create_reconciliation_with_purchase()
|
||||
reconciliationId = json.loads(
|
||||
createResponse.content.decode('utf-8')
|
||||
)['id']
|
||||
self.assertGreater(reconciliationId, 0)
|
||||
|
||||
url = f'/don_confiao/api/reconciliate_jar/{reconciliationId}/'
|
||||
response = self.client.get(url, content_type='application/json')
|
||||
content = json.loads(
|
||||
response.content.decode('utf-8')
|
||||
)
|
||||
self.assertEqual(reconciliationId, content['id'])
|
||||
self.assertGreater(len(content['Sales']), 0)
|
||||
self.assertIn(
|
||||
self.purchase.id,
|
||||
[sale['id'] for sale in content['Sales']]
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
'CASH',
|
||||
[sale['payment_method'] for sale in content['Sales']]
|
||||
)
|
||||
|
||||
def _create_simple_reconciliation(self):
|
||||
reconciliation = ReconciliationJar()
|
||||
reconciliation.date_time = "2024-07-30"
|
||||
reconciliation.total_cash_purchases = 0
|
||||
reconciliation.cash_taken = 0
|
||||
reconciliation.cash_discrepancy = 0
|
||||
reconciliation.clean()
|
||||
reconciliation.save()
|
||||
return reconciliation
|
||||
|
||||
def _create_reconciliation_with_purchase(self):
|
||||
url = '/don_confiao/reconciliate_jar'
|
||||
total_purchases = (11 * 72500) + (27 * 72500)
|
||||
data = {
|
||||
'date_time': '2024-12-02T21:07',
|
||||
'reconcilier': 'carlos',
|
||||
'total_cash_purchases': total_purchases,
|
||||
'cash_taken': total_purchases,
|
||||
'cash_discrepancy': 0,
|
||||
'cash_purchases': [
|
||||
self.purchase.id,
|
||||
self.purchase2.id,
|
||||
self.purchase.id,
|
||||
],
|
||||
}
|
||||
return self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
||||
content_type='application/json')
|
@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from django.test import TestCase
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
from ..models import Customer
|
||||
|
||||
|
||||
class TestCustomer(TestCase):
|
||||
|
||||
def test_create_customer(self):
|
||||
customer = Customer()
|
||||
customer.name = "Don Confiado Gonzalez"
|
||||
customer.address = "Pueblo Bonito"
|
||||
customer.save()
|
||||
|
||||
self.assertIsInstance(customer, Customer)
|
||||
|
||||
def test_don_create_customer_without_name(self):
|
||||
customer = Customer()
|
||||
with self.assertRaises(IntegrityError):
|
||||
customer.save()
|
@ -1,23 +0,0 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
# from ..models import PaymentMethods
|
||||
|
||||
class TestPaymentMethods(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_keys_in_payment_methods_to_select(self):
|
||||
response = self.client.get(
|
||||
'/don_confiao/payment_methods/all/select_format'
|
||||
)
|
||||
methods = response.json()
|
||||
for method in methods:
|
||||
self.assertEqual(set(method.keys()), {'text', 'value'})
|
||||
|
||||
def test_basic_payment_methods_to_select(self):
|
||||
methods = self.client.get(
|
||||
'/don_confiao/payment_methods/all/select_format'
|
||||
).json()
|
||||
self.assertIn('CASH', [method.get('value') for method in methods])
|
||||
self.assertIn('CONFIAR', [method.get('value') for method in methods])
|
||||
self.assertIn('BANCOLOMBIA', [method.get('value') for method in methods])
|
@ -1,104 +0,0 @@
|
||||
from django.test import Client, TestCase
|
||||
from django.conf import settings
|
||||
from ..models import ProductCategory, Product
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
class TestProducts(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_import_products(self):
|
||||
self._import_csv()
|
||||
all_products = self._get_products()
|
||||
self.assertEqual(
|
||||
len(all_products),
|
||||
3
|
||||
)
|
||||
|
||||
def test_import_products_with_categories(self):
|
||||
self._import_csv()
|
||||
all_products = self._get_products()
|
||||
self.assertIn("Aceites", all_products[0]["categories"])
|
||||
|
||||
def test_don_repeat_categories_on_import(self):
|
||||
self._import_csv()
|
||||
categories_on_csv = ["Cafes", "Alimentos", "Aceites"]
|
||||
categories = ProductCategory.objects.all()
|
||||
self.assertCountEqual(
|
||||
[c.name for c in categories],
|
||||
categories_on_csv
|
||||
)
|
||||
|
||||
def test_update_products(self):
|
||||
self._import_csv()
|
||||
first_products = self._get_products()
|
||||
self._import_csv('example_products2.csv')
|
||||
seconds_products = self._get_products()
|
||||
self.assertEqual(len(first_products), len(seconds_products))
|
||||
|
||||
def test_preserve_id_on_import(self):
|
||||
self._import_csv()
|
||||
id_aceite = Product.objects.get(name='Aceite').id
|
||||
self._import_csv('example_products2.csv')
|
||||
id_post_updated = Product.objects.get(name='Aceite').id
|
||||
self.assertEqual(id_aceite, id_post_updated)
|
||||
|
||||
def test_update_categories_on_import(self):
|
||||
self._import_csv()
|
||||
first_products = self._get_products()
|
||||
first_categories = {p["name"]: p["categories"] for p in first_products}
|
||||
self._import_csv('example_products2.csv')
|
||||
updated_products = self._get_products()
|
||||
updated_categories = {
|
||||
p["name"]: p["categories"] for p in updated_products}
|
||||
|
||||
self.assertIn('Cafes', first_categories['Arroz'])
|
||||
self.assertNotIn('Granos', first_categories['Arroz'])
|
||||
|
||||
self.assertIn('Granos', updated_categories['Arroz'])
|
||||
self.assertNotIn('Cafes', updated_categories['Arroz'])
|
||||
|
||||
def test_update_price(self):
|
||||
self._import_csv()
|
||||
first_products = self._get_products()
|
||||
first_prices = {p["name"]: p["price_list"] for p in first_products}
|
||||
expected_first_prices = {
|
||||
"Aceite": '50000.00',
|
||||
"Café": '14000.00',
|
||||
"Arroz": '7000.00'
|
||||
}
|
||||
self.assertDictEqual(
|
||||
expected_first_prices,
|
||||
first_prices
|
||||
)
|
||||
|
||||
self._import_csv('example_products2.csv')
|
||||
updated_products = self._get_products()
|
||||
updated_prices = {p["name"]: p["price_list"] for p in updated_products}
|
||||
expected_updated_prices = {
|
||||
"Aceite": '50000.00',
|
||||
"Café": '15000.00',
|
||||
"Arroz": '6000.00'
|
||||
}
|
||||
|
||||
self.assertDictEqual(
|
||||
expected_updated_prices,
|
||||
updated_prices
|
||||
)
|
||||
|
||||
def _get_products(self):
|
||||
products_response = self.client.get("/don_confiao/productos")
|
||||
return json.loads(products_response.content.decode('utf-8'))
|
||||
|
||||
def _import_csv(self, csv_file='example_products.csv'):
|
||||
app_name = "don_confiao"
|
||||
app_dir = os.path.join(settings.BASE_DIR, app_name)
|
||||
example_csv = os.path.join(app_dir, csv_file)
|
||||
with open(example_csv, 'rb') as csv:
|
||||
self.client.post(
|
||||
"/don_confiao/importar_productos",
|
||||
{"csv_file": csv}
|
||||
)
|
@ -1,51 +0,0 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from ..models import Payment, Sale, Product, Customer
|
||||
|
||||
|
||||
class TestPurchaseWithPayment(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.product = Product()
|
||||
self.product.name = "Arroz"
|
||||
self.product.price = 5000
|
||||
self.product.save()
|
||||
customer = Customer()
|
||||
customer.name = "Noelba Lopez"
|
||||
customer.save()
|
||||
self.customer = customer
|
||||
|
||||
def test_generate_payment_when_it_has_payment(self):
|
||||
quantity = 2
|
||||
unit_price = 2500
|
||||
total = 5000
|
||||
self.client.post(
|
||||
'/don_confiao/comprar',
|
||||
{
|
||||
"customer": str(self.customer.id),
|
||||
"date": "2024-07-27",
|
||||
"phone": "3010101000",
|
||||
"description": "Venta de contado",
|
||||
"saleline_set-TOTAL_FORMS": "1",
|
||||
"saleline_set-INITIAL_FORMS": "0",
|
||||
"saleline_set-MIN_NUM_FORMS": "0",
|
||||
"saleline_set-MAX_NUM_FORMS": "1000",
|
||||
"saleline_set-0-product": str(self.product.id),
|
||||
"saleline_set-0-quantity": str(quantity),
|
||||
"saleline_set-0-unit_price": str(unit_price),
|
||||
"saleline_set-0-description": "Linea de Venta",
|
||||
"saleline_set-0-sale": "",
|
||||
"saleline_set-0-id": "",
|
||||
"quantity_lines": "1",
|
||||
"quantity_products": str(quantity),
|
||||
"ammount": str(quantity * unit_price),
|
||||
"payment_method": "CASH",
|
||||
}
|
||||
)
|
||||
|
||||
purchases = Sale.objects.all()
|
||||
self.assertEqual(1, len(purchases))
|
||||
payments = Payment.objects.all()
|
||||
self.assertEqual(1, len(payments))
|
||||
self.assertEqual(total, payments[0].amount)
|
||||
self.assertEqual('CASH', payments[0].type_payment)
|
@ -1,53 +0,0 @@
|
||||
from django.test import TestCase, Client
|
||||
from ..models import Sale, Product, SaleLine, Customer
|
||||
|
||||
|
||||
class TestSummaryViewPurchase(TestCase):
|
||||
def setUp(self):
|
||||
customer = Customer()
|
||||
customer.name = 'Alejo Mono'
|
||||
customer.save()
|
||||
|
||||
self.client = Client()
|
||||
purchase = Sale()
|
||||
purchase.customer = customer
|
||||
purchase.date = "2024-07-30"
|
||||
purchase.clean()
|
||||
purchase.save()
|
||||
|
||||
product = Product()
|
||||
product.name = "cafe"
|
||||
product.price = "72500"
|
||||
product.save()
|
||||
|
||||
line = SaleLine()
|
||||
line.sale = purchase
|
||||
line.product = product
|
||||
line.quantity = "11"
|
||||
line.unit_price = "72500"
|
||||
line.save()
|
||||
self.purchase = purchase
|
||||
|
||||
def test_summary_has_customer(self):
|
||||
url = "/don_confiao/resumen_compra/" + str(self.purchase.id)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.context["purchase"].customer,
|
||||
self.purchase.customer
|
||||
)
|
||||
self.assertIn('Alejo Mono', response.content.decode('utf-8'))
|
||||
|
||||
def test_json_summary(self):
|
||||
url = f"/don_confiao/resumen_compra_json/{self.purchase.id}"
|
||||
response = self.client.get(url)
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('Alejo Mono', content)
|
||||
self.assertIn('cafe', content)
|
||||
self.assertIn('72500', content)
|
||||
self.assertIn('quantity', content)
|
||||
self.assertIn('11', content)
|
||||
self.assertIn('date', content)
|
||||
self.assertIn(self.purchase.date, content)
|
||||
self.assertIn('lines', content)
|
@ -1,96 +0,0 @@
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ..models import Customer, Product, Sale, SaleLine
|
||||
|
||||
|
||||
class ConfiaoTest(TestCase):
|
||||
def setUp(self):
|
||||
self.product = Product()
|
||||
self.product.name = "Pepino"
|
||||
self.product.price = 5000
|
||||
self.product.save()
|
||||
|
||||
self.customer = Customer()
|
||||
self.customer.name = "Don Confiao Gonzalez"
|
||||
self.customer.address = "Patio Bonito"
|
||||
self.customer.save()
|
||||
|
||||
def test_create_sale(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22 12:05:00"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
sale.save()
|
||||
|
||||
self.assertIsInstance(sale, Sale)
|
||||
|
||||
def test_can_create_sale_without_payment_method(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22 12:05:00"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
sale.payment_method = ''
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
sale.full_clean()
|
||||
|
||||
def test_create_sale_line(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
|
||||
line = SaleLine()
|
||||
line.sale = sale
|
||||
line.product = self.product
|
||||
line.quantity = 2
|
||||
line.unit_price = 2500
|
||||
line.amount = 5000
|
||||
sale.save()
|
||||
line.save()
|
||||
self.assertEqual(SaleLine.objects.all()[0].quantity, 2)
|
||||
|
||||
def test_create_sale_with_lines(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
|
||||
line1 = SaleLine()
|
||||
line1.sale = sale
|
||||
line1.product = self.product
|
||||
line1.quantity = 2
|
||||
line1.unit_price = 2500
|
||||
line1.amount = 5000
|
||||
|
||||
line2 = SaleLine()
|
||||
line2.sale = sale
|
||||
line2.product = self.product
|
||||
line2.quantity = 2
|
||||
line2.unit_price = 2500
|
||||
line2.amount = 5000
|
||||
|
||||
sale.save()
|
||||
line1.save()
|
||||
line2.save()
|
||||
|
||||
self.assertEqual(len(SaleLine.objects.all()), 2)
|
||||
self.assertEqual(
|
||||
Sale.objects.all()[0].saleline_set.all()[0].quantity,
|
||||
2
|
||||
)
|
||||
|
||||
def test_allow_sale_without_description(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22"
|
||||
sale.phone = '666666666'
|
||||
sale.description = None
|
||||
sale.save()
|
||||
|
||||
self.assertEqual(len(Sale.objects.all()), 1)
|
@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from django.test import TestCase
|
||||
from ..forms import PurchaseForm
|
||||
from ..models import Customer
|
||||
|
||||
_csrf_token = \
|
||||
"bVjBevJRavxRPFOlVgAWiyh9ceuiwPlyEcmbPZprNuCGHjFZRKZrBeunJvKTRgOx"
|
||||
|
||||
|
||||
class PurchaseFormTest(TestCase):
|
||||
def setUp(self):
|
||||
self.customer = Customer()
|
||||
self.customer.name = "Don Confiao Gonzalez"
|
||||
self.customer.address = "Patio Bonito"
|
||||
self.customer.save()
|
||||
|
||||
def test_add_purchase(self):
|
||||
form_data = {
|
||||
"csrfmiddlewaretoken": _csrf_token,
|
||||
"customer": self.customer.id,
|
||||
"date": "2024-08-03",
|
||||
"payment_method": "CASH",
|
||||
"phone": "sfasfd",
|
||||
"description": "dasdadad",
|
||||
"saleline_set-TOTAL_FORMS": "1",
|
||||
"saleline_set-INITIAL_FORMS": "0",
|
||||
"saleline_set-MIN_NUM_FORMS": "0",
|
||||
"saleline_set-MAX_NUM_FORMS": "1000",
|
||||
"saleline_set-0-product": "5",
|
||||
"saleline_set-0-quantity": "1",
|
||||
"saleline_set-0-unit_price": "500",
|
||||
"saleline_set-0-description": "afasdfasdf",
|
||||
"saleline_set-0-sale": "",
|
||||
"saleline_set-0-id": "",
|
||||
"quantity_lines": "1",
|
||||
"quantity_products": "1",
|
||||
"ammount": "500",
|
||||
"form": ""
|
||||
}
|
||||
|
||||
purchase_form = PurchaseForm(data=form_data)
|
||||
purchase_form.is_valid()
|
||||
|
||||
# raise Exception(purchase_form)
|
||||
self.assertTrue(purchase_form.is_valid())
|
@ -1,34 +0,0 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from . import views
|
||||
from . import api_views
|
||||
|
||||
app_name = 'don_confiao'
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'sales', api_views.SaleView, basename='sale')
|
||||
router.register(r'customers', api_views.CustomerView, basename='customer')
|
||||
router.register(r'products', api_views.ProductView, basename='product')
|
||||
router.register(r'reconciliate_jar', api_views.ReconciliateJarModelView,
|
||||
basename='reconciliate_jar')
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.index, name="wellcome"),
|
||||
path("comprar", views.buy, name="buy"),
|
||||
path("compras", views.purchases, name="purchases"),
|
||||
path("productos", views.products, name="products"),
|
||||
path("lista_productos", views.ProductListView.as_view(), name='product_list'),
|
||||
path("importar_productos", views.import_products, name="import_products"),
|
||||
path("importar_terceros", views.import_customers, name="import_customers"),
|
||||
path("exportar_ventas_para_tryton",
|
||||
views.exportar_ventas_para_tryton,
|
||||
name="exportar_ventas_para_tryton"),
|
||||
path("resumen_compra/<int:id>", views.purchase_summary, name="purchase_summary"),
|
||||
path("resumen_compra_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"),
|
||||
path("payment_methods/all/select_format", api_views.PaymentMethodView.as_view(), name="payment_methods_to_select"),
|
||||
path('purchases/for_reconciliation', api_views.SalesForReconciliationView.as_view(), name='sales_for_reconciliation'),
|
||||
path('reconciliate_jar', api_views.ReconciliateJarView.as_view()),
|
||||
path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()),
|
||||
path('api/', include(router.urls)),
|
||||
]
|
@ -1,239 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
||||
from django.views.generic import ListView
|
||||
from django.db import transaction
|
||||
|
||||
from .models import (
|
||||
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
|
||||
from .forms import (
|
||||
ImportProductsForm,
|
||||
ImportCustomersForm,
|
||||
PurchaseForm,
|
||||
SaleLineFormSet,
|
||||
PurchaseSummaryForm)
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class DecimalEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
def index(request):
|
||||
return render(request, 'don_confiao/index.html')
|
||||
|
||||
|
||||
def buy(request):
|
||||
if request.method == "POST":
|
||||
sale_form = PurchaseForm(request.POST)
|
||||
line_formset = SaleLineFormSet(request.POST)
|
||||
sale_summary_form = PurchaseSummaryForm(request.POST)
|
||||
forms_are_valid = all([
|
||||
sale_form.is_valid(),
|
||||
line_formset.is_valid(),
|
||||
sale_summary_form.is_valid()
|
||||
])
|
||||
payment_method = request.POST.get('payment_method')
|
||||
valid_payment_methods = [PaymentMethods.CASH]
|
||||
valid_payment_method = payment_method in valid_payment_methods
|
||||
if forms_are_valid:
|
||||
with transaction.atomic():
|
||||
sale = sale_form.save()
|
||||
line_formset.instance = sale
|
||||
line_formset.save()
|
||||
Payment.total_payment_from_sale(
|
||||
payment_method,
|
||||
sale
|
||||
)
|
||||
return HttpResponseRedirect("compras")
|
||||
else:
|
||||
sale_form = PurchaseForm()
|
||||
line_formset = SaleLineFormSet()
|
||||
sale_summary_form = PurchaseSummaryForm()
|
||||
return render(
|
||||
request,
|
||||
'don_confiao/purchase.html',
|
||||
{
|
||||
'sale_form': sale_form,
|
||||
'linea_formset': line_formset,
|
||||
'summary_form': sale_summary_form,
|
||||
'list_products': json.dumps(Product.to_list(), cls=DecimalEncoder),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def purchases(request):
|
||||
purchases = Sale.objects.all()
|
||||
context = {
|
||||
"purchases": purchases,
|
||||
}
|
||||
return render(request, 'don_confiao/purchases.html', context)
|
||||
|
||||
|
||||
def products(request):
|
||||
return JsonResponse(Product.to_list(), safe=False)
|
||||
|
||||
|
||||
def import_products(request):
|
||||
if request.method == "POST":
|
||||
form = ImportProductsForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
handle_import_products_file(request.FILES["csv_file"])
|
||||
return HttpResponseRedirect("productos")
|
||||
else:
|
||||
form = ImportProductsForm()
|
||||
return render(
|
||||
request,
|
||||
"don_confiao/import_products.html",
|
||||
{'form': form}
|
||||
)
|
||||
|
||||
|
||||
def import_customers(request):
|
||||
if request.method == "POST":
|
||||
form = ImportCustomersForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
handle_import_customers_file(request.FILES["csv_file"])
|
||||
return HttpResponseRedirect("productos")
|
||||
else:
|
||||
form = ImportCustomersForm()
|
||||
return render(
|
||||
request,
|
||||
"don_confiao/import_customers.html",
|
||||
{'form': form}
|
||||
)
|
||||
|
||||
|
||||
def reconciliations(request):
|
||||
return HttpResponse('<h1>Reconciliaciones</h1>')
|
||||
|
||||
|
||||
def purchase_summary(request, id):
|
||||
purchase = Sale.objects.get(pk=id)
|
||||
return render(
|
||||
request,
|
||||
"don_confiao/purchase_summary.html",
|
||||
{
|
||||
"purchase": purchase
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _categories_from_csv_string(categories_string, separator="&"):
|
||||
categories = categories_string.split(separator)
|
||||
clean_categories = [c.strip() for c in categories]
|
||||
return [_category_from_name(category) for category in clean_categories]
|
||||
|
||||
|
||||
def _category_from_name(name):
|
||||
return ProductCategory.objects.get_or_create(name=name)[0]
|
||||
|
||||
|
||||
def handle_import_products_file(csv_file):
|
||||
data = io.StringIO(csv_file.read().decode('utf-8'))
|
||||
reader = csv.DictReader(data, quotechar='"')
|
||||
for row in reader:
|
||||
product, created = Product.objects.update_or_create(
|
||||
name=row['producto'],
|
||||
defaults={
|
||||
'price': row['precio'],
|
||||
'measuring_unit': row['unidad']
|
||||
}
|
||||
)
|
||||
categories = _categories_from_csv_string(row["categorias"])
|
||||
product.categories.clear()
|
||||
for category in categories:
|
||||
product.categories.add(category)
|
||||
|
||||
def handle_import_customers_file(csv_file):
|
||||
data = io.StringIO(csv_file.read().decode('utf-8'))
|
||||
reader = csv.DictReader(data, quotechar='"')
|
||||
for row in reader:
|
||||
customer, created = Customer.objects.update_or_create(
|
||||
name=row['nombre'],
|
||||
defaults={
|
||||
'email': row['correo'],
|
||||
'phone': row['telefono']
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exportar_ventas_para_tryton(request):
|
||||
tryton_sales_header = [
|
||||
"Tercero",
|
||||
"Dirección de facturación",
|
||||
"Dirección de envío",
|
||||
"Descripción",
|
||||
"Referencia",
|
||||
"Fecha venta",
|
||||
"Plazo de pago",
|
||||
"Almacén",
|
||||
"Moneda",
|
||||
"Líneas/Producto",
|
||||
"Líneas/Cantidad",
|
||||
"Líneas/Precio unitario",
|
||||
"Líneas/Unidad",
|
||||
"Empresa",
|
||||
"Tienda",
|
||||
"Terminal de venta",
|
||||
"Autorecogida",
|
||||
"Comentario"
|
||||
]
|
||||
|
||||
if request.method == "GET":
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = "attachment; filename=sales.csv"
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(tryton_sales_header)
|
||||
|
||||
sales = Sale.objects.all()
|
||||
|
||||
for sale in sales:
|
||||
sale_lines = SaleLine.objects.filter(sale=sale.id)
|
||||
if not sale_lines:
|
||||
continue
|
||||
lines = []
|
||||
first_sale_line = sale_lines[0]
|
||||
customer_info = [sale.customer.name] * 3 + [sale.description] * 2
|
||||
first_line = customer_info + [
|
||||
sale.date,
|
||||
"Contado",
|
||||
"Almacén",
|
||||
"Peso colombiano",
|
||||
first_sale_line.product.name,
|
||||
first_sale_line.quantity,
|
||||
"Unidad",
|
||||
first_sale_line.unit_price,
|
||||
"TIENDA LA ILUSIÓN",
|
||||
"Tienda La Ilusion",
|
||||
"La Ilusion",
|
||||
True,
|
||||
sale.description]
|
||||
lines.append(first_line)
|
||||
for line in sale_lines[1:]:
|
||||
lines.append([""]*9+[
|
||||
line.product.name,
|
||||
line.quantity,
|
||||
line.unit_price,
|
||||
"Unidad"]+[""]*5)
|
||||
for row in lines:
|
||||
writer.writerow(row)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class ProductListView(ListView):
|
||||
model = Product
|
||||
template_model = 'don_confiao/product_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
name = self.request.GET.get('name')
|
||||
if name:
|
||||
return Product.objects.filter(name__icontains=name)
|
||||
return Product.objects.all()
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,16 +0,0 @@
|
||||
"""
|
||||
ASGI config for tienda_ilusion project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings')
|
||||
|
||||
application = get_asgi_application()
|
@ -1,128 +0,0 @@
|
||||
"""
|
||||
Django settings for tienda_ilusion project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.0.6.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'don_confiao.apps.DonConfiaoConfig',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
# 'don_confiao'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'tienda_ilusion.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'tienda_ilusion.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
FIXTURE_DIRS = ['don_confiao/tests/Fixtures']
|
@ -1,26 +0,0 @@
|
||||
"""
|
||||
URL configuration for tienda_ilusion project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
|
||||
app_name = "don_confiao"
|
||||
|
||||
urlpatterns = [
|
||||
path("don_confiao/", include("don_confiao.urls")),
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
"""
|
||||
WSGI config for tienda_ilusion project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings')
|
||||
|
||||
application = get_wsgi_application()
|
Loading…
Reference in New Issue
Block a user