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:
2026-05-28 15:25:27 -05:00
parent ecef46b4bb
commit f97b47081c
40 changed files with 948 additions and 1446 deletions

View File

@@ -20,7 +20,7 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:7001,http://localhos
# No additional DB configuration needed for SQLite
# Tryton ERP Configuration
TRYTON_HOST=localhost
TRYTON_DATABASE=tryton
TRYTON_USERNAME=admin
TRYTON_PASSWORD=admin
TRYTON_HOST=recreo.onecluster.com.co
TRYTON_DATABASE=ilusion_staging
TRYTON_USERNAME=alejandro.ayala
TRYTON_PASSWORD=cl4v3alejo

View File

@@ -9,44 +9,45 @@ post /token/
{
"username": "admin",
"password": "123"
"password": "admin"
}
**** respuesta
#+begin_src json
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3MTE4NzYxOSwiaWF0IjoxNzcxMTAxMjE5LCJqdGkiOiI5ZTgzNGRlM2QzMmQ0NmQyODEwZGQ2MjI2ODUwNjgzNyIsInVzZXJfaWQiOiIyIn0.JaUOqEAZ2T8vVT36mXfweMmYjEWsP7toD07jeeyrl1k",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzMDE5LCJpYXQiOjE3NzExMDEyMTksImp0aSI6ImFmOWFjNGM1MzBiZjQ4ZGE4Yzg2MWFjYzIzNjQ3NjU3IiwidXNlcl9pZCI6IjIifQ.6wH5sx1fyFn3Wt3DVZGYbiYi79rGthUZkgGmTqzebXc"
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3ODU1Njc5MywiaWF0IjoxNzc4NDcwMzkzLCJqdGkiOiJlMDU0NTVkNWExYzA0YjFkYWZhNWZkNzFkZGM5Mzc1NyIsInVzZXJfaWQiOiIxIn0.wZcbBrGoxDMPjZxI-GR1GTAuRtzU4qaT0rgGS5Oblf4",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA"
}
#+end_src
*** Perfil de usuario
get /users/me/
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzMDE5LCJpYXQiOjE3NzExMDEyMTksImp0aSI6ImFmOWFjNGM1MzBiZjQ4ZGE4Yzg2MWFjYzIzNjQ3NjU3IiwidXNlcl9pZCI6IjIifQ.6wH5sx1fyFn3Wt3DVZGYbiYi79rGthUZkgGmTqzebXc
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMTkzLCJpYXQiOjE3Nzg0NzAzOTMsImp0aSI6IjQxMzAxZjM4ZjUzMTQ2MTI4NTQ3NDk5NzI5YTBkNDBkIiwidXNlcl9pZCI6IjEifQ.mhKoW9vxCoS6J40lYZnr7xm-Qik9gyqZmJTzvsxGe1s
**** Respuesta
#+begin_src json
{
"id": 2,
"id": 1,
"username": "admin",
"email": "correo@example.com",
"email": "admin@admin.org",
"first_name": "",
"last_name": ""
"last_name": "",
"role": "administrator"
}
#+end_src
*** Renovar token
post /token/refresh/
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3MTE4NzYxOSwiaWF0IjoxNzcxMTAxMjE5LCJqdGkiOiI5ZTgzNGRlM2QzMmQ0NmQyODEwZGQ2MjI2ODUwNjgzNyIsInVzZXJfaWQiOiIyIn0.JaUOqEAZ2T8vVT36mXfweMmYjEWsP7toD07jeeyrl1k"
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3ODU1Njc5MywiaWF0IjoxNzc4NDcwMzkzLCJqdGkiOiJlMDU0NTVkNWExYzA0YjFkYWZhNWZkNzFkZGM5Mzc1NyIsInVzZXJfaWQiOiIxIn0.wZcbBrGoxDMPjZxI-GR1GTAuRtzU4qaT0rgGS5Oblf4"
}
**** response
#+begin_src json
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzNjA1LCJpYXQiOjE3NzExMDE4MDUsImp0aSI6ImJjZTY5ZTA3MTIyOTQxMTg5NmFjYzk1ZDNiOThhMTI0IiwidXNlcl9pZCI6IjIifQ.b4Z1c_Yi5tsLZ-7F0KZcM2tai-f1VeaE881j2pKDwYA"
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA"
}
#+end_src
** Don confiao :verb:
template http://localhost:7000/don_confiao/api/
Content-Type: application/json;
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcxMTAzNjA1LCJpYXQiOjE3NzExMDE4MDUsImp0aSI6ImJjZTY5ZTA3MTIyOTQxMTg5NmFjYzk1ZDNiOThhMTI0IiwidXNlcl9pZCI6IjIifQ.b4Z1c_Yi5tsLZ-7F0KZcM2tai-f1VeaE881j2pKDwYA
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4NDcyMjQ5LCJpYXQiOjE3Nzg0NzA0NDksImp0aSI6IjE5YTM0ZDQ5Mzk3ZDQzNGE4NDlkZTgyYzdkNWQyNjQ0IiwidXNlcl9pZCI6IjEifQ.jowmaa5SXKIWpmUGLV0dj9CydYFtuecc7s_RveJvjLA
*** todas las rutas
get
**** response
@@ -76,3 +77,13 @@ get customers/
#+end_src
*** products
get products/
*** Importar Clientes de Tryton
post importar_productos_de_tryton
{}
**** response
#+begin_src json
[]
#+end_src

View File

@@ -1,7 +1,9 @@
from django.contrib import admin
from .models import (
Customer, Sale, SaleLine, Product, ProductCategory, Payment,
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
admin.site.register(Customer)
admin.site.register(Sale)

View File

@@ -0,0 +1,5 @@
from rest_framework import viewsets
class CatalogSaleView(viewsets.ViewSet):
pass

View File

@@ -5,8 +5,22 @@ from rest_framework.views import APIView
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated
from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
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 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 .permissions import IsAdministrator
@@ -16,10 +30,10 @@ import io
import csv
import os
TRYTON_HOST = os.environ.get('TRYTON_HOST', 'localhost')
TRYTON_DATABASE = os.environ.get('TRYTON_DATABASE', 'tryton')
TRYTON_USERNAME = os.environ.get('TRYTON_USERNAME', 'admin')
TRYTON_PASSWORD = os.environ.get('TRYTON_PASSWORD', 'admin')
TRYTON_HOST = os.environ.get("TRYTON_HOST", "localhost")
TRYTON_DATABASE = os.environ.get("TRYTON_DATABASE", "tryton")
TRYTON_USERNAME = os.environ.get("TRYTON_USERNAME", "admin")
TRYTON_PASSWORD = os.environ.get("TRYTON_PASSWORD", "admin")
TRYTON_COP_CURRENCY = 31
TRYTON_COMPANY_ID = 1
TRYTON_SHOPS = [1]
@@ -27,7 +41,7 @@ TRYTON_SHOPS = [1]
class Pagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
page_size_query_param = "page_size"
class SaleView(viewsets.ModelViewSet):
@@ -36,32 +50,32 @@ class SaleView(viewsets.ModelViewSet):
def create(self, request):
data = request.data
customer = Customer.objects.get(pk=data['customer'])
date = data['date']
lines = data['saleline_set']
payment_method = data['payment_method']
description = data.get('notes', '')
customer = Customer.objects.get(pk=data["customer"])
date = data["date"]
lines = data["saleline_set"]
payment_method = data["payment_method"]
description = data.get("notes", "")
sale = Sale.objects.create(
customer=customer,
date=date,
payment_method=payment_method,
description=description
description=description,
)
for line in lines:
product = Product.objects.get(pk=line['product'])
quantity = line['quantity']
unit_price = line['unit_price']
product = Product.objects.get(pk=line["product"])
quantity = line["quantity"]
unit_price = line["unit_price"]
SaleLine.objects.create(
sale=sale,
product=product,
quantity=quantity,
unit_price=unit_price
unit_price=unit_price,
)
return Response(
{'id': sale.id, 'message': 'Venta creada con exito'},
status=201
{"id": sale.id, "message": "Venta creada con exito"},
status=201,
)
@@ -80,43 +94,56 @@ class ReconciliateJarView(APIView):
def post(self, request):
data = request.data
cash_purchases_id = data.get('cash_purchases')
cash_purchases_id = data.get("cash_purchases")
serializer = ReconciliationJarSerializer(data=data)
if serializer.is_valid():
cash_purchases = Sale.objects.filter(pk__in=cash_purchases_id)
if not self._is_valid_total(cash_purchases, data.get('total_cash_purchases')):
if not self._is_valid_total(
cash_purchases, data.get("total_cash_purchases")
):
return Response(
{'error': 'total_cash_purchases not equal to sum of all purchases.'},
status=HTTP_400_BAD_REQUEST
{
"error": "total_cash_purchases not equal to sum of all purchases."
},
status=HTTP_400_BAD_REQUEST,
)
reconciliation = serializer.save()
other_purchases = self._get_other_purchases(data.get('other_totals'))
other_purchases = self._get_other_purchases(
data.get("other_totals")
)
self._link_purchases(reconciliation, cash_purchases, other_purchases)
return Response({'id': reconciliation.id})
self._link_purchases(
reconciliation, cash_purchases, other_purchases
)
return Response({"id": reconciliation.id})
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
def get(self, request):
reconciliations = ReconciliationJar.objects.all()
serializer = ReconciliationJarSerializer(reconciliations, many=True)
serializer = ReconciliationJarSerializer(
reconciliations, many=True
)
return Response(serializer.data)
def _is_valid_total(self, purchases, total):
calculated_total = sum(p.get_total() for p in purchases)
return Decimal(calculated_total).quantize(Decimal('.0001')) == (
Decimal(total).quantize(Decimal('.0001')))
return Decimal(calculated_total).quantize(Decimal(".0001")) == (
Decimal(total).quantize(Decimal(".0001"))
)
def _get_other_purchases(self, other_totals):
if not other_totals:
return []
purchases = []
for method in other_totals:
purchases.extend(other_totals[method]['purchases'])
purchases.extend(other_totals[method]["purchases"])
if purchases:
return Sale.objects.filter(pk__in=purchases)
return []
def _link_purchases(self, reconciliation, cash_purchases, other_purchases):
def _link_purchases(
self, reconciliation, cash_purchases, other_purchases
):
for purchase in cash_purchases:
purchase.reconciliation = reconciliation
purchase.clean()
@@ -130,7 +157,9 @@ class ReconciliateJarView(APIView):
class PaymentMethodView(APIView):
def get(self, request):
serializer = PaymentMethodSerializer(PaymentMethods.choices, many=True)
serializer = PaymentMethodSerializer(
PaymentMethods.choices, many=True
)
return Response(serializer.data)
@@ -162,11 +191,11 @@ class AdminCodeValidateView(APIView):
def get(self, request, code):
codes = AdminCode.objects.filter(value=code)
return Response({'validCode': bool(codes)})
return Response({"validCode": bool(codes)})
class ReconciliateJarModelView(viewsets.ModelViewSet):
queryset = ReconciliationJar.objects.all().order_by('-date_time')
queryset = ReconciliationJar.objects.all().order_by("-date_time")
pagination_class = Pagination
serializer_class = ReconciliationJarSerializer
permission_classes = [IsAuthenticated, IsAdministrator]
@@ -178,7 +207,7 @@ class SalesForTrytonView(APIView):
def get(self, request):
sales = Sale.objects.all()
csv = self._generate_sales_CSV(sales)
return Response({'csv': csv})
return Response({"csv": csv})
def _generate_sales_CSV(self, sales):
output = io.StringIO()
@@ -198,12 +227,14 @@ class SalesToTrytonView(APIView):
hostname=TRYTON_HOST,
database=TRYTON_DATABASE,
username=TRYTON_USERNAME,
password=TRYTON_PASSWORD
password=TRYTON_PASSWORD,
)
tryton_client.connect()
method = 'model.sale.sale.create'
tryton_context = {'company': TRYTON_COMPANY_ID,
'shops': TRYTON_SHOPS}
method = "model.sale.sale.create"
tryton_context = {
"company": TRYTON_COMPANY_ID,
"shops": TRYTON_SHOPS,
}
successful = []
failed = []
@@ -212,20 +243,22 @@ class SalesToTrytonView(APIView):
for sale in sales:
try:
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)
sale.external_id = external_ids[0]
sale.save()
successful.append(sale.id)
except Exception as e:
print(f"Error al enviar la venta: {e}"
f"venta_id: {sale.id}")
print(
f"Error al enviar la venta: {e}" f"venta_id: {sale.id}"
)
failed.append(sale.id)
continue
return Response(
{'successful': successful, 'failed': failed},
status=200
{"successful": successful, "failed": failed}, status=200
)
def __to_tryton_params(self, sale, lines, tryton_context):
@@ -240,8 +273,12 @@ class TrytonSale:
self.lines = lines
def _format_date(self, _date):
return {"__class__": "date", "year": _date.year, "month": _date.month,
"day": _date.day}
return {
"__class__": "date",
"year": _date.year,
"month": _date.month,
"day": _date.day,
}
def to_tryton(self):
return {
@@ -249,17 +286,21 @@ class TrytonSale:
"shipment_address": self.sale.customer.address_external_id,
"invoice_address": self.sale.customer.address_external_id,
"currency": TRYTON_COP_CURRENCY,
"comment": self.sale.description or '',
"description": "Metodo pago: " + str(
self.sale.payment_method or ''
),
"comment": self.sale.description or "",
"description": "Metodo pago: "
+ str(self.sale.payment_method or ""),
"party": self.sale.customer.external_id,
"reference": "don_confiao " + str(self.sale.id),
"sale_date": self._format_date(self.sale.date),
"lines": [[
"create",
[TrytonLineSale(line).to_tryton() for line in self.lines]
]],
"lines": [
[
"create",
[
TrytonLineSale(line).to_tryton()
for line in self.lines
],
]
],
"self_pick_up": True,
}
@@ -277,7 +318,7 @@ class TrytonLineSale:
"quantity": self._format_decimal(self.sale_line.quantity),
"type": "line",
"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,
database=TRYTON_DATABASE,
username=TRYTON_USERNAME,
password=TRYTON_PASSWORD
password=TRYTON_PASSWORD,
)
tryton_client.connect()
method = 'model.product.product.search'
context = {'company': 1}
params = [[["salable", "=", True]], 0, 1000, [["rec_name", "ASC"], ["id", None]], context]
method = "model.product.product.search"
context = {"company": 1}
params = [
[["salable", "=", True]],
0,
1000,
[["rec_name", "ASC"], ["id", None]],
context,
]
product_ids = tryton_client.call(method, params)
tryton_products = self.__get_product_datails_from_tryton(
product_ids, tryton_client, context
@@ -308,7 +355,7 @@ class ProductsFromTrytonView(APIView):
for tryton_product in tryton_products:
try:
product = Product.objects.get(
external_id=tryton_product.get('id')
external_id=tryton_product.get("id")
)
except Product.DoesNotExist:
try:
@@ -316,9 +363,11 @@ class ProductsFromTrytonView(APIView):
created_products.append(product.id)
continue
except Exception as e:
print(f"Error al importar productos: {e}"
f"El producto: {tryton_product}")
failed_products.append(tryton_product.get('id'))
print(
f"Error al importar productos: {e}"
f"El producto: {tryton_product}"
)
failed_products.append(tryton_product.get("id"))
continue
if self.__need_update(product, tryton_product):
@@ -329,50 +378,57 @@ class ProductsFromTrytonView(APIView):
return Response(
{
'checked_tryton_products': checked_tryton_products,
'failed_products': failed_products,
'updated_products': updated_products,
'created_products': created_products,
'untouched_products': untouched_products,
"checked_tryton_products": checked_tryton_products,
"failed_products": failed_products,
"updated_products": updated_products,
"created_products": created_products,
"untouched_products": untouched_products,
},
status=200
status=200,
)
def __get_product_datails_from_tryton(self, product_ids, tryton_client, context):
tryton_fields = ['id', 'name', 'default_uom.id',
'default_uom.rec_name', 'list_price']
method = 'model.product.product.read'
def __get_product_datails_from_tryton(
self, product_ids, tryton_client, context
):
tryton_fields = [
"id",
"name",
"default_uom.id",
"default_uom.rec_name",
"list_price",
]
method = "model.product.product.read"
params = (product_ids, tryton_fields, context)
response = tryton_client.call(method, params)
return response
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
if not product.price == tryton_product.get('list_price'):
if not product.price == tryton_product.get("list_price"):
return True
unit = tryton_product.get('default_uom.')
if not product.measuring_unit == unit.get('rec_name'):
unit = tryton_product.get("default_uom.")
if not product.measuring_unit == unit.get("rec_name"):
return True
def __create_product(self, tryton_product):
product = Product()
product.name = tryton_product.get('name')
product.price = tryton_product.get('list_price')
product.external_id = tryton_product.get('id')
unit = tryton_product.get('default_uom.')
product.measuring_unit = unit.get('rec_name')
product.unit_external_id = unit.get('id')
product.name = tryton_product.get("name")
product.price = tryton_product.get("list_price")
product.external_id = tryton_product.get("id")
unit = tryton_product.get("default_uom.")
product.measuring_unit = unit.get("rec_name")
product.unit_external_id = unit.get("id")
product.save()
return product
def __update_product(self, product, tryton_product):
product.name = tryton_product.get('name')
product.price = tryton_product.get('list_price')
product.external_id = tryton_product.get('id')
unit = tryton_product.get('default_uom.')
product.measuring_unit = unit.get('rec_name')
product.unit_external_id = unit.get('id')
product.name = tryton_product.get("name")
product.price = tryton_product.get("list_price")
product.external_id = tryton_product.get("id")
unit = tryton_product.get("default_uom.")
product.measuring_unit = unit.get("rec_name")
product.unit_external_id = unit.get("id")
product.save()
@@ -384,11 +440,11 @@ class CustomersFromTrytonView(APIView):
hostname=TRYTON_HOST,
database=TRYTON_DATABASE,
username=TRYTON_USERNAME,
password=TRYTON_PASSWORD
password=TRYTON_PASSWORD,
)
tryton_client.connect()
method = 'model.party.party.search'
context = {'company': 1}
method = "model.party.party.search"
context = {"company": 1}
params = [[], 0, 1000, [["name", "ASC"], ["id", None]], context]
party_ids = tryton_client.call(method, params)
tryton_parties = self.__get_party_datails(
@@ -403,7 +459,7 @@ class CustomersFromTrytonView(APIView):
for tryton_party in tryton_parties:
try:
customer = Customer.objects.get(
external_id=tryton_party.get('id')
external_id=tryton_party.get("id")
)
except Customer.DoesNotExist:
customer = self.__create_customer(tryton_party)
@@ -417,41 +473,52 @@ class CustomersFromTrytonView(APIView):
return Response(
{
'checked_tryton_parties': checked_tryton_parties,
'failed_parties': failed_parties,
'updated_customers': updated_customers,
'created_customers': created_customers,
'untouched_customers': untouched_customers,
"checked_tryton_parties": checked_tryton_parties,
"failed_parties": failed_parties,
"updated_customers": updated_customers,
"created_customers": created_customers,
"untouched_customers": untouched_customers,
},
status=200
status=200,
)
def __get_party_datails(self, party_ids, tryton_client, context):
tryton_fields = ['id', 'name', 'addresses']
method = 'model.party.party.read'
tryton_fields = ["id", "name", "addresses"]
method = "model.party.party.read"
params = (party_ids, tryton_fields, context)
response = tryton_client.call(method, params)
return response
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
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
if not customer.address_external_id == str(tryton_party.get('addresses')[0]):
if (
tryton_party.get("addresses")
and tryton_party.get("addresses")[0]
):
if not customer.address_external_id == str(
tryton_party.get("addresses")[0]
):
return True
def __create_customer(self, tryton_party):
customer = Customer()
customer.name = tryton_party.get('name')
customer.external_id = tryton_party.get('id')
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
customer.address_external_id = tryton_party.get('addresses')[0]
customer.name = tryton_party.get("name")
customer.external_id = tryton_party.get("id")
if (
tryton_party.get("addresses")
and tryton_party.get("addresses")[0]
):
customer.address_external_id = tryton_party.get("addresses")[0]
customer.save()
return customer
def __update_customer(self, customer, tryton_party):
customer.name = tryton_party.get('name')
customer.external_id = tryton_party.get('id')
if tryton_party.get('addresses') and tryton_party.get('addresses')[0]:
customer.address_external_id = tryton_party.get('addresses')[0]
customer.name = tryton_party.get("name")
customer.external_id = tryton_party.get("id")
if (
tryton_party.get("addresses")
and tryton_party.get("addresses")[0]
):
customer.address_external_id = tryton_party.get("addresses")[0]
customer.save()

View File

@@ -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__'
)

View File

@@ -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)

View File

@@ -0,0 +1,5 @@
from django.db import models
class AdminCode(models.Model):
value = models.CharField(max_length=255, null=False, blank=False)

View 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

View 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.")}
)

View 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

View 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)

View File

@@ -1,33 +1,51 @@
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 Meta:
model = SaleLine
fields = ['id', 'sale', 'product', 'unit_price', 'quantity']
fields = ["id", "sale", "product", "unit_price", "quantity"]
class SaleSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField(source='get_total')
total = serializers.ReadOnlyField(source="get_total")
class Meta:
model = Sale
fields = ['id', 'customer', 'date', 'saleline_set',
'total', 'payment_method', 'external_id']
fields = [
"id",
"customer",
"date",
"saleline_set",
"total",
"payment_method",
"external_id",
]
class ProductSerializer(serializers.ModelSerializer):
class Meta:
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 Meta:
model = Customer
fields = ['id', 'name', 'address', 'email', 'phone', 'external_id']
fields = ["id", "name", "address", "email", "phone", "external_id"]
class ReconciliationJarSerializer(serializers.ModelSerializer):
@@ -36,13 +54,13 @@ class ReconciliationJarSerializer(serializers.ModelSerializer):
class Meta:
model = ReconciliationJar
fields = [
'id',
'date_time',
'reconcilier',
'cash_taken',
'cash_discrepancy',
'total_cash_purchases',
'Sales',
"id",
"date_time",
"reconcilier",
"cash_taken",
"cash_discrepancy",
"total_cash_purchases",
"Sales",
]
@@ -52,8 +70,8 @@ class PaymentMethodSerializer(serializers.Serializer):
def to_representation(self, instance):
return {
'text': instance[1],
'value': instance[0],
"text": instance[1],
"value": instance[0],
}
@@ -66,8 +84,8 @@ class SaleForRenconciliationSerializer(serializers.Serializer):
def get_customer(self, sale):
return {
'id': sale.customer.id,
'name': sale.customer.name,
"id": sale.customer.id,
"name": sale.customer.name,
}
def get_total(self, sale):
@@ -77,13 +95,13 @@ class SaleForRenconciliationSerializer(serializers.Serializer):
class ListCustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ['id', 'name']
fields = ["id", "name"]
class ListProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name']
fields = ["id", "name"]
class SummarySaleLineSerializer(serializers.ModelSerializer):
@@ -91,13 +109,13 @@ class SummarySaleLineSerializer(serializers.ModelSerializer):
class Meta:
model = SaleLine
fields = ['product', 'quantity', 'unit_price', 'description']
fields = ["product", "quantity", "unit_price", "description"]
class SaleSummarySerializer(serializers.ModelSerializer):
customer = ListCustomerSerializer()
lines = SummarySaleLineSerializer(many=True, source='saleline_set')
lines = SummarySaleLineSerializer(many=True, source="saleline_set")
class Meta:
model = Sale
fields = ['id', 'date', 'customer', 'payment_method', 'lines']
fields = ["id", "date", "customer", "payment_method", "lines"]

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,6 +1,6 @@
from django.test import TestCase
from ..models import AdminCode
from ..models.admin import AdminCode
from .Mixins import LoginMixin
import json
@@ -10,33 +10,33 @@ class TestAdminCode(TestCase, LoginMixin):
def setUp(self):
self.login()
self.valid_code = 'some valid code'
self.valid_code = "some valid code"
admin_code = AdminCode()
admin_code.value = self.valid_code
admin_code.clean()
admin_code.save()
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)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertTrue(content['validCode'])
content = json.loads(response.content.decode("utf-8"))
self.assertTrue(content["validCode"])
def test_invalid_code(self):
invalid_code = 'some invalid code'
url = '/don_confiao/api/admin_code/validate/' + invalid_code
invalid_code = "some invalid code"
url = "/don_confiao/api/admin_code/validate/" + invalid_code
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertFalse(content['validCode'])
content = json.loads(response.content.decode("utf-8"))
self.assertFalse(content["validCode"])
def test_empty_code(self):
empty_code = ''
url = '/don_confiao/api/admin_code/validate/' + empty_code
empty_code = ""
url = "/don_confiao/api/admin_code/validate/" + empty_code
response = self.client.get(url)
self.assertEqual(response.status_code, 404)

View File

@@ -4,7 +4,9 @@ import io
from rest_framework import status
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
@@ -13,18 +15,15 @@ class TestAPI(APITestCase, LoginMixin):
self.login()
self.product = Product.objects.create(
name='Panela',
price=5000,
measuring_unit='UNIT'
name="Panela", price=5000, measuring_unit="UNIT"
)
self.customer = Customer.objects.create(
name='Camilo',
external_id='18'
name="Camilo", external_id="18"
)
def test_create_sale(self):
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(Sale.objects.count(), 1)
sale = Sale.objects.all()[0]
@@ -32,79 +31,63 @@ class TestAPI(APITestCase, LoginMixin):
sale.customer.name,
self.customer.name,
)
self.assertEqual(
sale.id,
content['id']
)
self.assertEqual(sale.id, content["id"])
self.assertIsNone(sale.external_id)
def test_create_sale_with_decimal(self):
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(Sale.objects.count(), 1)
sale = Sale.objects.all()[0]
self.assertEqual(
sale.customer.name,
self.customer.name
)
self.assertEqual(
sale.id,
content['id']
)
self.assertEqual(
sale.get_total(),
16500.00
)
self.assertEqual(sale.customer.name, self.customer.name)
self.assertEqual(sale.id, content["id"])
self.assertEqual(sale.get_total(), 16500.00)
def test_get_products(self):
url = '/don_confiao/api/products/'
url = "/don_confiao/api/products/"
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(self.product.name, json_response[0]['name'])
self.assertEqual(self.product.name, json_response[0]["name"])
def test_get_customers(self):
url = '/don_confiao/api/customers/'
url = "/don_confiao/api/customers/"
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(self.customer.name, json_response[0]['name'])
self.assertEqual(self.customer.name, json_response[0]["name"])
self.assertEqual(
self.customer.external_id,
json_response[0]['external_id']
self.customer.external_id, json_response[0]["external_id"]
)
def test_get_sales(self):
url = '/don_confiao/api/sales/'
url = "/don_confiao/api/sales/"
self._create_sale()
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(self.customer.id, json_response[0]['customer'])
self.assertEqual(
None,
json_response[0]['external_id']
)
self.assertEqual(self.customer.id, json_response[0]["customer"])
self.assertEqual(None, json_response[0]["external_id"])
def test_get_sales_for_tryton(self):
url = '/don_confiao/api/sales/for_tryton'
url = "/don_confiao/api/sales/for_tryton"
self._create_sale()
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.assertIn('csv', json_response)
self.assertGreater(len(json_response['csv']), 0)
self.assertIn("csv", json_response)
self.assertGreater(len(json_response["csv"]), 0)
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()
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)
csv_reader = csv.reader(io.StringIO(json_response['csv']))
csv_reader = csv.reader(io.StringIO(json_response["csv"]))
expected_header = [
"Tercero",
"Dirección de facturación",
@@ -123,53 +106,115 @@ class TestAPI(APITestCase, LoginMixin):
"Tienda",
"Terminal de venta",
"Autorecogida",
"Comentario"
"Comentario",
]
self.assertEqual(next(csv_reader), expected_header)
expected_rows = [
[self.customer.name, self.customer.name, self.customer.name, "",
"", "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", "", "", "", "", ""
],
[
self.customer.name,
self.customer.name,
self.customer.name,
"",
"",
"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)
self.assertEqual(rows, expected_rows)
def _create_sale(self):
url = '/don_confiao/api/sales/'
url = "/don_confiao/api/sales/"
data = {
'customer': self.customer.id,
'date': '2024-09-02',
'payment_method': 'CASH',
'saleline_set': [
{'product': self.product.id, 'quantity': 2, 'unit_price': 3000},
{'product': self.product.id, 'quantity': 3, 'unit_price': 5000}
],
}
return self.client.post(url, data, format='json')
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': [
"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": 2,
"unit_price": 3000,
},
{
'product': self.product.id,
'quantity': 3,
'unit_price': 5000
}
"product": self.product.id,
"quantity": 3,
"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")

View File

@@ -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)

View File

@@ -2,7 +2,7 @@ import json
from unittest.mock import patch
from django.test import TestCase
from ..models import Customer
from ..models.customers import Customer
from .Mixins import LoginMixin
@@ -11,61 +11,71 @@ class TestCustomersFromTryton(TestCase, LoginMixin):
self.login()
self.customer = Customer.objects.create(
name='Calos',
external_id=5
name="Calos", external_id=5
)
self.customer.save()
self.customer2 = Customer.objects.create(
name='Cristian',
external_id=6
name="Cristian", external_id=6
)
self.customer2.save()
@patch('sabatron_tryton_rpc_client.client.Client.call')
@patch('sabatron_tryton_rpc_client.client.Client.connect')
@patch("sabatron_tryton_rpc_client.client.Client.call")
@patch("sabatron_tryton_rpc_client.client.Client.connect")
def test_create_import_customer(self, mock_connect, mock_call):
def fake_call(*args, **kwargs):
party_search = 'model.party.party.search'
search_args = [[], 0, 1000, [['name', 'ASC'], ['id', None]], {'company': 1}]
party_search = "model.party.party.search"
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]
party_read = 'model.party.party.read'
read_args = ([5, 6, 7, 8], ['id', 'name', 'addresses'], {'company': 1})
if (args == (party_read, read_args)):
party_read = "model.party.party.read"
read_args = (
[5, 6, 7, 8],
["id", "name", "addresses"],
{"company": 1},
)
if args == (party_read, read_args):
return [
{'id': 5, 'name': 'Carlos', 'addresses': [303]},
{'id': 6, 'name': 'Cristian', 'addresses': []},
{'id': 7, 'name': 'Ana', 'addresses': [302]},
{'id': 8, 'name': 'José', 'addresses': []},
{"id": 5, "name": "Carlos", "addresses": [303]},
{"id": 6, "name": "Cristian", "addresses": []},
{"id": 7, "name": "Ana", "addresses": [302]},
{"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
url = '/don_confiao/api/importar_clientes_de_tryton'
url = "/don_confiao/api/importar_clientes_de_tryton"
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
content = json.loads(response.content.decode("utf-8"))
expected_response = {
'checked_tryton_parties': [5, 6, 7, 8],
'created_customers': [3, 4],
'untouched_customers': [2],
'failed_parties': [],
'updated_customers': [1]
"checked_tryton_parties": [5, 6, 7, 8],
"created_customers": [3, 4],
"untouched_customers": [2],
"failed_parties": [],
"updated_customers": [1],
}
self.assertEqual(content, expected_response)
created_customer = Customer.objects.get(id=3)
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))
updated_customer = Customer.objects.get(id=1)
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))

View File

@@ -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"
]

View File

@@ -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]}])

View File

@@ -1,6 +1,12 @@
from django.test import TestCase
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
import json
@@ -11,13 +17,13 @@ class TestJarReconcliation(TestCase, LoginMixin):
self.login()
customer = Customer()
customer.name = 'Alejo Mono'
customer.name = "Alejo Mono"
customer.save()
purchase = Sale()
purchase.customer = customer
purchase.date = "2024-07-30"
purchase.payment_method = 'CASH'
purchase.payment_method = "CASH"
purchase.clean()
purchase.save()
@@ -37,7 +43,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
purchase2 = Sale()
purchase2.customer = customer
purchase2.date = "2024-07-30"
purchase.payment_method = 'CASH'
purchase.payment_method = "CASH"
purchase2.clean()
purchase2.save()
@@ -52,7 +58,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
purchase3 = Sale()
purchase3.customer = customer
purchase3.date = "2024-07-30"
purchase3.payment_method = 'CASH'
purchase3.payment_method = "CASH"
purchase3.clean()
purchase3.save()
@@ -67,7 +73,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
purchase4 = Sale()
purchase4.customer = customer
purchase4.date = "2024-07-30"
purchase4.payment_method = 'CONFIAR'
purchase4.payment_method = "CONFIAR"
purchase4.clean()
purchase4.save()
@@ -90,19 +96,19 @@ class TestJarReconcliation(TestCase, LoginMixin):
self.purchase3.clean()
self.purchase3.save()
url = '/don_confiao/purchases/for_reconciliation'
url = "/don_confiao/purchases/for_reconciliation"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
rawContent = response.content.decode('utf-8')
rawContent = response.content.decode("utf-8")
content = json.loads(rawContent)
self.assertIn('CASH', content.keys())
self.assertIn('CONFIAR', content.keys())
self.assertEqual(2, len(content.get('CASH')))
self.assertEqual(1, len(content.get('CONFIAR')))
self.assertNotIn(str(37*72500), rawContent)
self.assertIn(str(47*72500), rawContent)
self.assertIn("CASH", content.keys())
self.assertIn("CONFIAR", content.keys())
self.assertEqual(2, len(content.get("CASH")))
self.assertEqual(1, len(content.get("CONFIAR")))
self.assertNotIn(str(37 * 72500), rawContent)
self.assertIn(str(47 * 72500), rawContent)
def test_don_create_reconcialiation_with_bad_numbers(self):
reconciliation = ReconciliationJar()
@@ -114,131 +120,137 @@ class TestJarReconcliation(TestCase, LoginMixin):
reconciliation.clean()
reconciliation.save()
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(self):
url = '/don_confiao/reconciliate_jar'
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(
self,
):
url = "/don_confiao/reconciliate_jar"
total_purchases = (11 * 72500) + (27 * 72500)
bad_total_purchases = total_purchases + 2
data = {
'date_time': '2024-12-02T21:07',
'reconcilier': 'carlos',
'total_cash_purchases': bad_total_purchases,
'cash_taken': total_purchases,
'cash_discrepancy': 0,
'cash_purchases': [
"date_time": "2024-12-02T21:07",
"reconcilier": "carlos",
"total_cash_purchases": bad_total_purchases,
"cash_taken": total_purchases,
"cash_discrepancy": 0,
"cash_purchases": [
self.purchase.id,
self.purchase2.id,
self.purchase.id,
],
}
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
content_type='application/json')
rawContent = response.content.decode('utf-8')
response = self.client.post(
url,
data=json.dumps(data).encode("utf-8"),
content_type="application/json",
)
rawContent = response.content.decode("utf-8")
content = json.loads(rawContent)
self.assertEqual(response.status_code, 400)
self.assertIn('error', content)
self.assertIn('total_cash_purchases', content['error'])
self.assertIn("error", content)
self.assertIn("total_cash_purchases", content["error"])
def test_create_reconciliation_with_purchases(self):
response = self._create_reconciliation_with_purchase()
rawContent = response.content.decode('utf-8')
rawContent = response.content.decode("utf-8")
content = json.loads(rawContent)
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)
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)
data = {
'date_time': '2024-12-02T21:07',
'reconcilier': 'carlos',
'total_cash_purchases': total_purchases,
'cash_taken': total_purchases,
'cash_discrepancy': 0,
'cash_purchases': [
"date_time": "2024-12-02T21:07",
"reconcilier": "carlos",
"total_cash_purchases": total_purchases,
"cash_taken": total_purchases,
"cash_discrepancy": 0,
"cash_purchases": [
self.purchase.id,
self.purchase2.id,
],
'other_totals': {
'Confiar': {
'total': (47 * 72500) + 1,
'purchases': [self.purchase4.id],
"other_totals": {
"Confiar": {
"total": (47 * 72500) + 1,
"purchases": [self.purchase4.id],
},
},
}
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
content_type='application/json')
response = self.client.post(
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)
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)
def test_list_reconciliations(self):
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)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(2, content['count'])
self.assertEqual(2, len(content['results']))
self.assertEqual('2024-07-30T00:00:00Z',
content['results'][0]['date_time'])
content = json.loads(response.content.decode("utf-8"))
self.assertEqual(2, content["count"])
self.assertEqual(2, len(content["results"]))
self.assertEqual(
"2024-07-30T00:00:00Z", content["results"][0]["date_time"]
)
def test_list_reconciliations_pagination(self):
self._create_simple_reconciliation()
self._create_simple_reconciliation()
url = '/don_confiao/api/reconciliate_jar/?page=2&page_size=1'
url = "/don_confiao/api/reconciliate_jar/?page=2&page_size=1"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(1, len(content['results']))
self.assertEqual('2024-07-30T00:00:00Z',
content['results'][0]['date_time'])
content = json.loads(response.content.decode("utf-8"))
self.assertEqual(1, len(content["results"]))
self.assertEqual(
"2024-07-30T00:00:00Z", content["results"][0]["date_time"]
)
def test_get_single_reconciliation(self):
createResponse = self._create_reconciliation_with_purchase()
reconciliationId = json.loads(
createResponse.content.decode('utf-8')
)['id']
createResponse.content.decode("utf-8")
)["id"]
self.assertGreater(reconciliationId, 0)
url = f'/don_confiao/api/reconciliate_jar/{reconciliationId}/'
response = self.client.get(url, content_type='application/json')
content = json.loads(
response.content.decode('utf-8')
)
self.assertEqual(reconciliationId, content['id'])
self.assertGreater(len(content['Sales']), 0)
url = f"/don_confiao/api/reconciliate_jar/{reconciliationId}/"
response = self.client.get(url, content_type="application/json")
content = json.loads(response.content.decode("utf-8"))
self.assertEqual(reconciliationId, content["id"])
self.assertGreater(len(content["Sales"]), 0)
self.assertIn(
self.purchase.id,
[sale['id'] for sale in content['Sales']]
self.purchase.id, [sale["id"] for sale in content["Sales"]]
)
self.assertIn(
'CASH',
[sale['payment_method'] for sale in content['Sales']]
"CASH", [sale["payment_method"] for sale in content["Sales"]]
)
def test_create_reconciliation_with_decimal_on_sale_lines(self):
customer = Customer()
customer.name = 'Consumidor final'
customer.name = "Consumidor final"
customer.save()
product = Product()
@@ -249,7 +261,7 @@ class TestJarReconcliation(TestCase, LoginMixin):
purchase = Sale()
purchase.customer = customer
purchase.date = "2024-07-30"
purchase.payment_method = 'CASH'
purchase.payment_method = "CASH"
purchase.clean()
purchase.save()
@@ -260,23 +272,23 @@ class TestJarReconcliation(TestCase, LoginMixin):
line.unit_price = "57.50"
line.save()
url = '/don_confiao/reconciliate_jar'
url = "/don_confiao/reconciliate_jar"
total_purchases = 13.80
data = {
'date_time': '2024-12-02T21:07',
'reconcilier': 'carlos',
'total_cash_purchases': total_purchases,
'cash_taken': total_purchases,
'cash_discrepancy': 0,
'cash_purchases': [purchase.id],
"date_time": "2024-12-02T21:07",
"reconcilier": "carlos",
"total_cash_purchases": total_purchases,
"cash_taken": total_purchases,
"cash_discrepancy": 0,
"cash_purchases": [purchase.id],
}
response = self.client.post(
url, data=json.dumps(data).encode('utf-8'),
content_type='application/json'
url,
data=json.dumps(data).encode("utf-8"),
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
def _create_simple_reconciliation(self):
reconciliation = ReconciliationJar()
reconciliation.date_time = "2024-07-30"
@@ -288,19 +300,22 @@ class TestJarReconcliation(TestCase, LoginMixin):
return reconciliation
def _create_reconciliation_with_purchase(self):
url = '/don_confiao/reconciliate_jar'
url = "/don_confiao/reconciliate_jar"
total_purchases = (11 * 72500) + (27 * 72500)
data = {
'date_time': '2024-12-02T21:07',
'reconcilier': 'carlos',
'total_cash_purchases': total_purchases,
'cash_taken': total_purchases,
'cash_discrepancy': 0,
'cash_purchases': [
"date_time": "2024-12-02T21:07",
"reconcilier": "carlos",
"total_cash_purchases": total_purchases,
"cash_taken": total_purchases,
"cash_discrepancy": 0,
"cash_purchases": [
self.purchase.id,
self.purchase2.id,
self.purchase.id,
],
}
return self.client.post(url, data=json.dumps(data).encode('utf-8'),
content_type='application/json')
return self.client.post(
url,
data=json.dumps(data).encode("utf-8"),
content_type="application/json",
)

View File

@@ -2,7 +2,7 @@
from django.test import TestCase
from django.db.utils import IntegrityError
from ..models import Customer
from ..models.customers import Customer
class TestCustomer(TestCase):

View File

@@ -1,6 +1,6 @@
from django.test import Client, TestCase
from django.conf import settings
from ..models import ProductCategory, Product
from ..models.products import ProductCategory, Product
import os
import json
@@ -20,24 +20,6 @@ class TestProducts(TestCase):
self.assertIsNone(product.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):
self._import_csv()
first_products = self._get_products()
@@ -45,55 +27,6 @@ class TestProducts(TestCase):
seconds_products = self._get_products()
self.assertEqual(len(first_products), len(seconds_products))
def test_preserve_id_on_import(self):
self._import_csv()
id_aceite = Product.objects.get(name="Aceite").id
self._import_csv("example_products2.csv")
id_post_updated = Product.objects.get(name="Aceite").id
self.assertEqual(id_aceite, id_post_updated)
def test_update_categories_on_import(self):
self._import_csv()
first_products = self._get_products()
first_categories = {
p["name"]: p["categories"] for p in first_products
}
self._import_csv("example_products2.csv")
updated_products = self._get_products()
updated_categories = {
p["name"]: p["categories"] for p in updated_products
}
self.assertIn("Cafes", first_categories["Arroz"])
self.assertNotIn("Granos", first_categories["Arroz"])
self.assertIn("Granos", updated_categories["Arroz"])
self.assertNotIn("Cafes", updated_categories["Arroz"])
def test_update_price(self):
self._import_csv()
first_products = self._get_products()
first_prices = {p["name"]: p["price_list"] for p in first_products}
expected_first_prices = {
"Aceite": "50000.00",
"Café": "14000.00",
"Arroz": "7000.00",
}
self.assertDictEqual(expected_first_prices, first_prices)
self._import_csv("example_products2.csv")
updated_products = self._get_products()
updated_prices = {
p["name"]: p["price_list"] for p in updated_products
}
expected_updated_prices = {
"Aceite": "50000.00",
"Café": "15000.00",
"Arroz": "6000.00",
}
self.assertDictEqual(expected_updated_prices, updated_prices)
def _get_products(self):
products_response = self.client.get("/don_confiao/productos")
return json.loads(products_response.content.decode("utf-8"))

View File

@@ -3,7 +3,7 @@ from decimal import Decimal
from unittest.mock import patch
from django.test import TestCase
from ..models import Product
from ..models.products import Product
from .Mixins import LoginMixin
@@ -12,120 +12,163 @@ class TestProductsFromTryton(TestCase, LoginMixin):
self.login()
self.product = Product.objects.create(
name='Panela',
name="Panela",
price=5000,
measuring_unit='UNIT',
measuring_unit="UNIT",
unit_external_id=1,
external_id=191
external_id=191,
)
self.product.save()
self.product2 = Product.objects.create(
name='Papa',
name="Papa",
price=4500,
measuring_unit='Kilogram',
measuring_unit="Kilogram",
unit_external_id=2,
external_id=192
external_id=192,
)
self.product2.save()
@patch('sabatron_tryton_rpc_client.client.Client.call')
@patch('sabatron_tryton_rpc_client.client.Client.connect')
@patch("sabatron_tryton_rpc_client.client.Client.call")
@patch("sabatron_tryton_rpc_client.client.Client.connect")
def test_create_import_products(self, mock_connect, mock_call):
mock_connect.return_value = None
def fake_call(*args, **kwargs):
product_search = 'model.product.product.search'
search_args = [[["salable", "=", True]], 0, 1000, [['rec_name', 'ASC'], ['id', None]], {'company': 1}]
if (args == (product_search, search_args)):
product_search = "model.product.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]
product_read = 'model.product.product.read'
product_args = ([190, 191, 192],
['id', 'name', 'default_uom.id',
'default_uom.rec_name', 'list_price'],
{'company': 1}
)
if (args == (product_read, product_args)):
product_read = "model.product.product.read"
product_args = (
[190, 191, 192],
[
"id",
"name",
"default_uom.id",
"default_uom.rec_name",
"list_price",
],
{"company": 1},
)
if args == (product_read, product_args):
return [
{'id': 190, 'list_price': Decimal('25000'),
'name': 'Producto 1',
'default_uom.': {'id': 1, 'rec_name': 'Unit'}},
{'id': 191, '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'}},
{
"id": 190,
"list_price": Decimal("25000"),
"name": "Producto 1",
"default_uom.": {"id": 1, "rec_name": "Unit"},
},
{
"id": 191,
"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
url = '/don_confiao/api/importar_productos_de_tryton'
url = "/don_confiao/api/importar_productos_de_tryton"
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
content = json.loads(response.content.decode("utf-8"))
expected_response = {
'checked_tryton_products': [190, 191, 192],
'created_products': [3],
'untouched_products': [2],
'failed_products': [],
'updated_products': [1]
"checked_tryton_products": [190, 191, 192],
"created_products": [3],
"untouched_products": [2],
"failed_products": [],
"updated_products": [1],
}
self.assertEqual(content, expected_response)
created_product = Product.objects.get(id=3)
self.assertEqual(created_product.external_id, str(190))
self.assertEqual(created_product.name, 'Producto 1')
self.assertEqual(created_product.price, Decimal('25000'))
self.assertEqual(created_product.measuring_unit, 'Unit')
self.assertEqual(created_product.name, "Producto 1")
self.assertEqual(created_product.price, Decimal("25000"))
self.assertEqual(created_product.measuring_unit, "Unit")
updated_product = Product.objects.get(id=1)
self.assertEqual(updated_product.external_id, str(191))
self.assertEqual(updated_product.name, 'Panela2')
self.assertEqual(updated_product.price, Decimal('6000'))
self.assertEqual(updated_product.measuring_unit, 'Unit')
self.assertEqual(updated_product.name, "Panela2")
self.assertEqual(updated_product.price, Decimal("6000"))
self.assertEqual(updated_product.measuring_unit, "Unit")
@patch('sabatron_tryton_rpc_client.client.Client.call')
@patch('sabatron_tryton_rpc_client.client.Client.connect')
def test_import_duplicated_name_products(self, mock_connect, mock_call):
@patch("sabatron_tryton_rpc_client.client.Client.call")
@patch("sabatron_tryton_rpc_client.client.Client.connect")
def test_import_duplicated_name_products(
self, mock_connect, mock_call
):
mock_connect.return_value = None
def fake_call(*args, **kwargs):
product_search = 'model.product.product.search'
search_args = [[["salable", "=", True]], 0, 1000, [['rec_name', 'ASC'], ['id', None]], {'company': 1}]
if (args == (product_search, search_args)):
product_search = "model.product.product.search"
search_args = [
[["salable", "=", True]],
0,
1000,
[["rec_name", "ASC"], ["id", None]],
{"company": 1},
]
if args == (product_search, search_args):
return [200]
product_read = 'model.product.product.read'
product_args = ([200],
['id', 'name', 'default_uom.id',
'default_uom.rec_name', 'list_price'],
{'company': 1}
)
if (args == (product_read, product_args)):
product_read = "model.product.product.read"
product_args = (
[200],
[
"id",
"name",
"default_uom.id",
"default_uom.rec_name",
"list_price",
],
{"company": 1},
)
if args == (product_read, product_args):
return [
{'id': 200, 'list_price': Decimal('25000'),
'name': self.product.name,
'default_uom.': {'id': 1, 'rec_name': 'Unit'}},
{
"id": 200,
"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
url = '/don_confiao/api/importar_productos_de_tryton'
url = "/don_confiao/api/importar_productos_de_tryton"
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
content = json.loads(response.content.decode("utf-8"))
expected_response = {
'checked_tryton_products': [200],
'created_products': [],
'untouched_products': [],
'failed_products': [200],
'updated_products': [],
"checked_tryton_products": [200],
"created_products": [],
"untouched_products": [],
"failed_products": [200],
"updated_products": [],
}
self.assertEqual(content, expected_response)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,7 +1,9 @@
from django.test import TestCase
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):
@@ -20,7 +22,7 @@ class ConfiaoTest(TestCase):
sale = Sale()
sale.customer = self.customer
sale.date = "2024-06-22 12:05:00"
sale.phone = '666666666'
sale.phone = "666666666"
sale.description = "Description"
sale.save()
@@ -30,9 +32,9 @@ class ConfiaoTest(TestCase):
sale = Sale()
sale.customer = self.customer
sale.date = "2024-06-22 12:05:00"
sale.phone = '666666666'
sale.phone = "666666666"
sale.description = "Description"
sale.payment_method = ''
sale.payment_method = ""
with self.assertRaises(ValidationError):
sale.full_clean()
@@ -41,7 +43,7 @@ class ConfiaoTest(TestCase):
sale = Sale()
sale.customer = self.customer
sale.date = "2024-06-22"
sale.phone = '666666666'
sale.phone = "666666666"
sale.description = "Description"
line = SaleLine()
@@ -58,7 +60,7 @@ class ConfiaoTest(TestCase):
sale = Sale()
sale.customer = self.customer
sale.date = "2024-06-22"
sale.phone = '666666666'
sale.phone = "666666666"
sale.description = "Description"
line1 = SaleLine()
@@ -81,15 +83,14 @@ class ConfiaoTest(TestCase):
self.assertEqual(len(SaleLine.objects.all()), 2)
self.assertEqual(
Sale.objects.all()[0].saleline_set.all()[0].quantity,
2
Sale.objects.all()[0].saleline_set.all()[0].quantity, 2
)
def test_allow_sale_without_description(self):
sale = Sale()
sale.customer = self.customer
sale.date = "2024-06-22"
sale.phone = '666666666'
sale.phone = "666666666"
sale.description = None
sale.save()

View File

@@ -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())

View File

@@ -4,39 +4,55 @@ from rest_framework.routers import DefaultRouter
from . import views
from . import api_views
app_name = 'don_confiao'
app_name = "don_confiao"
router = DefaultRouter()
router.register(r'sales', api_views.SaleView, basename='sale')
router.register(r'customers', api_views.CustomerView, basename='customer')
router.register(r'products', api_views.ProductView, basename='product')
router.register(r'reconciliate_jar', api_views.ReconciliateJarModelView,
basename='reconciliate_jar')
router.register(r"sales", api_views.SaleView, basename="sale")
router.register(r"customers", api_views.CustomerView, basename="customer")
router.register(r"products", api_views.ProductView, basename="product")
router.register(
r"reconciliate_jar",
api_views.ReconciliateJarModelView,
basename="reconciliate_jar",
)
urlpatterns = [
path("", views.index, name="wellcome"),
path("comprar", views.buy, name="buy"),
path("compras", views.purchases, name="purchases"),
path("productos", views.products, name="products"),
path("lista_productos", views.ProductListView.as_view(), name='product_list'),
path("importar_productos", views.import_products, name="import_products"),
path('api/importar_productos_de_tryton',
api_views.ProductsFromTrytonView.as_view(),
name="products_from_tryton"),
path("importar_terceros", views.import_customers, name="import_customers"),
path('api/importar_clientes_de_tryton',
api_views.CustomersFromTrytonView.as_view(),
name="customers_from_tryton"),
path("exportar_ventas_para_tryton",
views.exportar_ventas_para_tryton,
name="exportar_ventas_para_tryton"),
path('api/enviar_ventas_a_tryton', api_views.SalesToTrytonView.as_view(), name="send_tryton"),
path("resumen_compra/<int:id>", views.purchase_summary, name="purchase_summary"),
path("resumen_compra_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"),
path("payment_methods/all/select_format", api_views.PaymentMethodView.as_view(), name="payment_methods_to_select"),
path('purchases/for_reconciliation', api_views.SalesForReconciliationView.as_view(), name='sales_for_reconciliation'),
path('reconciliate_jar', api_views.ReconciliateJarView.as_view()),
path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()),
path('api/sales/for_tryton', api_views.SalesForTrytonView.as_view()),
path('api/', include(router.urls)),
path(
"resumen_compra_json/<int:id>",
api_views.SaleSummary.as_view(),
name="purchase_json_summary",
),
path("api/", include(router.urls)),
path(
"api/importar_productos_de_tryton",
api_views.ProductsFromTrytonView.as_view(),
name="products_from_tryton",
),
path(
"api/importar_clientes_de_tryton",
api_views.CustomersFromTrytonView.as_view(),
name="customers_from_tryton",
),
path(
"api/enviar_ventas_a_tryton",
api_views.SalesToTrytonView.as_view(),
name="send_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("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()),
]

View File

@@ -3,14 +3,11 @@ from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.views.generic import ListView
from django.db import transaction
from .models import (
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
from .forms import (
ImportProductsForm,
ImportCustomersForm,
PurchaseForm,
SaleLineFormSet,
PurchaseSummaryForm)
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 PaymentMethods, ReconciliationJar
import csv
import io
@@ -26,105 +23,13 @@ class DecimalEncoder(json.JSONEncoder):
def index(request):
return render(request, 'don_confiao/index.html')
def buy(request):
if request.method == "POST":
sale_form = PurchaseForm(request.POST)
line_formset = SaleLineFormSet(request.POST)
sale_summary_form = PurchaseSummaryForm(request.POST)
forms_are_valid = all([
sale_form.is_valid(),
line_formset.is_valid(),
sale_summary_form.is_valid()
])
payment_method = request.POST.get('payment_method')
valid_payment_methods = [PaymentMethods.CASH]
valid_payment_method = payment_method in valid_payment_methods
if forms_are_valid:
with transaction.atomic():
sale = sale_form.save()
line_formset.instance = sale
line_formset.save()
Payment.total_payment_from_sale(
payment_method,
sale
)
return HttpResponseRedirect("compras")
else:
sale_form = PurchaseForm()
line_formset = SaleLineFormSet()
sale_summary_form = PurchaseSummaryForm()
return render(
request,
'don_confiao/purchase.html',
{
'sale_form': sale_form,
'linea_formset': line_formset,
'summary_form': sale_summary_form,
'list_products': json.dumps(Product.to_list(), cls=DecimalEncoder),
}
)
def purchases(request):
purchases = Sale.objects.all()
context = {
"purchases": purchases,
}
return render(request, 'don_confiao/purchases.html', context)
return render(request, "don_confiao/index.html")
def products(request):
return JsonResponse(Product.to_list(), safe=False)
def import_products(request):
if request.method == "POST":
form = ImportProductsForm(request.POST, request.FILES)
if form.is_valid():
handle_import_products_file(request.FILES["csv_file"])
return HttpResponseRedirect("productos")
else:
form = ImportProductsForm()
return render(
request,
"don_confiao/import_products.html",
{'form': form}
)
def import_customers(request):
if request.method == "POST":
form = ImportCustomersForm(request.POST, request.FILES)
if form.is_valid():
handle_import_customers_file(request.FILES["csv_file"])
return HttpResponseRedirect("productos")
else:
form = ImportCustomersForm()
return render(
request,
"don_confiao/import_customers.html",
{'form': form}
)
def reconciliations(request):
return HttpResponse('<h1>Reconciliaciones</h1>')
def purchase_summary(request, id):
purchase = Sale.objects.get(pk=id)
return render(
request,
"don_confiao/purchase_summary.html",
{
"purchase": purchase
}
)
def _categories_from_csv_string(categories_string, separator="&"):
categories = categories_string.split(separator)
clean_categories = [c.strip() for c in categories]
@@ -136,33 +41,32 @@ def _category_from_name(name):
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='"')
for row in reader:
product, created = Product.objects.update_or_create(
name=row['producto'],
name=row["producto"],
defaults={
'price': row['precio'],
'measuring_unit': row['unidad']
}
"price": row["precio"],
"measuring_unit": row["unidad"],
},
)
categories = _categories_from_csv_string(row["categorias"])
product.categories.clear()
for category in categories:
product.categories.add(category)
def handle_import_customers_file(csv_file):
data = io.StringIO(csv_file.read().decode('utf-8'))
data = io.StringIO(csv_file.read().decode("utf-8"))
reader = csv.DictReader(data, quotechar='"')
for row in reader:
customer, created = Customer.objects.update_or_create(
name=row['nombre'],
defaults={
'email': row['correo'],
'phone': row['telefono']
}
name=row["nombre"],
defaults={"email": row["correo"], "phone": row["telefono"]},
)
def sales_to_tryton_csv(sales):
tryton_sales_header = [
"Tercero",
@@ -182,7 +86,7 @@ def sales_to_tryton_csv(sales):
"Tienda",
"Terminal de venta",
"Autorecogida",
"Comentario"
"Comentario",
]
csv_data = [tryton_sales_header]
@@ -194,7 +98,7 @@ def sales_to_tryton_csv(sales):
first_sale_line = sale_lines[0]
customer_info = [sale.customer.name] * 3 + [sale.description] * 2
first_line = customer_info + [
sale.date.strftime('%Y-%m-%d'),
sale.date.strftime("%Y-%m-%d"),
"Contado",
"Almacén",
"Peso colombiano",
@@ -206,39 +110,21 @@ def sales_to_tryton_csv(sales):
"Tienda La Ilusion",
"La Ilusion",
True,
sale.description]
sale.description,
]
lines.append(first_line)
for line in sale_lines[1:]:
lines.append([""]*9+[
line.product.name,
line.quantity,
line.unit_price,
"Unidad"]+[""]*5)
lines.append(
[""] * 9
+ [
line.product.name,
line.quantity,
line.unit_price,
"Unidad",
]
+ [""] * 5
)
for row in lines:
csv_data.append(row)
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()