From 76e525735dea181d847a834bc157e37bfa980a6d Mon Sep 17 00:00:00 2001 From: Mono Mono Date: Sun, 27 Jul 2025 22:57:44 -0500 Subject: [PATCH] #9 feat(Tryton): create products from tryton. --- tienda_ilusion/don_confiao/api_views.py | 74 +++++++++++++++++++ .../tests/test_products_from_tryton.py | 57 ++++++++++++++ tienda_ilusion/don_confiao/urls.py | 3 + 3 files changed, 134 insertions(+) create mode 100644 tienda_ilusion/don_confiao/tests/test_products_from_tryton.py diff --git a/tienda_ilusion/don_confiao/api_views.py b/tienda_ilusion/don_confiao/api_views.py index c1f23fb..2b6a24e 100644 --- a/tienda_ilusion/don_confiao/api_views.py +++ b/tienda_ilusion/don_confiao/api_views.py @@ -249,3 +249,77 @@ class TrytonLineSale: "unit": self.sale_line.product.unit_external_id, "unit_price": self._format_decimal(self.sale_line.unit_price) } + + +class ProductsFromTrytonView(APIView): + def post(self, request): + tryton_client = Client( + hostname=TRYTON_HOST, + database=TRYTON_DATABASE, + username=TRYTON_USERNAME, + password=TRYTON_PASSWORD + ) + tryton_client.connect() + method = 'model.product.product.search' + context = {'company': 1} + params = [[], 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 + ) + checked_tryton_products = product_ids + failed_products = [] + updated_products = [] + created_products = [] + untouched_products = [] + + for tryton_product in tryton_products: + try: + product = Product.objects.get( + external_id=tryton_product.get('id') + ) + except Product.DoesNotExist: + product = self.__create_product(tryton_product) + created_products.append(product.id) + continue + if self.__need_update(product, tryton_product): + self.update_product(product, tryton_product) + updated_products.append(product.id) + else: + untouched_products.append(product.id) + + return Response( + { + 'checked_tryton_products': checked_tryton_products, + 'failed_products': failed_products, + 'updated_products': updated_products, + 'created_products': created_products, + 'untouched_products': untouched_products, + }, + 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' + 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'): + return True + if not product.price == tryton_product.get('price'): + 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.save() + return product diff --git a/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py b/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py new file mode 100644 index 0000000..529b19f --- /dev/null +++ b/tienda_ilusion/don_confiao/tests/test_products_from_tryton.py @@ -0,0 +1,57 @@ +import json +from decimal import Decimal +from unittest.mock import patch + +from django.test import Client, TestCase +from ..models import ProductCategory, Product + + +class TestProductsFromTryton(TestCase): + def setUp(self): + self.client = Client() + + @patch('sabatron_tryton_rpc_client.client.Client.call') + @patch('sabatron_tryton_rpc_client.client.Client.connect') + def test_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 = [[], 0, 1000, [['rec_name', 'ASC'], ['id', None]], {'company': 1}] + if (args == (product_search, search_args)): + return [190] + + product_read = 'model.product.product.read' + product_args = ([190], + ['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'}}] + + 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' + response = self.client.post(url) + self.assertEqual(response.status_code, 200) + + content = json.loads(response.content.decode('utf-8')) + expected_response = { + 'checked_tryton_products': [190], + 'created_products': [1], + 'untouched_products': [], + 'failed_products': [], + 'updated_products': [] + } + self.assertEqual(content, expected_response) + + created_product = Product.objects.get(id=1) + 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') diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index 9098660..cd7e062 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -20,6 +20,9 @@ urlpatterns = [ 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("exportar_ventas_para_tryton", views.exportar_ventas_para_tryton,