Merge pull request 'Creada vista para listar los cuadres de tarro #90' (#91) from view_for_jar_reconciliation_#90 into main

Reviewed-on: #91
This commit is contained in:
mono 2025-02-02 23:08:00 -05:00
commit 43756952fb
13 changed files with 402 additions and 20 deletions

View File

@ -2,6 +2,7 @@ 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.status import HTTP_400_BAD_REQUEST
from rest_framework.views import APIView from rest_framework.views import APIView
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
@ -9,6 +10,12 @@ from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer,
from decimal import Decimal from decimal import Decimal
import json import json
class Pagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
class SaleView(viewsets.ModelViewSet): class SaleView(viewsets.ModelViewSet):
queryset = Sale.objects.all() queryset = Sale.objects.all()
serializer_class = SaleSerializer serializer_class = SaleSerializer
@ -131,3 +138,9 @@ 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)
return Response({'validCode': bool(codes)}) return Response({'validCode': bool(codes)})
class ReconciliateJarModelView(viewsets.ModelViewSet):
queryset = ReconciliationJar.objects.all().order_by('-date_time')
pagination_class = Pagination
serializer_class = ReconciliationJarSerializer

View File

@ -29,7 +29,8 @@
menuItems: [ menuItems: [
{ title: 'Inicio', route: '/'}, { title: 'Inicio', route: '/'},
{ title: 'Comprar', route:'/comprar'}, { 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: { watch: {

View File

@ -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>

View File

@ -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 }}&nbsp; <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>

View File

@ -53,7 +53,7 @@
name: 'SummaryPurchase', name: 'SummaryPurchase',
props: { props: {
msg: String, msg: String,
id: String id: Number
}, },
data () { data () {
return { return {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -23,6 +23,14 @@ class Api {
return this.apiImplementation.getPurchasesForReconciliation(); return this.apiImplementation.getPurchasesForReconciliation();
} }
getListReconcliations(page=1, itemsPerPage=10) {
return this.apiImplementation.getListReconcliations(page, itemsPerPage);
}
getReconciliation(reconciliationId) {
return this.apiImplementation.getReconciliation(reconciliationId);
}
isValidAdminCode(code) { isValidAdminCode(code) {
return this.apiImplementation.isValidAdminCode(code); return this.apiImplementation.isValidAdminCode(code);
} }

View File

@ -24,6 +24,16 @@ class DjangoApi {
return this.getRequest(url); 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) { isValidAdminCode(code) {
const url = `/don_confiao/api/admin_code/validate/${code}` const url = `/don_confiao/api/admin_code/validate/${code}`
return this.getRequest(url) return this.getRequest(url)

View File

@ -10,9 +10,12 @@ class SaleLineSerializer(serializers.ModelSerializer):
class SaleSerializer(serializers.ModelSerializer): class SaleSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField(source='get_total')
class Meta: class Meta:
model = Sale model = Sale
fields = ['id', 'customer', 'date', 'saleline_set'] fields = ['id', 'customer', 'date', 'saleline_set',
'total', 'payment_method']
class ProductSerializer(serializers.ModelSerializer): class ProductSerializer(serializers.ModelSerializer):
@ -28,6 +31,8 @@ class CustomerSerializer(serializers.ModelSerializer):
class ReconciliationJarSerializer(serializers.ModelSerializer): class ReconciliationJarSerializer(serializers.ModelSerializer):
Sales = SaleSerializer(many=True, read_only=True)
class Meta: class Meta:
model = ReconciliationJar model = ReconciliationJar
fields = [ fields = [
@ -37,8 +42,10 @@ class ReconciliationJarSerializer(serializers.ModelSerializer):
'cash_taken', 'cash_taken',
'cash_discrepancy', 'cash_discrepancy',
'total_cash_purchases', 'total_cash_purchases',
'Sales',
] ]
class PaymentMethodSerializer(serializers.Serializer): class PaymentMethodSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
value = serializers.CharField() value = serializers.CharField()
@ -49,6 +56,7 @@ class PaymentMethodSerializer(serializers.Serializer):
'value': instance[0], 'value': instance[0],
} }
class SaleForRenconciliationSerializer(serializers.Serializer): class SaleForRenconciliationSerializer(serializers.Serializer):
id = serializers.IntegerField() id = serializers.IntegerField()
date = serializers.DateTimeField() date = serializers.DateTimeField()

View File

@ -139,22 +139,7 @@ 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/reconciliate_jar' response = self._create_reconciliation_with_purchase()
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')
rawContent = response.content.decode('utf-8') rawContent = response.content.decode('utf-8')
content = json.loads(rawContent) content = json.loads(rawContent)
@ -197,6 +182,59 @@ class TestJarReconcliation(TestCase):
purchases = Sale.objects.filter(reconciliation_id=content['id']) purchases = Sale.objects.filter(reconciliation_id=content['id'])
self.assertEqual(len(purchases), 3) 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): def _create_simple_reconciliation(self):
reconciliation = ReconciliationJar() reconciliation = ReconciliationJar()
reconciliation.date_time = "2024-07-30" reconciliation.date_time = "2024-07-30"
@ -206,3 +244,21 @@ class TestJarReconcliation(TestCase):
reconciliation.clean() reconciliation.clean()
reconciliation.save() reconciliation.save()
return reconciliation 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')

View File

@ -10,7 +10,8 @@ router = DefaultRouter()
router.register(r'sales', api_views.SaleView, basename='sale') router.register(r'sales', api_views.SaleView, basename='sale')
router.register(r'customers', api_views.CustomerView, basename='customer') router.register(r'customers', api_views.CustomerView, basename='customer')
router.register(r'products', api_views.ProductView, basename='product') router.register(r'products', api_views.ProductView, basename='product')
router.register(r'reconciliate_jar', api_views.ReconciliateJarModelView,
basename='reconciliate_jar')
urlpatterns = [ urlpatterns = [
path("", views.index, name="wellcome"), path("", views.index, name="wellcome"),