refactor: split models into modules, remove template-based views, and clean up code style
- Split monolithic models.py into models/ package (customers, products, sales, payments, admin) - Removed forms.py, all HTML templates, and associated template-based views - Added api/ package with CatalogSaleView placeholder - Updated all imports across project to use new model paths - Removed obsolete tests (form, export, purchase, summary tests) - Removed template-based URL patterns, kept only API endpoints - Standardized string quotes (single to double) and reformatted code
This commit is contained in:
@@ -20,7 +20,7 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:7001,http://localhos
|
|||||||
# No additional DB configuration needed for SQLite
|
# No additional DB configuration needed for SQLite
|
||||||
|
|
||||||
# Tryton ERP Configuration
|
# Tryton ERP Configuration
|
||||||
TRYTON_HOST=localhost
|
TRYTON_HOST=recreo.onecluster.com.co
|
||||||
TRYTON_DATABASE=tryton
|
TRYTON_DATABASE=ilusion_staging
|
||||||
TRYTON_USERNAME=admin
|
TRYTON_USERNAME=alejandro.ayala
|
||||||
TRYTON_PASSWORD=admin
|
TRYTON_PASSWORD=cl4v3alejo
|
||||||
|
|||||||
@@ -9,44 +9,45 @@ post /token/
|
|||||||
|
|
||||||
{
|
{
|
||||||
"username": "admin",
|
"username": "admin",
|
||||||
"password": "123"
|
"password": "admin"
|
||||||
}
|
}
|
||||||
**** respuesta
|
**** respuesta
|
||||||
#+begin_src json
|
#+begin_src json
|
||||||
{
|
{
|
||||||
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3MTE4NzYxOSwiaWF0IjoxNzcxMTAxMjE5LCJqdGkiOiI5ZTgzNGRlM2QzMmQ0NmQyODEwZGQ2MjI2ODUwNjgzNyIsInVzZXJfaWQiOiIyIn0.JaUOqEAZ2T8vVT36mXfweMmYjEWsP7toD07jeeyrl1k",
|
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3ODU1Njc5MywiaWF0IjoxNzc4NDcwMzkzLCJqdGkiOiJlMDU0NTVkNWExYzA0YjFkYWZhNWZkNzFkZGM5Mzc1NyIsInVzZXJfaWQiOiIxIn0.wZcbBrGoxDMPjZxI-GR1GTAuRtzU4qaT0rgGS5Oblf4",
|
||||||
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzMDE5LCJpYXQiOjE3NzExMDEyMTksImp0aSI6ImFmOWFjNGM1MzBiZjQ4ZGE4Yzg2MWFjYzIzNjQ3NjU3IiwidXNlcl9pZCI6IjIifQ.6wH5sx1fyFn3Wt3DVZGYbiYi79rGthUZkgGmTqzebXc"
|
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA"
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
*** Perfil de usuario
|
*** Perfil de usuario
|
||||||
get /users/me/
|
get /users/me/
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzMDE5LCJpYXQiOjE3NzExMDEyMTksImp0aSI6ImFmOWFjNGM1MzBiZjQ4ZGE4Yzg2MWFjYzIzNjQ3NjU3IiwidXNlcl9pZCI6IjIifQ.6wH5sx1fyFn3Wt3DVZGYbiYi79rGthUZkgGmTqzebXc
|
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMTkzLCJpYXQiOjE3Nzg0NzAzOTMsImp0aSI6IjQxMzAxZjM4ZjUzMTQ2MTI4NTQ3NDk5NzI5YTBkNDBkIiwidXNlcl9pZCI6IjEifQ.mhKoW9vxCoS6J40lYZnr7xm-Qik9gyqZmJTzvsxGe1s
|
||||||
**** Respuesta
|
**** Respuesta
|
||||||
#+begin_src json
|
#+begin_src json
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 1,
|
||||||
"username": "admin",
|
"username": "admin",
|
||||||
"email": "correo@example.com",
|
"email": "admin@admin.org",
|
||||||
"first_name": "",
|
"first_name": "",
|
||||||
"last_name": ""
|
"last_name": "",
|
||||||
|
"role": "administrator"
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
*** Renovar token
|
*** Renovar token
|
||||||
post /token/refresh/
|
post /token/refresh/
|
||||||
|
|
||||||
{
|
{
|
||||||
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3MTE4NzYxOSwiaWF0IjoxNzcxMTAxMjE5LCJqdGkiOiI5ZTgzNGRlM2QzMmQ0NmQyODEwZGQ2MjI2ODUwNjgzNyIsInVzZXJfaWQiOiIyIn0.JaUOqEAZ2T8vVT36mXfweMmYjEWsP7toD07jeeyrl1k"
|
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3ODU1Njc5MywiaWF0IjoxNzc4NDcwMzkzLCJqdGkiOiJlMDU0NTVkNWExYzA0YjFkYWZhNWZkNzFkZGM5Mzc1NyIsInVzZXJfaWQiOiIxIn0.wZcbBrGoxDMPjZxI-GR1GTAuRtzU4qaT0rgGS5Oblf4"
|
||||||
}
|
}
|
||||||
**** response
|
**** response
|
||||||
#+begin_src json
|
#+begin_src json
|
||||||
{
|
{
|
||||||
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzNjA1LCJpYXQiOjE3NzExMDE4MDUsImp0aSI6ImJjZTY5ZTA3MTIyOTQxMTg5NmFjYzk1ZDNiOThhMTI0IiwidXNlcl9pZCI6IjIifQ.b4Z1c_Yi5tsLZ-7F0KZcM2tai-f1VeaE881j2pKDwYA"
|
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA"
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
** Don confiao :verb:
|
** Don confiao :verb:
|
||||||
template http://localhost:7000/don_confiao/api/
|
template http://localhost:7000/don_confiao/api/
|
||||||
Content-Type: application/json;
|
Content-Type: application/json;
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzNjA1LCJpYXQiOjE3NzExMDE4MDUsImp0aSI6ImJjZTY5ZTA3MTIyOTQxMTg5NmFjYzk1ZDNiOThhMTI0IiwidXNlcl9pZCI6IjIifQ.b4Z1c_Yi5tsLZ-7F0KZcM2tai-f1VeaE881j2pKDwYA
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA
|
||||||
*** todas las rutas
|
*** todas las rutas
|
||||||
get
|
get
|
||||||
**** response
|
**** response
|
||||||
@@ -76,3 +77,13 @@ get customers/
|
|||||||
#+end_src
|
#+end_src
|
||||||
*** products
|
*** products
|
||||||
get products/
|
get products/
|
||||||
|
|
||||||
|
*** Importar Clientes de Tryton
|
||||||
|
post importar_productos_de_tryton
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
**** response
|
||||||
|
#+begin_src json
|
||||||
|
[]
|
||||||
|
#+end_src
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import (
|
from .models.sales import Sale, SaleLine
|
||||||
Customer, Sale, SaleLine, Product, ProductCategory, Payment,
|
from .models.customers import Customer
|
||||||
ReconciliationJar)
|
from .models.sales import Sale, SaleLine, Payment
|
||||||
|
from .models.products import Product, ProductCategory
|
||||||
|
from .models.payments import ReconciliationJar
|
||||||
|
|
||||||
admin.site.register(Customer)
|
admin.site.register(Customer)
|
||||||
admin.site.register(Sale)
|
admin.site.register(Sale)
|
||||||
|
|||||||
0
tienda_ilusion/don_confiao/api/__init__.py
Normal file
0
tienda_ilusion/don_confiao/api/__init__.py
Normal file
5
tienda_ilusion/don_confiao/api/sale_views.py
Normal file
5
tienda_ilusion/don_confiao/api/sale_views.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogSaleView(viewsets.ViewSet):
|
||||||
|
pass
|
||||||
@@ -5,8 +5,22 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode
|
from .models.sales import Sale, SaleLine
|
||||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
|
from .models.customers import Customer
|
||||||
|
from .models.sales import Sale, SaleLine, Payment
|
||||||
|
from .models.products import Product, ProductCategory
|
||||||
|
from .models.payments import PaymentMethods, ReconciliationJar
|
||||||
|
from .models.admin import AdminCode
|
||||||
|
|
||||||
|
from .serializers import (
|
||||||
|
SaleSerializer,
|
||||||
|
ProductSerializer,
|
||||||
|
CustomerSerializer,
|
||||||
|
ReconciliationJarSerializer,
|
||||||
|
PaymentMethodSerializer,
|
||||||
|
SaleForRenconciliationSerializer,
|
||||||
|
SaleSummarySerializer,
|
||||||
|
)
|
||||||
from .views import sales_to_tryton_csv
|
from .views import sales_to_tryton_csv
|
||||||
from .permissions import IsAdministrator
|
from .permissions import IsAdministrator
|
||||||
|
|
||||||
@@ -16,10 +30,10 @@ import io
|
|||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
|
|
||||||
TRYTON_HOST = os.environ.get('TRYTON_HOST', 'localhost')
|
TRYTON_HOST = os.environ.get("TRYTON_HOST", "localhost")
|
||||||
TRYTON_DATABASE = os.environ.get('TRYTON_DATABASE', 'tryton')
|
TRYTON_DATABASE = os.environ.get("TRYTON_DATABASE", "tryton")
|
||||||
TRYTON_USERNAME = os.environ.get('TRYTON_USERNAME', 'admin')
|
TRYTON_USERNAME = os.environ.get("TRYTON_USERNAME", "admin")
|
||||||
TRYTON_PASSWORD = os.environ.get('TRYTON_PASSWORD', 'admin')
|
TRYTON_PASSWORD = os.environ.get("TRYTON_PASSWORD", "admin")
|
||||||
TRYTON_COP_CURRENCY = 31
|
TRYTON_COP_CURRENCY = 31
|
||||||
TRYTON_COMPANY_ID = 1
|
TRYTON_COMPANY_ID = 1
|
||||||
TRYTON_SHOPS = [1]
|
TRYTON_SHOPS = [1]
|
||||||
@@ -27,7 +41,7 @@ TRYTON_SHOPS = [1]
|
|||||||
|
|
||||||
class Pagination(PageNumberPagination):
|
class Pagination(PageNumberPagination):
|
||||||
page_size = 10
|
page_size = 10
|
||||||
page_size_query_param = 'page_size'
|
page_size_query_param = "page_size"
|
||||||
|
|
||||||
|
|
||||||
class SaleView(viewsets.ModelViewSet):
|
class SaleView(viewsets.ModelViewSet):
|
||||||
@@ -36,32 +50,32 @@ class SaleView(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
customer = Customer.objects.get(pk=data['customer'])
|
customer = Customer.objects.get(pk=data["customer"])
|
||||||
date = data['date']
|
date = data["date"]
|
||||||
lines = data['saleline_set']
|
lines = data["saleline_set"]
|
||||||
payment_method = data['payment_method']
|
payment_method = data["payment_method"]
|
||||||
description = data.get('notes', '')
|
description = data.get("notes", "")
|
||||||
sale = Sale.objects.create(
|
sale = Sale.objects.create(
|
||||||
customer=customer,
|
customer=customer,
|
||||||
date=date,
|
date=date,
|
||||||
payment_method=payment_method,
|
payment_method=payment_method,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
product = Product.objects.get(pk=line['product'])
|
product = Product.objects.get(pk=line["product"])
|
||||||
quantity = line['quantity']
|
quantity = line["quantity"]
|
||||||
unit_price = line['unit_price']
|
unit_price = line["unit_price"]
|
||||||
SaleLine.objects.create(
|
SaleLine.objects.create(
|
||||||
sale=sale,
|
sale=sale,
|
||||||
product=product,
|
product=product,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
unit_price=unit_price
|
unit_price=unit_price,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{'id': sale.id, 'message': 'Venta creada con exito'},
|
{"id": sale.id, "message": "Venta creada con exito"},
|
||||||
status=201
|
status=201,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -80,43 +94,56 @@ class ReconciliateJarView(APIView):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
cash_purchases_id = data.get('cash_purchases')
|
cash_purchases_id = data.get("cash_purchases")
|
||||||
serializer = ReconciliationJarSerializer(data=data)
|
serializer = ReconciliationJarSerializer(data=data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
cash_purchases = Sale.objects.filter(pk__in=cash_purchases_id)
|
cash_purchases = Sale.objects.filter(pk__in=cash_purchases_id)
|
||||||
if not self._is_valid_total(cash_purchases, data.get('total_cash_purchases')):
|
if not self._is_valid_total(
|
||||||
|
cash_purchases, data.get("total_cash_purchases")
|
||||||
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{'error': 'total_cash_purchases not equal to sum of all purchases.'},
|
{
|
||||||
status=HTTP_400_BAD_REQUEST
|
"error": "total_cash_purchases not equal to sum of all purchases."
|
||||||
|
},
|
||||||
|
status=HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
reconciliation = serializer.save()
|
reconciliation = serializer.save()
|
||||||
other_purchases = self._get_other_purchases(data.get('other_totals'))
|
other_purchases = self._get_other_purchases(
|
||||||
|
data.get("other_totals")
|
||||||
|
)
|
||||||
|
|
||||||
self._link_purchases(reconciliation, cash_purchases, other_purchases)
|
self._link_purchases(
|
||||||
return Response({'id': reconciliation.id})
|
reconciliation, cash_purchases, other_purchases
|
||||||
|
)
|
||||||
|
return Response({"id": reconciliation.id})
|
||||||
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
reconciliations = ReconciliationJar.objects.all()
|
reconciliations = ReconciliationJar.objects.all()
|
||||||
serializer = ReconciliationJarSerializer(reconciliations, many=True)
|
serializer = ReconciliationJarSerializer(
|
||||||
|
reconciliations, many=True
|
||||||
|
)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def _is_valid_total(self, purchases, total):
|
def _is_valid_total(self, purchases, total):
|
||||||
calculated_total = sum(p.get_total() for p in purchases)
|
calculated_total = sum(p.get_total() for p in purchases)
|
||||||
return Decimal(calculated_total).quantize(Decimal('.0001')) == (
|
return Decimal(calculated_total).quantize(Decimal(".0001")) == (
|
||||||
Decimal(total).quantize(Decimal('.0001')))
|
Decimal(total).quantize(Decimal(".0001"))
|
||||||
|
)
|
||||||
|
|
||||||
def _get_other_purchases(self, other_totals):
|
def _get_other_purchases(self, other_totals):
|
||||||
if not other_totals:
|
if not other_totals:
|
||||||
return []
|
return []
|
||||||
purchases = []
|
purchases = []
|
||||||
for method in other_totals:
|
for method in other_totals:
|
||||||
purchases.extend(other_totals[method]['purchases'])
|
purchases.extend(other_totals[method]["purchases"])
|
||||||
if purchases:
|
if purchases:
|
||||||
return Sale.objects.filter(pk__in=purchases)
|
return Sale.objects.filter(pk__in=purchases)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _link_purchases(self, reconciliation, cash_purchases, other_purchases):
|
def _link_purchases(
|
||||||
|
self, reconciliation, cash_purchases, other_purchases
|
||||||
|
):
|
||||||
for purchase in cash_purchases:
|
for purchase in cash_purchases:
|
||||||
purchase.reconciliation = reconciliation
|
purchase.reconciliation = reconciliation
|
||||||
purchase.clean()
|
purchase.clean()
|
||||||
@@ -130,7 +157,9 @@ class ReconciliateJarView(APIView):
|
|||||||
|
|
||||||
class PaymentMethodView(APIView):
|
class PaymentMethodView(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
serializer = PaymentMethodSerializer(PaymentMethods.choices, many=True)
|
serializer = PaymentMethodSerializer(
|
||||||
|
PaymentMethods.choices, many=True
|
||||||
|
)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
@@ -162,11 +191,11 @@ class AdminCodeValidateView(APIView):
|
|||||||
|
|
||||||
def get(self, request, code):
|
def get(self, request, code):
|
||||||
codes = AdminCode.objects.filter(value=code)
|
codes = AdminCode.objects.filter(value=code)
|
||||||
return Response({'validCode': bool(codes)})
|
return Response({"validCode": bool(codes)})
|
||||||
|
|
||||||
|
|
||||||
class ReconciliateJarModelView(viewsets.ModelViewSet):
|
class ReconciliateJarModelView(viewsets.ModelViewSet):
|
||||||
queryset = ReconciliationJar.objects.all().order_by('-date_time')
|
queryset = ReconciliationJar.objects.all().order_by("-date_time")
|
||||||
pagination_class = Pagination
|
pagination_class = Pagination
|
||||||
serializer_class = ReconciliationJarSerializer
|
serializer_class = ReconciliationJarSerializer
|
||||||
permission_classes = [IsAuthenticated, IsAdministrator]
|
permission_classes = [IsAuthenticated, IsAdministrator]
|
||||||
@@ -178,7 +207,7 @@ class SalesForTrytonView(APIView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
sales = Sale.objects.all()
|
sales = Sale.objects.all()
|
||||||
csv = self._generate_sales_CSV(sales)
|
csv = self._generate_sales_CSV(sales)
|
||||||
return Response({'csv': csv})
|
return Response({"csv": csv})
|
||||||
|
|
||||||
def _generate_sales_CSV(self, sales):
|
def _generate_sales_CSV(self, sales):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
@@ -198,12 +227,14 @@ class SalesToTrytonView(APIView):
|
|||||||
hostname=TRYTON_HOST,
|
hostname=TRYTON_HOST,
|
||||||
database=TRYTON_DATABASE,
|
database=TRYTON_DATABASE,
|
||||||
username=TRYTON_USERNAME,
|
username=TRYTON_USERNAME,
|
||||||
password=TRYTON_PASSWORD
|
password=TRYTON_PASSWORD,
|
||||||
)
|
)
|
||||||
tryton_client.connect()
|
tryton_client.connect()
|
||||||
method = 'model.sale.sale.create'
|
method = "model.sale.sale.create"
|
||||||
tryton_context = {'company': TRYTON_COMPANY_ID,
|
tryton_context = {
|
||||||
'shops': TRYTON_SHOPS}
|
"company": TRYTON_COMPANY_ID,
|
||||||
|
"shops": TRYTON_SHOPS,
|
||||||
|
}
|
||||||
|
|
||||||
successful = []
|
successful = []
|
||||||
failed = []
|
failed = []
|
||||||
@@ -212,20 +243,22 @@ class SalesToTrytonView(APIView):
|
|||||||
for sale in sales:
|
for sale in sales:
|
||||||
try:
|
try:
|
||||||
lines = SaleLine.objects.filter(sale=sale.id)
|
lines = SaleLine.objects.filter(sale=sale.id)
|
||||||
tryton_params = self.__to_tryton_params(sale, lines, tryton_context)
|
tryton_params = self.__to_tryton_params(
|
||||||
|
sale, lines, tryton_context
|
||||||
|
)
|
||||||
external_ids = tryton_client.call(method, tryton_params)
|
external_ids = tryton_client.call(method, tryton_params)
|
||||||
sale.external_id = external_ids[0]
|
sale.external_id = external_ids[0]
|
||||||
sale.save()
|
sale.save()
|
||||||
successful.append(sale.id)
|
successful.append(sale.id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al enviar la venta: {e}"
|
print(
|
||||||
f"venta_id: {sale.id}")
|
f"Error al enviar la venta: {e}" f"venta_id: {sale.id}"
|
||||||
|
)
|
||||||
failed.append(sale.id)
|
failed.append(sale.id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{'successful': successful, 'failed': failed},
|
{"successful": successful, "failed": failed}, status=200
|
||||||
status=200
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __to_tryton_params(self, sale, lines, tryton_context):
|
def __to_tryton_params(self, sale, lines, tryton_context):
|
||||||
@@ -240,8 +273,12 @@ class TrytonSale:
|
|||||||
self.lines = lines
|
self.lines = lines
|
||||||
|
|
||||||
def _format_date(self, _date):
|
def _format_date(self, _date):
|
||||||
return {"__class__": "date", "year": _date.year, "month": _date.month,
|
return {
|
||||||
"day": _date.day}
|
"__class__": "date",
|
||||||
|
"year": _date.year,
|
||||||
|
"month": _date.month,
|
||||||
|
"day": _date.day,
|
||||||
|
}
|
||||||
|
|
||||||
def to_tryton(self):
|
def to_tryton(self):
|
||||||
return {
|
return {
|
||||||
@@ -249,17 +286,21 @@ class TrytonSale:
|
|||||||
"shipment_address": self.sale.customer.address_external_id,
|
"shipment_address": self.sale.customer.address_external_id,
|
||||||
"invoice_address": self.sale.customer.address_external_id,
|
"invoice_address": self.sale.customer.address_external_id,
|
||||||
"currency": TRYTON_COP_CURRENCY,
|
"currency": TRYTON_COP_CURRENCY,
|
||||||
"comment": self.sale.description or '',
|
"comment": self.sale.description or "",
|
||||||
"description": "Metodo pago: " + str(
|
"description": "Metodo pago: "
|
||||||
self.sale.payment_method or ''
|
+ str(self.sale.payment_method or ""),
|
||||||
),
|
|
||||||
"party": self.sale.customer.external_id,
|
"party": self.sale.customer.external_id,
|
||||||
"reference": "don_confiao " + str(self.sale.id),
|
"reference": "don_confiao " + str(self.sale.id),
|
||||||
"sale_date": self._format_date(self.sale.date),
|
"sale_date": self._format_date(self.sale.date),
|
||||||
"lines": [[
|
"lines": [
|
||||||
"create",
|
[
|
||||||
[TrytonLineSale(line).to_tryton() for line in self.lines]
|
"create",
|
||||||
]],
|
[
|
||||||
|
TrytonLineSale(line).to_tryton()
|
||||||
|
for line in self.lines
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
"self_pick_up": True,
|
"self_pick_up": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +318,7 @@ class TrytonLineSale:
|
|||||||
"quantity": self._format_decimal(self.sale_line.quantity),
|
"quantity": self._format_decimal(self.sale_line.quantity),
|
||||||
"type": "line",
|
"type": "line",
|
||||||
"unit": self.sale_line.product.unit_external_id,
|
"unit": self.sale_line.product.unit_external_id,
|
||||||
"unit_price": self._format_decimal(self.sale_line.unit_price)
|
"unit_price": self._format_decimal(self.sale_line.unit_price),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -289,12 +330,18 @@ class ProductsFromTrytonView(APIView):
|
|||||||
hostname=TRYTON_HOST,
|
hostname=TRYTON_HOST,
|
||||||
database=TRYTON_DATABASE,
|
database=TRYTON_DATABASE,
|
||||||
username=TRYTON_USERNAME,
|
username=TRYTON_USERNAME,
|
||||||
password=TRYTON_PASSWORD
|
password=TRYTON_PASSWORD,
|
||||||
)
|
)
|
||||||
tryton_client.connect()
|
tryton_client.connect()
|
||||||
method = 'model.product.product.search'
|
method = "model.product.product.search"
|
||||||
context = {'company': 1}
|
context = {"company": 1}
|
||||||
params = [[["salable", "=", True]], 0, 1000, [["rec_name", "ASC"], ["id", None]], context]
|
params = [
|
||||||
|
[["salable", "=", True]],
|
||||||
|
0,
|
||||||
|
1000,
|
||||||
|
[["rec_name", "ASC"], ["id", None]],
|
||||||
|
context,
|
||||||
|
]
|
||||||
product_ids = tryton_client.call(method, params)
|
product_ids = tryton_client.call(method, params)
|
||||||
tryton_products = self.__get_product_datails_from_tryton(
|
tryton_products = self.__get_product_datails_from_tryton(
|
||||||
product_ids, tryton_client, context
|
product_ids, tryton_client, context
|
||||||
@@ -308,7 +355,7 @@ class ProductsFromTrytonView(APIView):
|
|||||||
for tryton_product in tryton_products:
|
for tryton_product in tryton_products:
|
||||||
try:
|
try:
|
||||||
product = Product.objects.get(
|
product = Product.objects.get(
|
||||||
external_id=tryton_product.get('id')
|
external_id=tryton_product.get("id")
|
||||||
)
|
)
|
||||||
except Product.DoesNotExist:
|
except Product.DoesNotExist:
|
||||||
try:
|
try:
|
||||||
@@ -316,9 +363,11 @@ class ProductsFromTrytonView(APIView):
|
|||||||
created_products.append(product.id)
|
created_products.append(product.id)
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al importar productos: {e}"
|
print(
|
||||||
f"El producto: {tryton_product}")
|
f"Error al importar productos: {e}"
|
||||||
failed_products.append(tryton_product.get('id'))
|
f"El producto: {tryton_product}"
|
||||||
|
)
|
||||||
|
failed_products.append(tryton_product.get("id"))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.__need_update(product, tryton_product):
|
if self.__need_update(product, tryton_product):
|
||||||
@@ -329,50 +378,57 @@ class ProductsFromTrytonView(APIView):
|
|||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'checked_tryton_products': checked_tryton_products,
|
"checked_tryton_products": checked_tryton_products,
|
||||||
'failed_products': failed_products,
|
"failed_products": failed_products,
|
||||||
'updated_products': updated_products,
|
"updated_products": updated_products,
|
||||||
'created_products': created_products,
|
"created_products": created_products,
|
||||||
'untouched_products': untouched_products,
|
"untouched_products": untouched_products,
|
||||||
},
|
},
|
||||||
status=200
|
status=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __get_product_datails_from_tryton(self, product_ids, tryton_client, context):
|
def __get_product_datails_from_tryton(
|
||||||
tryton_fields = ['id', 'name', 'default_uom.id',
|
self, product_ids, tryton_client, context
|
||||||
'default_uom.rec_name', 'list_price']
|
):
|
||||||
method = 'model.product.product.read'
|
tryton_fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"default_uom.id",
|
||||||
|
"default_uom.rec_name",
|
||||||
|
"list_price",
|
||||||
|
]
|
||||||
|
method = "model.product.product.read"
|
||||||
params = (product_ids, tryton_fields, context)
|
params = (product_ids, tryton_fields, context)
|
||||||
response = tryton_client.call(method, params)
|
response = tryton_client.call(method, params)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def __need_update(self, product, tryton_product):
|
def __need_update(self, product, tryton_product):
|
||||||
if not product.name == tryton_product.get('name'):
|
if not product.name == tryton_product.get("name"):
|
||||||
return True
|
return True
|
||||||
if not product.price == tryton_product.get('list_price'):
|
if not product.price == tryton_product.get("list_price"):
|
||||||
return True
|
return True
|
||||||
unit = tryton_product.get('default_uom.')
|
unit = tryton_product.get("default_uom.")
|
||||||
if not product.measuring_unit == unit.get('rec_name'):
|
if not product.measuring_unit == unit.get("rec_name"):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __create_product(self, tryton_product):
|
def __create_product(self, tryton_product):
|
||||||
product = Product()
|
product = Product()
|
||||||
product.name = tryton_product.get('name')
|
product.name = tryton_product.get("name")
|
||||||
product.price = tryton_product.get('list_price')
|
product.price = tryton_product.get("list_price")
|
||||||
product.external_id = tryton_product.get('id')
|
product.external_id = tryton_product.get("id")
|
||||||
unit = tryton_product.get('default_uom.')
|
unit = tryton_product.get("default_uom.")
|
||||||
product.measuring_unit = unit.get('rec_name')
|
product.measuring_unit = unit.get("rec_name")
|
||||||
product.unit_external_id = unit.get('id')
|
product.unit_external_id = unit.get("id")
|
||||||
product.save()
|
product.save()
|
||||||
return product
|
return product
|
||||||
|
|
||||||
def __update_product(self, product, tryton_product):
|
def __update_product(self, product, tryton_product):
|
||||||
product.name = tryton_product.get('name')
|
product.name = tryton_product.get("name")
|
||||||
product.price = tryton_product.get('list_price')
|
product.price = tryton_product.get("list_price")
|
||||||
product.external_id = tryton_product.get('id')
|
product.external_id = tryton_product.get("id")
|
||||||
unit = tryton_product.get('default_uom.')
|
unit = tryton_product.get("default_uom.")
|
||||||
product.measuring_unit = unit.get('rec_name')
|
product.measuring_unit = unit.get("rec_name")
|
||||||
product.unit_external_id = unit.get('id')
|
product.unit_external_id = unit.get("id")
|
||||||
product.save()
|
product.save()
|
||||||
|
|
||||||
|
|
||||||
@@ -384,11 +440,11 @@ class CustomersFromTrytonView(APIView):
|
|||||||
hostname=TRYTON_HOST,
|
hostname=TRYTON_HOST,
|
||||||
database=TRYTON_DATABASE,
|
database=TRYTON_DATABASE,
|
||||||
username=TRYTON_USERNAME,
|
username=TRYTON_USERNAME,
|
||||||
password=TRYTON_PASSWORD
|
password=TRYTON_PASSWORD,
|
||||||
)
|
)
|
||||||
tryton_client.connect()
|
tryton_client.connect()
|
||||||
method = 'model.party.party.search'
|
method = "model.party.party.search"
|
||||||
context = {'company': 1}
|
context = {"company": 1}
|
||||||
params = [[], 0, 1000, [["name", "ASC"], ["id", None]], context]
|
params = [[], 0, 1000, [["name", "ASC"], ["id", None]], context]
|
||||||
party_ids = tryton_client.call(method, params)
|
party_ids = tryton_client.call(method, params)
|
||||||
tryton_parties = self.__get_party_datails(
|
tryton_parties = self.__get_party_datails(
|
||||||
@@ -403,7 +459,7 @@ class CustomersFromTrytonView(APIView):
|
|||||||
for tryton_party in tryton_parties:
|
for tryton_party in tryton_parties:
|
||||||
try:
|
try:
|
||||||
customer = Customer.objects.get(
|
customer = Customer.objects.get(
|
||||||
external_id=tryton_party.get('id')
|
external_id=tryton_party.get("id")
|
||||||
)
|
)
|
||||||
except Customer.DoesNotExist:
|
except Customer.DoesNotExist:
|
||||||
customer = self.__create_customer(tryton_party)
|
customer = self.__create_customer(tryton_party)
|
||||||
@@ -417,41 +473,52 @@ class CustomersFromTrytonView(APIView):
|
|||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'checked_tryton_parties': checked_tryton_parties,
|
"checked_tryton_parties": checked_tryton_parties,
|
||||||
'failed_parties': failed_parties,
|
"failed_parties": failed_parties,
|
||||||
'updated_customers': updated_customers,
|
"updated_customers": updated_customers,
|
||||||
'created_customers': created_customers,
|
"created_customers": created_customers,
|
||||||
'untouched_customers': untouched_customers,
|
"untouched_customers": untouched_customers,
|
||||||
},
|
},
|
||||||
status=200
|
status=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __get_party_datails(self, party_ids, tryton_client, context):
|
def __get_party_datails(self, party_ids, tryton_client, context):
|
||||||
tryton_fields = ['id', 'name', 'addresses']
|
tryton_fields = ["id", "name", "addresses"]
|
||||||
method = 'model.party.party.read'
|
method = "model.party.party.read"
|
||||||
params = (party_ids, tryton_fields, context)
|
params = (party_ids, tryton_fields, context)
|
||||||
response = tryton_client.call(method, params)
|
response = tryton_client.call(method, params)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def __need_update(self, customer, tryton_party):
|
def __need_update(self, customer, tryton_party):
|
||||||
if not customer.name == tryton_party.get('name'):
|
if not customer.name == tryton_party.get("name"):
|
||||||
return True
|
return True
|
||||||
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
|
if (
|
||||||
if not customer.address_external_id == str(tryton_party.get('addresses')[0]):
|
tryton_party.get("addresses")
|
||||||
|
and tryton_party.get("addresses")[0]
|
||||||
|
):
|
||||||
|
if not customer.address_external_id == str(
|
||||||
|
tryton_party.get("addresses")[0]
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __create_customer(self, tryton_party):
|
def __create_customer(self, tryton_party):
|
||||||
customer = Customer()
|
customer = Customer()
|
||||||
customer.name = tryton_party.get('name')
|
customer.name = tryton_party.get("name")
|
||||||
customer.external_id = tryton_party.get('id')
|
customer.external_id = tryton_party.get("id")
|
||||||
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
|
if (
|
||||||
customer.address_external_id = tryton_party.get('addresses')[0]
|
tryton_party.get("addresses")
|
||||||
|
and tryton_party.get("addresses")[0]
|
||||||
|
):
|
||||||
|
customer.address_external_id = tryton_party.get("addresses")[0]
|
||||||
customer.save()
|
customer.save()
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
def __update_customer(self, customer, tryton_party):
|
def __update_customer(self, customer, tryton_party):
|
||||||
customer.name = tryton_party.get('name')
|
customer.name = tryton_party.get("name")
|
||||||
customer.external_id = tryton_party.get('id')
|
customer.external_id = tryton_party.get("id")
|
||||||
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
|
if (
|
||||||
customer.address_external_id = tryton_party.get('addresses')[0]
|
tryton_party.get("addresses")
|
||||||
|
and tryton_party.get("addresses")[0]
|
||||||
|
):
|
||||||
|
customer.address_external_id = tryton_party.get("addresses")[0]
|
||||||
customer.save()
|
customer.save()
|
||||||
|
|||||||
@@ -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,224 +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")
|
|
||||||
CREDIT = "CREDIT", _("Crédito")
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
external_id = models.CharField(max_length=100, null=True, blank=True)
|
|
||||||
address_external_id = 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,
|
|
||||||
)
|
|
||||||
unit_external_id = models.CharField(
|
|
||||||
max_length=100, null=True, blank=True
|
|
||||||
)
|
|
||||||
categories = models.ManyToManyField(ProductCategory)
|
|
||||||
external_id = models.CharField(max_length=100, null=True, blank=True)
|
|
||||||
|
|
||||||
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,
|
|
||||||
"external_id": product.external_id,
|
|
||||||
"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,
|
|
||||||
)
|
|
||||||
external_id = models.CharField(max_length=100, null=True, blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.date} {self.customer}"
|
|
||||||
|
|
||||||
def get_total(self):
|
|
||||||
lines = self.saleline_set.all()
|
|
||||||
return sum([l.quantity * l.unit_price for l in lines])
|
|
||||||
|
|
||||||
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.DecimalField(
|
|
||||||
max_digits=10, decimal_places=2, 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)
|
|
||||||
0
tienda_ilusion/don_confiao/models/__init__.py
Normal file
0
tienda_ilusion/don_confiao/models/__init__.py
Normal file
5
tienda_ilusion/don_confiao/models/admin.py
Normal file
5
tienda_ilusion/don_confiao/models/admin.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class AdminCode(models.Model):
|
||||||
|
value = models.CharField(max_length=255, null=False, blank=False)
|
||||||
17
tienda_ilusion/don_confiao/models/customers.py
Normal file
17
tienda_ilusion/don_confiao/models/customers.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
external_id = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
address_external_id = models.CharField(
|
||||||
|
max_length=100, null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
38
tienda_ilusion/don_confiao/models/payments.py
Normal file
38
tienda_ilusion/don_confiao/models/payments.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentMethods(models.TextChoices):
|
||||||
|
CASH = "CASH", _("Efectivo")
|
||||||
|
CONFIAR = "CONFIAR", _("Confiar")
|
||||||
|
BANCOLOMBIA = "BANCOLOMBIA", _("Bancolombia")
|
||||||
|
CREDIT = "CREDIT", _("Crédito")
|
||||||
|
|
||||||
|
|
||||||
|
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.")}
|
||||||
|
)
|
||||||
47
tienda_ilusion/don_confiao/models/products.py
Normal file
47
tienda_ilusion/don_confiao/models/products.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
unit_external_id = models.CharField(
|
||||||
|
max_length=100, null=True, blank=True
|
||||||
|
)
|
||||||
|
categories = models.ManyToManyField(ProductCategory)
|
||||||
|
external_id = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
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,
|
||||||
|
"external_id": product.external_id,
|
||||||
|
"categories": [c.name for c in product.categories.all()],
|
||||||
|
}
|
||||||
|
products_list.append(rproduct)
|
||||||
|
return products_list
|
||||||
108
tienda_ilusion/don_confiao/models/sales.py
Normal file
108
tienda_ilusion/don_confiao/models/sales.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
from django.db import models
|
||||||
|
from .customers import Customer
|
||||||
|
from .products import Product
|
||||||
|
from .payments import PaymentMethods, ReconciliationJar
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
external_id = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.date} {self.customer}"
|
||||||
|
|
||||||
|
def get_total(self):
|
||||||
|
lines = self.saleline_set.all()
|
||||||
|
return sum([l.quantity * l.unit_price for l in lines])
|
||||||
|
|
||||||
|
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.DecimalField(
|
||||||
|
max_digits=10, decimal_places=2, 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 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)
|
||||||
@@ -1,33 +1,51 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import Sale, SaleLine, Product, Customer, ReconciliationJar
|
from .models.sales import Sale, SaleLine
|
||||||
|
from .models.customers import Customer
|
||||||
|
from .models.sales import Sale, SaleLine, Payment
|
||||||
|
from .models.products import Product, ProductCategory
|
||||||
|
from .models.payments import ReconciliationJar
|
||||||
|
|
||||||
|
|
||||||
class SaleLineSerializer(serializers.ModelSerializer):
|
class SaleLineSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SaleLine
|
model = SaleLine
|
||||||
fields = ['id', 'sale', 'product', 'unit_price', 'quantity']
|
fields = ["id", "sale", "product", "unit_price", "quantity"]
|
||||||
|
|
||||||
|
|
||||||
class SaleSerializer(serializers.ModelSerializer):
|
class SaleSerializer(serializers.ModelSerializer):
|
||||||
total = serializers.ReadOnlyField(source='get_total')
|
total = serializers.ReadOnlyField(source="get_total")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Sale
|
model = Sale
|
||||||
fields = ['id', 'customer', 'date', 'saleline_set',
|
fields = [
|
||||||
'total', 'payment_method', 'external_id']
|
"id",
|
||||||
|
"customer",
|
||||||
|
"date",
|
||||||
|
"saleline_set",
|
||||||
|
"total",
|
||||||
|
"payment_method",
|
||||||
|
"external_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProductSerializer(serializers.ModelSerializer):
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Product
|
model = Product
|
||||||
fields = ['id', 'name', 'price', 'measuring_unit', 'categories', 'external_id']
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"measuring_unit",
|
||||||
|
"categories",
|
||||||
|
"external_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CustomerSerializer(serializers.ModelSerializer):
|
class CustomerSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['id', 'name', 'address', 'email', 'phone', 'external_id']
|
fields = ["id", "name", "address", "email", "phone", "external_id"]
|
||||||
|
|
||||||
|
|
||||||
class ReconciliationJarSerializer(serializers.ModelSerializer):
|
class ReconciliationJarSerializer(serializers.ModelSerializer):
|
||||||
@@ -36,13 +54,13 @@ class ReconciliationJarSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ReconciliationJar
|
model = ReconciliationJar
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
"id",
|
||||||
'date_time',
|
"date_time",
|
||||||
'reconcilier',
|
"reconcilier",
|
||||||
'cash_taken',
|
"cash_taken",
|
||||||
'cash_discrepancy',
|
"cash_discrepancy",
|
||||||
'total_cash_purchases',
|
"total_cash_purchases",
|
||||||
'Sales',
|
"Sales",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -52,8 +70,8 @@ class PaymentMethodSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
return {
|
return {
|
||||||
'text': instance[1],
|
"text": instance[1],
|
||||||
'value': instance[0],
|
"value": instance[0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,8 +84,8 @@ class SaleForRenconciliationSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def get_customer(self, sale):
|
def get_customer(self, sale):
|
||||||
return {
|
return {
|
||||||
'id': sale.customer.id,
|
"id": sale.customer.id,
|
||||||
'name': sale.customer.name,
|
"name": sale.customer.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_total(self, sale):
|
def get_total(self, sale):
|
||||||
@@ -77,13 +95,13 @@ class SaleForRenconciliationSerializer(serializers.Serializer):
|
|||||||
class ListCustomerSerializer(serializers.ModelSerializer):
|
class ListCustomerSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['id', 'name']
|
fields = ["id", "name"]
|
||||||
|
|
||||||
|
|
||||||
class ListProductSerializer(serializers.ModelSerializer):
|
class ListProductSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Product
|
model = Product
|
||||||
fields = ['id', 'name']
|
fields = ["id", "name"]
|
||||||
|
|
||||||
|
|
||||||
class SummarySaleLineSerializer(serializers.ModelSerializer):
|
class SummarySaleLineSerializer(serializers.ModelSerializer):
|
||||||
@@ -91,13 +109,13 @@ class SummarySaleLineSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SaleLine
|
model = SaleLine
|
||||||
fields = ['product', 'quantity', 'unit_price', 'description']
|
fields = ["product", "quantity", "unit_price", "description"]
|
||||||
|
|
||||||
|
|
||||||
class SaleSummarySerializer(serializers.ModelSerializer):
|
class SaleSummarySerializer(serializers.ModelSerializer):
|
||||||
customer = ListCustomerSerializer()
|
customer = ListCustomerSerializer()
|
||||||
lines = SummarySaleLineSerializer(many=True, source='saleline_set')
|
lines = SummarySaleLineSerializer(many=True, source="saleline_set")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Sale
|
model = Sale
|
||||||
fields = ['id', 'date', 'customer', 'payment_method', 'lines']
|
fields = ["id", "date", "customer", "payment_method", "lines"]
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ..models import AdminCode
|
from ..models.admin import AdminCode
|
||||||
from .Mixins import LoginMixin
|
from .Mixins import LoginMixin
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -10,33 +10,33 @@ class TestAdminCode(TestCase, LoginMixin):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.valid_code = 'some valid code'
|
self.valid_code = "some valid code"
|
||||||
admin_code = AdminCode()
|
admin_code = AdminCode()
|
||||||
admin_code.value = self.valid_code
|
admin_code.value = self.valid_code
|
||||||
admin_code.clean()
|
admin_code.clean()
|
||||||
admin_code.save()
|
admin_code.save()
|
||||||
|
|
||||||
def test_validate_code(self):
|
def test_validate_code(self):
|
||||||
url = '/don_confiao/api/admin_code/validate/' + self.valid_code
|
url = "/don_confiao/api/admin_code/validate/" + self.valid_code
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertTrue(content['validCode'])
|
self.assertTrue(content["validCode"])
|
||||||
|
|
||||||
def test_invalid_code(self):
|
def test_invalid_code(self):
|
||||||
invalid_code = 'some invalid code'
|
invalid_code = "some invalid code"
|
||||||
url = '/don_confiao/api/admin_code/validate/' + invalid_code
|
url = "/don_confiao/api/admin_code/validate/" + invalid_code
|
||||||
|
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertFalse(content['validCode'])
|
self.assertFalse(content["validCode"])
|
||||||
|
|
||||||
def test_empty_code(self):
|
def test_empty_code(self):
|
||||||
empty_code = ''
|
empty_code = ""
|
||||||
url = '/don_confiao/api/admin_code/validate/' + empty_code
|
url = "/don_confiao/api/admin_code/validate/" + empty_code
|
||||||
|
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import io
|
|||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from ..models import Sale, Product, Customer
|
from ..models.sales import Sale
|
||||||
|
from ..models.customers import Customer
|
||||||
|
from ..models.products import Product
|
||||||
from .Mixins import LoginMixin
|
from .Mixins import LoginMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -13,18 +15,15 @@ class TestAPI(APITestCase, LoginMixin):
|
|||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.product = Product.objects.create(
|
self.product = Product.objects.create(
|
||||||
name='Panela',
|
name="Panela", price=5000, measuring_unit="UNIT"
|
||||||
price=5000,
|
|
||||||
measuring_unit='UNIT'
|
|
||||||
)
|
)
|
||||||
self.customer = Customer.objects.create(
|
self.customer = Customer.objects.create(
|
||||||
name='Camilo',
|
name="Camilo", external_id="18"
|
||||||
external_id='18'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_sale(self):
|
def test_create_sale(self):
|
||||||
response = self._create_sale()
|
response = self._create_sale()
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(Sale.objects.count(), 1)
|
self.assertEqual(Sale.objects.count(), 1)
|
||||||
sale = Sale.objects.all()[0]
|
sale = Sale.objects.all()[0]
|
||||||
@@ -32,79 +31,63 @@ class TestAPI(APITestCase, LoginMixin):
|
|||||||
sale.customer.name,
|
sale.customer.name,
|
||||||
self.customer.name,
|
self.customer.name,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(sale.id, content["id"])
|
||||||
sale.id,
|
|
||||||
content['id']
|
|
||||||
)
|
|
||||||
self.assertIsNone(sale.external_id)
|
self.assertIsNone(sale.external_id)
|
||||||
|
|
||||||
def test_create_sale_with_decimal(self):
|
def test_create_sale_with_decimal(self):
|
||||||
response = self._create_sale_with_decimal()
|
response = self._create_sale_with_decimal()
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(Sale.objects.count(), 1)
|
self.assertEqual(Sale.objects.count(), 1)
|
||||||
sale = Sale.objects.all()[0]
|
sale = Sale.objects.all()[0]
|
||||||
self.assertEqual(
|
self.assertEqual(sale.customer.name, self.customer.name)
|
||||||
sale.customer.name,
|
self.assertEqual(sale.id, content["id"])
|
||||||
self.customer.name
|
self.assertEqual(sale.get_total(), 16500.00)
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
sale.id,
|
|
||||||
content['id']
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
sale.get_total(),
|
|
||||||
16500.00
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_products(self):
|
def test_get_products(self):
|
||||||
url = '/don_confiao/api/products/'
|
url = "/don_confiao/api/products/"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
json_response = json.loads(response.content.decode('utf-8'))
|
json_response = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.product.name, json_response[0]['name'])
|
self.assertEqual(self.product.name, json_response[0]["name"])
|
||||||
|
|
||||||
def test_get_customers(self):
|
def test_get_customers(self):
|
||||||
url = '/don_confiao/api/customers/'
|
url = "/don_confiao/api/customers/"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
json_response = json.loads(response.content.decode('utf-8'))
|
json_response = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.customer.name, json_response[0]['name'])
|
self.assertEqual(self.customer.name, json_response[0]["name"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.customer.external_id,
|
self.customer.external_id, json_response[0]["external_id"]
|
||||||
json_response[0]['external_id']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_sales(self):
|
def test_get_sales(self):
|
||||||
url = '/don_confiao/api/sales/'
|
url = "/don_confiao/api/sales/"
|
||||||
self._create_sale()
|
self._create_sale()
|
||||||
|
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
json_response = json.loads(response.content.decode('utf-8'))
|
json_response = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.customer.id, json_response[0]['customer'])
|
self.assertEqual(self.customer.id, json_response[0]["customer"])
|
||||||
self.assertEqual(
|
self.assertEqual(None, json_response[0]["external_id"])
|
||||||
None,
|
|
||||||
json_response[0]['external_id']
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_sales_for_tryton(self):
|
def test_get_sales_for_tryton(self):
|
||||||
url = '/don_confiao/api/sales/for_tryton'
|
url = "/don_confiao/api/sales/for_tryton"
|
||||||
self._create_sale()
|
self._create_sale()
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
json_response = json.loads(response.content.decode('utf-8'))
|
json_response = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn('csv', json_response)
|
self.assertIn("csv", json_response)
|
||||||
self.assertGreater(len(json_response['csv']), 0)
|
self.assertGreater(len(json_response["csv"]), 0)
|
||||||
|
|
||||||
def test_csv_structure_in_sales_for_tryton(self):
|
def test_csv_structure_in_sales_for_tryton(self):
|
||||||
url = '/don_confiao/api/sales/for_tryton'
|
url = "/don_confiao/api/sales/for_tryton"
|
||||||
self._create_sale()
|
self._create_sale()
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
json_response = json.loads(response.content.decode('utf-8'))
|
json_response = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
csv_reader = csv.reader(io.StringIO(json_response['csv']))
|
csv_reader = csv.reader(io.StringIO(json_response["csv"]))
|
||||||
expected_header = [
|
expected_header = [
|
||||||
"Tercero",
|
"Tercero",
|
||||||
"Dirección de facturación",
|
"Dirección de facturación",
|
||||||
@@ -123,53 +106,115 @@ class TestAPI(APITestCase, LoginMixin):
|
|||||||
"Tienda",
|
"Tienda",
|
||||||
"Terminal de venta",
|
"Terminal de venta",
|
||||||
"Autorecogida",
|
"Autorecogida",
|
||||||
"Comentario"
|
"Comentario",
|
||||||
]
|
]
|
||||||
self.assertEqual(next(csv_reader), expected_header)
|
self.assertEqual(next(csv_reader), expected_header)
|
||||||
|
|
||||||
expected_rows = [
|
expected_rows = [
|
||||||
[self.customer.name, self.customer.name, self.customer.name, "",
|
[
|
||||||
"", "2024-09-02", "Contado", "Almacén",
|
self.customer.name,
|
||||||
"Peso colombiano", self.product.name, "2.00", "3000.00", "Unidad",
|
self.customer.name,
|
||||||
"TIENDA LA ILUSIÓN", "Tienda La Ilusion", "La Ilusion", "True", ""
|
self.customer.name,
|
||||||
],
|
"",
|
||||||
["", "", "", "", "", "", "", "", "", self.product.name, "3.00",
|
"",
|
||||||
"5000.00", "Unidad", "", "", "", "", ""
|
"2024-09-02",
|
||||||
],
|
"Contado",
|
||||||
|
"Almacén",
|
||||||
|
"Peso colombiano",
|
||||||
|
self.product.name,
|
||||||
|
"2.00",
|
||||||
|
"3000.00",
|
||||||
|
"Unidad",
|
||||||
|
"TIENDA LA ILUSIÓN",
|
||||||
|
"Tienda La Ilusion",
|
||||||
|
"La Ilusion",
|
||||||
|
"True",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
self.product.name,
|
||||||
|
"3.00",
|
||||||
|
"5000.00",
|
||||||
|
"Unidad",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
],
|
||||||
]
|
]
|
||||||
rows = list(csv_reader)
|
rows = list(csv_reader)
|
||||||
self.assertEqual(rows, expected_rows)
|
self.assertEqual(rows, expected_rows)
|
||||||
|
|
||||||
def _create_sale(self):
|
def _create_sale(self):
|
||||||
url = '/don_confiao/api/sales/'
|
url = "/don_confiao/api/sales/"
|
||||||
data = {
|
data = {
|
||||||
'customer': self.customer.id,
|
"customer": self.customer.id,
|
||||||
'date': '2024-09-02',
|
"date": "2024-09-02",
|
||||||
'payment_method': 'CASH',
|
"payment_method": "CASH",
|
||||||
'saleline_set': [
|
"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')
|
|
||||||
|
|
||||||
def _create_sale_with_decimal(self):
|
|
||||||
url = '/don_confiao/api/sales/'
|
|
||||||
data = {
|
|
||||||
'customer': self.customer.id,
|
|
||||||
'date': '2024-09-02',
|
|
||||||
'payment_method': 'CASH',
|
|
||||||
'saleline_set': [
|
|
||||||
{
|
{
|
||||||
'product': self.product.id,
|
"product": self.product.id,
|
||||||
'quantity': 0.5,
|
"quantity": 2,
|
||||||
'unit_price': 3000
|
"unit_price": 3000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'product': self.product.id,
|
"product": self.product.id,
|
||||||
'quantity': 3,
|
"quantity": 3,
|
||||||
'unit_price': 5000
|
"unit_price": 5000,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return self.client.post(url, data, format='json')
|
return self.client.post(url, data, format="json")
|
||||||
|
|
||||||
|
def _create_catalog_sale(self):
|
||||||
|
url = "/don_confiao/api/catalog_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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"catalog_sale": True,
|
||||||
|
}
|
||||||
|
return self.client.post(url, data, format="json")
|
||||||
|
|
||||||
|
def _create_sale_with_decimal(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": 0.5,
|
||||||
|
"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)
|
|
||||||
@@ -2,7 +2,7 @@ import json
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from ..models import Customer
|
from ..models.customers import Customer
|
||||||
from .Mixins import LoginMixin
|
from .Mixins import LoginMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -11,61 +11,71 @@ class TestCustomersFromTryton(TestCase, LoginMixin):
|
|||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.customer = Customer.objects.create(
|
self.customer = Customer.objects.create(
|
||||||
name='Calos',
|
name="Calos", external_id=5
|
||||||
external_id=5
|
|
||||||
)
|
)
|
||||||
self.customer.save()
|
self.customer.save()
|
||||||
|
|
||||||
self.customer2 = Customer.objects.create(
|
self.customer2 = Customer.objects.create(
|
||||||
name='Cristian',
|
name="Cristian", external_id=6
|
||||||
external_id=6
|
|
||||||
)
|
)
|
||||||
self.customer2.save()
|
self.customer2.save()
|
||||||
|
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.call')
|
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.connect')
|
@patch("sabatron_tryton_rpc_client.client.Client.connect")
|
||||||
def test_create_import_customer(self, mock_connect, mock_call):
|
def test_create_import_customer(self, mock_connect, mock_call):
|
||||||
def fake_call(*args, **kwargs):
|
def fake_call(*args, **kwargs):
|
||||||
party_search = 'model.party.party.search'
|
party_search = "model.party.party.search"
|
||||||
search_args = [[], 0, 1000, [['name', 'ASC'], ['id', None]], {'company': 1}]
|
search_args = [
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
1000,
|
||||||
|
[["name", "ASC"], ["id", None]],
|
||||||
|
{"company": 1},
|
||||||
|
]
|
||||||
|
|
||||||
if (args == (party_search, search_args)):
|
if args == (party_search, search_args):
|
||||||
return [5, 6, 7, 8]
|
return [5, 6, 7, 8]
|
||||||
|
|
||||||
party_read = 'model.party.party.read'
|
party_read = "model.party.party.read"
|
||||||
read_args = ([5, 6, 7, 8], ['id', 'name', 'addresses'], {'company': 1})
|
read_args = (
|
||||||
if (args == (party_read, read_args)):
|
[5, 6, 7, 8],
|
||||||
|
["id", "name", "addresses"],
|
||||||
|
{"company": 1},
|
||||||
|
)
|
||||||
|
if args == (party_read, read_args):
|
||||||
return [
|
return [
|
||||||
{'id': 5, 'name': 'Carlos', 'addresses': [303]},
|
{"id": 5, "name": "Carlos", "addresses": [303]},
|
||||||
{'id': 6, 'name': 'Cristian', 'addresses': []},
|
{"id": 6, "name": "Cristian", "addresses": []},
|
||||||
{'id': 7, 'name': 'Ana', 'addresses': [302]},
|
{"id": 7, "name": "Ana", "addresses": [302]},
|
||||||
{'id': 8, 'name': 'José', 'addresses': []},
|
{"id": 8, "name": "José", "addresses": []},
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(f"Sorry, args non expected on this test: {args}")
|
raise Exception(
|
||||||
|
f"Sorry, args non expected on this test: {args}"
|
||||||
|
)
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
|
|
||||||
url = '/don_confiao/api/importar_clientes_de_tryton'
|
url = "/don_confiao/api/importar_clientes_de_tryton"
|
||||||
response = self.client.post(url)
|
response = self.client.post(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
|
||||||
expected_response = {
|
expected_response = {
|
||||||
'checked_tryton_parties': [5, 6, 7, 8],
|
"checked_tryton_parties": [5, 6, 7, 8],
|
||||||
'created_customers': [3, 4],
|
"created_customers": [3, 4],
|
||||||
'untouched_customers': [2],
|
"untouched_customers": [2],
|
||||||
'failed_parties': [],
|
"failed_parties": [],
|
||||||
'updated_customers': [1]
|
"updated_customers": [1],
|
||||||
}
|
}
|
||||||
self.assertEqual(content, expected_response)
|
self.assertEqual(content, expected_response)
|
||||||
|
|
||||||
created_customer = Customer.objects.get(id=3)
|
created_customer = Customer.objects.get(id=3)
|
||||||
self.assertEqual(created_customer.external_id, str(7))
|
self.assertEqual(created_customer.external_id, str(7))
|
||||||
self.assertEqual(created_customer.name, 'Ana')
|
self.assertEqual(created_customer.name, "Ana")
|
||||||
self.assertEqual(created_customer.address_external_id, str(302))
|
self.assertEqual(created_customer.address_external_id, str(302))
|
||||||
|
|
||||||
updated_customer = Customer.objects.get(id=1)
|
updated_customer = Customer.objects.get(id=1)
|
||||||
self.assertEqual(updated_customer.external_id, str(5))
|
self.assertEqual(updated_customer.external_id, str(5))
|
||||||
self.assertEqual(updated_customer.name, 'Carlos')
|
self.assertEqual(updated_customer.name, "Carlos")
|
||||||
self.assertIn(updated_customer.address_external_id, str(303))
|
self.assertIn(updated_customer.address_external_id, str(303))
|
||||||
|
|||||||
@@ -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,105 +0,0 @@
|
|||||||
import csv
|
|
||||||
import json
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from ..models import Sale, SaleLine, Product, Customer
|
|
||||||
from .Mixins import LoginMixin
|
|
||||||
|
|
||||||
|
|
||||||
class TestExportarVentasParaTryton(TestCase, LoginMixin):
|
|
||||||
def setUp(self):
|
|
||||||
self.login()
|
|
||||||
|
|
||||||
self.product = Product.objects.create(
|
|
||||||
name='Panela',
|
|
||||||
price=5000,
|
|
||||||
measuring_unit='UNIT',
|
|
||||||
unit_external_id=1,
|
|
||||||
external_id=1
|
|
||||||
)
|
|
||||||
self.customer = Customer.objects.create(
|
|
||||||
name='Camilo',
|
|
||||||
external_id=1,
|
|
||||||
address_external_id=307,
|
|
||||||
)
|
|
||||||
self.sale = Sale.objects.create(
|
|
||||||
customer=self.customer,
|
|
||||||
date='2024-09-02',
|
|
||||||
payment_method='CASH',
|
|
||||||
description='un comentario'
|
|
||||||
)
|
|
||||||
self.sale_line1 = SaleLine.objects.create(
|
|
||||||
product=self.product,
|
|
||||||
quantity=2,
|
|
||||||
unit_price=3000,
|
|
||||||
sale=self.sale
|
|
||||||
)
|
|
||||||
self.sale_line2 = SaleLine.objects.create(
|
|
||||||
product=self.product,
|
|
||||||
quantity=3,
|
|
||||||
unit_price=5000,
|
|
||||||
sale=self.sale
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_exportar_ventas_para_tryton(self):
|
|
||||||
url = '/don_confiao/exportar_ventas_para_tryton'
|
|
||||||
response = self.client.get(url)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
|
||||||
csv_content = response.content.decode('utf-8')
|
|
||||||
|
|
||||||
expected_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"
|
|
||||||
]
|
|
||||||
csv_reader = csv.reader(csv_content.splitlines())
|
|
||||||
self.assertEqual(next(csv_reader), expected_header)
|
|
||||||
|
|
||||||
expected_rows = [
|
|
||||||
["Camilo", "Camilo", "Camilo", "un comentario", "un comentario", "2024-09-02", "Contado", "Almacén", "Peso colombiano", "Panela", "2.00", "3000.00", "Unidad", "TIENDA LA ILUSIÓN", "Tienda La Ilusion", "La Ilusion", "True", "un comentario"],
|
|
||||||
["", "", "", "", "", "", "", "", "", "Panela", "3.00", "5000.00", "Unidad", "", "", "", "", ""],
|
|
||||||
]
|
|
||||||
csv_rows = list(csv_reader)
|
|
||||||
self.assertEqual(csv_rows[0], expected_rows[0])
|
|
||||||
self.assertEqual(csv_rows[1], expected_rows[1])
|
|
||||||
|
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.call')
|
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.connect')
|
|
||||||
def test_send_sales_to_tryton(self, mock_connect, mock_call):
|
|
||||||
external_id = '23423'
|
|
||||||
url = '/don_confiao/api/enviar_ventas_a_tryton'
|
|
||||||
mock_connect.return_value = None
|
|
||||||
mock_call.return_value = [external_id]
|
|
||||||
response = self.client.post(url)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
|
||||||
expected_response = {
|
|
||||||
'successful': [self.sale.id],
|
|
||||||
'failed': [],
|
|
||||||
}
|
|
||||||
self.assertEqual(content, expected_response)
|
|
||||||
|
|
||||||
updated_sale = Sale.objects.get(id=self.sale.id)
|
|
||||||
self.assertEqual(updated_sale.external_id, external_id)
|
|
||||||
mock_connect.assert_called_once()
|
|
||||||
mock_call.assert_called_once()
|
|
||||||
mock_call.assert_called_with('model.sale.sale.create', [[{'company': 1, 'shipment_address': '307', 'invoice_address': '307', 'currency': 31, 'comment': 'un comentario', 'description': 'Metodo pago: CASH', 'party': '1', 'reference': 'don_confiao 1', 'sale_date': {'__class__': 'date', 'year': 2024, 'month': 9, 'day': 2}, 'lines': [['create', [{'product': '1', 'quantity': {'__class__': 'Decimal', 'decimal': '2.00'}, 'type': 'line', 'unit': '1', 'unit_price': {'__class__': 'Decimal', 'decimal': '3000.00'}}, {'product': '1', 'quantity': {'__class__': 'Decimal', 'decimal': '3.00'}, 'type': 'line', 'unit': '1', 'unit_price': {'__class__': 'Decimal', 'decimal': '5000.00'}}]]], 'self_pick_up': True}], {'company': 1, 'shops': [1]}])
|
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from ..models import Sale, Product, SaleLine, Customer, ReconciliationJar
|
from ..models.sales import (
|
||||||
|
Sale,
|
||||||
|
Product,
|
||||||
|
SaleLine,
|
||||||
|
Customer,
|
||||||
|
ReconciliationJar,
|
||||||
|
)
|
||||||
from .Mixins import LoginMixin
|
from .Mixins import LoginMixin
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -11,13 +17,13 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
customer = Customer()
|
customer = Customer()
|
||||||
customer.name = 'Alejo Mono'
|
customer.name = "Alejo Mono"
|
||||||
customer.save()
|
customer.save()
|
||||||
|
|
||||||
purchase = Sale()
|
purchase = Sale()
|
||||||
purchase.customer = customer
|
purchase.customer = customer
|
||||||
purchase.date = "2024-07-30"
|
purchase.date = "2024-07-30"
|
||||||
purchase.payment_method = 'CASH'
|
purchase.payment_method = "CASH"
|
||||||
purchase.clean()
|
purchase.clean()
|
||||||
purchase.save()
|
purchase.save()
|
||||||
|
|
||||||
@@ -37,7 +43,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
purchase2 = Sale()
|
purchase2 = Sale()
|
||||||
purchase2.customer = customer
|
purchase2.customer = customer
|
||||||
purchase2.date = "2024-07-30"
|
purchase2.date = "2024-07-30"
|
||||||
purchase.payment_method = 'CASH'
|
purchase.payment_method = "CASH"
|
||||||
purchase2.clean()
|
purchase2.clean()
|
||||||
purchase2.save()
|
purchase2.save()
|
||||||
|
|
||||||
@@ -52,7 +58,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
purchase3 = Sale()
|
purchase3 = Sale()
|
||||||
purchase3.customer = customer
|
purchase3.customer = customer
|
||||||
purchase3.date = "2024-07-30"
|
purchase3.date = "2024-07-30"
|
||||||
purchase3.payment_method = 'CASH'
|
purchase3.payment_method = "CASH"
|
||||||
purchase3.clean()
|
purchase3.clean()
|
||||||
purchase3.save()
|
purchase3.save()
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
purchase4 = Sale()
|
purchase4 = Sale()
|
||||||
purchase4.customer = customer
|
purchase4.customer = customer
|
||||||
purchase4.date = "2024-07-30"
|
purchase4.date = "2024-07-30"
|
||||||
purchase4.payment_method = 'CONFIAR'
|
purchase4.payment_method = "CONFIAR"
|
||||||
purchase4.clean()
|
purchase4.clean()
|
||||||
purchase4.save()
|
purchase4.save()
|
||||||
|
|
||||||
@@ -90,19 +96,19 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
self.purchase3.clean()
|
self.purchase3.clean()
|
||||||
self.purchase3.save()
|
self.purchase3.save()
|
||||||
|
|
||||||
url = '/don_confiao/purchases/for_reconciliation'
|
url = "/don_confiao/purchases/for_reconciliation"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
rawContent = response.content.decode('utf-8')
|
rawContent = response.content.decode("utf-8")
|
||||||
content = json.loads(rawContent)
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
self.assertIn('CASH', content.keys())
|
self.assertIn("CASH", content.keys())
|
||||||
self.assertIn('CONFIAR', content.keys())
|
self.assertIn("CONFIAR", content.keys())
|
||||||
self.assertEqual(2, len(content.get('CASH')))
|
self.assertEqual(2, len(content.get("CASH")))
|
||||||
self.assertEqual(1, len(content.get('CONFIAR')))
|
self.assertEqual(1, len(content.get("CONFIAR")))
|
||||||
self.assertNotIn(str(37*72500), rawContent)
|
self.assertNotIn(str(37 * 72500), rawContent)
|
||||||
self.assertIn(str(47*72500), rawContent)
|
self.assertIn(str(47 * 72500), rawContent)
|
||||||
|
|
||||||
def test_don_create_reconcialiation_with_bad_numbers(self):
|
def test_don_create_reconcialiation_with_bad_numbers(self):
|
||||||
reconciliation = ReconciliationJar()
|
reconciliation = ReconciliationJar()
|
||||||
@@ -114,131 +120,137 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
reconciliation.clean()
|
reconciliation.clean()
|
||||||
reconciliation.save()
|
reconciliation.save()
|
||||||
|
|
||||||
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(self):
|
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(
|
||||||
url = '/don_confiao/reconciliate_jar'
|
self,
|
||||||
|
):
|
||||||
|
url = "/don_confiao/reconciliate_jar"
|
||||||
total_purchases = (11 * 72500) + (27 * 72500)
|
total_purchases = (11 * 72500) + (27 * 72500)
|
||||||
bad_total_purchases = total_purchases + 2
|
bad_total_purchases = total_purchases + 2
|
||||||
data = {
|
data = {
|
||||||
'date_time': '2024-12-02T21:07',
|
"date_time": "2024-12-02T21:07",
|
||||||
'reconcilier': 'carlos',
|
"reconcilier": "carlos",
|
||||||
'total_cash_purchases': bad_total_purchases,
|
"total_cash_purchases": bad_total_purchases,
|
||||||
'cash_taken': total_purchases,
|
"cash_taken": total_purchases,
|
||||||
'cash_discrepancy': 0,
|
"cash_discrepancy": 0,
|
||||||
'cash_purchases': [
|
"cash_purchases": [
|
||||||
self.purchase.id,
|
self.purchase.id,
|
||||||
self.purchase2.id,
|
self.purchase2.id,
|
||||||
self.purchase.id,
|
self.purchase.id,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
response = self.client.post(
|
||||||
content_type='application/json')
|
url,
|
||||||
rawContent = response.content.decode('utf-8')
|
data=json.dumps(data).encode("utf-8"),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
rawContent = response.content.decode("utf-8")
|
||||||
content = json.loads(rawContent)
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertIn('error', content)
|
self.assertIn("error", content)
|
||||||
self.assertIn('total_cash_purchases', content['error'])
|
self.assertIn("total_cash_purchases", content["error"])
|
||||||
|
|
||||||
def test_create_reconciliation_with_purchases(self):
|
def test_create_reconciliation_with_purchases(self):
|
||||||
response = self._create_reconciliation_with_purchase()
|
response = self._create_reconciliation_with_purchase()
|
||||||
|
|
||||||
rawContent = response.content.decode('utf-8')
|
rawContent = response.content.decode("utf-8")
|
||||||
content = json.loads(rawContent)
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('id', content)
|
self.assertIn("id", content)
|
||||||
|
|
||||||
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
purchases = Sale.objects.filter(reconciliation_id=content["id"])
|
||||||
self.assertEqual(len(purchases), 2)
|
self.assertEqual(len(purchases), 2)
|
||||||
|
|
||||||
def test_create_reconciliation_with_purchases_and_other_totals(self):
|
def test_create_reconciliation_with_purchases_and_other_totals(self):
|
||||||
url = '/don_confiao/reconciliate_jar'
|
url = "/don_confiao/reconciliate_jar"
|
||||||
total_purchases = (11 * 72500) + (27 * 72500)
|
total_purchases = (11 * 72500) + (27 * 72500)
|
||||||
data = {
|
data = {
|
||||||
'date_time': '2024-12-02T21:07',
|
"date_time": "2024-12-02T21:07",
|
||||||
'reconcilier': 'carlos',
|
"reconcilier": "carlos",
|
||||||
'total_cash_purchases': total_purchases,
|
"total_cash_purchases": total_purchases,
|
||||||
'cash_taken': total_purchases,
|
"cash_taken": total_purchases,
|
||||||
'cash_discrepancy': 0,
|
"cash_discrepancy": 0,
|
||||||
'cash_purchases': [
|
"cash_purchases": [
|
||||||
self.purchase.id,
|
self.purchase.id,
|
||||||
self.purchase2.id,
|
self.purchase2.id,
|
||||||
],
|
],
|
||||||
'other_totals': {
|
"other_totals": {
|
||||||
'Confiar': {
|
"Confiar": {
|
||||||
'total': (47 * 72500) + 1,
|
"total": (47 * 72500) + 1,
|
||||||
'purchases': [self.purchase4.id],
|
"purchases": [self.purchase4.id],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
response = self.client.post(
|
||||||
content_type='application/json')
|
url,
|
||||||
|
data=json.dumps(data).encode("utf-8"),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
rawContent = response.content.decode('utf-8')
|
rawContent = response.content.decode("utf-8")
|
||||||
content = json.loads(rawContent)
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('id', content)
|
self.assertIn("id", content)
|
||||||
|
|
||||||
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
purchases = Sale.objects.filter(reconciliation_id=content["id"])
|
||||||
self.assertEqual(len(purchases), 3)
|
self.assertEqual(len(purchases), 3)
|
||||||
|
|
||||||
def test_list_reconciliations(self):
|
def test_list_reconciliations(self):
|
||||||
self._create_simple_reconciliation()
|
self._create_simple_reconciliation()
|
||||||
self._create_simple_reconciliation()
|
self._create_simple_reconciliation()
|
||||||
|
|
||||||
url = '/don_confiao/api/reconciliate_jar/'
|
url = "/don_confiao/api/reconciliate_jar/"
|
||||||
|
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(2, content['count'])
|
self.assertEqual(2, content["count"])
|
||||||
self.assertEqual(2, len(content['results']))
|
self.assertEqual(2, len(content["results"]))
|
||||||
self.assertEqual('2024-07-30T00:00:00Z',
|
self.assertEqual(
|
||||||
content['results'][0]['date_time'])
|
"2024-07-30T00:00:00Z", content["results"][0]["date_time"]
|
||||||
|
)
|
||||||
|
|
||||||
def test_list_reconciliations_pagination(self):
|
def test_list_reconciliations_pagination(self):
|
||||||
self._create_simple_reconciliation()
|
self._create_simple_reconciliation()
|
||||||
self._create_simple_reconciliation()
|
self._create_simple_reconciliation()
|
||||||
|
|
||||||
url = '/don_confiao/api/reconciliate_jar/?page=2&page_size=1'
|
url = "/don_confiao/api/reconciliate_jar/?page=2&page_size=1"
|
||||||
|
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
self.assertEqual(1, len(content['results']))
|
self.assertEqual(1, len(content["results"]))
|
||||||
self.assertEqual('2024-07-30T00:00:00Z',
|
self.assertEqual(
|
||||||
content['results'][0]['date_time'])
|
"2024-07-30T00:00:00Z", content["results"][0]["date_time"]
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_single_reconciliation(self):
|
def test_get_single_reconciliation(self):
|
||||||
createResponse = self._create_reconciliation_with_purchase()
|
createResponse = self._create_reconciliation_with_purchase()
|
||||||
reconciliationId = json.loads(
|
reconciliationId = json.loads(
|
||||||
createResponse.content.decode('utf-8')
|
createResponse.content.decode("utf-8")
|
||||||
)['id']
|
)["id"]
|
||||||
self.assertGreater(reconciliationId, 0)
|
self.assertGreater(reconciliationId, 0)
|
||||||
|
|
||||||
url = f'/don_confiao/api/reconciliate_jar/{reconciliationId}/'
|
url = f"/don_confiao/api/reconciliate_jar/{reconciliationId}/"
|
||||||
response = self.client.get(url, content_type='application/json')
|
response = self.client.get(url, content_type="application/json")
|
||||||
content = json.loads(
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
response.content.decode('utf-8')
|
self.assertEqual(reconciliationId, content["id"])
|
||||||
)
|
self.assertGreater(len(content["Sales"]), 0)
|
||||||
self.assertEqual(reconciliationId, content['id'])
|
|
||||||
self.assertGreater(len(content['Sales']), 0)
|
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
self.purchase.id,
|
self.purchase.id, [sale["id"] for sale in content["Sales"]]
|
||||||
[sale['id'] for sale in content['Sales']]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'CASH',
|
"CASH", [sale["payment_method"] for sale in content["Sales"]]
|
||||||
[sale['payment_method'] for sale in content['Sales']]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_reconciliation_with_decimal_on_sale_lines(self):
|
def test_create_reconciliation_with_decimal_on_sale_lines(self):
|
||||||
customer = Customer()
|
customer = Customer()
|
||||||
customer.name = 'Consumidor final'
|
customer.name = "Consumidor final"
|
||||||
customer.save()
|
customer.save()
|
||||||
|
|
||||||
product = Product()
|
product = Product()
|
||||||
@@ -249,7 +261,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
purchase = Sale()
|
purchase = Sale()
|
||||||
purchase.customer = customer
|
purchase.customer = customer
|
||||||
purchase.date = "2024-07-30"
|
purchase.date = "2024-07-30"
|
||||||
purchase.payment_method = 'CASH'
|
purchase.payment_method = "CASH"
|
||||||
purchase.clean()
|
purchase.clean()
|
||||||
purchase.save()
|
purchase.save()
|
||||||
|
|
||||||
@@ -260,23 +272,23 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
line.unit_price = "57.50"
|
line.unit_price = "57.50"
|
||||||
line.save()
|
line.save()
|
||||||
|
|
||||||
url = '/don_confiao/reconciliate_jar'
|
url = "/don_confiao/reconciliate_jar"
|
||||||
total_purchases = 13.80
|
total_purchases = 13.80
|
||||||
data = {
|
data = {
|
||||||
'date_time': '2024-12-02T21:07',
|
"date_time": "2024-12-02T21:07",
|
||||||
'reconcilier': 'carlos',
|
"reconcilier": "carlos",
|
||||||
'total_cash_purchases': total_purchases,
|
"total_cash_purchases": total_purchases,
|
||||||
'cash_taken': total_purchases,
|
"cash_taken": total_purchases,
|
||||||
'cash_discrepancy': 0,
|
"cash_discrepancy": 0,
|
||||||
'cash_purchases': [purchase.id],
|
"cash_purchases": [purchase.id],
|
||||||
}
|
}
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
url, data=json.dumps(data).encode('utf-8'),
|
url,
|
||||||
content_type='application/json'
|
data=json.dumps(data).encode("utf-8"),
|
||||||
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
def _create_simple_reconciliation(self):
|
def _create_simple_reconciliation(self):
|
||||||
reconciliation = ReconciliationJar()
|
reconciliation = ReconciliationJar()
|
||||||
reconciliation.date_time = "2024-07-30"
|
reconciliation.date_time = "2024-07-30"
|
||||||
@@ -288,19 +300,22 @@ class TestJarReconcliation(TestCase, LoginMixin):
|
|||||||
return reconciliation
|
return reconciliation
|
||||||
|
|
||||||
def _create_reconciliation_with_purchase(self):
|
def _create_reconciliation_with_purchase(self):
|
||||||
url = '/don_confiao/reconciliate_jar'
|
url = "/don_confiao/reconciliate_jar"
|
||||||
total_purchases = (11 * 72500) + (27 * 72500)
|
total_purchases = (11 * 72500) + (27 * 72500)
|
||||||
data = {
|
data = {
|
||||||
'date_time': '2024-12-02T21:07',
|
"date_time": "2024-12-02T21:07",
|
||||||
'reconcilier': 'carlos',
|
"reconcilier": "carlos",
|
||||||
'total_cash_purchases': total_purchases,
|
"total_cash_purchases": total_purchases,
|
||||||
'cash_taken': total_purchases,
|
"cash_taken": total_purchases,
|
||||||
'cash_discrepancy': 0,
|
"cash_discrepancy": 0,
|
||||||
'cash_purchases': [
|
"cash_purchases": [
|
||||||
self.purchase.id,
|
self.purchase.id,
|
||||||
self.purchase2.id,
|
self.purchase2.id,
|
||||||
self.purchase.id,
|
self.purchase.id,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
return self.client.post(
|
||||||
content_type='application/json')
|
url,
|
||||||
|
data=json.dumps(data).encode("utf-8"),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
from ..models import Customer
|
from ..models.customers import Customer
|
||||||
|
|
||||||
|
|
||||||
class TestCustomer(TestCase):
|
class TestCustomer(TestCase):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from ..models import ProductCategory, Product
|
from ..models.products import ProductCategory, Product
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
@@ -20,24 +20,6 @@ class TestProducts(TestCase):
|
|||||||
self.assertIsNone(product.external_id)
|
self.assertIsNone(product.external_id)
|
||||||
self.assertIsNone(product.unit_external_id)
|
self.assertIsNone(product.unit_external_id)
|
||||||
|
|
||||||
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):
|
def test_update_products(self):
|
||||||
self._import_csv()
|
self._import_csv()
|
||||||
first_products = self._get_products()
|
first_products = self._get_products()
|
||||||
@@ -45,55 +27,6 @@ class TestProducts(TestCase):
|
|||||||
seconds_products = self._get_products()
|
seconds_products = self._get_products()
|
||||||
self.assertEqual(len(first_products), len(seconds_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):
|
def _get_products(self):
|
||||||
products_response = self.client.get("/don_confiao/productos")
|
products_response = self.client.get("/don_confiao/productos")
|
||||||
return json.loads(products_response.content.decode("utf-8"))
|
return json.loads(products_response.content.decode("utf-8"))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from decimal import Decimal
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from ..models import Product
|
from ..models.products import Product
|
||||||
from .Mixins import LoginMixin
|
from .Mixins import LoginMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -12,120 +12,163 @@ class TestProductsFromTryton(TestCase, LoginMixin):
|
|||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.product = Product.objects.create(
|
self.product = Product.objects.create(
|
||||||
name='Panela',
|
name="Panela",
|
||||||
price=5000,
|
price=5000,
|
||||||
measuring_unit='UNIT',
|
measuring_unit="UNIT",
|
||||||
unit_external_id=1,
|
unit_external_id=1,
|
||||||
external_id=191
|
external_id=191,
|
||||||
)
|
)
|
||||||
self.product.save()
|
self.product.save()
|
||||||
|
|
||||||
self.product2 = Product.objects.create(
|
self.product2 = Product.objects.create(
|
||||||
name='Papa',
|
name="Papa",
|
||||||
price=4500,
|
price=4500,
|
||||||
measuring_unit='Kilogram',
|
measuring_unit="Kilogram",
|
||||||
unit_external_id=2,
|
unit_external_id=2,
|
||||||
external_id=192
|
external_id=192,
|
||||||
)
|
)
|
||||||
self.product2.save()
|
self.product2.save()
|
||||||
|
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.call')
|
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.connect')
|
@patch("sabatron_tryton_rpc_client.client.Client.connect")
|
||||||
def test_create_import_products(self, mock_connect, mock_call):
|
def test_create_import_products(self, mock_connect, mock_call):
|
||||||
mock_connect.return_value = None
|
mock_connect.return_value = None
|
||||||
|
|
||||||
def fake_call(*args, **kwargs):
|
def fake_call(*args, **kwargs):
|
||||||
product_search = 'model.product.product.search'
|
product_search = "model.product.product.search"
|
||||||
search_args = [[["salable", "=", True]], 0, 1000, [['rec_name', 'ASC'], ['id', None]], {'company': 1}]
|
search_args = [
|
||||||
if (args == (product_search, search_args)):
|
[["salable", "=", True]],
|
||||||
|
0,
|
||||||
|
1000,
|
||||||
|
[["rec_name", "ASC"], ["id", None]],
|
||||||
|
{"company": 1},
|
||||||
|
]
|
||||||
|
if args == (product_search, search_args):
|
||||||
return [190, 191, 192]
|
return [190, 191, 192]
|
||||||
|
|
||||||
product_read = 'model.product.product.read'
|
product_read = "model.product.product.read"
|
||||||
product_args = ([190, 191, 192],
|
product_args = (
|
||||||
['id', 'name', 'default_uom.id',
|
[190, 191, 192],
|
||||||
'default_uom.rec_name', 'list_price'],
|
[
|
||||||
{'company': 1}
|
"id",
|
||||||
)
|
"name",
|
||||||
if (args == (product_read, product_args)):
|
"default_uom.id",
|
||||||
|
"default_uom.rec_name",
|
||||||
|
"list_price",
|
||||||
|
],
|
||||||
|
{"company": 1},
|
||||||
|
)
|
||||||
|
if args == (product_read, product_args):
|
||||||
return [
|
return [
|
||||||
{'id': 190, 'list_price': Decimal('25000'),
|
{
|
||||||
'name': 'Producto 1',
|
"id": 190,
|
||||||
'default_uom.': {'id': 1, 'rec_name': 'Unit'}},
|
"list_price": Decimal("25000"),
|
||||||
{'id': 191, 'list_price': Decimal('6000'),
|
"name": "Producto 1",
|
||||||
'name': 'Panela2',
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
'default_uom.': {'id': 1, 'rec_name': 'Unit'}},
|
},
|
||||||
{'id': 192, 'list_price': Decimal('4500'),
|
{
|
||||||
'name': 'Papa',
|
"id": 191,
|
||||||
'default_uom.': {'id': 2, 'rec_name': 'Kilogram'}},
|
"list_price": Decimal("6000"),
|
||||||
|
"name": "Panela2",
|
||||||
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 192,
|
||||||
|
"list_price": Decimal("4500"),
|
||||||
|
"name": "Papa",
|
||||||
|
"default_uom.": {"id": 2, "rec_name": "Kilogram"},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(f"Sorry, args non expected on this test: {args}")
|
raise Exception(
|
||||||
|
f"Sorry, args non expected on this test: {args}"
|
||||||
|
)
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
|
|
||||||
url = '/don_confiao/api/importar_productos_de_tryton'
|
url = "/don_confiao/api/importar_productos_de_tryton"
|
||||||
response = self.client.post(url)
|
response = self.client.post(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
expected_response = {
|
expected_response = {
|
||||||
'checked_tryton_products': [190, 191, 192],
|
"checked_tryton_products": [190, 191, 192],
|
||||||
'created_products': [3],
|
"created_products": [3],
|
||||||
'untouched_products': [2],
|
"untouched_products": [2],
|
||||||
'failed_products': [],
|
"failed_products": [],
|
||||||
'updated_products': [1]
|
"updated_products": [1],
|
||||||
}
|
}
|
||||||
self.assertEqual(content, expected_response)
|
self.assertEqual(content, expected_response)
|
||||||
|
|
||||||
created_product = Product.objects.get(id=3)
|
created_product = Product.objects.get(id=3)
|
||||||
self.assertEqual(created_product.external_id, str(190))
|
self.assertEqual(created_product.external_id, str(190))
|
||||||
self.assertEqual(created_product.name, 'Producto 1')
|
self.assertEqual(created_product.name, "Producto 1")
|
||||||
self.assertEqual(created_product.price, Decimal('25000'))
|
self.assertEqual(created_product.price, Decimal("25000"))
|
||||||
self.assertEqual(created_product.measuring_unit, 'Unit')
|
self.assertEqual(created_product.measuring_unit, "Unit")
|
||||||
|
|
||||||
updated_product = Product.objects.get(id=1)
|
updated_product = Product.objects.get(id=1)
|
||||||
self.assertEqual(updated_product.external_id, str(191))
|
self.assertEqual(updated_product.external_id, str(191))
|
||||||
self.assertEqual(updated_product.name, 'Panela2')
|
self.assertEqual(updated_product.name, "Panela2")
|
||||||
self.assertEqual(updated_product.price, Decimal('6000'))
|
self.assertEqual(updated_product.price, Decimal("6000"))
|
||||||
self.assertEqual(updated_product.measuring_unit, 'Unit')
|
self.assertEqual(updated_product.measuring_unit, "Unit")
|
||||||
|
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.call')
|
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||||
@patch('sabatron_tryton_rpc_client.client.Client.connect')
|
@patch("sabatron_tryton_rpc_client.client.Client.connect")
|
||||||
def test_import_duplicated_name_products(self, mock_connect, mock_call):
|
def test_import_duplicated_name_products(
|
||||||
|
self, mock_connect, mock_call
|
||||||
|
):
|
||||||
mock_connect.return_value = None
|
mock_connect.return_value = None
|
||||||
|
|
||||||
def fake_call(*args, **kwargs):
|
def fake_call(*args, **kwargs):
|
||||||
product_search = 'model.product.product.search'
|
product_search = "model.product.product.search"
|
||||||
search_args = [[["salable", "=", True]], 0, 1000, [['rec_name', 'ASC'], ['id', None]], {'company': 1}]
|
search_args = [
|
||||||
if (args == (product_search, search_args)):
|
[["salable", "=", True]],
|
||||||
|
0,
|
||||||
|
1000,
|
||||||
|
[["rec_name", "ASC"], ["id", None]],
|
||||||
|
{"company": 1},
|
||||||
|
]
|
||||||
|
if args == (product_search, search_args):
|
||||||
return [200]
|
return [200]
|
||||||
|
|
||||||
product_read = 'model.product.product.read'
|
product_read = "model.product.product.read"
|
||||||
product_args = ([200],
|
product_args = (
|
||||||
['id', 'name', 'default_uom.id',
|
[200],
|
||||||
'default_uom.rec_name', 'list_price'],
|
[
|
||||||
{'company': 1}
|
"id",
|
||||||
)
|
"name",
|
||||||
if (args == (product_read, product_args)):
|
"default_uom.id",
|
||||||
|
"default_uom.rec_name",
|
||||||
|
"list_price",
|
||||||
|
],
|
||||||
|
{"company": 1},
|
||||||
|
)
|
||||||
|
if args == (product_read, product_args):
|
||||||
return [
|
return [
|
||||||
{'id': 200, 'list_price': Decimal('25000'),
|
{
|
||||||
'name': self.product.name,
|
"id": 200,
|
||||||
'default_uom.': {'id': 1, 'rec_name': 'Unit'}},
|
"list_price": Decimal("25000"),
|
||||||
|
"name": self.product.name,
|
||||||
|
"default_uom.": {"id": 1, "rec_name": "Unit"},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
raise Exception(f"Sorry, args non expected on this test: {args}")
|
raise Exception(
|
||||||
|
f"Sorry, args non expected on this test: {args}"
|
||||||
|
)
|
||||||
|
|
||||||
mock_call.side_effect = fake_call
|
mock_call.side_effect = fake_call
|
||||||
|
|
||||||
url = '/don_confiao/api/importar_productos_de_tryton'
|
url = "/don_confiao/api/importar_productos_de_tryton"
|
||||||
response = self.client.post(url)
|
response = self.client.post(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
content = json.loads(response.content.decode('utf-8'))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
expected_response = {
|
expected_response = {
|
||||||
'checked_tryton_products': [200],
|
"checked_tryton_products": [200],
|
||||||
'created_products': [],
|
"created_products": [],
|
||||||
'untouched_products': [],
|
"untouched_products": [],
|
||||||
'failed_products': [200],
|
"failed_products": [200],
|
||||||
'updated_products': [],
|
"updated_products": [],
|
||||||
}
|
}
|
||||||
self.assertEqual(content, expected_response)
|
self.assertEqual(content, expected_response)
|
||||||
|
|||||||
@@ -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,55 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
from ..models import Sale, Product, SaleLine, Customer
|
|
||||||
from .Mixins import LoginMixin
|
|
||||||
|
|
||||||
|
|
||||||
class TestSummaryViewPurchase(TestCase, LoginMixin):
|
|
||||||
def setUp(self):
|
|
||||||
self.login()
|
|
||||||
|
|
||||||
customer = Customer()
|
|
||||||
customer.name = 'Alejo Mono'
|
|
||||||
customer.save()
|
|
||||||
|
|
||||||
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,7 +1,9 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from ..models import Customer, Product, Sale, SaleLine
|
from ..models.customers import Customer
|
||||||
|
from ..models.products import Product
|
||||||
|
from ..models.sales import Sale, SaleLine
|
||||||
|
|
||||||
|
|
||||||
class ConfiaoTest(TestCase):
|
class ConfiaoTest(TestCase):
|
||||||
@@ -20,7 +22,7 @@ class ConfiaoTest(TestCase):
|
|||||||
sale = Sale()
|
sale = Sale()
|
||||||
sale.customer = self.customer
|
sale.customer = self.customer
|
||||||
sale.date = "2024-06-22 12:05:00"
|
sale.date = "2024-06-22 12:05:00"
|
||||||
sale.phone = '666666666'
|
sale.phone = "666666666"
|
||||||
sale.description = "Description"
|
sale.description = "Description"
|
||||||
sale.save()
|
sale.save()
|
||||||
|
|
||||||
@@ -30,9 +32,9 @@ class ConfiaoTest(TestCase):
|
|||||||
sale = Sale()
|
sale = Sale()
|
||||||
sale.customer = self.customer
|
sale.customer = self.customer
|
||||||
sale.date = "2024-06-22 12:05:00"
|
sale.date = "2024-06-22 12:05:00"
|
||||||
sale.phone = '666666666'
|
sale.phone = "666666666"
|
||||||
sale.description = "Description"
|
sale.description = "Description"
|
||||||
sale.payment_method = ''
|
sale.payment_method = ""
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
sale.full_clean()
|
sale.full_clean()
|
||||||
@@ -41,7 +43,7 @@ class ConfiaoTest(TestCase):
|
|||||||
sale = Sale()
|
sale = Sale()
|
||||||
sale.customer = self.customer
|
sale.customer = self.customer
|
||||||
sale.date = "2024-06-22"
|
sale.date = "2024-06-22"
|
||||||
sale.phone = '666666666'
|
sale.phone = "666666666"
|
||||||
sale.description = "Description"
|
sale.description = "Description"
|
||||||
|
|
||||||
line = SaleLine()
|
line = SaleLine()
|
||||||
@@ -58,7 +60,7 @@ class ConfiaoTest(TestCase):
|
|||||||
sale = Sale()
|
sale = Sale()
|
||||||
sale.customer = self.customer
|
sale.customer = self.customer
|
||||||
sale.date = "2024-06-22"
|
sale.date = "2024-06-22"
|
||||||
sale.phone = '666666666'
|
sale.phone = "666666666"
|
||||||
sale.description = "Description"
|
sale.description = "Description"
|
||||||
|
|
||||||
line1 = SaleLine()
|
line1 = SaleLine()
|
||||||
@@ -81,15 +83,14 @@ class ConfiaoTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(len(SaleLine.objects.all()), 2)
|
self.assertEqual(len(SaleLine.objects.all()), 2)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
Sale.objects.all()[0].saleline_set.all()[0].quantity,
|
Sale.objects.all()[0].saleline_set.all()[0].quantity, 2
|
||||||
2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_allow_sale_without_description(self):
|
def test_allow_sale_without_description(self):
|
||||||
sale = Sale()
|
sale = Sale()
|
||||||
sale.customer = self.customer
|
sale.customer = self.customer
|
||||||
sale.date = "2024-06-22"
|
sale.date = "2024-06-22"
|
||||||
sale.phone = '666666666'
|
sale.phone = "666666666"
|
||||||
sale.description = None
|
sale.description = None
|
||||||
sale.save()
|
sale.save()
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
|
||||||
@@ -4,39 +4,55 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from . import views
|
from . import views
|
||||||
from . import api_views
|
from . import api_views
|
||||||
|
|
||||||
app_name = 'don_confiao'
|
app_name = "don_confiao"
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'sales', api_views.SaleView, basename='sale')
|
router.register(r"sales", api_views.SaleView, basename="sale")
|
||||||
router.register(r'customers', api_views.CustomerView, basename='customer')
|
router.register(r"customers", api_views.CustomerView, basename="customer")
|
||||||
router.register(r'products', api_views.ProductView, basename='product')
|
router.register(r"products", api_views.ProductView, basename="product")
|
||||||
router.register(r'reconciliate_jar', api_views.ReconciliateJarModelView,
|
router.register(
|
||||||
basename='reconciliate_jar')
|
r"reconciliate_jar",
|
||||||
|
api_views.ReconciliateJarModelView,
|
||||||
|
basename="reconciliate_jar",
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
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("productos", views.products, name="products"),
|
||||||
path("lista_productos", views.ProductListView.as_view(), name='product_list'),
|
path(
|
||||||
path("importar_productos", views.import_products, name="import_products"),
|
"resumen_compra_json/<int:id>",
|
||||||
path('api/importar_productos_de_tryton',
|
api_views.SaleSummary.as_view(),
|
||||||
api_views.ProductsFromTrytonView.as_view(),
|
name="purchase_json_summary",
|
||||||
name="products_from_tryton"),
|
),
|
||||||
path("importar_terceros", views.import_customers, name="import_customers"),
|
path("api/", include(router.urls)),
|
||||||
path('api/importar_clientes_de_tryton',
|
path(
|
||||||
api_views.CustomersFromTrytonView.as_view(),
|
"api/importar_productos_de_tryton",
|
||||||
name="customers_from_tryton"),
|
api_views.ProductsFromTrytonView.as_view(),
|
||||||
path("exportar_ventas_para_tryton",
|
name="products_from_tryton",
|
||||||
views.exportar_ventas_para_tryton,
|
),
|
||||||
name="exportar_ventas_para_tryton"),
|
path(
|
||||||
path('api/enviar_ventas_a_tryton', api_views.SalesToTrytonView.as_view(), name="send_tryton"),
|
"api/importar_clientes_de_tryton",
|
||||||
path("resumen_compra/<int:id>", views.purchase_summary, name="purchase_summary"),
|
api_views.CustomersFromTrytonView.as_view(),
|
||||||
path("resumen_compra_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"),
|
name="customers_from_tryton",
|
||||||
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(
|
||||||
path('reconciliate_jar', api_views.ReconciliateJarView.as_view()),
|
"api/enviar_ventas_a_tryton",
|
||||||
path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()),
|
api_views.SalesToTrytonView.as_view(),
|
||||||
path('api/sales/for_tryton', api_views.SalesForTrytonView.as_view()),
|
name="send_tryton",
|
||||||
path('api/', include(router.urls)),
|
),
|
||||||
|
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/sales/for_tryton", api_views.SalesForTrytonView.as_view()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
|||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from .models import (
|
from .models.sales import Sale, SaleLine
|
||||||
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
|
from .models.customers import Customer
|
||||||
from .forms import (
|
from .models.sales import Sale, SaleLine, Payment
|
||||||
ImportProductsForm,
|
from .models.products import Product, ProductCategory
|
||||||
ImportCustomersForm,
|
from .models.payments import PaymentMethods, ReconciliationJar
|
||||||
PurchaseForm,
|
|
||||||
SaleLineFormSet,
|
|
||||||
PurchaseSummaryForm)
|
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
@@ -26,105 +23,13 @@ class DecimalEncoder(json.JSONEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'don_confiao/index.html')
|
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):
|
def products(request):
|
||||||
return JsonResponse(Product.to_list(), safe=False)
|
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="&"):
|
def _categories_from_csv_string(categories_string, separator="&"):
|
||||||
categories = categories_string.split(separator)
|
categories = categories_string.split(separator)
|
||||||
clean_categories = [c.strip() for c in categories]
|
clean_categories = [c.strip() for c in categories]
|
||||||
@@ -136,33 +41,32 @@ def _category_from_name(name):
|
|||||||
|
|
||||||
|
|
||||||
def handle_import_products_file(csv_file):
|
def handle_import_products_file(csv_file):
|
||||||
data = io.StringIO(csv_file.read().decode('utf-8'))
|
data = io.StringIO(csv_file.read().decode("utf-8"))
|
||||||
reader = csv.DictReader(data, quotechar='"')
|
reader = csv.DictReader(data, quotechar='"')
|
||||||
for row in reader:
|
for row in reader:
|
||||||
product, created = Product.objects.update_or_create(
|
product, created = Product.objects.update_or_create(
|
||||||
name=row['producto'],
|
name=row["producto"],
|
||||||
defaults={
|
defaults={
|
||||||
'price': row['precio'],
|
"price": row["precio"],
|
||||||
'measuring_unit': row['unidad']
|
"measuring_unit": row["unidad"],
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
categories = _categories_from_csv_string(row["categorias"])
|
categories = _categories_from_csv_string(row["categorias"])
|
||||||
product.categories.clear()
|
product.categories.clear()
|
||||||
for category in categories:
|
for category in categories:
|
||||||
product.categories.add(category)
|
product.categories.add(category)
|
||||||
|
|
||||||
|
|
||||||
def handle_import_customers_file(csv_file):
|
def handle_import_customers_file(csv_file):
|
||||||
data = io.StringIO(csv_file.read().decode('utf-8'))
|
data = io.StringIO(csv_file.read().decode("utf-8"))
|
||||||
reader = csv.DictReader(data, quotechar='"')
|
reader = csv.DictReader(data, quotechar='"')
|
||||||
for row in reader:
|
for row in reader:
|
||||||
customer, created = Customer.objects.update_or_create(
|
customer, created = Customer.objects.update_or_create(
|
||||||
name=row['nombre'],
|
name=row["nombre"],
|
||||||
defaults={
|
defaults={"email": row["correo"], "phone": row["telefono"]},
|
||||||
'email': row['correo'],
|
|
||||||
'phone': row['telefono']
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sales_to_tryton_csv(sales):
|
def sales_to_tryton_csv(sales):
|
||||||
tryton_sales_header = [
|
tryton_sales_header = [
|
||||||
"Tercero",
|
"Tercero",
|
||||||
@@ -182,7 +86,7 @@ def sales_to_tryton_csv(sales):
|
|||||||
"Tienda",
|
"Tienda",
|
||||||
"Terminal de venta",
|
"Terminal de venta",
|
||||||
"Autorecogida",
|
"Autorecogida",
|
||||||
"Comentario"
|
"Comentario",
|
||||||
]
|
]
|
||||||
|
|
||||||
csv_data = [tryton_sales_header]
|
csv_data = [tryton_sales_header]
|
||||||
@@ -194,7 +98,7 @@ def sales_to_tryton_csv(sales):
|
|||||||
first_sale_line = sale_lines[0]
|
first_sale_line = sale_lines[0]
|
||||||
customer_info = [sale.customer.name] * 3 + [sale.description] * 2
|
customer_info = [sale.customer.name] * 3 + [sale.description] * 2
|
||||||
first_line = customer_info + [
|
first_line = customer_info + [
|
||||||
sale.date.strftime('%Y-%m-%d'),
|
sale.date.strftime("%Y-%m-%d"),
|
||||||
"Contado",
|
"Contado",
|
||||||
"Almacén",
|
"Almacén",
|
||||||
"Peso colombiano",
|
"Peso colombiano",
|
||||||
@@ -206,39 +110,21 @@ def sales_to_tryton_csv(sales):
|
|||||||
"Tienda La Ilusion",
|
"Tienda La Ilusion",
|
||||||
"La Ilusion",
|
"La Ilusion",
|
||||||
True,
|
True,
|
||||||
sale.description]
|
sale.description,
|
||||||
|
]
|
||||||
lines.append(first_line)
|
lines.append(first_line)
|
||||||
for line in sale_lines[1:]:
|
for line in sale_lines[1:]:
|
||||||
lines.append([""]*9+[
|
lines.append(
|
||||||
line.product.name,
|
[""] * 9
|
||||||
line.quantity,
|
+ [
|
||||||
line.unit_price,
|
line.product.name,
|
||||||
"Unidad"]+[""]*5)
|
line.quantity,
|
||||||
|
line.unit_price,
|
||||||
|
"Unidad",
|
||||||
|
]
|
||||||
|
+ [""] * 5
|
||||||
|
)
|
||||||
for row in lines:
|
for row in lines:
|
||||||
csv_data.append(row)
|
csv_data.append(row)
|
||||||
|
|
||||||
return csv_data
|
return csv_data
|
||||||
|
|
||||||
|
|
||||||
def exportar_ventas_para_tryton(request):
|
|
||||||
if request.method == "GET":
|
|
||||||
response = HttpResponse(content_type='text/csv')
|
|
||||||
response['Content-Disposition'] = "attachment; filename=sales.csv"
|
|
||||||
sales = Sale.objects.all()
|
|
||||||
csv_data = sales_to_tryton_csv(sales)
|
|
||||||
writer = csv.writer(response)
|
|
||||||
for row in csv_data:
|
|
||||||
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()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user