Compare commits
	
		
			10 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f8ff6b7905 | |||
| c7e1af9c81 | |||
| 32149b10e2 | |||
| 8791c5fa2d | |||
| 3c318f2751 | |||
| 5ecf0f4bf5 | |||
| 0e38255a9d | |||
| 2364583952 | |||
| 585d92c64c | |||
| 99e1198819 | 
| @@ -6,9 +6,11 @@ from rest_framework.pagination import PageNumberPagination | ||||
|  | ||||
| from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode | ||||
| from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer | ||||
| from .views import sales_to_tryton_csv | ||||
|  | ||||
| from decimal import Decimal | ||||
| import json | ||||
| import io | ||||
| import csv | ||||
|  | ||||
|  | ||||
| class Pagination(PageNumberPagination): | ||||
| @@ -128,12 +130,14 @@ class SalesForReconciliationView(APIView): | ||||
|  | ||||
|         return Response(grouped_sales) | ||||
|  | ||||
|  | ||||
| class SaleSummary(APIView): | ||||
|     def get(self, request, id): | ||||
|         sale = Sale.objects.get(pk=id) | ||||
|         serializer = SaleSummarySerializer(sale) | ||||
|         return Response(serializer.data) | ||||
|  | ||||
|  | ||||
| class AdminCodeValidateView(APIView): | ||||
|     def get(self, request, code): | ||||
|         codes = AdminCode.objects.filter(value=code) | ||||
| @@ -144,3 +148,19 @@ class ReconciliateJarModelView(viewsets.ModelViewSet): | ||||
|     queryset = ReconciliationJar.objects.all().order_by('-date_time') | ||||
|     pagination_class = Pagination | ||||
|     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() | ||||
|   | ||||
| @@ -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), | ||||
|         ), | ||||
|     ] | ||||
| @@ -128,7 +128,7 @@ 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.IntegerField(null=True) | ||||
|     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) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
| import json | ||||
| import csv | ||||
| import io | ||||
|  | ||||
| from django.urls import reverse | ||||
| from rest_framework import status | ||||
| from rest_framework.test import APITestCase | ||||
| @@ -31,6 +34,25 @@ class TestAPI(APITestCase): | ||||
|             content['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( | ||||
|             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/' | ||||
|         response = self.client.get(url) | ||||
| @@ -45,6 +67,58 @@ class TestAPI(APITestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(self.customer.name, json_response[0]['name']) | ||||
|  | ||||
|     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): | ||||
|         url = '/don_confiao/api/sales/' | ||||
|         data = { | ||||
| @@ -57,3 +131,24 @@ class TestAPI(APITestCase): | ||||
|             ], | ||||
|         } | ||||
|         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,73 @@ | ||||
| import csv | ||||
|  | ||||
| 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' | ||||
|         ) | ||||
|         self.customer = Customer.objects.create( | ||||
|             name='Camilo' | ||||
|         ) | ||||
|         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]) | ||||
| @@ -30,5 +30,6 @@ urlpatterns = [ | ||||
|     path('purchases/for_reconciliation', api_views.SalesForReconciliationView.as_view(), name='sales_for_reconciliation'), | ||||
|     path('reconciliate_jar', api_views.ReconciliateJarView.as_view()), | ||||
|     path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()), | ||||
|     path('api/sales/for_tryton', api_views.SalesForTrytonView.as_view()), | ||||
|     path('api/', include(router.urls)), | ||||
| ] | ||||
|   | ||||
| @@ -163,8 +163,7 @@ def handle_import_customers_file(csv_file): | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def exportar_ventas_para_tryton(request): | ||||
| def sales_to_tryton_csv(sales): | ||||
|     tryton_sales_header = [ | ||||
|         "Tercero", | ||||
|         "Dirección de facturación", | ||||
| @@ -186,44 +185,50 @@ def exportar_ventas_para_tryton(request): | ||||
|         "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": | ||||
|         response = HttpResponse(content_type='text/csv') | ||||
|         response['Content-Disposition'] = "attachment; filename=sales.csv" | ||||
|         writer = csv.writer(response) | ||||
|         writer.writerow(tryton_sales_header) | ||||
|  | ||||
|         sales = Sale.objects.all() | ||||
|  | ||||
|         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, | ||||
|                 "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) | ||||
|         csv_data = sales_to_tryton_csv(sales) | ||||
|         writer = csv.writer(response) | ||||
|         for row in csv_data: | ||||
|             writer.writerow(row) | ||||
|  | ||||
|         return response | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,7 @@ https://docs.djangoproject.com/en/5.0/topics/settings/ | ||||
| For the full list of settings and their values, see | ||||
| https://docs.djangoproject.com/en/5.0/ref/settings/ | ||||
| """ | ||||
| import os | ||||
|  | ||||
| from pathlib import Path | ||||
|  | ||||
| @@ -20,14 +21,18 @@ BASE_DIR = Path(__file__).resolve().parent.parent | ||||
| # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ | ||||
|  | ||||
| # SECURITY WARNING: keep the secret key used in production secret! | ||||
| SECRET_KEY = 'django-insecure-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v' | ||||
| SECRET_KEY = os.environ.get( | ||||
|     "SECRET_KEY", | ||||
|     "django-insecure-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v" | ||||
| ) | ||||
|  | ||||
| # SECURITY WARNING: don't run with debug turned on in production! | ||||
| DEBUG = True | ||||
| DEBUG = True if os.environ.get("DEBUG", 'False') in ['True', '1'] else False | ||||
| ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',') | ||||
|  | ||||
| ALLOWED_HOSTS = ['localhost'] | ||||
|  | ||||
| CORS_ALLOWED_ORIGINS = ['http://localhost:3000', 'http://localhost:7001'] | ||||
| CORS_ALLOWED_ORIGINS = os.environ.get( | ||||
|     'CORS_ALLOWED_ORIGINS', | ||||
|     'http://localhost:3000,http://localhost:7001').split(',') | ||||
| # Application definition | ||||
|  | ||||
| INSTALLED_APPS = [ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user