Creada vista para listar los cuadres de tarro #90 #91
| @@ -2,6 +2,7 @@ from rest_framework import viewsets | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.status import HTTP_400_BAD_REQUEST | ||||
| from rest_framework.views import APIView | ||||
| 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 | ||||
| @@ -9,6 +10,12 @@ from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, | ||||
| from decimal import Decimal | ||||
| import json | ||||
|  | ||||
|  | ||||
| class Pagination(PageNumberPagination): | ||||
|     page_size = 10 | ||||
|     page_size_query_param = 'page_size' | ||||
|  | ||||
|  | ||||
| class SaleView(viewsets.ModelViewSet): | ||||
|     queryset = Sale.objects.all() | ||||
|     serializer_class = SaleSerializer | ||||
| @@ -131,3 +138,9 @@ class AdminCodeValidateView(APIView): | ||||
|     def get(self, request, code): | ||||
|         codes = AdminCode.objects.filter(value=code) | ||||
|         return Response({'validCode': bool(codes)}) | ||||
|  | ||||
|  | ||||
| class ReconciliateJarModelView(viewsets.ModelViewSet): | ||||
|     queryset = ReconciliationJar.objects.all().order_by('-date_time') | ||||
|     pagination_class = Pagination | ||||
|     serializer_class = ReconciliationJarSerializer | ||||
|   | ||||
| @@ -29,7 +29,8 @@ | ||||
|          menuItems: [ | ||||
|              { title: 'Inicio', route: '/'}, | ||||
|              { title: 'Comprar', route:'/comprar'}, | ||||
|              { title: 'Cuadrar tarro', route: '/cuadrar_tarro'} | ||||
|              { title: 'Cuadrar tarro', route: '/cuadrar_tarro'}, | ||||
|              { title: 'Cuadres de tarro', route: '/cuadres_de_tarro'}, | ||||
|          ], | ||||
|      }), | ||||
|      watch: { | ||||
|   | ||||
| @@ -0,0 +1,64 @@ | ||||
| <template> | ||||
|   <v-container> | ||||
|     <v-toolbar> | ||||
|       <v-toolbar-title> Cuadres del Tarro </v-toolbar-title> | ||||
|     </v-toolbar> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <v-data-table-server | ||||
|           v-model:items-per-page="itemsPerPage" | ||||
|           :headers="headers" | ||||
|           :items="serverItems" | ||||
|           :items-length="totalItems" | ||||
|           :loading="loading" | ||||
|           :search="search" | ||||
|           @update:options="loadItems" | ||||
|         > | ||||
|           <template v-slot:item.id="{ item }"> | ||||
|             <v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn> | ||||
|           </template> | ||||
|         </v-data-table-server> | ||||
|         <SummaryReconciliationModal :id="selectedReconciliationId" ref="summaryModal" /> | ||||
|       </v-card-text> | ||||
|     </v-card> | ||||
|   </v-container> | ||||
| </template> | ||||
| <script> | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         api: inject('api'), | ||||
|         selectedReconciliationId: null, | ||||
|         itemsPerPage: 10, | ||||
|         headers: [ | ||||
|           { title: 'Acciones', key: 'id'}, | ||||
|           { title: 'Fecha', key: 'date_time'}, | ||||
|           { title: 'Reconciliador', key: 'reconcilier'}, | ||||
|           { title: 'Total Compras Efectivo', key: 'total_cash_purchases'}, | ||||
|           { title: 'Recogido', key: 'cash_taken'}, | ||||
|           { title: 'Descuadre', key: 'cash_discrepancy'}, | ||||
|         ], | ||||
|         search: '', | ||||
|         serverItems: [], | ||||
|         loading: true, | ||||
|         totalItems: 0, | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       loadItems ({page, itemsPerPage}) { | ||||
|         this.loading = true; | ||||
|         this.api.getListReconcliations(page, itemsPerPage) | ||||
|             .then(data => { | ||||
|               this.serverItems = data['results']; | ||||
|               this.totalItems = data['count']; | ||||
|               this.loading = false; | ||||
|             }) | ||||
|             .catch(error => console.log('Error:', error)); | ||||
|       }, | ||||
|       openSummaryModal(id) { | ||||
|         this.selectedReconciliationId = id.toString(); | ||||
|         this.$refs.summaryModal.dialog = true; | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
| @@ -0,0 +1,140 @@ | ||||
| <template> | ||||
|   <v-container> | ||||
|     <v-toolbar> | ||||
|       <v-toolbar-title> Cuadre de Tarro: {{ id }}</v-toolbar-title> | ||||
|     </v-toolbar> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.date_time" | ||||
|           label="Fecha" | ||||
|           required | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.reconcilier" | ||||
|           label="Cajero" | ||||
|           required | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.total_cash_purchases" | ||||
|           label="Total Ventas en efectivo" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_taken" | ||||
|           label="Dinero Recogido" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_discrepancy" | ||||
|           label="Descuadre" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-tabs v-model="tab"> | ||||
|           <v-tab | ||||
|             v-for="(elements, paymentMethod) in purchases" | ||||
|             :key="paymentMethod" | ||||
|           > | ||||
|             {{ paymentMethod }}  <CurrencyText :value="elements.total"</CurrencyText> | ||||
|           </v-tab> | ||||
|         </v-tabs> | ||||
|         <v-tabs-window v-model="tab"> | ||||
|           <v-tabs-window-item | ||||
|             v-for="(elements, paymentMethod) in purchases" | ||||
|             :key="paymentMethod" | ||||
|           > | ||||
|             <v-table> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th>Id</th> | ||||
|                   <th>Fecha</th> | ||||
|                   <th>Cliente</th> | ||||
|                   <th>Total</th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 <tr v-for="purchase in elements.purchases" :key="purchase.id"> | ||||
|                   <td><v-btn @click="openSummaryModal(purchase.id)">{{ purchase.id }}</v-btn></td> | ||||
|                   <td>{{ purchase.date }}</td> | ||||
|                   <td>{{ purchase.customer }}</td> | ||||
|                   <td><CurrencyText :value="purchase.total"</CurrencyText></td> | ||||
|  | ||||
|                 </tr> | ||||
|               </tbody> | ||||
|             </v-table> | ||||
|  | ||||
|           </v-tabs-window-item> | ||||
|         </v-tabs-window> | ||||
|         <SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" /> | ||||
|       </v-card-text> | ||||
|     </v-card> | ||||
|   </v-container> | ||||
| </template> | ||||
| <script> | ||||
|   import { inject } from 'vue'; | ||||
|  | ||||
|   export default { | ||||
|     name: 'ReconciliationJar View', | ||||
|     props: { | ||||
|       msg: String, | ||||
|       id: { | ||||
|         type: String, | ||||
|         required: true | ||||
|       } | ||||
|     }, | ||||
|     data () { | ||||
|       return { | ||||
|         tab: '0', | ||||
|         selectedPurchaseId: null, | ||||
|         api: inject('api'), | ||||
|         valid: null, | ||||
|         reconciliation: { | ||||
|         }, | ||||
|         purchases: {}, | ||||
|       }; | ||||
|     }, | ||||
|     created() { | ||||
|       if (this.id) { | ||||
|         this.fetchReconciliation(this.id); | ||||
|       } else { | ||||
|         console.error('No se proporcionó ID'); | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       fetchReconciliation(reconciliationId) { | ||||
|         this.api.getReconciliation(reconciliationId) | ||||
|             .then(data => { | ||||
|               this.reconciliation = data; | ||||
|               this.groupPurchases(); | ||||
|             }) | ||||
|             .catch(error => console.error(error)); | ||||
|       }, | ||||
|       groupPurchases() { | ||||
|         if (this.reconciliation.Sales) { | ||||
|           this.purchases = this.reconciliation.Sales.reduce((grouped, sale) => { | ||||
|             const paymentMethod = sale.payment_method; | ||||
|             if (!grouped[paymentMethod]) { | ||||
|               grouped[paymentMethod] = { | ||||
|                 purchases: [], | ||||
|                 total: 0, | ||||
|               }; | ||||
|             } | ||||
|             grouped[paymentMethod].purchases.push(sale); | ||||
|             grouped[paymentMethod].total += sale.total; | ||||
|             return grouped; | ||||
|           }, {}); | ||||
|         } | ||||
|       }, | ||||
|       openSummaryModal(id) { | ||||
|         this.selectedPurchaseId = id; | ||||
|         this.$refs.summaryModal.dialog = true; | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
| @@ -53,7 +53,7 @@ | ||||
|     name: 'SummaryPurchase', | ||||
|     props: { | ||||
|       msg: String, | ||||
|       id: String | ||||
|       id: Number | ||||
|     }, | ||||
|     data () { | ||||
|       return { | ||||
|   | ||||
| @@ -0,0 +1,30 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <SummaryPurchase :id="id"/> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'SummaryPurchase Modal', | ||||
|     props: { | ||||
|       id: { | ||||
|         type: Number, | ||||
|         required: true, | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
| @@ -0,0 +1,31 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     resumen | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <ReconciliationJarView :id="id"/> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'Summary Reconciliation Modal', | ||||
|     props: { | ||||
|       id: { | ||||
|         type: String, | ||||
|         required: true, | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
| @@ -0,0 +1,20 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <CodeDialog @code-verified="(verified) => showComponent = verified" /> | ||||
|   </div> | ||||
|   <ReconciliationJarIndex v-if="showComponent" /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import CodeDialog from '../components/CodeDialog.vue' | ||||
|  | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         showComponent: false, | ||||
|       } | ||||
|     }, | ||||
|     components: { CodeDialog }, | ||||
|     methods: {}, | ||||
|   } | ||||
| </script> | ||||
| @@ -23,6 +23,14 @@ class Api { | ||||
|     return this.apiImplementation.getPurchasesForReconciliation(); | ||||
|   } | ||||
|  | ||||
|   getListReconcliations(page=1, itemsPerPage=10) { | ||||
|     return this.apiImplementation.getListReconcliations(page, itemsPerPage); | ||||
|   } | ||||
|  | ||||
|   getReconciliation(reconciliationId) { | ||||
|     return this.apiImplementation.getReconciliation(reconciliationId); | ||||
|   } | ||||
|  | ||||
|   isValidAdminCode(code) { | ||||
|     return this.apiImplementation.isValidAdminCode(code); | ||||
|   } | ||||
|   | ||||
| @@ -24,6 +24,16 @@ class DjangoApi { | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getListReconcliations(page, itemsPerPage) { | ||||
|     const url = `/don_confiao/api/reconciliate_jar/?page=${page}&page_size=${itemsPerPage}`; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getReconciliation(reconciliationId) { | ||||
|     const url = `/don_confiao/api/reconciliate_jar/${reconciliationId}/`; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   isValidAdminCode(code) { | ||||
|     const url = `/don_confiao/api/admin_code/validate/${code}` | ||||
|     return this.getRequest(url) | ||||
|   | ||||
| @@ -10,9 +10,12 @@ class SaleLineSerializer(serializers.ModelSerializer): | ||||
|  | ||||
|  | ||||
| class SaleSerializer(serializers.ModelSerializer): | ||||
|     total = serializers.ReadOnlyField(source='get_total') | ||||
|  | ||||
|     class Meta: | ||||
|         model = Sale | ||||
|         fields = ['id', 'customer', 'date', 'saleline_set'] | ||||
|         fields = ['id', 'customer', 'date', 'saleline_set', | ||||
|                   'total', 'payment_method'] | ||||
|  | ||||
|  | ||||
| class ProductSerializer(serializers.ModelSerializer): | ||||
| @@ -28,6 +31,8 @@ class CustomerSerializer(serializers.ModelSerializer): | ||||
|  | ||||
|  | ||||
| class ReconciliationJarSerializer(serializers.ModelSerializer): | ||||
|     Sales = SaleSerializer(many=True, read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = ReconciliationJar | ||||
|         fields = [ | ||||
| @@ -37,8 +42,10 @@ class ReconciliationJarSerializer(serializers.ModelSerializer): | ||||
|             'cash_taken', | ||||
|             'cash_discrepancy', | ||||
|             'total_cash_purchases', | ||||
|             'Sales', | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class PaymentMethodSerializer(serializers.Serializer): | ||||
|     text = serializers.CharField() | ||||
|     value = serializers.CharField() | ||||
| @@ -49,6 +56,7 @@ class PaymentMethodSerializer(serializers.Serializer): | ||||
|             'value': instance[0], | ||||
|         } | ||||
|  | ||||
|  | ||||
| class SaleForRenconciliationSerializer(serializers.Serializer): | ||||
|     id = serializers.IntegerField() | ||||
|     date = serializers.DateTimeField() | ||||
|   | ||||
| @@ -139,22 +139,7 @@ class TestJarReconcliation(TestCase): | ||||
|         self.assertIn('total_cash_purchases', content['error']) | ||||
|  | ||||
|     def test_create_reconciliation_with_purchases(self): | ||||
|         url = '/don_confiao/reconciliate_jar' | ||||
|         total_purchases = (11 * 72500) + (27 * 72500) | ||||
|         data = { | ||||
|             'date_time': '2024-12-02T21:07', | ||||
|             'reconcilier': 'carlos', | ||||
|             'total_cash_purchases': total_purchases, | ||||
|             'cash_taken': total_purchases, | ||||
|             'cash_discrepancy': 0, | ||||
|             'cash_purchases': [ | ||||
|                 self.purchase.id, | ||||
|                 self.purchase2.id, | ||||
|                 self.purchase.id, | ||||
|             ], | ||||
|         } | ||||
|         response = self.client.post(url, data=json.dumps(data).encode('utf-8'), | ||||
|                                     content_type='application/json') | ||||
|         response = self._create_reconciliation_with_purchase() | ||||
|  | ||||
|         rawContent = response.content.decode('utf-8') | ||||
|         content = json.loads(rawContent) | ||||
| @@ -197,6 +182,59 @@ class TestJarReconcliation(TestCase): | ||||
|         purchases = Sale.objects.filter(reconciliation_id=content['id']) | ||||
|         self.assertEqual(len(purchases), 3) | ||||
|  | ||||
|     def test_list_reconciliations(self): | ||||
|         self._create_simple_reconciliation() | ||||
|         self._create_simple_reconciliation() | ||||
|  | ||||
|         url = '/don_confiao/api/reconciliate_jar/' | ||||
|  | ||||
|         response = self.client.get(url) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         content = json.loads(response.content.decode('utf-8')) | ||||
|         self.assertEqual(2, content['count']) | ||||
|         self.assertEqual(2, len(content['results'])) | ||||
|         self.assertEqual('2024-07-30T00:00:00Z', | ||||
|                          content['results'][0]['date_time']) | ||||
|  | ||||
|     def test_list_reconciliations_pagination(self): | ||||
|         self._create_simple_reconciliation() | ||||
|         self._create_simple_reconciliation() | ||||
|  | ||||
|         url = '/don_confiao/api/reconciliate_jar/?page=2&page_size=1' | ||||
|  | ||||
|         response = self.client.get(url) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         content = json.loads(response.content.decode('utf-8')) | ||||
|         self.assertEqual(1, len(content['results'])) | ||||
|         self.assertEqual('2024-07-30T00:00:00Z', | ||||
|                          content['results'][0]['date_time']) | ||||
|  | ||||
|     def test_get_single_reconciliation(self): | ||||
|         createResponse = self._create_reconciliation_with_purchase() | ||||
|         reconciliationId = json.loads( | ||||
|             createResponse.content.decode('utf-8') | ||||
|         )['id'] | ||||
|         self.assertGreater(reconciliationId, 0) | ||||
|  | ||||
|         url = f'/don_confiao/api/reconciliate_jar/{reconciliationId}/' | ||||
|         response = self.client.get(url, content_type='application/json') | ||||
|         content = json.loads( | ||||
|             response.content.decode('utf-8') | ||||
|         ) | ||||
|         self.assertEqual(reconciliationId, content['id']) | ||||
|         self.assertGreater(len(content['Sales']), 0) | ||||
|         self.assertIn( | ||||
|             self.purchase.id, | ||||
|             [sale['id'] for sale in content['Sales']] | ||||
|         ) | ||||
|  | ||||
|         self.assertIn( | ||||
|             'CASH', | ||||
|             [sale['payment_method'] for sale in content['Sales']] | ||||
|         ) | ||||
|  | ||||
|     def _create_simple_reconciliation(self): | ||||
|         reconciliation = ReconciliationJar() | ||||
|         reconciliation.date_time = "2024-07-30" | ||||
| @@ -206,3 +244,21 @@ class TestJarReconcliation(TestCase): | ||||
|         reconciliation.clean() | ||||
|         reconciliation.save() | ||||
|         return reconciliation | ||||
|  | ||||
|     def _create_reconciliation_with_purchase(self): | ||||
|         url = '/don_confiao/reconciliate_jar' | ||||
|         total_purchases = (11 * 72500) + (27 * 72500) | ||||
|         data = { | ||||
|             'date_time': '2024-12-02T21:07', | ||||
|             'reconcilier': 'carlos', | ||||
|             'total_cash_purchases': total_purchases, | ||||
|             'cash_taken': total_purchases, | ||||
|             'cash_discrepancy': 0, | ||||
|             'cash_purchases': [ | ||||
|                 self.purchase.id, | ||||
|                 self.purchase2.id, | ||||
|                 self.purchase.id, | ||||
|             ], | ||||
|         } | ||||
|         return self.client.post(url, data=json.dumps(data).encode('utf-8'), | ||||
|                                 content_type='application/json') | ||||
|   | ||||
| @@ -10,7 +10,8 @@ router = DefaultRouter() | ||||
| router.register(r'sales', api_views.SaleView, basename='sale') | ||||
| router.register(r'customers', api_views.CustomerView, basename='customer') | ||||
| router.register(r'products', api_views.ProductView, basename='product') | ||||
|  | ||||
| router.register(r'reconciliate_jar', api_views.ReconciliateJarModelView, | ||||
|                 basename='reconciliate_jar') | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("", views.index, name="wellcome"), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user