#69 feat(ReconciliationJar): send reconciliation jar from frontend
This commit is contained in:
		@@ -1,9 +1,13 @@
 | 
				
			|||||||
from rest_framework import viewsets
 | 
					from rest_framework import viewsets
 | 
				
			||||||
from rest_framework.response import Response
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					from rest_framework.status import HTTP_400_BAD_REQUEST
 | 
				
			||||||
 | 
					from rest_framework.views import APIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Sale, SaleLine, Customer, Product
 | 
					from .models import Sale, SaleLine, Customer, Product, ReconciliationJar
 | 
				
			||||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer
 | 
					from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SaleView(viewsets.ModelViewSet):
 | 
					class SaleView(viewsets.ModelViewSet):
 | 
				
			||||||
    queryset = Sale.objects.all()
 | 
					    queryset = Sale.objects.all()
 | 
				
			||||||
@@ -46,3 +50,30 @@ class ProductView(viewsets.ModelViewSet):
 | 
				
			|||||||
class CustomerView(viewsets.ModelViewSet):
 | 
					class CustomerView(viewsets.ModelViewSet):
 | 
				
			||||||
    queryset = Customer.objects.all()
 | 
					    queryset = Customer.objects.all()
 | 
				
			||||||
    serializer_class = CustomerSerializer
 | 
					    serializer_class = CustomerSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReconciliateJarView(APIView):
 | 
				
			||||||
 | 
					    def post(self, request):
 | 
				
			||||||
 | 
					        data = request.data
 | 
				
			||||||
 | 
					        cash_purchases_id = json.loads(data.get('cash_purchases'))
 | 
				
			||||||
 | 
					        serializer = ReconciliationJarSerializer(data=data)
 | 
				
			||||||
 | 
					        if serializer.is_valid():
 | 
				
			||||||
 | 
					            purchases = Sale.objects.filter(pk__in=cash_purchases_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            total_cash_purchases = sum(p.get_total() for p in purchases)
 | 
				
			||||||
 | 
					            if total_cash_purchases != Decimal(data.get('total_cash_purchases')):
 | 
				
			||||||
 | 
					                return Response(
 | 
				
			||||||
 | 
					                    {'error': 'total_cash_purchases not equal to sum of all purchases.'},
 | 
				
			||||||
 | 
					                    status=HTTP_400_BAD_REQUEST
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            reconciliation = serializer.save()
 | 
				
			||||||
 | 
					            for purchase in purchases:
 | 
				
			||||||
 | 
					                purchase.reconciliation = reconciliation
 | 
				
			||||||
 | 
					                purchase.clean()
 | 
				
			||||||
 | 
					                purchase.save()
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
 | 
					        return Response(serializer.data)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,16 +5,17 @@
 | 
				
			|||||||
    </v-toolbar>
 | 
					    </v-toolbar>
 | 
				
			||||||
    <v-card>
 | 
					    <v-card>
 | 
				
			||||||
      <v-card-text>
 | 
					      <v-card-text>
 | 
				
			||||||
 | 
					        <v-form ref="taker" v-model="valid">
 | 
				
			||||||
 | 
					          <v-text-field
 | 
				
			||||||
 | 
					            v-model="reconciliation.date_time"
 | 
				
			||||||
 | 
					            label="Fecha"
 | 
				
			||||||
 | 
					            type="datetime-local"
 | 
				
			||||||
 | 
					            :rules="[rules.required]"
 | 
				
			||||||
 | 
					            required
 | 
				
			||||||
 | 
					            readonly
 | 
				
			||||||
 | 
					          ></v-text-field>
 | 
				
			||||||
        <v-text-field
 | 
					        <v-text-field
 | 
				
			||||||
          v-model="reconciliation.datetime"
 | 
					          v-model="reconciliation.reconcilier"
 | 
				
			||||||
          label="Fecha"
 | 
					 | 
				
			||||||
          type="datetime-local"
 | 
					 | 
				
			||||||
          :rules="[rules.required]"
 | 
					 | 
				
			||||||
          required
 | 
					 | 
				
			||||||
          readonly
 | 
					 | 
				
			||||||
        ></v-text-field>
 | 
					 | 
				
			||||||
        <v-text-field
 | 
					 | 
				
			||||||
          v-model="reconciliation.cashman"
 | 
					 | 
				
			||||||
          label="Cajero"
 | 
					          label="Cajero"
 | 
				
			||||||
          :rules="[rules.required]"
 | 
					          :rules="[rules.required]"
 | 
				
			||||||
          required
 | 
					          required
 | 
				
			||||||
@@ -41,6 +42,8 @@
 | 
				
			|||||||
          prefix="$"
 | 
					          prefix="$"
 | 
				
			||||||
          type="number"
 | 
					          type="number"
 | 
				
			||||||
        ></v-text-field>
 | 
					        ></v-text-field>
 | 
				
			||||||
 | 
					        <v-btn @click="submit" color="green">Recoger Dinero</v-btn>
 | 
				
			||||||
 | 
					        </v-form>
 | 
				
			||||||
      </v-card-text>
 | 
					      </v-card-text>
 | 
				
			||||||
    </v-card>
 | 
					    </v-card>
 | 
				
			||||||
    <v-tabs v-model="selectedTab">
 | 
					    <v-tabs v-model="selectedTab">
 | 
				
			||||||
@@ -93,10 +96,12 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    data () {
 | 
					    data () {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
 | 
					        valid: null,
 | 
				
			||||||
        selectedPurchaseId: null,
 | 
					        selectedPurchaseId: null,
 | 
				
			||||||
        selectedTab: 'CASH',
 | 
					        selectedTab: 'CASH',
 | 
				
			||||||
        reconciliation: {
 | 
					        reconciliation: {
 | 
				
			||||||
          datetime: '',
 | 
					          csrfmiddlewaretoken: null,
 | 
				
			||||||
 | 
					          date_time: '',
 | 
				
			||||||
          total_cash_purchases: 0,
 | 
					          total_cash_purchases: 0,
 | 
				
			||||||
          cash_taken: 0,
 | 
					          cash_taken: 0,
 | 
				
			||||||
          cash_discrepancy: 0,
 | 
					          cash_discrepancy: 0,
 | 
				
			||||||
@@ -104,6 +109,7 @@
 | 
				
			|||||||
            confiar: 0,
 | 
					            confiar: 0,
 | 
				
			||||||
            bancolombia: 0
 | 
					            bancolombia: 0
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          cash_purchases: [],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        summary: {
 | 
					        summary: {
 | 
				
			||||||
          headers: [
 | 
					          headers: [
 | 
				
			||||||
@@ -122,7 +128,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
      this.fetchPurchases();
 | 
					      this.fetchPurchases();
 | 
				
			||||||
      this.reconciliation.datetime = this.getCurrentDate();
 | 
					      this.reconciliation.date_time = this.getCurrentDate();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    watch: {
 | 
					    watch: {
 | 
				
			||||||
      'reconciliation.cash_taken'() {
 | 
					      'reconciliation.cash_taken'() {
 | 
				
			||||||
@@ -151,18 +157,51 @@
 | 
				
			|||||||
        this.selectedPurchaseId = id;
 | 
					        this.selectedPurchaseId = id;
 | 
				
			||||||
        this.$refs.summaryModal.dialog = true;
 | 
					        this.$refs.summaryModal.dialog = true;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      getCsrfToken() {
 | 
				
			||||||
 | 
					        const cookies = document.cookie.split(';');
 | 
				
			||||||
 | 
					        const csrfToken = cookies.find(cookie => cookie.startsWith('csrftoken='));
 | 
				
			||||||
 | 
					        if (csrfToken) {
 | 
				
			||||||
 | 
					          this.reconciliation.csrfmiddlewaretoken = csrfToken.split('=')[1];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      fetchPurchases() {
 | 
					      fetchPurchases() {
 | 
				
			||||||
        const endpoint = '/don_confiao/purchases/for_reconciliation';
 | 
					        const endpoint = '/don_confiao/purchases/for_reconciliation';
 | 
				
			||||||
        fetch(endpoint)
 | 
					        fetch(endpoint)
 | 
				
			||||||
          .then(response => response.json())
 | 
					          .then(response => response.json())
 | 
				
			||||||
          .then(data => {
 | 
					          .then(data => {
 | 
				
			||||||
            this.summary.purchases = data;
 | 
					            this.summary.purchases = data;
 | 
				
			||||||
 | 
					            this.reconciliation.cash_purchases = this.summary.purchases['CASH'].map(purchase => purchase.id);
 | 
				
			||||||
            this.reconciliation.total_cash_purchases = this.totalByMethod('CASH');
 | 
					            this.reconciliation.total_cash_purchases = this.totalByMethod('CASH');
 | 
				
			||||||
 | 
					            this.getCsrfToken();
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          .catch(error => {
 | 
					          .catch(error => {
 | 
				
			||||||
            console.error(error);
 | 
					            console.error(error);
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      async submit() {
 | 
				
			||||||
 | 
					        this.$refs.taker.validate();
 | 
				
			||||||
 | 
					        if (this.valid) {
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            const response = await fetch('/don_confiao/reconciliate_jar', {
 | 
				
			||||||
 | 
					              method: 'POST',
 | 
				
			||||||
 | 
					              headers: {
 | 
				
			||||||
 | 
					              'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              body: JSON.stringify(this.reconciliation),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (response.ok) {
 | 
				
			||||||
 | 
					              const data = await response.json();
 | 
				
			||||||
 | 
					              console.log('Cuadre enviado:', data);
 | 
				
			||||||
 | 
					              this.$router.push({path: "/"});
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              console.error('Error al enviar el cuadre', response.statusText);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } catch (error) {
 | 
				
			||||||
 | 
					            console.error('Error de red:', error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
from rest_framework import serializers
 | 
					from rest_framework import serializers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Sale, SaleLine, Product, Customer
 | 
					from .models import Sale, SaleLine, Product, Customer, ReconciliationJar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SaleLineSerializer(serializers.ModelSerializer):
 | 
					class SaleLineSerializer(serializers.ModelSerializer):
 | 
				
			||||||
@@ -20,7 +20,21 @@ class ProductSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
        model = Product
 | 
					        model = Product
 | 
				
			||||||
        fields = ['id', 'name', 'price', 'measuring_unit', 'categories']
 | 
					        fields = ['id', 'name', 'price', 'measuring_unit', 'categories']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReconciliationJarSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = ReconciliationJar
 | 
				
			||||||
 | 
					        fields = [
 | 
				
			||||||
 | 
					            'id',
 | 
				
			||||||
 | 
					            'date_time',
 | 
				
			||||||
 | 
					            'reconcilier',
 | 
				
			||||||
 | 
					            'cash_taken',
 | 
				
			||||||
 | 
					            'cash_discrepancy',
 | 
				
			||||||
 | 
					            'total_cash_purchases',
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -138,15 +138,15 @@ class TestJarReconcliation(TestCase):
 | 
				
			|||||||
        self.assertIn('total_cash_purchases', content['error'])
 | 
					        self.assertIn('total_cash_purchases', content['error'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_create_reconciliation_with_purchases(self):
 | 
					    def test_create_reconciliation_with_purchases(self):
 | 
				
			||||||
        url = '/don_confiao/reconciliation_jar/create'
 | 
					        url = '/don_confiao/reconciliate_jar'
 | 
				
			||||||
        total_purchases = (11 * 72500) + (27 * 72500)
 | 
					        total_purchases = (11 * 72500) + (27 * 72500)
 | 
				
			||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
            'date_time': '2024-12-02T21:07',
 | 
					            'date_time': '2024-12-02T21:07',
 | 
				
			||||||
            'cashman': 'carlos',
 | 
					            'reconcilier': 'carlos',
 | 
				
			||||||
            'total_cash_purchases': total_purchases,
 | 
					            'total_cash_purchases': total_purchases,
 | 
				
			||||||
            'cash_taken': total_purchases,
 | 
					            'cash_taken': total_purchases,
 | 
				
			||||||
            'cash_discrepancy': 0,
 | 
					            'cash_discrepancy': 0,
 | 
				
			||||||
            'purchases': json.dumps([#machete por error al codificar el json en el request
 | 
					            'cash_purchases': json.dumps([#machete por error al codificar el json en el request
 | 
				
			||||||
                self.purchase.id,
 | 
					                self.purchase.id,
 | 
				
			||||||
                self.purchase2.id,
 | 
					                self.purchase2.id,
 | 
				
			||||||
                self.purchase.id,
 | 
					                self.purchase.id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,5 +28,6 @@ urlpatterns = [
 | 
				
			|||||||
    path("payment_methods/all/select_format", views.payment_methods_to_select, name="payment_methods_to_select"),
 | 
					    path("payment_methods/all/select_format", views.payment_methods_to_select, name="payment_methods_to_select"),
 | 
				
			||||||
    path('purchases/for_reconciliation', views.sales_for_reconciliation, name='sales_for_reconciliation'),
 | 
					    path('purchases/for_reconciliation', views.sales_for_reconciliation, name='sales_for_reconciliation'),
 | 
				
			||||||
    path('reconciliation_jar/create', views.reconciliate_jar, name='reconciliate_jar'),
 | 
					    path('reconciliation_jar/create', views.reconciliate_jar, name='reconciliate_jar'),
 | 
				
			||||||
 | 
					    path('reconciliate_jar', api_views.ReconciliateJarView.as_view()),
 | 
				
			||||||
    path('api/', include(router.urls)),
 | 
					    path('api/', include(router.urls)),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ from django.shortcuts import render
 | 
				
			|||||||
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
 | 
					from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
 | 
				
			||||||
from django.views.generic import ListView
 | 
					from django.views.generic import ListView
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
 | 
					from django.middleware.csrf import get_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import (
 | 
					from .models import (
 | 
				
			||||||
    Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
 | 
					    Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
 | 
				
			||||||
@@ -204,7 +205,12 @@ def sales_for_reconciliation(request):
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            'total': sale.get_total(),
 | 
					            'total': sale.get_total(),
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    return JsonResponse(grouped_sales, safe=False)
 | 
					    response = JsonResponse(grouped_sales, safe=False)
 | 
				
			||||||
 | 
					    csrf_token = get_token(request)
 | 
				
			||||||
 | 
					    response['X-CSRFToken'] = csrf_token
 | 
				
			||||||
 | 
					    response['Access-Control-Allow-Headers'] = 'X-CSRFToken'
 | 
				
			||||||
 | 
					    response.set_cookie('csrftoken', csrf_token)
 | 
				
			||||||
 | 
					    return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _mask_phone(phone):
 | 
					def _mask_phone(phone):
 | 
				
			||||||
    digits = str(phone)[-3:] if phone else " " * 3
 | 
					    digits = str(phone)[-3:] if phone else " " * 3
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user