Compare commits
101 Commits
95fab71898
...
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 | |||
|
|
543e927fb0 | ||
| fe1e6e8336 | |||
|
|
00c6bac1d2 | ||
| 4ed6bb9024 | |||
|
|
b4467e0292 | ||
| 7c2a7fb1a1 | |||
|
|
8f48accb4f | ||
| d0edba9c28 | |||
| 6aca2007e0 | |||
| b2756ac7ce | |||
| 159bd737c4 | |||
| 201333ab4b | |||
| a5ac06704b | |||
| f1b8cbdce1 | |||
| 2d86aba3e5 | |||
| edea82e77b | |||
| d047edaa3f | |||
| 5c24266e7b | |||
| 9d602c8ddc | |||
| 99f2f77b78 | |||
| 4f0f899c70 | |||
| c85f0554fb | |||
| acd9bf53c6 | |||
| 8f62dfb9ec | |||
| ea77124ee4 | |||
| 66495e25ff | |||
| b7c4cf5d44 | |||
| d5b7c99b79 | |||
| 6bef38e457 | |||
| b80e415393 | |||
| 8ba20acaea | |||
| 65e99b7ce2 | |||
| f6146c177b | |||
| 7c36524763 | |||
| ac5c962ca7 | |||
| 4472d8b6b8 | |||
| 8b15d9dd9d | |||
|
|
02a010b50d | ||
| 50534ef5b1 | |||
| 8c00b89fb8 | |||
| b134b88791 | |||
| 1519b3c8bb | |||
|
|
589b7c0bfb | ||
| 2ed8b5b3a5 | |||
|
|
ffa1622870 | ||
| a90fb4d937 | |||
| 2a908d4e05 | |||
| 4cbeaa2560 | |||
| eeb9821675 | |||
| 2ea8cc7fd8 | |||
| 49ac668c14 |
34
Rakefile
34
Rakefile
@@ -10,7 +10,7 @@ namespace :live do
|
||||
compose('up', '--build', '-d', compose: DOCKER_COMPOSE)
|
||||
end
|
||||
|
||||
desc 'monitorear salida'
|
||||
desc 'monitorear salida'
|
||||
task :tail do
|
||||
compose('logs', '-f', 'django', compose: DOCKER_COMPOSE)
|
||||
end
|
||||
@@ -20,7 +20,12 @@ namespace :live do
|
||||
compose('logs', '-f', '-n 50', 'django', compose: DOCKER_COMPOSE)
|
||||
end
|
||||
|
||||
desc 'detener entorno'
|
||||
desc 'iniciar entorno'
|
||||
task :start do
|
||||
compose('start', compose: DOCKER_COMPOSE)
|
||||
end
|
||||
|
||||
desc 'bajar entorno'
|
||||
task :down do
|
||||
compose('down', compose: DOCKER_COMPOSE)
|
||||
end
|
||||
@@ -52,6 +57,31 @@ namespace :live do
|
||||
|
||||
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)
|
||||
sh "docker compose -f #{compose} #{arg.join(' ')}"
|
||||
end
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
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 .models import Sale, SaleLine, Customer, Product
|
||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer
|
||||
from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode
|
||||
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
|
||||
|
||||
from decimal import Decimal
|
||||
import json
|
||||
|
||||
class SaleView(viewsets.ModelViewSet):
|
||||
queryset = Sale.objects.all()
|
||||
@@ -14,7 +18,12 @@ class SaleView(viewsets.ModelViewSet):
|
||||
customer = Customer.objects.get(pk=data['customer'])
|
||||
date = data['date']
|
||||
lines = data['saleline_set']
|
||||
sale = Sale.objects.create(customer=customer, date=date)
|
||||
payment_method = data['payment_method']
|
||||
sale = Sale.objects.create(
|
||||
customer=customer,
|
||||
date=date,
|
||||
payment_method=payment_method
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
product = Product.objects.get(pk=line['product'])
|
||||
@@ -27,7 +36,10 @@ class SaleView(viewsets.ModelViewSet):
|
||||
unit_price=unit_price
|
||||
)
|
||||
|
||||
return Response({'message': 'Venta creada con exito'}, status=201)
|
||||
return Response(
|
||||
{'id': sale.id, 'message': 'Venta creada con exito'},
|
||||
status=201
|
||||
)
|
||||
|
||||
|
||||
class ProductView(viewsets.ModelViewSet):
|
||||
@@ -38,3 +50,84 @@ class ProductView(viewsets.ModelViewSet):
|
||||
class CustomerView(viewsets.ModelViewSet):
|
||||
queryset = Customer.objects.all()
|
||||
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 .models import Sale, SaleLine, ReconciliationJar, PaymentMethods
|
||||
from .models import Sale, SaleLine, PaymentMethods
|
||||
|
||||
readonly_number_widget = forms.NumberInput(attrs={'readonly': 'readonly'})
|
||||
|
||||
@@ -64,18 +64,3 @@ SaleLineFormSet = inlineformset_factory(
|
||||
extra=1,
|
||||
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",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
@@ -10,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"core-js": "^3.37.1",
|
||||
"cors": "^2.8.5",
|
||||
"roboto-fontface": "*",
|
||||
"vee-validate": "^4.14.6",
|
||||
"vue": "^3.4.31",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title>Calcular Devuelta</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model.number="purchase"
|
||||
label="Total de la compra"
|
||||
type="number"
|
||||
prefix="$"
|
||||
readonly
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model.number="money"
|
||||
label="Dinero"
|
||||
type="number"
|
||||
prefix="$"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model.number="change_cash"
|
||||
label="Devuelta"
|
||||
type="number"
|
||||
prefix="$"
|
||||
readonly
|
||||
></v-text-field>
|
||||
</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 {
|
||||
props: {
|
||||
total_purchase: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
money: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
purchase() {
|
||||
return this.total_purchase
|
||||
},
|
||||
change_cash() {
|
||||
return (this.money || 0) - this.total_purchase
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -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,9 +45,11 @@
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
api: inject('api'),
|
||||
valid: false,
|
||||
customer: {
|
||||
name: '',
|
||||
address: '',
|
||||
email: '',
|
||||
phone: ''
|
||||
},
|
||||
@@ -69,36 +71,26 @@
|
||||
this.resetForm();
|
||||
},
|
||||
async submitForm() {
|
||||
console.log(this.customer)
|
||||
if (this.$refs.form.validate()) {
|
||||
try {
|
||||
console.log(this.customer)
|
||||
const response = await fetch('/don_confiao/api/customers/', {
|
||||
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);
|
||||
this.closeModal();
|
||||
} else {
|
||||
console.error('Error al Crear el Cliente:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error de red:', error);
|
||||
}
|
||||
this.api.createCustomer(this.customer)
|
||||
.then(data => {
|
||||
console.log('Cliente Guardado:', data);
|
||||
this.$emit('customerCreated', data);
|
||||
this.closeModal();
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.customer = {
|
||||
name: '',
|
||||
address: '',
|
||||
email: '',
|
||||
phone: ''
|
||||
};
|
||||
this.$refs.form.reset();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -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,7 +29,7 @@
|
||||
menuItems: [
|
||||
{ title: 'Inicio', route: '/'},
|
||||
{ title: 'Comprar', route:'/comprar'},
|
||||
{ title: 'Resumen de Compra', route:'/resumen_compra'},
|
||||
{ title: 'Cuadrar tarro', route: '/cuadrar_tarro'}
|
||||
],
|
||||
}),
|
||||
watch: {
|
||||
|
||||
@@ -1,94 +1,106 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-form ref="form" v-model="valid">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-container>
|
||||
<v-form ref="purchase" v-model="valid" @change="onFormChange">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-autocomplete
|
||||
v-model="purchase.customer"
|
||||
:items="filteredClients"
|
||||
:search="client_search"
|
||||
no-data-text="No se hallaron clientes"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
label="Cliente"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
class="mr-4"
|
||||
></v-autocomplete>
|
||||
<v-btn color="primary" @click="openModal">Agregar Cliente</v-btn>
|
||||
<CreateCustomerModal ref="customerModal" />
|
||||
</v-col>
|
||||
<v-col
|
||||
lg="2"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="purchase.date"
|
||||
label="Fecha"
|
||||
type="date"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-textarea
|
||||
v-model="purchase.notes"
|
||||
label="Notas"
|
||||
rows="2"
|
||||
></v-textarea>
|
||||
<v-divider></v-divider>
|
||||
<v-container>
|
||||
<v-toolbar>
|
||||
<v-toolbar-title secondary>Productos</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-container v-for="(line, index) in purchase.saleline_set" :key="line.id">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-autocomplete
|
||||
v-model="line.product"
|
||||
:items="filteredProducts"
|
||||
:search="product_search"
|
||||
@update:modelValue="onProductChange(index)"
|
||||
no-data-text="No se hallaron productos"
|
||||
v-model="purchase.customer"
|
||||
:items="filteredClients"
|
||||
:search="client_search"
|
||||
no-data-text="No se hallaron clientes"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
item-subtitle="Price"
|
||||
label="Producto"
|
||||
@update:model-value="onFormChange"
|
||||
label="Cliente"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
class="mr-4"
|
||||
></v-autocomplete>
|
||||
<v-btn color="primary" @click="openModal">Agregar Cliente</v-btn>
|
||||
<CreateCustomerModal ref="customerModal" @customerCreated="handleNewCustomer"/>
|
||||
</v-col>
|
||||
<v-col lg="4">
|
||||
<v-text-field
|
||||
v-model="purchase.date"
|
||||
label="Fecha"
|
||||
type="datetime-local"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
readonly
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-textarea
|
||||
v-model="purchase.notes"
|
||||
label="Notas"
|
||||
rows="2"
|
||||
></v-textarea>
|
||||
<v-divider></v-divider>
|
||||
<v-container>
|
||||
<v-toolbar>
|
||||
<v-toolbar-title secondary>Productos</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-container v-for="(line, index) in purchase.saleline_set" :key="line.id">
|
||||
<v-row>
|
||||
<v-col
|
||||
lg="9">
|
||||
<v-autocomplete
|
||||
v-model="line.product"
|
||||
:items="filteredProducts"
|
||||
:search="product_search"
|
||||
@update:modelValue="onProductChange(index)"
|
||||
no-data-text="No se hallaron productos"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
item-subtitle="Price"
|
||||
label="Producto"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props" :title="item.raw.name" :subtitle="formatPrice(item.raw.price)"></v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props" :title="item.raw.name" :subtitle="formatPrice(item.raw.price)"></v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
<v-col
|
||||
lg="2"
|
||||
>
|
||||
<v-text-field
|
||||
v-model.number="line.unit_price"
|
||||
label="Precio"
|
||||
type="number"
|
||||
:rules="[rules.required]"
|
||||
prefix="$"
|
||||
required
|
||||
readonly
|
||||
v-model.number="line.quantity"
|
||||
label="Cantidad"
|
||||
type="number"
|
||||
:rules="[rules.required,rules.positive]"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model.number="line.quantity"
|
||||
label="Cantidad"
|
||||
type="number"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model.number="line.unit_price"
|
||||
label="Precio"
|
||||
type="number"
|
||||
:rules="[rules.required]"
|
||||
prefix="$"
|
||||
required
|
||||
readonly
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
type="number"
|
||||
:value="calculateSubtotal(line)"
|
||||
label="Subtotal"
|
||||
prefix="$"
|
||||
readonly
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="line.measuring_unit"
|
||||
label="UdM"
|
||||
persistent-placeholder="true"
|
||||
readonly
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
type="number"
|
||||
:value="calculateSubtotal(line)"
|
||||
label="Subtotal"
|
||||
prefix="$"
|
||||
readonly
|
||||
disable
|
||||
persistent-placeholder="true"
|
||||
></v-text-field>
|
||||
@@ -96,8 +108,11 @@
|
||||
<v-col>
|
||||
<v-btn @click="removeLine(index)" color="red">Eliminar</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-row>
|
||||
<v-alert type="warning" :duration="2000" closable v-model="show_alert_lines">
|
||||
No se puede eliminar la única línea.
|
||||
</v-alert>
|
||||
</v-container>
|
||||
<v-btn @click="addLine" color="blue">Agregar</v-btn>
|
||||
</v-container>
|
||||
<v-divider></v-divider>
|
||||
@@ -108,53 +123,86 @@
|
||||
readonly
|
||||
persistent-placeholder="true"
|
||||
></v-text-field>
|
||||
<v-container v-if="calculateTotal > 0">
|
||||
<v-select
|
||||
:items="payment_methods"
|
||||
v-model="purchase.payment_method"
|
||||
item-title="text"
|
||||
item-value="value"
|
||||
label="Pago en"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
></v-select>
|
||||
<v-btn @click="openCasherModal" v-if="purchase.payment_method === 'CASH'">Calcular Devuelta</v-btn>
|
||||
<CasherModal :total_purchase="calculateTotal" ref="casherModal"</CasherModal>
|
||||
</v-container>
|
||||
<v-btn @click="submit" color="green">Comprar</v-btn>
|
||||
</v-form>
|
||||
</v-container>
|
||||
<v-alert type="error" :duration="2000" closable v-model="show_alert_purchase">
|
||||
Verifique los campos obligatorios.
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomerForm from './CreateCustomerModal.vue';
|
||||
import CustomerForm from './CreateCustomerModal.vue';
|
||||
import CasherModal from './CasherModal.vue';
|
||||
import { inject } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'DonConfiao',
|
||||
components: {
|
||||
CustomerForm
|
||||
export default {
|
||||
name: 'DonConfiao',
|
||||
components: {
|
||||
CustomerForm,
|
||||
CasherModal,
|
||||
},
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
api: inject('api'),
|
||||
valid: false,
|
||||
form_changed: false,
|
||||
show_alert_lines: false,
|
||||
show_alert_purchase: false,
|
||||
client_search: '',
|
||||
product_search: '',
|
||||
payment_methods: null,
|
||||
purchase: {
|
||||
date: this.getCurrentDate(),
|
||||
client: null,
|
||||
customer: null,
|
||||
notes: '',
|
||||
saleline_set: [{product:'', unit_price: 0, quantity: 0}],
|
||||
payment_method: null,
|
||||
saleline_set: [{product:'', unit_price: 0, quantity: 0, unit: ''}],
|
||||
},
|
||||
rules: {
|
||||
required: value => !!value || 'Requerido.',
|
||||
required: value => !!value || 'Requerido.',
|
||||
positive: value => value > 0 || 'La cantidad debe ser mayor que 0.',
|
||||
},
|
||||
menuItems: [
|
||||
{ title: 'Inicio', route: '/'},
|
||||
{ title: 'Compras', route:'/compras'},
|
||||
],
|
||||
clients: [],
|
||||
products: [],
|
||||
menuItems: [
|
||||
{ title: 'Inicio', route: '/'},
|
||||
{ title: 'Compras', route:'/compras'},
|
||||
],
|
||||
clients: [],
|
||||
products: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
created() {
|
||||
this.fetchClients();
|
||||
this.fetchProducts();
|
||||
this.fetchPaymentMethods();
|
||||
},
|
||||
watch: {
|
||||
group () {
|
||||
this.drawer = false
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
window.addEventListener('beforeunload', this.confirmLeave);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('beforeunload', this.confirmLeave);
|
||||
},
|
||||
computed: {
|
||||
calculateTotal() {
|
||||
return this.purchase.saleline_set.reduce((total, saleline) => {
|
||||
@@ -184,69 +232,105 @@
|
||||
openModal() {
|
||||
this.$refs.customerModal.openModal();
|
||||
},
|
||||
getCurrentDate() {
|
||||
const today = new Date();
|
||||
const yyyy = today.getFullYear();
|
||||
const mm = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(today.getDate()).padStart(2, '0');
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
onFormChange() {
|
||||
this.form_changed = true;
|
||||
},
|
||||
|
||||
onProductChange(index) {
|
||||
const selectedProductId = this.purchase.saleline_set[index].product;
|
||||
openCasherModal() {
|
||||
this.$refs.casherModal.dialog = true
|
||||
},
|
||||
confirmLeave(event) {
|
||||
if (this.form_changed) {
|
||||
const message = '¿seguro que quieres salir? Perderas la información diligenciada';
|
||||
event.preventDefault();
|
||||
event.returnValue = message;
|
||||
return message;
|
||||
}
|
||||
},
|
||||
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;
|
||||
},
|
||||
onProductChange(index) {
|
||||
const selectedProductId = this.purchase.saleline_set[index].product;
|
||||
const selectedProduct = this.products.find(p => p.id == selectedProductId);
|
||||
this.purchase.saleline_set[index].unit_price = selectedProduct.price;
|
||||
this.purchase.saleline_set[index].unit_price = selectedProduct.price;
|
||||
this.purchase.saleline_set[index].measuring_unit = selectedProduct.measuring_unit;
|
||||
},
|
||||
fetchClients() {
|
||||
fetch('/don_confiao/api/customers/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.clients = data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
this.api.getCustomers()
|
||||
.then(data => {
|
||||
this.clients = data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
handleNewCustomer(newCustomer){
|
||||
this.clients.push(newCustomer);
|
||||
this.purchase.customer = newCustomer.id;
|
||||
},
|
||||
fetchProducts() {
|
||||
fetch('/don_confiao/api/products/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.products = data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
this.api.getProducts()
|
||||
.then(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 => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
fetchPaymentMethods() {
|
||||
this.api.getPaymentMethods()
|
||||
.then(data => {
|
||||
this.payment_methods = data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
addLine() {
|
||||
this.purchase.saleline_set.push({ product: '', unit_price: 0, quantity:0 });
|
||||
this.purchase.saleline_set.push({ product: '', unit_price: 0, quantity:0, measuring_unit: ''});
|
||||
},
|
||||
removeLine(index) {
|
||||
this.purchase.saleline_set.splice(index, 1);
|
||||
if (this.purchase.saleline_set.length > 1) {
|
||||
this.purchase.saleline_set.splice(index, 1);
|
||||
} else {
|
||||
this.show_alert_lines = true;
|
||||
setTimeout(() => {
|
||||
this.show_alert_lines = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
calculateSubtotal(line) {
|
||||
return line.unit_price * line.quantity;
|
||||
},
|
||||
async submit() {
|
||||
if (this.$refs.form.validate()) {
|
||||
try {
|
||||
const response = await fetch('/don_confiao/api/sales/', {
|
||||
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);
|
||||
this.$router.push("SummaryPurchase");
|
||||
} else {
|
||||
console.error('Error al enviar la compra:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error de red:', error);
|
||||
}
|
||||
this.$refs.purchase.validate();
|
||||
if (this.valid) {
|
||||
this.api.createPurchase(this.purchase)
|
||||
.then(data => {
|
||||
console.log('Compra enviada:', data);
|
||||
this.$router.push({
|
||||
path: "/summary_purchase",
|
||||
query : {id: parseInt(data.id)}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error al enviarl la compra:', error));
|
||||
} else {
|
||||
this.show_alert_purchase = true;
|
||||
setTimeout(() => {
|
||||
this.show_alert_purchase = false;
|
||||
}, 4000);
|
||||
}
|
||||
},
|
||||
navigate(route) {
|
||||
@@ -256,6 +340,9 @@
|
||||
return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'COP' }).format(price);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchClients();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<strong class="text-red-darken-4">
|
||||
<slot></slot>
|
||||
</strong>
|
||||
</template>
|
||||
@@ -1,17 +1,106 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-navigation-drawer app>
|
||||
</v-navigation-drawer>
|
||||
<v-app-bar>
|
||||
Resumen de la compra
|
||||
</v-app-bar>
|
||||
<v-container>
|
||||
Pon aqui la información de la compra
|
||||
</v-container>
|
||||
</v-app>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<v-container>
|
||||
<v-container v-show="!id">
|
||||
<v-toolbar>
|
||||
<v-toolbar-title> No se indicó Id de la compra</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
</v-container>
|
||||
<v-container v-show="id">
|
||||
<v-toolbar>
|
||||
<v-toolbar-title> Resumen de la compra {{ id }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-list-item-title>Fecha:</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ purchase.date }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<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>
|
||||
<v-list-item>
|
||||
<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>
|
||||
<v-list-item>
|
||||
<v-list-item-title>Total:</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="purchase.lines">{{ currencyFormat(calculateTotal(purchase.lines)) }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-data-table-virtual
|
||||
:headers="headers"
|
||||
:items="purchase.lines"
|
||||
>
|
||||
<template v-slot:item.unit_price="{ item }">
|
||||
{{ currencyFormat(item.unit_price) }}
|
||||
</template>
|
||||
<template v-slot:item.subtotal="{ item }">
|
||||
{{ currencyFormat(calculateSubtotal(item.unit_price, item.quantity)) }}
|
||||
</template>
|
||||
</v-data-table-virtual>
|
||||
<div class="text-center">
|
||||
<v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn>
|
||||
</div>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'SummaryPurchase',
|
||||
props: {
|
||||
msg: String,
|
||||
id: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
api: inject('api'),
|
||||
purchase: {},
|
||||
headers: [
|
||||
{ title: 'Producto', value: 'product.name' },
|
||||
{ title: 'Precio', value: 'unit_price' },
|
||||
{ title: 'Cantidad', value: 'quantity' },
|
||||
{ title: 'Subtotal', value: 'subtotal' },
|
||||
],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.id) {
|
||||
this.fetchPurchase(this.id);
|
||||
} else {
|
||||
console.error('No se proporcionó un ID de compra.');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchPurchase(purchaseId) {
|
||||
this.api.getSummaryPurchase(purchaseId)
|
||||
.then(data => {
|
||||
this.purchase = data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
currencyFormat(value) {
|
||||
return new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(value);
|
||||
},
|
||||
calculateSubtotal(price, quantity) {
|
||||
price = parseFloat(price || 0);
|
||||
quantity = parseFloat(quantity || 0);
|
||||
return price * quantity;
|
||||
},
|
||||
calculateTotal(lines) {
|
||||
let total = 0;
|
||||
lines.forEach(line => {
|
||||
total += this.calculateSubtotal(line.unit_price, line.quantity);
|
||||
});
|
||||
return total;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
|
||||
|
||||
@@ -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,34 @@
|
||||
<template>
|
||||
<v-container >
|
||||
<v-responsive>
|
||||
<v-toolbar>
|
||||
<v-toolbar-title>Don Confiao te atiende</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card>
|
||||
<v-card-title>Hacer parte de la tienda la ilusión</v-card-title>
|
||||
<v-card-text>
|
||||
Recuerda que participando de esta tienda le apuestas a la economía solidaria, al mercado justo, a la alimentación sana, al campesinado colombiano y a un mundo mejor.
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>En desarrollo</v-card-title>
|
||||
<v-card-text>
|
||||
Don confiao apenas esta entendiendo como funciona esta tienda y por ahora <ResaltedText>solo puede atender las compras de contado</ResaltedText>, ya sea en efectivo o consignación.
|
||||
|
||||
<v-alert type="warning">
|
||||
Si no vas a pagar tu compra recuerda que debes hacerlo en la planilla manual</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card>
|
||||
<v-card-title>A comprar</v-card-title>
|
||||
<v-card-text>
|
||||
El siguiente botón te permitirá registrar tu compra. Cuando finalices te pedimos que ingrese el número de la compra, la fecha y el valor en la planilla física.
|
||||
<div class="text-center">
|
||||
<v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-responsive>
|
||||
</v-container>
|
||||
</template>
|
||||
@@ -9,11 +9,25 @@ import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
import ApiImplementation from './services/api-implementation';
|
||||
|
||||
// Composables
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SummaryPurchase />
|
||||
<Purchase />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
@@ -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>
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<Purchase />
|
||||
<Wellcome />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<SummaryPurchase :id="$route.query.id"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
</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 { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -63,6 +65,19 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
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: {
|
||||
outDir: '../../static/frontend/',
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-11-09 17:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0032_customer_address'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='payment_method',
|
||||
field=models.CharField(choices=[('CASH', 'Cash'), ('CONFIAR', 'Confiar'), ('BANCOLOMBIA', 'Bancolombia')], default='CASH', max_length=30),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.0.6 on 2024-11-16 20:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0033_sale_payment_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
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='date',
|
||||
field=models.DateTimeField(verbose_name='Date'),
|
||||
),
|
||||
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,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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -6,6 +6,12 @@ from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PaymentMethods(models.TextChoices):
|
||||
CASH = 'CASH', _('Efectivo')
|
||||
CONFIAR = 'CONFIAR', _('Confiar')
|
||||
BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia')
|
||||
|
||||
|
||||
class Customer(models.Model):
|
||||
name = models.CharField(max_length=100, default=None, null=False, blank=False)
|
||||
address = models.CharField(max_length=100, null=True, blank=True)
|
||||
@@ -56,11 +62,49 @@ class Product(models.Model):
|
||||
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):
|
||||
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
|
||||
date = models.DateField("Date")
|
||||
date = models.DateTimeField("Date")
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
description = models.CharField(max_length=255, null=True, blank=True)
|
||||
payment_method = models.CharField(
|
||||
max_length=30,
|
||||
choices=PaymentMethods.choices,
|
||||
default=PaymentMethods.CASH,
|
||||
blank=False,
|
||||
null=False
|
||||
)
|
||||
reconciliation = models.ForeignKey(
|
||||
ReconciliationJar,
|
||||
on_delete=models.RESTRICT,
|
||||
related_name='Sales',
|
||||
null=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} {self.customer}"
|
||||
@@ -69,6 +113,10 @@ class Sale(models.Model):
|
||||
lines = self.saleline_set.all()
|
||||
return sum([l.quantity * l.unit_price for l in lines])
|
||||
|
||||
def clean(self):
|
||||
if self.payment_method not in PaymentMethods.values:
|
||||
raise ValidationError({'payment_method': "Invalid payment method"})
|
||||
|
||||
@classmethod
|
||||
def sale_header_csv(cls):
|
||||
sale_header_csv = [field.name for field in cls._meta.fields]
|
||||
@@ -88,12 +136,6 @@ class SaleLine(models.Model):
|
||||
return f"{self.sale} - {self.product}"
|
||||
|
||||
|
||||
class PaymentMethods(models.TextChoices):
|
||||
CASH = 'CASH', _('Cash')
|
||||
CONFIAR = 'CONFIAR', _('Confiar')
|
||||
BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia')
|
||||
|
||||
|
||||
class ReconciliationJarSummary():
|
||||
def __init__(self, payments):
|
||||
self._validate_payments(payments)
|
||||
@@ -111,38 +153,6 @@ class ReconciliationJarSummary():
|
||||
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):
|
||||
date_time = models.DateTimeField()
|
||||
type_payment = models.CharField(
|
||||
@@ -188,3 +198,7 @@ class Payment(models.Model):
|
||||
class PaymentSale(models.Model):
|
||||
payment = models.ForeignKey(Payment, 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 .models import Sale, SaleLine, Product, Customer
|
||||
from .models import Sale, SaleLine, Product, Customer, ReconciliationJar
|
||||
|
||||
|
||||
class SaleLineSerializer(serializers.ModelSerializer):
|
||||
@@ -20,7 +20,76 @@ class ProductSerializer(serializers.ModelSerializer):
|
||||
model = Product
|
||||
fields = ['id', 'name', 'price', 'measuring_unit', 'categories']
|
||||
|
||||
|
||||
class CustomerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ['id', 'name', 'address']
|
||||
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/importar_productos'>Importar Productos</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>
|
||||
</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>
|
||||
|
||||
@@ -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)
|
||||
@@ -17,22 +17,19 @@ class TestAPI(APITestCase):
|
||||
)
|
||||
|
||||
def test_create_sale(self):
|
||||
url = '/don_confiao/api/sales/'
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'date': '2024-09-02',
|
||||
'saleline_set': [
|
||||
{'product': self.product.id, 'quantity': 2, 'unit_price': 3000},
|
||||
{'product': self.product.id, 'quantity': 3, 'unit_price': 5000}
|
||||
],
|
||||
}
|
||||
response = self.client.post(url, data, format='json')
|
||||
response = self._create_sale()
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Sale.objects.count(), 1)
|
||||
sale = Sale.objects.all()[0]
|
||||
self.assertEqual(
|
||||
Sale.objects.all()[0].customer.name,
|
||||
sale.customer.name,
|
||||
self.customer.name
|
||||
)
|
||||
self.assertEqual(
|
||||
sale.id,
|
||||
content['id']
|
||||
)
|
||||
|
||||
def test_get_products(self):
|
||||
url = '/don_confiao/api/products/'
|
||||
@@ -47,3 +44,16 @@ class TestAPI(APITestCase):
|
||||
json_response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(self.customer.name, json_response[0]['name'])
|
||||
|
||||
def _create_sale(self):
|
||||
url = '/don_confiao/api/sales/'
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'date': '2024-09-02',
|
||||
'payment_method': 'CASH',
|
||||
'saleline_set': [
|
||||
{'product': self.product.id, 'quantity': 2, 'unit_price': 3000},
|
||||
{'product': self.product.id, 'quantity': 3, 'unit_price': 5000}
|
||||
],
|
||||
}
|
||||
return self.client.post(url, data, format='json')
|
||||
|
||||
@@ -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
|
||||
23
tienda_ilusion/don_confiao/tests/test_payment_methods.py
Normal file
23
tienda_ilusion/don_confiao/tests/test_payment_methods.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
# from ..models import PaymentMethods
|
||||
|
||||
class TestPaymentMethods(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_keys_in_payment_methods_to_select(self):
|
||||
response = self.client.get(
|
||||
'/don_confiao/payment_methods/all/select_format'
|
||||
)
|
||||
methods = response.json()
|
||||
for method in methods:
|
||||
self.assertEqual(set(method.keys()), {'text', 'value'})
|
||||
|
||||
def test_basic_payment_methods_to_select(self):
|
||||
methods = self.client.get(
|
||||
'/don_confiao/payment_methods/all/select_format'
|
||||
).json()
|
||||
self.assertIn('CASH', [method.get('value') for method in methods])
|
||||
self.assertIn('CONFIAR', [method.get('value') for method in methods])
|
||||
self.assertIn('BANCOLOMBIA', [method.get('value') for method in methods])
|
||||
@@ -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()
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.test import TestCase, Client
|
||||
from ..models import Sale, Product, SaleLine, Customer
|
||||
|
||||
|
||||
class TestSummaryViewPurchase(TestCase):
|
||||
def setUp(self):
|
||||
customer = Customer()
|
||||
@@ -22,13 +23,31 @@ class TestSummaryViewPurchase(TestCase):
|
||||
line = SaleLine()
|
||||
line.sale = purchase
|
||||
line.product = product
|
||||
line.quantity = "2"
|
||||
line.quantity = "11"
|
||||
line.unit_price = "72500"
|
||||
line.save()
|
||||
self.purchase = purchase
|
||||
|
||||
def test_summary_has_customer(self):
|
||||
response = self.client.get("/don_confiao/resumen_compra/" + str(self.purchase.id))
|
||||
url = "/don_confiao/resumen_compra/" + str(self.purchase.id)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context["purchase"].customer, self.purchase.customer)
|
||||
self.assertEqual(
|
||||
response.context["purchase"].customer,
|
||||
self.purchase.customer
|
||||
)
|
||||
self.assertIn('Alejo Mono', response.content.decode('utf-8'))
|
||||
|
||||
def test_json_summary(self):
|
||||
url = f"/don_confiao/resumen_compra_json/{self.purchase.id}"
|
||||
response = self.client.get(url)
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('Alejo Mono', content)
|
||||
self.assertIn('cafe', content)
|
||||
self.assertIn('72500', content)
|
||||
self.assertIn('quantity', content)
|
||||
self.assertIn('11', content)
|
||||
self.assertIn('date', content)
|
||||
self.assertIn(self.purchase.date, content)
|
||||
self.assertIn('lines', content)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ..models import Customer, Product, Sale, SaleLine
|
||||
|
||||
|
||||
@@ -17,13 +19,24 @@ class ConfiaoTest(TestCase):
|
||||
def test_create_sale(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22"
|
||||
sale.date = "2024-06-22 12:05:00"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
sale.save()
|
||||
|
||||
self.assertIsInstance(sale, Sale)
|
||||
|
||||
def test_can_create_sale_without_payment_method(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
sale.date = "2024-06-22 12:05:00"
|
||||
sale.phone = '666666666'
|
||||
sale.description = "Description"
|
||||
sale.payment_method = ''
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
sale.full_clean()
|
||||
|
||||
def test_create_sale_line(self):
|
||||
sale = Sale()
|
||||
sale.customer = self.customer
|
||||
|
||||
@@ -19,6 +19,7 @@ class PurchaseFormTest(TestCase):
|
||||
"csrfmiddlewaretoken": _csrf_token,
|
||||
"customer": self.customer.id,
|
||||
"date": "2024-08-03",
|
||||
"payment_method": "CASH",
|
||||
"phone": "sfasfd",
|
||||
"description": "dasdadad",
|
||||
"saleline_set-TOTAL_FORMS": "1",
|
||||
|
||||
@@ -23,8 +23,11 @@ urlpatterns = [
|
||||
path("exportar_ventas_para_tryton",
|
||||
views.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_json/<int:id>", api_views.SaleSummary.as_view(), name="purchase_json_summary"),
|
||||
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)),
|
||||
]
|
||||
|
||||
@@ -4,13 +4,12 @@ from django.views.generic import ListView
|
||||
from django.db import transaction
|
||||
|
||||
from .models import (
|
||||
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods)
|
||||
Sale, SaleLine, Product, Customer, ProductCategory, Payment, PaymentMethods, ReconciliationJar)
|
||||
from .forms import (
|
||||
ImportProductsForm,
|
||||
ImportCustomersForm,
|
||||
PurchaseForm,
|
||||
SaleLineFormSet,
|
||||
ReconciliationJarForm,
|
||||
PurchaseSummaryForm)
|
||||
|
||||
import csv
|
||||
@@ -95,6 +94,7 @@ def import_products(request):
|
||||
{'form': form}
|
||||
)
|
||||
|
||||
|
||||
def import_customers(request):
|
||||
if request.method == "POST":
|
||||
form = ImportCustomersForm(request.POST, request.FILES)
|
||||
@@ -109,28 +109,11 @@ def import_customers(request):
|
||||
{'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):
|
||||
return HttpResponse('<h1>Reconciliaciones</h1>')
|
||||
|
||||
|
||||
def purchase_summary(request, id):
|
||||
purchase = Sale.objects.get(pk=id)
|
||||
return render(
|
||||
|
||||
@@ -28,6 +28,10 @@ DEBUG = True
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:8000",
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
||||
Reference in New Issue
Block a user