Compare commits
51 Commits
543e927fb0
...
TrytonApiC
| Author | SHA1 | Date | |
|---|---|---|---|
| 03d38f0b64 | |||
| a097bf7141 | |||
| eb75a13857 | |||
| 2a937653df | |||
| e5ae1bb142 | |||
| 9ff1deb687 | |||
| 0caa6fbb56 | |||
| a7e3b9aaa8 | |||
| 0308da7370 | |||
| 0dec800637 | |||
| 0a88641d34 | |||
| 871a82eee5 | |||
| 9ad8ff8706 | |||
| d9f6be8b54 | |||
| 34259921d9 | |||
| 8f9917c3a4 | |||
| fcb83d05fb | |||
| 6d6322b0cd | |||
| a4048473ae | |||
| 9a6a931481 | |||
| 432b7dad74 | |||
| 69d8b1d2ad | |||
| 8b7c2efcb3 | |||
| 5dff7565f4 | |||
| 023beaa0ee | |||
| b5fdd7fefd | |||
| eaf1afdcb4 | |||
| 5cfefdf91a | |||
| 9c0eebd07d | |||
| 8ab7903a0a | |||
| 1b425542b3 | |||
| f6620db6e2 | |||
| 1f2f484e95 | |||
| ef721a6b53 | |||
| f0201a86b2 | |||
| bea08da17d | |||
| a3d5fb1b45 | |||
| 4679170ab9 | |||
| 0d61e457c7 | |||
| 3294b8e814 | |||
| 3189363ba9 | |||
| 0a64373037 | |||
| 9a20212b27 | |||
| a6b4c1c5b6 | |||
| b7984f7556 | |||
| ef1a520838 | |||
| bd6d4221b2 | |||
| 9aa543662b | |||
| 746686afcc | |||
| c709dad36e | |||
| 6aca2007e0 |
32
Rakefile
32
Rakefile
@@ -20,7 +20,12 @@ namespace :live do
|
|||||||
compose('logs', '-f', '-n 50', 'django', compose: DOCKER_COMPOSE)
|
compose('logs', '-f', '-n 50', 'django', compose: DOCKER_COMPOSE)
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'detener entorno'
|
desc 'iniciar entorno'
|
||||||
|
task :start do
|
||||||
|
compose('start', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'bajar entorno'
|
||||||
task :down do
|
task :down do
|
||||||
compose('down', compose: DOCKER_COMPOSE)
|
compose('down', compose: DOCKER_COMPOSE)
|
||||||
end
|
end
|
||||||
@@ -52,6 +57,31 @@ namespace :live do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc 'Desarrollo'
|
||||||
|
namespace :dev do
|
||||||
|
|
||||||
|
desc 'correr test de django'
|
||||||
|
task :test do
|
||||||
|
compose('exec', 'django', 'python', '/app/manage.py', 'test', '/app/don_confiao')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'terminal django'
|
||||||
|
task :djangoShell do
|
||||||
|
compose('exec', 'django', 'python', '/app/manage.py', 'shell')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'crear migraciones'
|
||||||
|
task :makemigrations do
|
||||||
|
compose('exec', 'django', 'python', '/app/manage.py', 'makemigrations')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'aplicar migraciones'
|
||||||
|
task :migrate do
|
||||||
|
compose('exec', 'django', 'python', '/app/manage.py', 'migrate')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
def compose(*arg, compose: DOCKER_COMPOSE)
|
def compose(*arg, compose: DOCKER_COMPOSE)
|
||||||
sh "docker compose -f #{compose} #{arg.join(' ')}"
|
sh "docker compose -f #{compose} #{arg.join(' ')}"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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, PaymentMethods, AdminCode
|
||||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer
|
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
|
||||||
|
|
||||||
|
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,84 @@ 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 = data.get('cash_purchases')
|
||||||
|
serializer = ReconciliationJarSerializer(data=data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
cash_purchases = Sale.objects.filter(pk__in=cash_purchases_id)
|
||||||
|
if not self._is_valid_total(cash_purchases, 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()
|
||||||
|
other_purchases = self._get_other_purchases(data.get('other_totals'))
|
||||||
|
|
||||||
|
self._link_purchases(reconciliation, cash_purchases, other_purchases)
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _is_valid_total(self, purchases, total):
|
||||||
|
calculated_total = sum(p.get_total() for p in purchases)
|
||||||
|
return calculated_total == Decimal(total)
|
||||||
|
|
||||||
|
def _get_other_purchases(self, other_totals):
|
||||||
|
if not other_totals:
|
||||||
|
return []
|
||||||
|
purchases = []
|
||||||
|
for method in other_totals:
|
||||||
|
purchases.extend(other_totals[method]['purchases'])
|
||||||
|
if purchases:
|
||||||
|
return Sale.objects.filter(pk__in=purchases)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _link_purchases(self, reconciliation, cash_purchases, other_purchases):
|
||||||
|
for purchase in cash_purchases:
|
||||||
|
purchase.reconciliation = reconciliation
|
||||||
|
purchase.clean()
|
||||||
|
purchase.save()
|
||||||
|
|
||||||
|
for purchase in other_purchases:
|
||||||
|
purchase.reconciliation = reconciliation
|
||||||
|
purchase.clean()
|
||||||
|
purchase.save()
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentMethodView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
serializer = PaymentMethodSerializer(PaymentMethods.choices, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class SalesForReconciliationView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
sales = Sale.objects.filter(reconciliation=None)
|
||||||
|
grouped_sales = {}
|
||||||
|
|
||||||
|
for sale in sales:
|
||||||
|
if sale.payment_method not in grouped_sales.keys():
|
||||||
|
grouped_sales[sale.payment_method] = []
|
||||||
|
serializer = SaleForRenconciliationSerializer(sale)
|
||||||
|
grouped_sales[sale.payment_method].append(serializer.data)
|
||||||
|
|
||||||
|
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)
|
||||||
|
return Response({'validCode': bool(codes)})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.forms.models import inlineformset_factory
|
|||||||
|
|
||||||
from django.forms.widgets import DateInput, DateTimeInput
|
from django.forms.widgets import DateInput, DateTimeInput
|
||||||
|
|
||||||
from .models import Sale, SaleLine, ReconciliationJar, PaymentMethods
|
from .models import Sale, SaleLine, PaymentMethods
|
||||||
|
|
||||||
readonly_number_widget = forms.NumberInput(attrs={'readonly': 'readonly'})
|
readonly_number_widget = forms.NumberInput(attrs={'readonly': 'readonly'})
|
||||||
|
|
||||||
@@ -64,18 +64,3 @@ SaleLineFormSet = inlineformset_factory(
|
|||||||
extra=1,
|
extra=1,
|
||||||
fields='__all__'
|
fields='__all__'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReconciliationJarForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = ReconciliationJar
|
|
||||||
fields = [
|
|
||||||
'date_time',
|
|
||||||
'description',
|
|
||||||
'reconcilier',
|
|
||||||
'cash_taken',
|
|
||||||
'cash_discrepancy',
|
|
||||||
]
|
|
||||||
widgets = {
|
|
||||||
'date_time': DateTimeInput(attrs={'type': 'datetime-local'})
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "don-confiao",
|
"name": "don-confiao",
|
||||||
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.37.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"vee-validate": "^4.14.6",
|
"vee-validate": "^4.14.6",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.31",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" persistent>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
Ingrese el código
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form id="code-form" @submit.prevent="verifyCode">
|
||||||
|
<v-text-field v-model="code" label="Código" type="password" autocomplete="off" />
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn type="submit" form="code-form">Aceptar</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
api: inject('api'),
|
||||||
|
dialog: true,
|
||||||
|
code: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
verifyCode() {
|
||||||
|
this.api.isValidAdminCode(this.code)
|
||||||
|
.then(data => {
|
||||||
|
if (data['validCode']) {
|
||||||
|
this.$emit('code-verified', true);
|
||||||
|
this.dialog = false;
|
||||||
|
} else {
|
||||||
|
alert('Código incorrecto');
|
||||||
|
this.$emit('code-verified', false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert('Error al validar el código');
|
||||||
|
this.$emit('code-verified', false);
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showModal: false,
|
showModal: false,
|
||||||
|
api: inject('api'),
|
||||||
valid: false,
|
valid: false,
|
||||||
customer: {
|
customer: {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -72,25 +73,13 @@
|
|||||||
async submitForm() {
|
async submitForm() {
|
||||||
console.log(this.customer)
|
console.log(this.customer)
|
||||||
if (this.$refs.form.validate()) {
|
if (this.$refs.form.validate()) {
|
||||||
try {
|
this.api.createCustomer(this.customer)
|
||||||
const response = await fetch('/don_confiao/api/customers/', {
|
.then(data => {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.customer),
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('Cliente Guardado:', data);
|
console.log('Cliente Guardado:', data);
|
||||||
this.$emit('customerCreated', data);
|
this.$emit('customerCreated', data);
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
} else {
|
})
|
||||||
console.error('Error al Crear el Cliente:', response.statusText);
|
.catch(error => console.error('Error:', error));
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error de red:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<span>{{ formattedValue }}</span>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
default: 'es-CO',
|
||||||
|
},
|
||||||
|
currency: {
|
||||||
|
type: String,
|
||||||
|
default: 'COP',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formattedValue() {
|
||||||
|
return new Intl.NumberFormat(this.locale, { style: 'currency', currency: this.currency }).format(this.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
menuItems: [
|
menuItems: [
|
||||||
{ title: 'Inicio', route: '/'},
|
{ title: 'Inicio', route: '/'},
|
||||||
{ title: 'Comprar', route:'/comprar'},
|
{ title: 'Comprar', route:'/comprar'},
|
||||||
|
{ title: 'Cuadrar tarro', route: '/cuadrar_tarro'}
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@@ -147,6 +147,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import CustomerForm from './CreateCustomerModal.vue';
|
import CustomerForm from './CreateCustomerModal.vue';
|
||||||
import CasherModal from './CasherModal.vue';
|
import CasherModal from './CasherModal.vue';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DonConfiao',
|
name: 'DonConfiao',
|
||||||
@@ -159,6 +160,7 @@
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
api: inject('api'),
|
||||||
valid: false,
|
valid: false,
|
||||||
form_changed: false,
|
form_changed: false,
|
||||||
show_alert_lines: false,
|
show_alert_lines: false,
|
||||||
@@ -259,8 +261,7 @@
|
|||||||
this.purchase.saleline_set[index].measuring_unit = selectedProduct.measuring_unit;
|
this.purchase.saleline_set[index].measuring_unit = selectedProduct.measuring_unit;
|
||||||
},
|
},
|
||||||
fetchClients() {
|
fetchClients() {
|
||||||
fetch('/don_confiao/api/customers/')
|
this.api.getCustomers()
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.clients = data;
|
this.clients = data;
|
||||||
})
|
})
|
||||||
@@ -273,18 +274,23 @@
|
|||||||
this.purchase.customer = newCustomer.id;
|
this.purchase.customer = newCustomer.id;
|
||||||
},
|
},
|
||||||
fetchProducts() {
|
fetchProducts() {
|
||||||
fetch('/don_confiao/api/products/')
|
this.api.getProducts()
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.products = data;
|
console.log(data)
|
||||||
|
const transformed_products = data.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
price: item["template."]?.list_price?.decimal,
|
||||||
|
measuring_unit: item["default_uom"]?.name,
|
||||||
|
categories: []
|
||||||
|
}));
|
||||||
|
this.products = transformed_products;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchPaymentMethods() {
|
fetchPaymentMethods() {
|
||||||
fetch('/don_confiao/payment_methods/all/select_format')
|
this.api.getPaymentMethods()
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.payment_methods = data;
|
this.payment_methods = data;
|
||||||
})
|
})
|
||||||
@@ -311,27 +317,15 @@
|
|||||||
async submit() {
|
async submit() {
|
||||||
this.$refs.purchase.validate();
|
this.$refs.purchase.validate();
|
||||||
if (this.valid) {
|
if (this.valid) {
|
||||||
try {
|
this.api.createPurchase(this.purchase)
|
||||||
const response = await fetch('/don_confiao/api/sales/', {
|
.then(data => {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.purchase),
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('Compra enviada:', data);
|
console.log('Compra enviada:', data);
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: "/summary_purchase",
|
path: "/summary_purchase",
|
||||||
query : {id: parseInt(data.id)}
|
query : {id: parseInt(data.id)}
|
||||||
});
|
});
|
||||||
} else {
|
})
|
||||||
console.error('Error al enviar la compra:', response.statusText);
|
.catch(error => console.error('Error al enviarl la compra:', error));
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error de red:', error);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.show_alert_purchase = true;
|
this.show_alert_purchase = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -347,7 +341,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchClients(); // Llama a fetchClients al montar el componente
|
this.fetchClients();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-toolbar>
|
||||||
|
<v-toolbar-title> Cuadre del Tarro </v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card>
|
||||||
|
<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-model="reconciliation.reconcilier"
|
||||||
|
label="Cajero"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.total_cash_purchases"
|
||||||
|
label="Total Ventas en efectivo"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
readonly
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.cash_taken"
|
||||||
|
label="Dinero Recogido"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.cash_discrepancy"
|
||||||
|
label="Descuadre"
|
||||||
|
:rules="[rules.integer]"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
></v-text-field>
|
||||||
|
<v-btn @click="submit" color="green">Recoger Dinero</v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<v-tabs v-model="selectedTab">
|
||||||
|
<v-tab
|
||||||
|
v-for="(purchases, payment_method) in summary.purchases"
|
||||||
|
:key="payment_method"
|
||||||
|
:value="payment_method"
|
||||||
|
>
|
||||||
|
{{ payment_method }} <CurrencyText :value="totalByMethod(payment_method)"</CurrencyText>
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
|
||||||
|
<v-tabs-window v-model="selectedTab">
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<v-tabs-window-item
|
||||||
|
v-for="(purchases, payment_method) in summary.purchases"
|
||||||
|
:key="payment_method"
|
||||||
|
:value="payment_method"
|
||||||
|
>
|
||||||
|
<v-data-table-virtual
|
||||||
|
:headers="summary.headers"
|
||||||
|
:items="summary.purchases[payment_method]"
|
||||||
|
>
|
||||||
|
<template v-slot:item.id="{ item }">
|
||||||
|
<v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.total="{ item }">
|
||||||
|
<CurrencyText :value="parseFloat(item.total)"></CurrencyText>
|
||||||
|
</template>
|
||||||
|
</v-data-table-virtual>
|
||||||
|
</v-tabs-window-item>
|
||||||
|
<SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-tabs-window>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import CurrencyText from './CurrencyText.vue';
|
||||||
|
import SummaryPurchaseModal from './SummaryPurchaseModal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ReconciliationJar',
|
||||||
|
props: {
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
SummaryPurchaseModal,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
api: inject('api'),
|
||||||
|
valid: null,
|
||||||
|
selectedPurchaseId: null,
|
||||||
|
selectedTab: 'CASH',
|
||||||
|
reconciliation: {
|
||||||
|
date_time: '',
|
||||||
|
total_cash_purchases: 0,
|
||||||
|
cash_taken: 0,
|
||||||
|
cash_discrepancy: 0,
|
||||||
|
other_totals: {
|
||||||
|
},
|
||||||
|
cash_purchases: [],
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
headers: [
|
||||||
|
{title: 'Id', value: 'id'},
|
||||||
|
{title: 'Fecha', value: 'date'},
|
||||||
|
{title: 'Cliente', value: 'customer.name'},
|
||||||
|
{title: 'Total', value: 'total'},
|
||||||
|
],
|
||||||
|
purchases: {},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
required: value => !!value || 'Requerido.',
|
||||||
|
integer: value => !!value || value === 0 || 'Requerido.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchPurchases();
|
||||||
|
this.reconciliation.date_time = this.getCurrentDate();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'reconciliation.cash_taken'() {
|
||||||
|
this.updateDiscrepancy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
totalByMethod(method) {
|
||||||
|
if (method in this.summary.purchases) {
|
||||||
|
return this.summary.purchases[method].reduce((a, b) => a + parseFloat(b.total), 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
idsBymethod(method) {
|
||||||
|
if (method in this.summary.purchases) {
|
||||||
|
return this.summary.purchases[method].map(purchase => purchase.id)
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
processOtherMethods() {
|
||||||
|
for (const method of Object.keys(this.summary.purchases)) {
|
||||||
|
if (method !== 'CASH') {
|
||||||
|
this.reconciliation.other_totals[method] = {
|
||||||
|
total: this.totalByMethod(method),
|
||||||
|
purchases: this.idsBymethod(method),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateDiscrepancy() {
|
||||||
|
this.reconciliation.cash_discrepancy = (this.reconciliation.total_cash_purchases || 0 ) - (this.reconciliation.cash_taken || 0);
|
||||||
|
},
|
||||||
|
getCurrentDate() {
|
||||||
|
const today = new Date();
|
||||||
|
const gmtOffSet = -5;
|
||||||
|
const localDate = new Date(today.getTime() + (gmtOffSet * 60 * 60 * 1000));
|
||||||
|
// Formatear la fecha y hora en el formato YYYY-MM-DDTHH:MM
|
||||||
|
const formattedDate = localDate.toISOString().slice(0,16);
|
||||||
|
return formattedDate;
|
||||||
|
},
|
||||||
|
openSummaryModal(id) {
|
||||||
|
this.selectedPurchaseId = id;
|
||||||
|
this.$refs.summaryModal.dialog = true;
|
||||||
|
},
|
||||||
|
fetchPurchases() {
|
||||||
|
this.api.getPurchasesForReconciliation()
|
||||||
|
.then(data => {
|
||||||
|
this.summary.purchases = data;
|
||||||
|
this.reconciliation.cash_purchases = this.idsBymethod('CASH');
|
||||||
|
this.reconciliation.total_cash_purchases = this.totalByMethod('CASH');
|
||||||
|
this.processOtherMethods();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
this.$refs.taker.validate();
|
||||||
|
if (this.valid) {
|
||||||
|
this.api.createReconciliationJar(this.reconciliation)
|
||||||
|
.then(data => {
|
||||||
|
console.log('Cuadre enviado:', data);
|
||||||
|
this.$router.push({path: "/"});
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -12,33 +12,25 @@
|
|||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Fecha:</v-list-item-title>
|
<v-list-item-title>Fecha:</v-list-item-title>
|
||||||
<v-list-item-subtitle>{{ purchase.date }}</v-list-item-subtitle>
|
<v-list-item-subtitle>{{ purchase.date }}</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Cliente:</v-list-item-title>
|
<v-list-item-title>Cliente:</v-list-item-title>
|
||||||
<v-list-item-subtitle v-if="purchase.customer">{{ purchase.customer.name }}</v-list-item-subtitle>
|
<v-list-item-subtitle v-if="purchase.customer">{{ purchase.customer.name }}</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Pagado en:</v-list-item-title>
|
<v-list-item-title>Pagado en:</v-list-item-title>
|
||||||
<v-list-item-subtitle v-if="purchase.payment_method">{{ purchase.payment_method }}</v-list-item-subtitle>
|
<v-list-item-subtitle v-if="purchase.payment_method">{{ purchase.payment_method }}</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Total:</v-list-item-title>
|
<v-list-item-title>Total:</v-list-item-title>
|
||||||
<v-list-item-subtitle v-if="purchase.set_lines">{{ currencyFormat(calculateTotal(purchase.set_lines)) }}</v-list-item-subtitle>
|
<v-list-item-subtitle v-if="purchase.lines">{{ currencyFormat(calculateTotal(purchase.lines)) }}</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
<v-data-table-virtual
|
<v-data-table-virtual
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:items="purchase.set_lines"
|
:items="purchase.lines"
|
||||||
>
|
>
|
||||||
<template v-slot:item.unit_price="{ item }">
|
<template v-slot:item.unit_price="{ item }">
|
||||||
{{ currencyFormat(item.unit_price) }}
|
{{ currencyFormat(item.unit_price) }}
|
||||||
@@ -55,6 +47,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SummaryPurchase',
|
name: 'SummaryPurchase',
|
||||||
props: {
|
props: {
|
||||||
@@ -63,6 +57,7 @@
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
api: inject('api'),
|
||||||
purchase: {},
|
purchase: {},
|
||||||
headers: [
|
headers: [
|
||||||
{ title: 'Producto', value: 'product.name' },
|
{ title: 'Producto', value: 'product.name' },
|
||||||
@@ -81,8 +76,7 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchPurchase(purchaseId) {
|
fetchPurchase(purchaseId) {
|
||||||
fetch(`/don_confiao/resumen_compra_json/${purchaseId}`)
|
this.api.getSummaryPurchase(purchaseId)
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.purchase = data;
|
this.purchase = data;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -9,11 +9,25 @@ import { registerPlugins } from '@/plugins'
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import ApiImplementation from './services/api-implementation';
|
||||||
|
|
||||||
// Composables
|
// Composables
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import cors from 'cors';
|
||||||
|
|
||||||
const app = createApp(App)
|
// const cors = require('cors');
|
||||||
|
|
||||||
|
process.env.API_IMPLEMENTATION = 'tryton';
|
||||||
|
// process.env.API_IMPLEMENTATION = 'django';
|
||||||
|
let apiImplementation = new ApiImplementation();
|
||||||
|
const api = apiImplementation.getApi();
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
// app.use(cors({
|
||||||
|
// origin: '*', // Permitir todas las solicitudes de origen
|
||||||
|
// exposedHeaders: ['X-Custom-Header', 'Content-Length'], // Exponer headers específicos
|
||||||
|
// }));
|
||||||
|
app.provide('api', api);
|
||||||
|
|
||||||
registerPlugins(app)
|
registerPlugins(app)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CodeDialog @code-verified="(verified) => showComponent = verified"/>
|
||||||
|
</div>
|
||||||
|
<ReconciliationJar v-if="showComponent" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script >
|
||||||
|
import CodeDialog from '../components/CodeDialog.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showComponent: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { CodeDialog },
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import DjangoApi from './django-api';
|
||||||
|
import TrytonApiClient from './tryton-api';
|
||||||
|
import Api from './api';
|
||||||
|
|
||||||
|
class ApiImplementation {
|
||||||
|
constructor() {
|
||||||
|
const implementation = process.env.API_IMPLEMENTATION;
|
||||||
|
let apiImplementation;
|
||||||
|
if (implementation === 'django') {
|
||||||
|
apiImplementation = new DjangoApi();
|
||||||
|
} else if (implementation === 'tryton'){
|
||||||
|
const url = 'http://192.168.0.114:18030';
|
||||||
|
const key = '9a9ffc430146447d81e6698240199a4be2b0e774cb18474999d0f60e33b5b1eb1cfff9d9141346a98844879b5a9e787489c891ddc8fb45cc903b7244cab64fb1';
|
||||||
|
const db = 'tryton';
|
||||||
|
const applicationName = 'sale_don_confiao';
|
||||||
|
apiImplementation = new TrytonApiClient(
|
||||||
|
url, key, db, applicationName);
|
||||||
|
} else {
|
||||||
|
throw new Error("API implementation don't configured");
|
||||||
|
}
|
||||||
|
this.api = new Api(apiImplementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
getApi() {
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApiImplementation;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
class Api {
|
||||||
|
constructor (apiImplementation) {
|
||||||
|
this.apiImplementation = apiImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomers() {
|
||||||
|
return this.apiImplementation.getCustomers();
|
||||||
|
}
|
||||||
|
|
||||||
|
getProducts() {
|
||||||
|
return this.apiImplementation.getProducts();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getPaymentMethods() {
|
||||||
|
return this.apiImplementation.getPaymentMethods();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSummaryPurchase(purchaseId) {
|
||||||
|
return this.apiImplementation.getSummaryPurchase(purchaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchasesForReconciliation() {
|
||||||
|
return this.apiImplementation.getPurchasesForReconciliation();
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidAdminCode(code) {
|
||||||
|
return this.apiImplementation.isValidAdminCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPurchase(purchase) {
|
||||||
|
return this.apiImplementation.createPurchase(purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
createReconciliationJar(reconciliation) {
|
||||||
|
return this.apiImplementation.createReconciliationJar(reconciliation);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomer(customer) {
|
||||||
|
return this.apiImplementation.createCustomer(customer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Api;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
class DjangoApi {
|
||||||
|
getCustomers() {
|
||||||
|
const url = '/don_confiao/api/customers/';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProducts() {
|
||||||
|
const url = '/don_confiao/api/products/';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPaymentMethods() {
|
||||||
|
const url = '/don_confiao/payment_methods/all/select_format';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSummaryPurchase(purchaseId) {
|
||||||
|
const url = `/don_confiao/resumen_compra_json/${purchaseId}`;
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchasesForReconciliation() {
|
||||||
|
const url = '/don_confiao/purchases/for_reconciliation';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidAdminCode(code) {
|
||||||
|
const url = `/don_confiao/api/admin_code/validate/${code}`
|
||||||
|
return this.getRequest(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPurchase(purchase) {
|
||||||
|
const url = '/don_confiao/api/sales/';
|
||||||
|
return this.postRequest(url, purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
createReconciliationJar(reconciliation) {
|
||||||
|
const url = '/don_confiao/reconciliate_jar';
|
||||||
|
return this.postRequest(url, reconciliation);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomer(customer) {
|
||||||
|
const url = '/don_confiao/api/customers/';
|
||||||
|
return this.postRequest(url, customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest(url) {
|
||||||
|
return new Promise ((resolve, reject) => {
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
postRequest(url, content) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(content)
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
reject(new Error(`Error ${response.status}: ${response.statusText}`));
|
||||||
|
} else {
|
||||||
|
response.json().then(data => {
|
||||||
|
if (!data) {
|
||||||
|
reject(new Error('La respuesta no es un JSON válido'));
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DjangoApi;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
class TrytonApiClient {
|
||||||
|
constructor(url, key, db, applicationName) {
|
||||||
|
this.baseUrl = `${url}/${db}/${applicationName}`;
|
||||||
|
this.headers = {
|
||||||
|
'Authorization': `Bearer ${key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
mode: 'cors'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomers() {
|
||||||
|
const url = this.baseUrl + '/parties';
|
||||||
|
const customers = this.getRequest(url);
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProducts() {
|
||||||
|
const url = this.baseUrl + '/products'
|
||||||
|
const products = this.getRequest(url);
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPaymentMethods() {
|
||||||
|
const url = '/don_confiao/payment_methods/all/select_format';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSummaryPurchase(purchaseId) {
|
||||||
|
const url = `/don_confiao/resumen_compra_json/${purchaseId}`;
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchasesForReconciliation() {
|
||||||
|
const url = '/don_confiao/purchases/for_reconciliation';
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidAdminCode(code) {
|
||||||
|
const url = `/don_confiao/api/admin_code/validate/${code}`
|
||||||
|
return this.getRequest(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPurchase(purchase) {
|
||||||
|
const url = '/don_confiao/api/sales/';
|
||||||
|
return this.postRequest(url, purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
createReconciliationJar(reconciliation) {
|
||||||
|
const url = '/don_confiao/reconciliate_jar';
|
||||||
|
return this.postRequest(url, reconciliation);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomer(customer) {
|
||||||
|
const url = '/don_confiao/api/customers/';
|
||||||
|
return this.postRequest(url, customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest(url) {
|
||||||
|
return new Promise ((resolve, reject) => {
|
||||||
|
fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
postRequest(url, content) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify(content)
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
reject(new Error(`Error ${response.status}: ${response.statusText}`));
|
||||||
|
} else {
|
||||||
|
response.json().then(data => {
|
||||||
|
if (!data) {
|
||||||
|
reject(new Error('La respuesta no es un JSON válido'));
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TrytonApiClient;
|
||||||
@@ -11,6 +11,8 @@ import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -63,6 +65,19 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
cors: {
|
||||||
|
origin: '*',
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
'/sale_don_confiao': {
|
||||||
|
target: "http://127.0.0.1:8000/tryton/sale_don_confiao", // Cambia esto a la URL de tu API
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/sale_don_confiao/, '/tryton/sale_don_confiao'), // Opcional: reescribe la ruta
|
||||||
|
ws: true
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: '../../static/frontend/',
|
outDir: '../../static/frontend/',
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-11-18 03:16
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0033_sale_payment_method'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sale',
|
||||||
|
name='reconciliation',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='Sales', to='don_confiao.reconciliationjar'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='payment',
|
||||||
|
name='type_payment',
|
||||||
|
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sale',
|
||||||
|
name='payment_method',
|
||||||
|
field=models.CharField(choices=[('CASH', 'Efectivo'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-12-03 02:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0034_sale_reconciliation_alter_payment_type_payment_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reconciliationjar',
|
||||||
|
name='total_cash_purchases',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-12-28 22:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0034_alter_payment_type_payment_alter_sale_date_and_more'),
|
||||||
|
('don_confiao', '0035_reconciliationjar_total_cash_purchases'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
20
tienda_ilusion/don_confiao/migrations/0037_admincode.py
Normal file
20
tienda_ilusion/don_confiao/migrations/0037_admincode.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2025-01-11 23:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0036_merge_20241228_2212'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdminCode',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('value', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -62,6 +62,31 @@ class Product(models.Model):
|
|||||||
return products_list
|
return products_list
|
||||||
|
|
||||||
|
|
||||||
|
class ReconciliationJar(models.Model):
|
||||||
|
is_valid = models.BooleanField(default=False)
|
||||||
|
date_time = models.DateTimeField()
|
||||||
|
description = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
reconcilier = models.CharField(max_length=255, null=False, blank=False)
|
||||||
|
cash_taken = models.DecimalField(max_digits=9, decimal_places=2)
|
||||||
|
cash_discrepancy = models.DecimalField(max_digits=9, decimal_places=2)
|
||||||
|
total_cash_purchases = models.DecimalField(max_digits=9, decimal_places=2)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self._validate_taken_ammount()
|
||||||
|
|
||||||
|
def add_payments(self, payments):
|
||||||
|
for payment in payments:
|
||||||
|
self.payment_set.add(payment)
|
||||||
|
self.is_valid = True
|
||||||
|
|
||||||
|
def _validate_taken_ammount(self):
|
||||||
|
ammount_cash = self.cash_taken + self.cash_discrepancy
|
||||||
|
if not self.total_cash_purchases == ammount_cash:
|
||||||
|
raise ValidationError(
|
||||||
|
{"cash_taken": _("The taken ammount has discrepancy.")}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Sale(models.Model):
|
class Sale(models.Model):
|
||||||
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
|
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
|
||||||
date = models.DateTimeField("Date")
|
date = models.DateTimeField("Date")
|
||||||
@@ -74,6 +99,12 @@ class Sale(models.Model):
|
|||||||
blank=False,
|
blank=False,
|
||||||
null=False
|
null=False
|
||||||
)
|
)
|
||||||
|
reconciliation = models.ForeignKey(
|
||||||
|
ReconciliationJar,
|
||||||
|
on_delete=models.RESTRICT,
|
||||||
|
related_name='Sales',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.date} {self.customer}"
|
return f"{self.date} {self.customer}"
|
||||||
@@ -122,38 +153,6 @@ class ReconciliationJarSummary():
|
|||||||
return self._payments
|
return self._payments
|
||||||
|
|
||||||
|
|
||||||
class ReconciliationJar(models.Model):
|
|
||||||
is_valid = models.BooleanField(default=False)
|
|
||||||
date_time = models.DateTimeField()
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
|
||||||
reconcilier = models.CharField(max_length=255, null=False, blank=False)
|
|
||||||
cash_taken = models.DecimalField(max_digits=9, decimal_places=2)
|
|
||||||
cash_discrepancy = models.DecimalField(max_digits=9, decimal_places=2)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if not self.is_valid:
|
|
||||||
payments = Payment.get_reconciliation_jar_summary().payments
|
|
||||||
else:
|
|
||||||
payments = self.payment_set.all()
|
|
||||||
|
|
||||||
payments_amount = Decimal(sum([p.amount for p in payments]))
|
|
||||||
reconciliation_ammount = Decimal(sum([
|
|
||||||
self.cash_taken,
|
|
||||||
self.cash_discrepancy,
|
|
||||||
]))
|
|
||||||
|
|
||||||
equal_ammounts = reconciliation_ammount.compare(payments_amount) == Decimal('0')
|
|
||||||
if not equal_ammounts:
|
|
||||||
raise ValidationError(
|
|
||||||
{"cash_taken": _("The taken ammount has discrepancy.")}
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_payments(self, payments):
|
|
||||||
for payment in payments:
|
|
||||||
self.payment_set.add(payment)
|
|
||||||
self.is_valid = True
|
|
||||||
|
|
||||||
|
|
||||||
class Payment(models.Model):
|
class Payment(models.Model):
|
||||||
date_time = models.DateTimeField()
|
date_time = models.DateTimeField()
|
||||||
type_payment = models.CharField(
|
type_payment = models.CharField(
|
||||||
@@ -199,3 +198,7 @@ class Payment(models.Model):
|
|||||||
class PaymentSale(models.Model):
|
class PaymentSale(models.Model):
|
||||||
payment = models.ForeignKey(Payment, on_delete=models.CASCADE)
|
payment = models.ForeignKey(Payment, on_delete=models.CASCADE)
|
||||||
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminCode(models.Model):
|
||||||
|
value = models.CharField(max_length=255, null=False, blank=False)
|
||||||
|
|||||||
@@ -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,76 @@ 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',
|
||||||
|
]
|
||||||
|
|
||||||
|
class PaymentMethodSerializer(serializers.Serializer):
|
||||||
|
text = serializers.CharField()
|
||||||
|
value = serializers.CharField()
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
return {
|
||||||
|
'text': instance[1],
|
||||||
|
'value': instance[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaleForRenconciliationSerializer(serializers.Serializer):
|
||||||
|
id = serializers.IntegerField()
|
||||||
|
date = serializers.DateTimeField()
|
||||||
|
payment_method = serializers.CharField()
|
||||||
|
customer = serializers.SerializerMethodField()
|
||||||
|
total = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_customer(self, sale):
|
||||||
|
return {
|
||||||
|
'id': sale.customer.id,
|
||||||
|
'name': sale.customer.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_total(self, sale):
|
||||||
|
return sale.get_total()
|
||||||
|
|
||||||
|
|
||||||
|
class ListCustomerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Customer
|
||||||
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class ListProductSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class SummarySaleLineSerializer(serializers.ModelSerializer):
|
||||||
|
product = ListProductSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SaleLine
|
||||||
|
fields = ['product', 'quantity', 'unit_price', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
class SaleSummarySerializer(serializers.ModelSerializer):
|
||||||
|
customer = ListCustomerSerializer()
|
||||||
|
lines = SummarySaleLineSerializer(many=True, source='saleline_set')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Sale
|
||||||
|
fields = ['id', 'date', 'customer', 'payment_method', 'lines']
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<li><a href='/don_confiao/lista_productos'>Productos</a></li>
|
<li><a href='/don_confiao/lista_productos'>Productos</a></li>
|
||||||
<li><a href='/don_confiao/importar_productos'>Importar Productos</a></li>
|
<li><a href='/don_confiao/importar_productos'>Importar Productos</a></li>
|
||||||
<li><a href='/don_confiao/importar_terceros'>Importar Terceros</a></li>
|
<li><a href='/don_confiao/importar_terceros'>Importar Terceros</a></li>
|
||||||
<li><a href='/don_confiao/cuadrar_tarro'>Cuadrar tarro</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<p id="page_title" class="text-center decoration-solid font-mono font-bold text-lg page_title">Don Confiao - Tienda la Ilusión</p>
|
<p id="page_title" class="text-center decoration-solid font-mono font-bold text-lg page_title">Don Confiao - Tienda la Ilusión</p>
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
{% extends 'don_confiao/base.html' %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
{% if summary.total %}
|
|
||||||
<div class="reconciliate_jar summary" style="border: solid 1px brown; margin: 10px">
|
|
||||||
<h2>Pagos No reconciliados</h2>
|
|
||||||
<table style="border: solid 1px blue; margin: 10px">
|
|
||||||
<thead>
|
|
||||||
<tr><th>Fecha</th><th>Monto</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for payment in summary.payments %}
|
|
||||||
<tr><td>{{ payment.date_time }}</td><td>{{ payment.amount }}</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr><th>Total</th><td>{{ summary.total }}</td></tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<form method="POST">
|
|
||||||
<table style="border: solid 1px blue; margin: 10px">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_table }}
|
|
||||||
</table>
|
|
||||||
<br/><button name="form" type="submit" >Recoger dinero</button>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<div class="reconciliate_jar information noform">
|
|
||||||
<h2>No hay pagos registrados.</h2>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
41
tienda_ilusion/don_confiao/tests/test_admin_code.py
Normal file
41
tienda_ilusion/don_confiao/tests/test_admin_code.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from django.test import TestCase, Client
|
||||||
|
|
||||||
|
from ..models import AdminCode
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdminCode(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.valid_code = 'some valid code'
|
||||||
|
admin_code = AdminCode()
|
||||||
|
admin_code.value = self.valid_code
|
||||||
|
admin_code.clean()
|
||||||
|
admin_code.save()
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_validate_code(self):
|
||||||
|
url = '/don_confiao/api/admin_code/validate/' + self.valid_code
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = json.loads(response.content.decode('utf-8'))
|
||||||
|
self.assertTrue(content['validCode'])
|
||||||
|
|
||||||
|
def test_invalid_code(self):
|
||||||
|
invalid_code = 'some invalid code'
|
||||||
|
url = '/don_confiao/api/admin_code/validate/' + invalid_code
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = json.loads(response.content.decode('utf-8'))
|
||||||
|
self.assertFalse(content['validCode'])
|
||||||
|
|
||||||
|
def test_empty_code(self):
|
||||||
|
empty_code = ''
|
||||||
|
url = '/don_confiao/api/admin_code/validate/' + empty_code
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from ..models import Payment, ReconciliationJar
|
|
||||||
|
|
||||||
|
|
||||||
class TestBilling(TestCase):
|
|
||||||
|
|
||||||
def test_reconciliation_jar_summary(self):
|
|
||||||
cash_payment1, cash_payment2 = self._create_two_cash_payments()
|
|
||||||
jar_summary = Payment.get_reconciliation_jar_summary()
|
|
||||||
self.assertEqual(164000, jar_summary.total)
|
|
||||||
self.assertSetEqual(
|
|
||||||
{cash_payment1, cash_payment2},
|
|
||||||
set(jar_summary.payments)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_reconciliation_jar_summary_use_only_cash(self):
|
|
||||||
cash_payment1, cash_payment2 = self._create_two_cash_payments()
|
|
||||||
|
|
||||||
confiar_payment = Payment()
|
|
||||||
confiar_payment.date_time = '2024-07-07 16:00:00'
|
|
||||||
confiar_payment.type_payment = 'CONFIAR'
|
|
||||||
confiar_payment.amount = 85000
|
|
||||||
confiar_payment.save()
|
|
||||||
|
|
||||||
bancolombia_payment = Payment()
|
|
||||||
bancolombia_payment.date_time = '2024-07-07 12:30:00'
|
|
||||||
bancolombia_payment.type_payment = 'BANCOLOMBIA'
|
|
||||||
bancolombia_payment.amount = 12000
|
|
||||||
bancolombia_payment.save()
|
|
||||||
|
|
||||||
jar_summary = Payment.get_reconciliation_jar_summary()
|
|
||||||
self.assertEqual(164000, jar_summary.total)
|
|
||||||
self.assertSetEqual(
|
|
||||||
{cash_payment1, cash_payment2},
|
|
||||||
set(jar_summary.payments)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_fail_validate_reconciliation_jar_with_discrepancy_values(self):
|
|
||||||
cash_payment1, cash_payment2 = self._create_two_cash_payments()
|
|
||||||
|
|
||||||
jar_summary = Payment.get_reconciliation_jar_summary()
|
|
||||||
|
|
||||||
reconciliation_jar = ReconciliationJar()
|
|
||||||
reconciliation_jar.date_time = '2024-07-13 13:02:00'
|
|
||||||
reconciliation_jar.description = "test reconcialiation jar"
|
|
||||||
reconciliation_jar.reconcilier = 'Jorge'
|
|
||||||
reconciliation_jar.cash_float = 0
|
|
||||||
reconciliation_jar.cash_taken = 0
|
|
||||||
reconciliation_jar.cash_discrepancy = 0
|
|
||||||
reconciliation_jar.save()
|
|
||||||
|
|
||||||
reconciliation_jar.add_payments(jar_summary.payments)
|
|
||||||
with self.assertRaises(ValidationError):
|
|
||||||
reconciliation_jar.clean()
|
|
||||||
|
|
||||||
def test_validate_reconciliation_jar_with_cash_float(self):
|
|
||||||
cash_payment1, cash_payment2 = self._create_two_cash_payments()
|
|
||||||
jar_summary = Payment.get_reconciliation_jar_summary()
|
|
||||||
|
|
||||||
reconciliation_jar = ReconciliationJar()
|
|
||||||
reconciliation_jar.date_time = '2024-07-13 13:02:00'
|
|
||||||
reconciliation_jar.description = "test reconcialiation jar"
|
|
||||||
reconciliation_jar.reconcilier = 'Jorge'
|
|
||||||
reconciliation_jar.cash_taken = jar_summary.total
|
|
||||||
reconciliation_jar.cash_discrepancy = 0
|
|
||||||
reconciliation_jar.save()
|
|
||||||
|
|
||||||
reconciliation_jar.add_payments(jar_summary.payments)
|
|
||||||
reconciliation_jar.clean()
|
|
||||||
reconciliation_jar.save()
|
|
||||||
self.assertTrue(reconciliation_jar.is_valid)
|
|
||||||
|
|
||||||
def _create_two_cash_payments(self):
|
|
||||||
cash_payment1 = Payment()
|
|
||||||
cash_payment1.date_time = '2024-07-07 12:00:00'
|
|
||||||
cash_payment1.type_payment = 'CASH'
|
|
||||||
cash_payment1.amount = 132000
|
|
||||||
cash_payment1.description = 'Saldo en compra'
|
|
||||||
cash_payment1.save()
|
|
||||||
|
|
||||||
cash_payment2 = Payment()
|
|
||||||
cash_payment2.date_time = '2024-07-07 13:05:00'
|
|
||||||
cash_payment2.type_payment = 'CASH'
|
|
||||||
cash_payment2.amount = 32000
|
|
||||||
cash_payment2.save()
|
|
||||||
|
|
||||||
return [cash_payment1, cash_payment2]
|
|
||||||
208
tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py
Normal file
208
tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from ..models import Sale, Product, SaleLine, Customer, ReconciliationJar
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class TestJarReconcliation(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
customer = Customer()
|
||||||
|
customer.name = 'Alejo Mono'
|
||||||
|
customer.save()
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
purchase = Sale()
|
||||||
|
purchase.customer = customer
|
||||||
|
purchase.date = "2024-07-30"
|
||||||
|
purchase.payment_method = 'CASH'
|
||||||
|
purchase.clean()
|
||||||
|
purchase.save()
|
||||||
|
|
||||||
|
product = Product()
|
||||||
|
product.name = "cafe"
|
||||||
|
product.price = "72500"
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
line = SaleLine()
|
||||||
|
line.sale = purchase
|
||||||
|
line.product = product
|
||||||
|
line.quantity = "11"
|
||||||
|
line.unit_price = "72500"
|
||||||
|
line.save()
|
||||||
|
self.purchase = purchase
|
||||||
|
|
||||||
|
purchase2 = Sale()
|
||||||
|
purchase2.customer = customer
|
||||||
|
purchase2.date = "2024-07-30"
|
||||||
|
purchase.payment_method = 'CASH'
|
||||||
|
purchase2.clean()
|
||||||
|
purchase2.save()
|
||||||
|
|
||||||
|
line2 = SaleLine()
|
||||||
|
line2.sale = purchase2
|
||||||
|
line2.product = product
|
||||||
|
line2.quantity = "27"
|
||||||
|
line2.unit_price = "72500"
|
||||||
|
line2.save()
|
||||||
|
self.purchase2 = purchase2
|
||||||
|
|
||||||
|
purchase3 = Sale()
|
||||||
|
purchase3.customer = customer
|
||||||
|
purchase3.date = "2024-07-30"
|
||||||
|
purchase3.payment_method = 'CASH'
|
||||||
|
purchase3.clean()
|
||||||
|
purchase3.save()
|
||||||
|
|
||||||
|
line3 = SaleLine()
|
||||||
|
line3.sale = purchase3
|
||||||
|
line3.product = product
|
||||||
|
line3.quantity = "37"
|
||||||
|
line3.unit_price = "72500"
|
||||||
|
line3.save()
|
||||||
|
self.purchase3 = purchase3
|
||||||
|
|
||||||
|
purchase4 = Sale()
|
||||||
|
purchase4.customer = customer
|
||||||
|
purchase4.date = "2024-07-30"
|
||||||
|
purchase4.payment_method = 'CONFIAR'
|
||||||
|
purchase4.clean()
|
||||||
|
purchase4.save()
|
||||||
|
|
||||||
|
line4 = SaleLine()
|
||||||
|
line4.sale = purchase4
|
||||||
|
line4.product = product
|
||||||
|
line4.quantity = "47"
|
||||||
|
line4.unit_price = "72500"
|
||||||
|
line4.save()
|
||||||
|
self.purchase4 = purchase4
|
||||||
|
|
||||||
|
def test_create_reconciliation_jar(self):
|
||||||
|
reconciliation = self._create_simple_reconciliation()
|
||||||
|
self.assertTrue(isinstance(reconciliation, ReconciliationJar))
|
||||||
|
|
||||||
|
def test_get_purchases_for_reconciliation(self):
|
||||||
|
# link purchase to reconciliation to exclude from list
|
||||||
|
reconciliation = self._create_simple_reconciliation()
|
||||||
|
self.purchase3.reconciliation = reconciliation
|
||||||
|
self.purchase3.clean()
|
||||||
|
self.purchase3.save()
|
||||||
|
|
||||||
|
url = '/don_confiao/purchases/for_reconciliation'
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
rawContent = response.content.decode('utf-8')
|
||||||
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
|
self.assertIn('CASH', content.keys())
|
||||||
|
self.assertIn('CONFIAR', content.keys())
|
||||||
|
self.assertEqual(2, len(content.get('CASH')))
|
||||||
|
self.assertEqual(1, len(content.get('CONFIAR')))
|
||||||
|
self.assertNotIn(str(37*72500), rawContent)
|
||||||
|
self.assertIn(str(47*72500), rawContent)
|
||||||
|
|
||||||
|
def test_don_create_reconcialiation_with_bad_numbers(self):
|
||||||
|
reconciliation = ReconciliationJar()
|
||||||
|
reconciliation.date_time = "2024-07-30"
|
||||||
|
reconciliation.total_cash_purchases = 145000
|
||||||
|
reconciliation.cash_taken = 143000
|
||||||
|
reconciliation.cash_discrepancy = 1000
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
reconciliation.clean()
|
||||||
|
reconciliation.save()
|
||||||
|
|
||||||
|
def test_fail_create_reconciliation_with_wrong_total_purchases_purchases(self):
|
||||||
|
url = '/don_confiao/reconciliate_jar'
|
||||||
|
total_purchases = (11 * 72500) + (27 * 72500)
|
||||||
|
bad_total_purchases = total_purchases + 2
|
||||||
|
data = {
|
||||||
|
'date_time': '2024-12-02T21:07',
|
||||||
|
'reconcilier': 'carlos',
|
||||||
|
'total_cash_purchases': bad_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')
|
||||||
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertIn('error', content)
|
||||||
|
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')
|
||||||
|
|
||||||
|
rawContent = response.content.decode('utf-8')
|
||||||
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn('id', content)
|
||||||
|
|
||||||
|
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
||||||
|
self.assertEqual(len(purchases), 2)
|
||||||
|
|
||||||
|
def test_create_reconciliation_with_purchases_and_other_totals(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,
|
||||||
|
],
|
||||||
|
'other_totals': {
|
||||||
|
'Confiar': {
|
||||||
|
'total': (47 * 72500) + 1,
|
||||||
|
'purchases': [self.purchase4.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
response = self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
rawContent = response.content.decode('utf-8')
|
||||||
|
content = json.loads(rawContent)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn('id', content)
|
||||||
|
|
||||||
|
purchases = Sale.objects.filter(reconciliation_id=content['id'])
|
||||||
|
self.assertEqual(len(purchases), 3)
|
||||||
|
|
||||||
|
def _create_simple_reconciliation(self):
|
||||||
|
reconciliation = ReconciliationJar()
|
||||||
|
reconciliation.date_time = "2024-07-30"
|
||||||
|
reconciliation.total_cash_purchases = 0
|
||||||
|
reconciliation.cash_taken = 0
|
||||||
|
reconciliation.cash_discrepancy = 0
|
||||||
|
reconciliation.clean()
|
||||||
|
reconciliation.save()
|
||||||
|
return reconciliation
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from django.test import Client, TestCase
|
|
||||||
from django.contrib.auth.models import AnonymousUser, User
|
|
||||||
|
|
||||||
from ..models import Payment
|
|
||||||
|
|
||||||
|
|
||||||
class TestReconciliationJarClient(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.client = Client()
|
|
||||||
|
|
||||||
def test_get_summary_info_on_view(self):
|
|
||||||
self._generate_two_cash_payments()
|
|
||||||
response = self.client.get("/don_confiao/cuadrar_tarro")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.context["summary"].total, 160000)
|
|
||||||
self.assertIn('160000', response.content.decode('utf-8'))
|
|
||||||
|
|
||||||
def test_create_reconciliation_jar(self):
|
|
||||||
self._generate_two_cash_payments()
|
|
||||||
response = self.client.post(
|
|
||||||
"/don_confiao/cuadrar_tarro",
|
|
||||||
{
|
|
||||||
"date_time": "2024-07-20T00:00",
|
|
||||||
"description": "Cuadre de prueba",
|
|
||||||
"reconcilier": "Jorge",
|
|
||||||
"cash_taken": "100000",
|
|
||||||
"cash_discrepancy": "60000",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.assertRedirects(response, '/don_confiao/cuadres')
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_two_cash_payments(self):
|
|
||||||
cash_payment1 = Payment()
|
|
||||||
cash_payment1.date_time = '2024-07-07 12:00:00'
|
|
||||||
cash_payment1.type_payment = 'CASH'
|
|
||||||
cash_payment1.amount = 130000
|
|
||||||
cash_payment1.description = 'Saldo en compra'
|
|
||||||
cash_payment1.save()
|
|
||||||
|
|
||||||
cash_payment2 = Payment()
|
|
||||||
cash_payment2.date_time = '2024-07-07 13:05:00'
|
|
||||||
cash_payment2.type_payment = 'CASH'
|
|
||||||
cash_payment2.amount = 30000
|
|
||||||
cash_payment2.save()
|
|
||||||
@@ -41,11 +41,13 @@ class TestSummaryViewPurchase(TestCase):
|
|||||||
def test_json_summary(self):
|
def test_json_summary(self):
|
||||||
url = f"/don_confiao/resumen_compra_json/{self.purchase.id}"
|
url = f"/don_confiao/resumen_compra_json/{self.purchase.id}"
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
|
content = response.content.decode('utf-8')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('Alejo Mono', response.content.decode('utf-8'))
|
self.assertIn('Alejo Mono', content)
|
||||||
self.assertIn('cafe', response.content.decode('utf-8'))
|
self.assertIn('cafe', content)
|
||||||
self.assertIn('72500', response.content.decode('utf-8'))
|
self.assertIn('72500', content)
|
||||||
self.assertIn('quantity', response.content.decode('utf-8'))
|
self.assertIn('quantity', content)
|
||||||
self.assertIn('11', response.content.decode('utf-8'))
|
self.assertIn('11', content)
|
||||||
self.assertIn('date', response.content.decode('utf-8'))
|
self.assertIn('date', content)
|
||||||
self.assertIn(self.purchase.date, response.content.decode('utf-8'))
|
self.assertIn(self.purchase.date, content)
|
||||||
|
self.assertIn('lines', content)
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ urlpatterns = [
|
|||||||
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("cuadrar_tarro", views.reconciliate_jar, name="reconciliate_jar"),
|
|
||||||
path("cuadres", views.reconciliate_jar, name="reconciliations"),
|
|
||||||
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>", views.purchase_json_summary, 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", views.payment_methods_to_select, 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('reconciliate_jar', api_views.ReconciliateJarView.as_view()),
|
||||||
|
path('api/admin_code/validate/<code>', api_views.AdminCodeValidateView.as_view()),
|
||||||
path('api/', include(router.urls)),
|
path('api/', include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ from django.views.generic import ListView
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods)
|
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
|
||||||
from .forms import (
|
from .forms import (
|
||||||
ImportProductsForm,
|
ImportProductsForm,
|
||||||
ImportCustomersForm,
|
ImportCustomersForm,
|
||||||
PurchaseForm,
|
PurchaseForm,
|
||||||
SaleLineFormSet,
|
SaleLineFormSet,
|
||||||
ReconciliationJarForm,
|
|
||||||
PurchaseSummaryForm)
|
PurchaseSummaryForm)
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
@@ -95,6 +94,7 @@ def import_products(request):
|
|||||||
{'form': form}
|
{'form': form}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def import_customers(request):
|
def import_customers(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = ImportCustomersForm(request.POST, request.FILES)
|
form = ImportCustomersForm(request.POST, request.FILES)
|
||||||
@@ -109,24 +109,6 @@ def import_customers(request):
|
|||||||
{'form': form}
|
{'form': form}
|
||||||
)
|
)
|
||||||
|
|
||||||
def reconciliate_jar(request):
|
|
||||||
summary = Payment.get_reconciliation_jar_summary()
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = ReconciliationJarForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
reconciliation = form.save()
|
|
||||||
reconciliation.add_payments(summary.payments)
|
|
||||||
reconciliation.clean()
|
|
||||||
reconciliation.save()
|
|
||||||
return HttpResponseRedirect('cuadres')
|
|
||||||
else:
|
|
||||||
form = ReconciliationJarForm()
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"don_confiao/reconciliate_jar.html",
|
|
||||||
{'summary': summary, 'form': form}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def reconciliations(request):
|
def reconciliations(request):
|
||||||
return HttpResponse('<h1>Reconciliaciones</h1>')
|
return HttpResponse('<h1>Reconciliaciones</h1>')
|
||||||
@@ -143,46 +125,6 @@ def purchase_summary(request, id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def purchase_json_summary(request, id):
|
|
||||||
purchase = Sale.objects.get(pk=id)
|
|
||||||
lines = []
|
|
||||||
for line in purchase.saleline_set.all():
|
|
||||||
lines.append({
|
|
||||||
'product': {
|
|
||||||
'id': line.product.id,
|
|
||||||
'name': line.product.name,
|
|
||||||
},
|
|
||||||
'quantity': line.quantity,
|
|
||||||
'unit_price': line.unit_price,
|
|
||||||
'description': line.description,
|
|
||||||
})
|
|
||||||
to_response = {
|
|
||||||
'id': purchase.id,
|
|
||||||
'date': purchase.date,
|
|
||||||
'customer': {
|
|
||||||
'id': purchase.customer.id,
|
|
||||||
'name': purchase.customer.name,
|
|
||||||
# 'phone': _mask_phone(purchase.customer.phone)
|
|
||||||
},
|
|
||||||
'payment_method': purchase.payment_method,
|
|
||||||
'set_lines': lines,
|
|
||||||
}
|
|
||||||
return JsonResponse(to_response, safe=False)
|
|
||||||
|
|
||||||
|
|
||||||
def payment_methods_to_select(request):
|
|
||||||
methods = [
|
|
||||||
{'text': choice[1], 'value': choice[0]}
|
|
||||||
for choice in PaymentMethods.choices
|
|
||||||
]
|
|
||||||
return JsonResponse(methods, safe=False)
|
|
||||||
|
|
||||||
|
|
||||||
def _mask_phone(phone):
|
|
||||||
digits = str(phone)[-3:] if phone else " " * 3
|
|
||||||
return "X" * 7 + digits
|
|
||||||
|
|
||||||
|
|
||||||
def _categories_from_csv_string(categories_string, separator="&"):
|
def _categories_from_csv_string(categories_string, separator="&"):
|
||||||
categories = categories_string.split(separator)
|
categories = categories_string.split(separator)
|
||||||
clean_categories = [c.strip() for c in categories]
|
clean_categories = [c.strip() for c in categories]
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ DEBUG = True
|
|||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:8000",
|
||||||
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
|||||||
Reference in New Issue
Block a user