From cf0f6dc4b5c6827cb70ec4b3a2300a7d6ee49eee Mon Sep 17 00:00:00 2001 From: Mono Mono Date: Sat, 19 Jul 2025 19:00:33 -0500 Subject: [PATCH] #9 feat(Tryton): enviar_ventas_a_tryton --- requirements.txt | 1 + tienda_ilusion/don_confiao/api_views.py | 77 +++++++++++++++++++ .../tests/test_exportar_ventas_para_tryton.py | 33 +++++++- tienda_ilusion/don_confiao/urls.py | 1 + 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e7bbb4d..360a414 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.0.6 djangorestframework django-cors-headers +sabatron-tryton-rpc-client==7.4.0 diff --git a/tienda_ilusion/don_confiao/api_views.py b/tienda_ilusion/don_confiao/api_views.py index 23dc12e..9059957 100644 --- a/tienda_ilusion/don_confiao/api_views.py +++ b/tienda_ilusion/don_confiao/api_views.py @@ -9,6 +9,7 @@ from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, from .views import sales_to_tryton_csv from decimal import Decimal +from sabatron_tryton_rpc_client.client import Client import io import csv @@ -164,3 +165,79 @@ class SalesForTrytonView(APIView): for row in csv_data: writer.writerow(row) return output.getvalue() + + +class SalesToTrytonView(APIView): + def post(self, request): + tryton_client = Client( + hostname='localhost', + database='tryton', + username='admin', + password='admin' + ) + tryton_client.connect() + method = 'model.sale.sale.create' + tryton_context = {} + + successful = [] + failed = [] + + sales = Sale.objects.filter(external_id=None) + for sale in sales: + lines = SaleLine.objects.filter(sale=sale.id) + 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) + + return Response( + {'successful': successful, 'failed': failed}, + status=200 + ) + + def __to_tryton_params(self, sale, lines, tryton_context): + sale_tryton = TrytonSale(sale, lines) + return [[sale_tryton.to_tryton()], tryton_context] + + +class TrytonSale: + + def __init__(self, sale, lines): + self.sale = sale + self.lines = lines + + def _format_date(self, _date): + return {"__class__": "date", "year": _date.year, "month": _date.month, + "day": _date.day} + + def to_tryton(self): + return { + "company": 1, + "currency": 1, + "description": self.sale.description 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] + ]] + } + + +class TrytonLineSale: + def __init__(self, sale_line): + self.sale_line = sale_line + + def _format_decimal(self, number): + return {"__class__": "Decimal", "decimal": str(number)} + + def to_tryton(self): + return { + "product": self.sale_line.product.external_id, + "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) + } 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 index dd6a5de..dde3a9b 100644 --- a/tienda_ilusion/don_confiao/tests/test_exportar_ventas_para_tryton.py +++ b/tienda_ilusion/don_confiao/tests/test_exportar_ventas_para_tryton.py @@ -1,4 +1,6 @@ import csv +import json +from unittest.mock import patch from django.test import TestCase, Client from django.urls import reverse @@ -10,10 +12,13 @@ class TestExportarVentasParaTryton(TestCase): self.product = Product.objects.create( name='Panela', price=5000, - measuring_unit='UNIT' + measuring_unit='UNIT', + unit_external_id=1, + external_id=1 ) self.customer = Customer.objects.create( - name='Camilo' + name='Camilo', + external_id=1 ) self.sale = Sale.objects.create( customer=self.customer, @@ -71,3 +76,27 @@ class TestExportarVentasParaTryton(TestCase): 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): + client = Client() + external_id = '23423' + url = '/don_confiao/api/enviar_ventas_a_tryton' + mock_connect.return_value = None + mock_call.return_value = [external_id] + response = 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, 'currency': 1, 'description': '', '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'}}]]]}], {}]) diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index dc731b7..9098660 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ 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/", views.purchase_summary, name="purchase_summary"), path("resumen_compra_json/", api_views.SaleSummary.as_view(), name="purchase_json_summary"), path("payment_methods/all/select_format", api_views.PaymentMethodView.as_view(), name="payment_methods_to_select"),