diff --git a/.env.development b/.env.development index c3ea7db..a0dc625 100644 --- a/.env.development +++ b/.env.development @@ -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 diff --git a/doc/requests.org b/doc/requests.org index e0d3a6f..703c6ca 100644 --- a/doc/requests.org +++ b/doc/requests.org @@ -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 diff --git a/tienda_ilusion/don_confiao/admin.py b/tienda_ilusion/don_confiao/admin.py index f9f2fe7..83d4e1c 100644 --- a/tienda_ilusion/don_confiao/admin.py +++ b/tienda_ilusion/don_confiao/admin.py @@ -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) diff --git a/tienda_ilusion/don_confiao/api/__init__.py b/tienda_ilusion/don_confiao/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tienda_ilusion/don_confiao/api/sale_views.py b/tienda_ilusion/don_confiao/api/sale_views.py new file mode 100644 index 0000000..fa93046 --- /dev/null +++ b/tienda_ilusion/don_confiao/api/sale_views.py @@ -0,0 +1,5 @@ +from rest_framework import viewsets + + +class CatalogSaleView(viewsets.ViewSet): + pass diff --git a/tienda_ilusion/don_confiao/api_views.py b/tienda_ilusion/don_confiao/api_views.py index 5b7509f..0109cc3 100644 --- a/tienda_ilusion/don_confiao/api_views.py +++ b/tienda_ilusion/don_confiao/api_views.py @@ -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() diff --git a/tienda_ilusion/don_confiao/forms.py b/tienda_ilusion/don_confiao/forms.py deleted file mode 100644 index faef067..0000000 --- a/tienda_ilusion/don_confiao/forms.py +++ /dev/null @@ -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__' -) diff --git a/tienda_ilusion/don_confiao/models.py b/tienda_ilusion/don_confiao/models.py deleted file mode 100644 index 39085fd..0000000 --- a/tienda_ilusion/don_confiao/models.py +++ /dev/null @@ -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) diff --git a/tienda_ilusion/don_confiao/models/__init__.py b/tienda_ilusion/don_confiao/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tienda_ilusion/don_confiao/models/admin.py b/tienda_ilusion/don_confiao/models/admin.py new file mode 100644 index 0000000..c34a5e6 --- /dev/null +++ b/tienda_ilusion/don_confiao/models/admin.py @@ -0,0 +1,5 @@ +from django.db import models + + +class AdminCode(models.Model): + value = models.CharField(max_length=255, null=False, blank=False) diff --git a/tienda_ilusion/don_confiao/models/customers.py b/tienda_ilusion/don_confiao/models/customers.py new file mode 100644 index 0000000..17f9fe4 --- /dev/null +++ b/tienda_ilusion/don_confiao/models/customers.py @@ -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 diff --git a/tienda_ilusion/don_confiao/models/payments.py b/tienda_ilusion/don_confiao/models/payments.py new file mode 100644 index 0000000..97e0061 --- /dev/null +++ b/tienda_ilusion/don_confiao/models/payments.py @@ -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.")} + ) diff --git a/tienda_ilusion/don_confiao/models/products.py b/tienda_ilusion/don_confiao/models/products.py new file mode 100644 index 0000000..bc82226 --- /dev/null +++ b/tienda_ilusion/don_confiao/models/products.py @@ -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 diff --git a/tienda_ilusion/don_confiao/models/sales.py b/tienda_ilusion/don_confiao/models/sales.py new file mode 100644 index 0000000..1cf8f2f --- /dev/null +++ b/tienda_ilusion/don_confiao/models/sales.py @@ -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) diff --git a/tienda_ilusion/don_confiao/serializers.py b/tienda_ilusion/don_confiao/serializers.py index 92cb5ab..0fa113a 100644 --- a/tienda_ilusion/don_confiao/serializers.py +++ b/tienda_ilusion/don_confiao/serializers.py @@ -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"] diff --git a/tienda_ilusion/don_confiao/templates/don_confiao/base.html b/tienda_ilusion/don_confiao/templates/don_confiao/base.html deleted file mode 100644 index f7a1a77..0000000 --- a/tienda_ilusion/don_confiao/templates/don_confiao/base.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load static %} - - - -
-
-
- Don Confiao - Tienda la Ilusión
-Resumen de Venta
- {{ sale_form }} - {{ summary_form }} - -No hay Compras
-{% endif %} - -{% endblock %} diff --git a/tienda_ilusion/don_confiao/tests/test_admin_code.py b/tienda_ilusion/don_confiao/tests/test_admin_code.py index 10d1533..5ae01c9 100644 --- a/tienda_ilusion/don_confiao/tests/test_admin_code.py +++ b/tienda_ilusion/don_confiao/tests/test_admin_code.py @@ -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) diff --git a/tienda_ilusion/don_confiao/tests/test_api.py b/tienda_ilusion/don_confiao/tests/test_api.py index dcc6441..ccaf3dd 100644 --- a/tienda_ilusion/don_confiao/tests/test_api.py +++ b/tienda_ilusion/don_confiao/tests/test_api.py @@ -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") diff --git a/tienda_ilusion/don_confiao/tests/test_buy_form.py b/tienda_ilusion/don_confiao/tests/test_buy_form.py deleted file mode 100644 index 5a61817..0000000 --- a/tienda_ilusion/don_confiao/tests/test_buy_form.py +++ /dev/null @@ -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) diff --git a/tienda_ilusion/don_confiao/tests/test_customers_from_tryton.py b/tienda_ilusion/don_confiao/tests/test_customers_from_tryton.py index 6af2382..aec294f 100644 --- a/tienda_ilusion/don_confiao/tests/test_customers_from_tryton.py +++ b/tienda_ilusion/don_confiao/tests/test_customers_from_tryton.py @@ -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)) diff --git a/tienda_ilusion/don_confiao/tests/test_export_sales.py b/tienda_ilusion/don_confiao/tests/test_export_sales.py deleted file mode 100644 index 9dc050f..0000000 --- a/tienda_ilusion/don_confiao/tests/test_export_sales.py +++ /dev/null @@ -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" - ] diff --git a/tienda_ilusion/don_confiao/tests/test_exportar_ventas_para_tryton.py b/tienda_ilusion/don_confiao/tests/test_exportar_ventas_para_tryton.py deleted file mode 100644 index 8ef8503..0000000 --- a/tienda_ilusion/don_confiao/tests/test_exportar_ventas_para_tryton.py +++ /dev/null @@ -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]}]) diff --git a/tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py b/tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py index 7f1386c..60b5272 100644 --- a/tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py +++ b/tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py @@ -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", + ) diff --git a/tienda_ilusion/don_confiao/tests/test_party.py b/tienda_ilusion/don_confiao/tests/test_party.py index 4e1da29..77a5c44 100644 --- a/tienda_ilusion/don_confiao/tests/test_party.py +++ b/tienda_ilusion/don_confiao/tests/test_party.py @@ -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): diff --git a/tienda_ilusion/don_confiao/tests/test_products.py b/tienda_ilusion/don_confiao/tests/test_products.py index a7f3e50..1f41fb6 100644 --- a/tienda_ilusion/don_confiao/tests/test_products.py +++ b/tienda_ilusion/don_confiao/tests/test_products.py @@ -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")) diff --git a/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py b/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py index 52a80ab..6f122c2 100644 --- a/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py +++ b/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py @@ -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) diff --git a/tienda_ilusion/don_confiao/tests/test_purchase_with_payment.py b/tienda_ilusion/don_confiao/tests/test_purchase_with_payment.py deleted file mode 100644 index 962aaa2..0000000 --- a/tienda_ilusion/don_confiao/tests/test_purchase_with_payment.py +++ /dev/null @@ -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) diff --git a/tienda_ilusion/don_confiao/tests/test_summary_view_purchase.py b/tienda_ilusion/don_confiao/tests/test_summary_view_purchase.py deleted file mode 100644 index 63e3cd6..0000000 --- a/tienda_ilusion/don_confiao/tests/test_summary_view_purchase.py +++ /dev/null @@ -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) diff --git a/tienda_ilusion/don_confiao/tests/tests.py b/tienda_ilusion/don_confiao/tests/tests.py index 976518b..1bbbe72 100644 --- a/tienda_ilusion/don_confiao/tests/tests.py +++ b/tienda_ilusion/don_confiao/tests/tests.py @@ -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() diff --git a/tienda_ilusion/don_confiao/tests/tests_purchase_form.py b/tienda_ilusion/don_confiao/tests/tests_purchase_form.py deleted file mode 100644 index 7cfd236..0000000 --- a/tienda_ilusion/don_confiao/tests/tests_purchase_form.py +++ /dev/null @@ -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()) diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index b2ebf87..8df75b1 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -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/', api_views.AdminCodeValidateView.as_view()),
- path('api/sales/for_tryton', api_views.SalesForTrytonView.as_view()),
- path('api/', include(router.urls)),
+ path(
+ "resumen_compra_json/",
+ 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/",
+ api_views.AdminCodeValidateView.as_view(),
+ ),
+ path("api/sales/for_tryton", api_views.SalesForTrytonView.as_view()),
]
diff --git a/tienda_ilusion/don_confiao/views.py b/tienda_ilusion/don_confiao/views.py
index d383242..4a16a3c 100644
--- a/tienda_ilusion/don_confiao/views.py
+++ b/tienda_ilusion/don_confiao/views.py
@@ -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('Reconciliaciones
')
-
-
-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()