Compare commits
	
		
			41 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a265b94460 | |||
| 253fcbae27 | |||
| d127609508 | |||
| 604bbd3ab9 | |||
| e17b8f6973 | |||
| e3f571afc5 | |||
| 477405a094 | |||
| 4dae669397 | |||
| 937fe06de4 | |||
| 69185f2460 | |||
| 7ac28154eb | |||
| e7eda79c69 | |||
| 5f40b4098c | |||
|  | 80864137b6 | ||
|  | 2e8e956b69 | ||
| 2e4c6592a3 | |||
| 6b149b0134 | |||
| 1b30076876 | |||
| 271f9b2942 | |||
| 4734636b4f | |||
| 59fbc8872a | |||
| d1e137d387 | |||
| c33c6f630a | |||
| 76e525735d | |||
| 3a5e13624f | |||
| 3d9feeac43 | |||
| 81e4c0bc0d | |||
| cf0f6dc4b5 | |||
| 1d3160ae92 | |||
| ba9ef039f4 | |||
| 46e7181653 | |||
| 62d39c97c0 | |||
| f8ff6b7905 | |||
| c7e1af9c81 | |||
| 32149b10e2 | |||
| 8791c5fa2d | |||
| 3c318f2751 | |||
| 5ecf0f4bf5 | |||
| 0e38255a9d | |||
| 2364583952 | |||
| 585d92c64c | 
							
								
								
									
										4
									
								
								.env_example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.env_example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | TRYTON_HOST=localhost | ||||||
|  | TRYTON_DATABASE=tryton | ||||||
|  | TRYTON_USERNAME=admin | ||||||
|  | TRYTON_PASSWORD=admin | ||||||
| @@ -3,6 +3,8 @@ services: | |||||||
|     build: |     build: | ||||||
|       context: ./ |       context: ./ | ||||||
|       dockerfile: django.Dockerfile |       dockerfile: django.Dockerfile | ||||||
|  |     env_file: | ||||||
|  |       - .env | ||||||
|     volumes: |     volumes: | ||||||
|       - ./tienda_ilusion:/app/ |       - ./tienda_ilusion:/app/ | ||||||
|     ports: |     ports: | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
| Django==5.0.6 | Django==5.0.6 | ||||||
| djangorestframework | djangorestframework | ||||||
| django-cors-headers | django-cors-headers | ||||||
|  | sabatron-tryton-rpc-client==7.4.0 | ||||||
|   | |||||||
| @@ -6,9 +6,21 @@ from rest_framework.pagination import PageNumberPagination | |||||||
|  |  | ||||||
| from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode | from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode | ||||||
| from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer | from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer | ||||||
|  | from .views import sales_to_tryton_csv | ||||||
|  |  | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
| import json | from sabatron_tryton_rpc_client.client import Client | ||||||
|  | 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_COP_CURRENCY = 31 | ||||||
|  | TRYTON_COMPANY_ID = 1 | ||||||
|  | TRYTON_SHOPS = [1] | ||||||
|  |  | ||||||
|  |  | ||||||
| class Pagination(PageNumberPagination): | class Pagination(PageNumberPagination): | ||||||
| @@ -128,12 +140,14 @@ class SalesForReconciliationView(APIView): | |||||||
|  |  | ||||||
|         return Response(grouped_sales) |         return Response(grouped_sales) | ||||||
|  |  | ||||||
|  |  | ||||||
| class SaleSummary(APIView): | class SaleSummary(APIView): | ||||||
|     def get(self, request, id): |     def get(self, request, id): | ||||||
|         sale = Sale.objects.get(pk=id) |         sale = Sale.objects.get(pk=id) | ||||||
|         serializer = SaleSummarySerializer(sale) |         serializer = SaleSummarySerializer(sale) | ||||||
|         return Response(serializer.data) |         return Response(serializer.data) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdminCodeValidateView(APIView): | class AdminCodeValidateView(APIView): | ||||||
|     def get(self, request, code): |     def get(self, request, code): | ||||||
|         codes = AdminCode.objects.filter(value=code) |         codes = AdminCode.objects.filter(value=code) | ||||||
| @@ -144,3 +158,276 @@ class ReconciliateJarModelView(viewsets.ModelViewSet): | |||||||
|     queryset = ReconciliationJar.objects.all().order_by('-date_time') |     queryset = ReconciliationJar.objects.all().order_by('-date_time') | ||||||
|     pagination_class = Pagination |     pagination_class = Pagination | ||||||
|     serializer_class = ReconciliationJarSerializer |     serializer_class = ReconciliationJarSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SalesForTrytonView(APIView): | ||||||
|  |     def get(self, request): | ||||||
|  |         sales = Sale.objects.all() | ||||||
|  |         csv = self._generate_sales_CSV(sales) | ||||||
|  |         return Response({'csv': csv}) | ||||||
|  |  | ||||||
|  |     def _generate_sales_CSV(self, sales): | ||||||
|  |         output = io.StringIO() | ||||||
|  |         writer = csv.writer(output) | ||||||
|  |         csv_data = sales_to_tryton_csv(sales) | ||||||
|  |  | ||||||
|  |         for row in csv_data: | ||||||
|  |             writer.writerow(row) | ||||||
|  |         return output.getvalue() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SalesToTrytonView(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.sale.sale.create' | ||||||
|  |         tryton_context = {'company': TRYTON_COMPANY_ID, | ||||||
|  |                           'shops': TRYTON_SHOPS} | ||||||
|  |  | ||||||
|  |         successful = [] | ||||||
|  |         failed = [] | ||||||
|  |  | ||||||
|  |         sales = Sale.objects.filter(external_id=None) | ||||||
|  |         for sale in sales: | ||||||
|  |             try: | ||||||
|  |                 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) | ||||||
|  |             except Exception as e: | ||||||
|  |                 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 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     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": TRYTON_COMPANY_ID, | ||||||
|  |             "shipment_address": self.sale.customer.address_external_id, | ||||||
|  |             "invoice_address": self.sale.customer.address_external_id, | ||||||
|  |             "currency": TRYTON_COP_CURRENCY, | ||||||
|  |             "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) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 = [[["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 | ||||||
|  |         ) | ||||||
|  |         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: | ||||||
|  |                 try: | ||||||
|  |                     product = self.__create_product(tryton_product) | ||||||
|  |                     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')) | ||||||
|  |                     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('list_price'): | ||||||
|  |             return True | ||||||
|  |         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.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.save() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CustomersFromTrytonView(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.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( | ||||||
|  |             party_ids, tryton_client, context | ||||||
|  |         ) | ||||||
|  |         checked_tryton_parties = party_ids | ||||||
|  |         failed_parties = [] | ||||||
|  |         updated_customers = [] | ||||||
|  |         created_customers = [] | ||||||
|  |         untouched_customers = [] | ||||||
|  |  | ||||||
|  |         for tryton_party in tryton_parties: | ||||||
|  |             try: | ||||||
|  |                 customer = Customer.objects.get( | ||||||
|  |                     external_id=tryton_party.get('id') | ||||||
|  |                 ) | ||||||
|  |             except Customer.DoesNotExist: | ||||||
|  |                 customer = self.__create_customer(tryton_party) | ||||||
|  |                 created_customers.append(customer.id) | ||||||
|  |                 continue | ||||||
|  |             if self.__need_update(customer, tryton_party): | ||||||
|  |                 self.__update_customer(customer, tryton_party) | ||||||
|  |                 updated_customers.append(customer.id) | ||||||
|  |             else: | ||||||
|  |                 untouched_customers.append(customer.id) | ||||||
|  |  | ||||||
|  |         return Response( | ||||||
|  |             { | ||||||
|  |                 'checked_tryton_parties': checked_tryton_parties, | ||||||
|  |                 'failed_parties': failed_parties, | ||||||
|  |                 'updated_customers': updated_customers, | ||||||
|  |                 'created_customers': created_customers, | ||||||
|  |                 'untouched_customers': untouched_customers, | ||||||
|  |             }, | ||||||
|  |             status=200 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def __get_party_datails(self, party_ids, tryton_client, context): | ||||||
|  |         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'): | ||||||
|  |             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]): | ||||||
|  |                 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.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.save() | ||||||
|   | |||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-04-06 20:59 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0037_admincode'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='saleline', | ||||||
|  |             name='quantity', | ||||||
|  |             field=models.DecimalField(decimal_places=2, max_digits=10, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-07-19 15:56 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0038_alter_saleline_quantity'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='sale', | ||||||
|  |             name='external_id', | ||||||
|  |             field=models.CharField(blank=True, max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-07-19 22:27 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0039_sale_external_id'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='customer', | ||||||
|  |             name='external_id', | ||||||
|  |             field=models.CharField(blank=True, max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-07-19 22:32 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0040_customer_external_id'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='product', | ||||||
|  |             name='external_id', | ||||||
|  |             field=models.CharField(blank=True, max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-07-19 23:15 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0041_product_external_id'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='product', | ||||||
|  |             name='unit_external_id', | ||||||
|  |             field=models.CharField(blank=True, max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.0.6 on 2025-07-20 00:04 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('don_confiao', '0042_product_unit_external_id'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='customer', | ||||||
|  |             name='address_external_id', | ||||||
|  |             field=models.CharField(blank=True, max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -17,6 +17,8 @@ class Customer(models.Model): | |||||||
|     address = models.CharField(max_length=100, null=True, blank=True) |     address = models.CharField(max_length=100, null=True, blank=True) | ||||||
|     email = 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) |     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): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
| @@ -41,7 +43,9 @@ class Product(models.Model): | |||||||
|         choices=MeasuringUnits.choices, |         choices=MeasuringUnits.choices, | ||||||
|         default=MeasuringUnits.UNIT |         default=MeasuringUnits.UNIT | ||||||
|     ) |     ) | ||||||
|  |     unit_external_id = models.CharField(max_length=100, null=True, blank=True) | ||||||
|     categories = models.ManyToManyField(ProductCategory) |     categories = models.ManyToManyField(ProductCategory) | ||||||
|  |     external_id = models.CharField(max_length=100, null=True, blank=True) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
| @@ -105,6 +109,7 @@ class Sale(models.Model): | |||||||
|         related_name='Sales', |         related_name='Sales', | ||||||
|         null=True |         null=True | ||||||
|     ) |     ) | ||||||
|  |     external_id = models.CharField(max_length=100, null=True, blank=True) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return f"{self.date} {self.customer}" |         return f"{self.date} {self.customer}" | ||||||
| @@ -128,7 +133,7 @@ class SaleLine(models.Model): | |||||||
|  |  | ||||||
|     sale = models.ForeignKey(Sale, on_delete=models.CASCADE) |     sale = models.ForeignKey(Sale, on_delete=models.CASCADE) | ||||||
|     product = models.ForeignKey(Product, null=False, blank=False, on_delete=models.CASCADE) |     product = models.ForeignKey(Product, null=False, blank=False, on_delete=models.CASCADE) | ||||||
|     quantity = models.IntegerField(null=True) |     quantity = models.DecimalField(max_digits=10, decimal_places=2, null=True) | ||||||
|     unit_price = models.DecimalField(max_digits=9, decimal_places=2) |     unit_price = models.DecimalField(max_digits=9, decimal_places=2) | ||||||
|     description = models.CharField(max_length=255, null=True, blank=True) |     description = models.CharField(max_length=255, null=True, blank=True) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ class SaleSerializer(serializers.ModelSerializer): | |||||||
|     class Meta: |     class Meta: | ||||||
|         model = Sale |         model = Sale | ||||||
|         fields = ['id', 'customer', 'date', 'saleline_set', |         fields = ['id', 'customer', 'date', 'saleline_set', | ||||||
|                   'total', 'payment_method'] |                   'total', 'payment_method', 'external_id'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProductSerializer(serializers.ModelSerializer): | class ProductSerializer(serializers.ModelSerializer): | ||||||
| @@ -27,7 +27,7 @@ class ProductSerializer(serializers.ModelSerializer): | |||||||
| class CustomerSerializer(serializers.ModelSerializer): | class CustomerSerializer(serializers.ModelSerializer): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Customer |         model = Customer | ||||||
|         fields = ['id', 'name', 'address', 'email', 'phone'] |         fields = ['id', 'name', 'address', 'email', 'phone', 'external_id'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ReconciliationJarSerializer(serializers.ModelSerializer): | class ReconciliationJarSerializer(serializers.ModelSerializer): | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import json | import json | ||||||
|  | import csv | ||||||
|  | import io | ||||||
|  |  | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from rest_framework import status | from rest_framework import status | ||||||
| from rest_framework.test import APITestCase | from rest_framework.test import APITestCase | ||||||
| @@ -13,7 +16,8 @@ class TestAPI(APITestCase): | |||||||
|             measuring_unit='UNIT' |             measuring_unit='UNIT' | ||||||
|         ) |         ) | ||||||
|         self.customer = Customer.objects.create( |         self.customer = Customer.objects.create( | ||||||
|             name='Camilo' |             name='Camilo', | ||||||
|  |             external_id='18' | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_create_sale(self): |     def test_create_sale(self): | ||||||
| @@ -22,6 +26,22 @@ class TestAPI(APITestCase): | |||||||
|         self.assertEqual(response.status_code, status.HTTP_201_CREATED) |         self.assertEqual(response.status_code, status.HTTP_201_CREATED) | ||||||
|         self.assertEqual(Sale.objects.count(), 1) |         self.assertEqual(Sale.objects.count(), 1) | ||||||
|         sale = Sale.objects.all()[0] |         sale = Sale.objects.all()[0] | ||||||
|  |         self.assertEqual( | ||||||
|  |             sale.customer.name, | ||||||
|  |             self.customer.name, | ||||||
|  |         ) | ||||||
|  |         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')) | ||||||
|  |         self.assertEqual(response.status_code, status.HTTP_201_CREATED) | ||||||
|  |         self.assertEqual(Sale.objects.count(), 1) | ||||||
|  |         sale = Sale.objects.all()[0] | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             sale.customer.name, |             sale.customer.name, | ||||||
|             self.customer.name |             self.customer.name | ||||||
| @@ -30,6 +50,10 @@ class TestAPI(APITestCase): | |||||||
|             sale.id, |             sale.id, | ||||||
|             content['id'] |             content['id'] | ||||||
|         ) |         ) | ||||||
|  |         self.assertEqual( | ||||||
|  |             sale.get_total(), | ||||||
|  |             16500.00 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_get_products(self): |     def test_get_products(self): | ||||||
|         url = '/don_confiao/api/products/' |         url = '/don_confiao/api/products/' | ||||||
| @@ -44,6 +68,75 @@ class TestAPI(APITestCase): | |||||||
|         json_response = json.loads(response.content.decode('utf-8')) |         json_response = json.loads(response.content.decode('utf-8')) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|         self.assertEqual(self.customer.name, json_response[0]['name']) |         self.assertEqual(self.customer.name, json_response[0]['name']) | ||||||
|  |         self.assertEqual( | ||||||
|  |             self.customer.external_id, | ||||||
|  |             json_response[0]['external_id'] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def test_get_sales(self): | ||||||
|  |         url = '/don_confiao/api/sales/' | ||||||
|  |         self._create_sale() | ||||||
|  |  | ||||||
|  |         response = self.client.get(url) | ||||||
|  |         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'] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def test_get_sales_for_tryton(self): | ||||||
|  |         url = '/don_confiao/api/sales/for_tryton' | ||||||
|  |         self._create_sale() | ||||||
|  |         response = self.client.get(url) | ||||||
|  |         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) | ||||||
|  |  | ||||||
|  |     def test_csv_structure_in_sales_for_tryton(self): | ||||||
|  |         url = '/don_confiao/api/sales/for_tryton' | ||||||
|  |         self._create_sale() | ||||||
|  |         response = self.client.get(url) | ||||||
|  |         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'])) | ||||||
|  |         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" | ||||||
|  |         ] | ||||||
|  |         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", "", "", "", "", "" | ||||||
|  |              ], | ||||||
|  |         ] | ||||||
|  |         rows = list(csv_reader) | ||||||
|  |         self.assertEqual(rows, expected_rows) | ||||||
|  |  | ||||||
|     def _create_sale(self): |     def _create_sale(self): | ||||||
|         url = '/don_confiao/api/sales/' |         url = '/don_confiao/api/sales/' | ||||||
| @@ -57,3 +150,24 @@ class TestAPI(APITestCase): | |||||||
|             ], |             ], | ||||||
|         } |         } | ||||||
|         return self.client.post(url, data, format='json') |         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') | ||||||
|   | |||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | import json | ||||||
|  | from unittest.mock import patch | ||||||
|  |  | ||||||
|  | from django.test import Client, TestCase | ||||||
|  | from ..models import Customer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestCustomersFromTryton(TestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         self.customer = Customer.objects.create( | ||||||
|  |             name='Calos', | ||||||
|  |             external_id=5 | ||||||
|  |         ) | ||||||
|  |         self.customer.save() | ||||||
|  |  | ||||||
|  |         self.customer2 = Customer.objects.create( | ||||||
|  |             name='Cristian', | ||||||
|  |             external_id=6 | ||||||
|  |         ) | ||||||
|  |         self.customer2.save() | ||||||
|  |  | ||||||
|  |     @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}] | ||||||
|  |  | ||||||
|  |             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)): | ||||||
|  |                 return [ | ||||||
|  |                     {'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}") | ||||||
|  |         mock_call.side_effect = fake_call | ||||||
|  |  | ||||||
|  |         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')) | ||||||
|  |         expected_response = { | ||||||
|  |             '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.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.assertIn(updated_customer.address_external_id, str(303)) | ||||||
| @@ -0,0 +1,103 @@ | |||||||
|  | import csv | ||||||
|  | import json | ||||||
|  | from unittest.mock import patch | ||||||
|  |  | ||||||
|  | from django.test import TestCase, Client | ||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
|  | from ..models import Sale, SaleLine, Product, Customer | ||||||
|  |  | ||||||
|  | class TestExportarVentasParaTryton(TestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         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', | ||||||
|  |         ) | ||||||
|  |         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): | ||||||
|  |         client = Client() | ||||||
|  |         url = '/don_confiao/exportar_ventas_para_tryton' | ||||||
|  |         response = 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", "", "", "2024-09-02", "Contado", "Almacén", "Peso colombiano", "Panela", "2.00", "3000.00", "Unidad", "TIENDA LA ILUSIÓN", "Tienda La Ilusion", "La Ilusion", "True", ""], | ||||||
|  |             ["", "", "", "", "", "", "", "", "", "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): | ||||||
|  |         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, 'shipment_address': '307', 'invoice_address': '307', 'currency': 31, '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'}}]]]}], {'company': 1, 'shops': [1]}]) | ||||||
| @@ -14,6 +14,8 @@ class TestCustomer(TestCase): | |||||||
|         customer.save() |         customer.save() | ||||||
|  |  | ||||||
|         self.assertIsInstance(customer, Customer) |         self.assertIsInstance(customer, Customer) | ||||||
|  |         self.assertIsNone(customer.external_id) | ||||||
|  |         self.assertIsNone(customer.address_external_id) | ||||||
|  |  | ||||||
|     def test_don_create_customer_without_name(self): |     def test_don_create_customer_without_name(self): | ||||||
|         customer = Customer() |         customer = Customer() | ||||||
|   | |||||||
| @@ -10,6 +10,16 @@ class TestProducts(TestCase): | |||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         self.client = Client() |         self.client = Client() | ||||||
|  |  | ||||||
|  |     def test_create_product(self): | ||||||
|  |         product = Product() | ||||||
|  |         product.name = "Un producto" | ||||||
|  |         product.price = 1000 | ||||||
|  |         product.save() | ||||||
|  |  | ||||||
|  |         self.assertIsInstance(product, Product) | ||||||
|  |         self.assertIsNone(product.external_id) | ||||||
|  |         self.assertIsNone(product.unit_external_id) | ||||||
|  |  | ||||||
|     def test_import_products(self): |     def test_import_products(self): | ||||||
|         self._import_csv() |         self._import_csv() | ||||||
|         all_products = self._get_products() |         all_products = self._get_products() | ||||||
|   | |||||||
							
								
								
									
										128
									
								
								tienda_ilusion/don_confiao/tests/test_products_from_tryton.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								tienda_ilusion/don_confiao/tests/test_products_from_tryton.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | |||||||
|  | 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.product = Product.objects.create( | ||||||
|  |             name='Panela', | ||||||
|  |             price=5000, | ||||||
|  |             measuring_unit='UNIT', | ||||||
|  |             unit_external_id=1, | ||||||
|  |             external_id=191 | ||||||
|  |         ) | ||||||
|  |         self.product.save() | ||||||
|  |  | ||||||
|  |         self.product2 = Product.objects.create( | ||||||
|  |             name='Papa', | ||||||
|  |             price=4500, | ||||||
|  |             measuring_unit='Kilogram', | ||||||
|  |             unit_external_id=2, | ||||||
|  |             external_id=192 | ||||||
|  |         ) | ||||||
|  |         self.product2.save() | ||||||
|  |  | ||||||
|  |     @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)): | ||||||
|  |                 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)): | ||||||
|  |                 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'}}, | ||||||
|  |                 ] | ||||||
|  |  | ||||||
|  |             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, 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') | ||||||
|  |  | ||||||
|  |         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') | ||||||
|  |  | ||||||
|  |     @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)): | ||||||
|  |                 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)): | ||||||
|  |                 return [ | ||||||
|  |                     {'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}") | ||||||
|  |  | ||||||
|  |         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': [200], | ||||||
|  |             'created_products': [], | ||||||
|  |             'untouched_products': [], | ||||||
|  |             'failed_products': [200], | ||||||
|  |             'updated_products': [], | ||||||
|  |         } | ||||||
|  |         self.assertEqual(content, expected_response) | ||||||
| @@ -20,15 +20,23 @@ urlpatterns = [ | |||||||
|     path("productos", views.products, name="products"), |     path("productos", views.products, name="products"), | ||||||
|     path("lista_productos", views.ProductListView.as_view(), name='product_list'), |     path("lista_productos", views.ProductListView.as_view(), name='product_list'), | ||||||
|     path("importar_productos", views.import_products, name="import_products"), |     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("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", |     path("exportar_ventas_para_tryton", | ||||||
|          views.exportar_ventas_para_tryton, |          views.exportar_ventas_para_tryton, | ||||||
|          name="exportar_ventas_para_tryton"), |          name="exportar_ventas_para_tryton"), | ||||||
|  |     path('api/enviar_ventas_a_tryton', api_views.SalesToTrytonView.as_view(), name="send_tryton"), | ||||||
|     path("resumen_compra/<int:id>", views.purchase_summary, name="purchase_summary"), |     path("resumen_compra/<int:id>", views.purchase_summary, name="purchase_summary"), | ||||||
|     path("resumen_compra_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"), |     path("resumen_compra_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"), | ||||||
|     path("payment_methods/all/select_format", api_views.PaymentMethodView.as_view(), name="payment_methods_to_select"), |     path("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('purchases/for_reconciliation', api_views.SalesForReconciliationView.as_view(), name='sales_for_reconciliation'), | ||||||
|     path('reconciliate_jar', api_views.ReconciliateJarView.as_view()), |     path('reconciliate_jar', api_views.ReconciliateJarView.as_view()), | ||||||
|     path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()), |     path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()), | ||||||
|  |     path('api/sales/for_tryton', api_views.SalesForTrytonView.as_view()), | ||||||
|     path('api/', include(router.urls)), |     path('api/', include(router.urls)), | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -163,8 +163,7 @@ def handle_import_customers_file(csv_file): | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | def sales_to_tryton_csv(sales): | ||||||
| def exportar_ventas_para_tryton(request): |  | ||||||
|     tryton_sales_header = [ |     tryton_sales_header = [ | ||||||
|         "Tercero", |         "Tercero", | ||||||
|         "Dirección de facturación", |         "Dirección de facturación", | ||||||
| @@ -186,44 +185,50 @@ def exportar_ventas_para_tryton(request): | |||||||
|         "Comentario" |         "Comentario" | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |     csv_data = [tryton_sales_header] | ||||||
|  |     for sale in sales: | ||||||
|  |         sale_lines = SaleLine.objects.filter(sale=sale.id) | ||||||
|  |         if not sale_lines: | ||||||
|  |             continue | ||||||
|  |         lines = [] | ||||||
|  |         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'), | ||||||
|  |             "Contado", | ||||||
|  |             "Almacén", | ||||||
|  |             "Peso colombiano", | ||||||
|  |             first_sale_line.product.name, | ||||||
|  |             first_sale_line.quantity, | ||||||
|  |             first_sale_line.unit_price, | ||||||
|  |             "Unidad", | ||||||
|  |             "TIENDA LA ILUSIÓN", | ||||||
|  |             "Tienda La Ilusion", | ||||||
|  |             "La Ilusion", | ||||||
|  |             True, | ||||||
|  |             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) | ||||||
|  |         for row in lines: | ||||||
|  |             csv_data.append(row) | ||||||
|  |  | ||||||
|  |     return csv_data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def exportar_ventas_para_tryton(request): | ||||||
|     if request.method == "GET": |     if request.method == "GET": | ||||||
|         response = HttpResponse(content_type='text/csv') |         response = HttpResponse(content_type='text/csv') | ||||||
|         response['Content-Disposition'] = "attachment; filename=sales.csv" |         response['Content-Disposition'] = "attachment; filename=sales.csv" | ||||||
|         writer = csv.writer(response) |  | ||||||
|         writer.writerow(tryton_sales_header) |  | ||||||
|  |  | ||||||
|         sales = Sale.objects.all() |         sales = Sale.objects.all() | ||||||
|  |         csv_data = sales_to_tryton_csv(sales) | ||||||
|         for sale in sales: |         writer = csv.writer(response) | ||||||
|             sale_lines = SaleLine.objects.filter(sale=sale.id) |         for row in csv_data: | ||||||
|             if not sale_lines: |             writer.writerow(row) | ||||||
|                 continue |  | ||||||
|             lines = [] |  | ||||||
|             first_sale_line = sale_lines[0] |  | ||||||
|             customer_info = [sale.customer.name] * 3 + [sale.description] * 2 |  | ||||||
|             first_line = customer_info + [ |  | ||||||
|                 sale.date, |  | ||||||
|                 "Contado", |  | ||||||
|                 "Almacén", |  | ||||||
|                 "Peso colombiano", |  | ||||||
|                 first_sale_line.product.name, |  | ||||||
|                 first_sale_line.quantity, |  | ||||||
|                 "Unidad", |  | ||||||
|                 first_sale_line.unit_price, |  | ||||||
|                 "TIENDA LA ILUSIÓN", |  | ||||||
|                 "Tienda La Ilusion", |  | ||||||
|                 "La Ilusion", |  | ||||||
|                 True, |  | ||||||
|                 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) |  | ||||||
|             for row in lines: |  | ||||||
|                 writer.writerow(row) |  | ||||||
|  |  | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user