Compare commits
208 Commits
8d29202d3a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 43756952fb | |||
| 6a346bdf8a | |||
| 2a983c8056 | |||
| 2c6449c727 | |||
| 77a0c4ac0d | |||
| 2fcf884cce | |||
| 75d39c6ca7 | |||
| a5d4c1977a | |||
| d85ad7cc38 | |||
| aa45ea44ac | |||
| 1b06818583 | |||
| baa0677e7a | |||
| d9d3239662 | |||
| 8bc2d02572 | |||
| c9cfc7f873 | |||
| 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 | |||
| 95fab71898 | |||
|
|
83f3bbdc85 | ||
|
|
5910c0c227 | ||
| 8a2a568739 | |||
| 49ac668c14 | |||
| c2e91328fb | |||
| 18df5742d5 | |||
| 91f3d897e5 | |||
| a97c424abc | |||
| 0477e00f75 | |||
| 63f6b3da10 | |||
| d47afabade | |||
| 8506f7c1bd | |||
|
|
5df97c588e | ||
| f110c750a0 | |||
| 7a89eee9fb | |||
| 80ef5c4d9d | |||
| 506a280f6e | |||
| 0b8f9e54e9 | |||
| ee37bfe2cf | |||
| dd62baa3f0 | |||
| 46904d0825 | |||
| 172915d1c1 | |||
| d00f3e60fd | |||
| 2d519f7ca9 | |||
|
|
b2de6d3e9d | ||
| 982b2d0c32 | |||
| 7dc843d8f5 | |||
| 78c81549a2 | |||
| 1519453591 | |||
| 6199a93061 | |||
| 71e3808c78 | |||
| cf913884f0 | |||
| 413c77b1fe | |||
| 0c065beb30 | |||
| f2befda953 | |||
| 98d173bf00 | |||
| a517c2323f | |||
| 9857b90cd4 | |||
| f14d27e83c | |||
| 2c8911fb78 | |||
| a7147d1850 | |||
| 8329f3231d | |||
| ffbb2e53d8 | |||
| cf93fecf9f | |||
| a43891942b | |||
| 86b231a828 | |||
| 19a618a671 | |||
| 5adf9a9ce7 | |||
| eaa856e9da | |||
|
|
c0329c789a | ||
| a83cd970e6 | |||
| b81d95a9ba | |||
| 603230beda | |||
| 02fbf51659 | |||
| 74bc963096 | |||
| 99ae098c1d | |||
| 1b1a504bf5 | |||
| f1d96467d6 | |||
|
|
0b5163c680 | ||
| 9d06a588c8 | |||
| 23ec2bc298 | |||
| 77361c13db | |||
| e9e4e0e38d | |||
| 384fd3c7f8 | |||
| d9f54ce12b | |||
| 157095dede | |||
| 027f7d75b6 | |||
| b0251b882e | |||
| edaf8f4c77 | |||
| 939745cd54 | |||
| e84c45b238 | |||
| f250cdada2 | |||
| 0e15192f37 | |||
| 980deb61f9 | |||
| 844372d585 | |||
| 9260230d38 | |||
| f847a0e16a | |||
| 0c95c21666 | |||
| 1f37e57e00 | |||
| 7eb8f40d7a | |||
| a401029082 | |||
| 2a9a73c430 | |||
| 4c0c4737ac | |||
| 204bdbcb33 | |||
| 1a54426af6 | |||
| 45199030a0 | |||
| c10aa4b9ca | |||
| efad80970b | |||
| 8f611a523b | |||
| 398f7f2c36 | |||
| de4bb52346 | |||
| 521d3b63fa | |||
|
|
6decc963f6 | ||
|
|
91bb2bce93 | ||
|
|
108d983d74 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -327,3 +327,9 @@ pip-selfcheck.json
|
|||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/emacs,python,django,venv
|
# End of https://www.toptal.com/developers/gitignore/api/emacs,python,django,venv
|
||||||
|
|
||||||
|
/tienda_ilusion/don_confiao/static/frontend/
|
||||||
|
/tienda_ilusion/don_confiao/frontend/don-confiao/.vite/
|
||||||
|
/tienda_ilusion/don_confiao/frontend/don-confiao/.eslintrc.js
|
||||||
|
/tienda_ilusion/don_confiao/frontend/don-confiao/.eslintrc-auto-import.json
|
||||||
|
/tienda_ilusion/don_confiao/frontend/don-confiao/.editorconfig
|
||||||
|
/tienda_ilusion/don_confiao/frontend/don-confiao/.browserslistrc
|
||||||
|
|||||||
87
Rakefile
Normal file
87
Rakefile
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
require 'bundler/setup'
|
||||||
|
require 'yaml'
|
||||||
|
require 'digest'
|
||||||
|
|
||||||
|
DOCKER_COMPOSE='docker-compose.yml'
|
||||||
|
|
||||||
|
desc 'entorno vivo'
|
||||||
|
namespace :live do
|
||||||
|
task :up do
|
||||||
|
compose('up', '--build', '-d', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'monitorear salida'
|
||||||
|
task :tail do
|
||||||
|
compose('logs', '-f', 'django', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'monitorear salida'
|
||||||
|
task :tail_end do
|
||||||
|
compose('logs', '-f', '-n 50', 'django', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'iniciar entorno'
|
||||||
|
task :start do
|
||||||
|
compose('start', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'bajar entorno'
|
||||||
|
task :down do
|
||||||
|
compose('down', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'detener entorno'
|
||||||
|
task :stop do
|
||||||
|
compose('stop', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'eliminar entorno'
|
||||||
|
task :del do
|
||||||
|
compose('down', '-v', '--rmi', 'all', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'reiniciar entorno'
|
||||||
|
task :restart do
|
||||||
|
compose('restart', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'detener entorno'
|
||||||
|
task :stop do
|
||||||
|
compose('stop', compose: DOCKER_COMPOSE)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'terminal'
|
||||||
|
task :sh do
|
||||||
|
compose('exec', 'django', 'bash')
|
||||||
|
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)
|
||||||
|
sh "docker compose -f #{compose} #{arg.join(' ')}"
|
||||||
|
end
|
||||||
8
django.Dockerfile
Normal file
8
django.Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "manage.py", "runserver", "0.0.0.0:9090"]
|
||||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: nginx.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "7000:80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
- ./tienda_ilusion/don_confiao/static/frontend:/var/www/frontend/
|
||||||
|
django:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: django.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./tienda_ilusion:/app/
|
||||||
|
ports:
|
||||||
|
- "7001:9090"
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: vuetify.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./tienda_ilusion/don_confiao/frontend/don-confiao:/app/
|
||||||
|
ports:
|
||||||
|
- "7003:3000"
|
||||||
|
|
||||||
10
nginx.Dockerfile
Normal file
10
nginx.Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM nginx:latest
|
||||||
|
|
||||||
|
# Copiamos el archivo de configuración NGINX
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Establecemos la variable de entorno para el proxy inverso
|
||||||
|
ENV DJANGO_PROXY_URL http://django:8000
|
||||||
|
|
||||||
|
# Creamos un directorio estático
|
||||||
|
RUN mkdir -p /var/www/frontend
|
||||||
25
nginx.conf
Normal file
25
nginx.conf
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name donconfiao.org;
|
||||||
|
|
||||||
|
# location /frontend {
|
||||||
|
# alias /var/www/frontend/;
|
||||||
|
# autoindex on;
|
||||||
|
# }
|
||||||
|
|
||||||
|
location /frontend {
|
||||||
|
proxy_pass http://frontend:3000/frontend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://django:9090;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
Django==5.0.6
|
Django==5.0.6
|
||||||
|
djangorestframework
|
||||||
|
|||||||
146
tienda_ilusion/don_confiao/api_views.py
Normal file
146
tienda_ilusion/don_confiao/api_views.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
|
from .models import Sale, SaleLine, Customer, Product, ReconciliationJar, PaymentMethods, AdminCode
|
||||||
|
from .serializers import SaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, PaymentMethodSerializer, SaleForRenconciliationSerializer, SaleSummarySerializer
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Pagination(PageNumberPagination):
|
||||||
|
page_size = 10
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
|
||||||
|
|
||||||
|
class SaleView(viewsets.ModelViewSet):
|
||||||
|
queryset = Sale.objects.all()
|
||||||
|
serializer_class = SaleSerializer
|
||||||
|
|
||||||
|
def create(self, request):
|
||||||
|
data = request.data
|
||||||
|
customer = Customer.objects.get(pk=data['customer'])
|
||||||
|
date = data['date']
|
||||||
|
lines = data['saleline_set']
|
||||||
|
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'])
|
||||||
|
quantity = line['quantity']
|
||||||
|
unit_price = line['unit_price']
|
||||||
|
SaleLine.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
product=product,
|
||||||
|
quantity=quantity,
|
||||||
|
unit_price=unit_price
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{'id': sale.id, 'message': 'Venta creada con exito'},
|
||||||
|
status=201
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductView(viewsets.ModelViewSet):
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
|
||||||
|
|
||||||
|
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)})
|
||||||
|
|
||||||
|
|
||||||
|
class ReconciliateJarModelView(viewsets.ModelViewSet):
|
||||||
|
queryset = ReconciliationJar.objects.all().order_by('-date_time')
|
||||||
|
pagination_class = Pagination
|
||||||
|
serializer_class = ReconciliationJarSerializer
|
||||||
4
tienda_ilusion/don_confiao/example_customer.csv
Normal file
4
tienda_ilusion/don_confiao/example_customer.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
nombre,correo,telefono
|
||||||
|
Alejandro Ayala,mono@disroot.org,3232321
|
||||||
|
Mono Francisco,pablo@onecluster.org,321312312
|
||||||
|
Pablo Bolivar,alejo@onecluster.org,3243242
|
||||||
|
1
tienda_ilusion/don_confiao/export_csv.py
Normal file
1
tienda_ilusion/don_confiao/export_csv.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
@@ -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'})
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ class ImportProductsForm(forms.Form):
|
|||||||
csv_file = forms.FileField()
|
csv_file = forms.FileField()
|
||||||
|
|
||||||
|
|
||||||
|
class ImportCustomersForm(forms.Form):
|
||||||
|
csv_file = forms.FileField()
|
||||||
|
|
||||||
|
|
||||||
class PurchaseForm(forms.ModelForm):
|
class PurchaseForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Sale
|
model = Sale
|
||||||
@@ -50,29 +54,13 @@ class PurchaseSummaryForm(forms.Form):
|
|||||||
widget=readonly_number_widget
|
widget=readonly_number_widget
|
||||||
)
|
)
|
||||||
payment_method = forms.ChoiceField(
|
payment_method = forms.ChoiceField(
|
||||||
choices=PaymentMethods.choices,
|
choices=[(PaymentMethods.CASH, PaymentMethods.CASH)],
|
||||||
widget=forms.Select(attrs={'disabled': 'disabled'})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
LineaFormSet = inlineformset_factory(
|
SaleLineFormSet = inlineformset_factory(
|
||||||
Sale,
|
Sale,
|
||||||
SaleLine,
|
SaleLine,
|
||||||
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'})
|
|
||||||
}
|
|
||||||
|
|||||||
22
tienda_ilusion/don_confiao/frontend/don-confiao/.gitignore
vendored
Normal file
22
tienda_ilusion/don_confiao/frontend/don-confiao/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
79
tienda_ilusion/don_confiao/frontend/don-confiao/README.md
Normal file
79
tienda_ilusion/don_confiao/frontend/don-confiao/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Vuetify (Default)
|
||||||
|
|
||||||
|
This is the official scaffolding tool for Vuetify, designed to give you a head start in building your new Vuetify application. It sets up a base template with all the necessary configurations and standard directory structure, enabling you to begin development without the hassle of setting up the project from scratch.
|
||||||
|
|
||||||
|
## ❗️ Important Links
|
||||||
|
|
||||||
|
- 📄 [Docs](https://vuetifyjs.com/)
|
||||||
|
- 🚨 [Issues](https://issues.vuetifyjs.com/)
|
||||||
|
- 🏬 [Store](https://store.vuetifyjs.com/)
|
||||||
|
- 🎮 [Playground](https://play.vuetifyjs.com/)
|
||||||
|
- 💬 [Discord](https://community.vuetifyjs.com)
|
||||||
|
|
||||||
|
## 💿 Install
|
||||||
|
|
||||||
|
Set up your project using your preferred package manager. Use the corresponding command to install the dependencies:
|
||||||
|
|
||||||
|
| Package Manager | Command |
|
||||||
|
|---------------------------------------------------------------|----------------|
|
||||||
|
| [yarn](https://yarnpkg.com/getting-started) | `yarn install` |
|
||||||
|
| [npm](https://docs.npmjs.com/cli/v7/commands/npm-install) | `npm install` |
|
||||||
|
| [pnpm](https://pnpm.io/installation) | `pnpm install` |
|
||||||
|
| [bun](https://bun.sh/#getting-started) | `bun install` |
|
||||||
|
|
||||||
|
After completing the installation, your environment is ready for Vuetify development.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖼️ **Optimized Front-End Stack**: Leverage the latest Vue 3 and Vuetify 3 for a modern, reactive UI development experience. [Vue 3](https://v3.vuejs.org/) | [Vuetify 3](https://vuetifyjs.com/en/)
|
||||||
|
- 🗃️ **State Management**: Integrated with [Pinia](https://pinia.vuejs.org/), the intuitive, modular state management solution for Vue.
|
||||||
|
- 🚦 **Routing and Layouts**: Utilizes Vue Router for SPA navigation and vite-plugin-vue-layouts for organizing Vue file layouts. [Vue Router](https://router.vuejs.org/) | [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts)
|
||||||
|
- ⚡ **Next-Gen Tooling**: Powered by Vite, experience fast cold starts and instant HMR (Hot Module Replacement). [Vite](https://vitejs.dev/)
|
||||||
|
- 🧩 **Automated Component Importing**: Streamline your workflow with unplugin-vue-components, automatically importing components as you use them. [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
|
||||||
|
|
||||||
|
These features are curated to provide a seamless development experience from setup to deployment, ensuring that your Vuetify application is both powerful and maintainable.
|
||||||
|
|
||||||
|
## 💡 Usage
|
||||||
|
|
||||||
|
This section covers how to start the development server and build your project for production.
|
||||||
|
|
||||||
|
### Starting the Development Server
|
||||||
|
|
||||||
|
To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
To build your project for production, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
Once the build process is completed, your application will be ready for deployment in a production environment.
|
||||||
|
|
||||||
|
## 💪 Support Vuetify Development
|
||||||
|
|
||||||
|
This project is built with [Vuetify](https://vuetifyjs.com/en/), a UI Library with a comprehensive collection of Vue components. Vuetify is an MIT licensed Open Source project that has been made possible due to the generous contributions by our [sponsors and backers](https://vuetifyjs.com/introduction/sponsors-and-backers/). If you are interested in supporting this project, please consider:
|
||||||
|
|
||||||
|
- [Requesting Enterprise Support](https://support.vuetifyjs.com/)
|
||||||
|
- [Sponsoring John on Github](https://github.com/users/johnleider/sponsorship)
|
||||||
|
- [Sponsoring Kael on Github](https://github.com/users/kaelwd/sponsorship)
|
||||||
|
- [Supporting the team on Open Collective](https://opencollective.com/vuetify)
|
||||||
|
- [Becoming a sponsor on Patreon](https://www.patreon.com/vuetify)
|
||||||
|
- [Becoming a subscriber on Tidelift](https://tidelift.com/subscription/npm/vuetify)
|
||||||
|
- [Making a one-time donation with Paypal](https://paypal.me/vuetify)
|
||||||
|
|
||||||
|
## 📑 License
|
||||||
|
[MIT](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-present Vuetify, LLC
|
||||||
16
tienda_ilusion/don_confiao/frontend/don-confiao/index.html
Normal file
16
tienda_ilusion/don_confiao/frontend/don-confiao/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Don Confiao</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5546
tienda_ilusion/don_confiao/frontend/don-confiao/package-lock.json
generated
Normal file
5546
tienda_ilusion/don_confiao/frontend/don-confiao/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
tienda_ilusion/don_confiao/frontend/don-confiao/package.json
Normal file
39
tienda_ilusion/don_confiao/frontend/don-confiao/package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "don-confiao",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host 0.0.0.0",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --fix --ignore-path .gitignore"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mdi/font": "7.4.47",
|
||||||
|
"core-js": "^3.37.1",
|
||||||
|
"roboto-fontface": "*",
|
||||||
|
"vee-validate": "^4.14.6",
|
||||||
|
"vue": "^3.4.31",
|
||||||
|
"vuetify": "^3.6.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-standard": "^17.1.0",
|
||||||
|
"eslint-config-vuetify": "^1.0.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-n": "^16.6.2",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-promise": "^6.4.0",
|
||||||
|
"eslint-plugin-vue": "^9.27.0",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"sass": "1.77.6",
|
||||||
|
"unplugin-auto-import": "^0.17.6",
|
||||||
|
"unplugin-fonts": "^1.1.1",
|
||||||
|
"unplugin-vue-components": "^0.27.2",
|
||||||
|
"unplugin-vue-router": "^0.10.0",
|
||||||
|
"vite": "^5.3.3",
|
||||||
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
|
"vite-plugin-vuetify": "^2.0.3",
|
||||||
|
"vue-router": "^4.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tienda_ilusion/don_confiao/frontend/don-confiao/public/1.ico
Normal file
BIN
tienda_ilusion/don_confiao/frontend/don-confiao/public/1.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
19
tienda_ilusion/don_confiao/frontend/don-confiao/src/App.vue
Normal file
19
tienda_ilusion/don_confiao/frontend/don-confiao/src/App.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<NavBar />
|
||||||
|
<v-main>
|
||||||
|
<router-view />
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavBar from './components/NavBar.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
NavBar,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 576 B |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/>
|
||||||
|
<path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/>
|
||||||
|
<path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/>
|
||||||
|
<path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<v-footer height="40" app>
|
||||||
|
<a
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.title"
|
||||||
|
:href="item.href"
|
||||||
|
:title="item.title"
|
||||||
|
class="d-inline-block mx-2 social-link"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
:icon="item.icon"
|
||||||
|
:size="item.icon === '$vuetify' ? 24 : 16"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="text-caption text-disabled"
|
||||||
|
style="position: absolute; right: 16px;"
|
||||||
|
>
|
||||||
|
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
||||||
|
—
|
||||||
|
<a
|
||||||
|
class="text-decoration-none on-surface"
|
||||||
|
href="https://vuetifyjs.com/about/licensing/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
MIT License
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</v-footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
title: 'Vuetify Documentation',
|
||||||
|
icon: `$vuetify`,
|
||||||
|
href: 'https://vuetifyjs.com/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vuetify Support',
|
||||||
|
icon: 'mdi-shield-star-outline',
|
||||||
|
href: 'https://support.vuetifyjs.com/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vuetify X',
|
||||||
|
icon: ['M2.04875 3.00002L9.77052 13.3248L1.99998 21.7192H3.74882L10.5519 14.3697L16.0486 21.7192H22L13.8437 10.8137L21.0765 3.00002H19.3277L13.0624 9.76874L8.0001 3.00002H2.04875ZM4.62054 4.28821H7.35461L19.4278 20.4308H16.6937L4.62054 4.28821Z'],
|
||||||
|
href: 'https://x.com/vuetifyjs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vuetify GitHub',
|
||||||
|
icon: `mdi-github`,
|
||||||
|
href: 'https://github.com/vuetifyjs/vuetify',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vuetify Discord',
|
||||||
|
icon: ['M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z'],
|
||||||
|
href: 'https://community.vuetifyjs.com/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vuetify Reddit',
|
||||||
|
icon: `mdi-reddit`,
|
||||||
|
href: 'https://reddit.com/r/vuetifyjs',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="sass">
|
||||||
|
.social-link :deep(.v-icon)
|
||||||
|
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||||
|
text-decoration: none
|
||||||
|
transition: .2s ease-in-out
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: rgba(25, 118, 210, 1)
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="showModal" max-width="600px">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span class="headline">Información del Cliente</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="form" v-model="valid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="customer.name"
|
||||||
|
label="Nombre"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="customer.address"
|
||||||
|
label="Direccion"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="customer.email"
|
||||||
|
label="Correo Electrónico"
|
||||||
|
:rules="[rules.required, rules.email]"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="customer.phone"
|
||||||
|
label="Teléfono"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue darken-1" text @click="closeModal">Cancelar</v-btn>
|
||||||
|
<v-btn color="blue darken-1" text @click="submitForm">Guardar</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showModal: false,
|
||||||
|
api: inject('api'),
|
||||||
|
valid: false,
|
||||||
|
customer: {
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
email: '',
|
||||||
|
phone: ''
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
required: value => !!value || 'Este campo es requerido.',
|
||||||
|
email: value => {
|
||||||
|
const pattern = /^[^ ]+@[^ ]+\.[a-z]{2,3}$/;
|
||||||
|
return pattern.test(value) || 'El correo no es válido.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.showModal = true;
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.showModal = false;
|
||||||
|
this.resetForm();
|
||||||
|
},
|
||||||
|
async submitForm() {
|
||||||
|
console.log(this.customer)
|
||||||
|
if (this.$refs.form.validate()) {
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="fill-height">
|
||||||
|
<v-responsive
|
||||||
|
class="align-centerfill-height mx-auto"
|
||||||
|
max-width="900"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
class="mb-4"
|
||||||
|
height="150"
|
||||||
|
src="@/assets/logo.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div>
|
||||||
|
|
||||||
|
<h1 class="text-h2 font-weight-bold">Vuetify</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-4" />
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card
|
||||||
|
class="py-4"
|
||||||
|
color="surface-variant"
|
||||||
|
image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png"
|
||||||
|
prepend-icon="mdi-rocket-launch-outline"
|
||||||
|
rounded="lg"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
<template #image>
|
||||||
|
<v-img position="top right" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #title>
|
||||||
|
<h2 class="text-h5 font-weight-bold">Get started</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #subtitle>
|
||||||
|
<div class="text-subtitle-1">
|
||||||
|
Replace this page by removing <v-kbd>{{ `<HelloWorld />` }}</v-kbd> in <v-kbd>pages/index.vue</v-kbd>.
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-overlay
|
||||||
|
opacity=".12"
|
||||||
|
scrim="primary"
|
||||||
|
contained
|
||||||
|
model-value
|
||||||
|
persistent
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-card
|
||||||
|
append-icon="mdi-open-in-new"
|
||||||
|
class="py-4"
|
||||||
|
color="surface-variant"
|
||||||
|
href="https://vuetifyjs.com/"
|
||||||
|
prepend-icon="mdi-text-box-outline"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
rounded="lg"
|
||||||
|
subtitle="Learn about all things Vuetify in our documentation."
|
||||||
|
target="_blank"
|
||||||
|
title="Documentation"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
<v-overlay
|
||||||
|
opacity=".06"
|
||||||
|
scrim="primary"
|
||||||
|
contained
|
||||||
|
model-value
|
||||||
|
persistent
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-card
|
||||||
|
append-icon="mdi-open-in-new"
|
||||||
|
class="py-4"
|
||||||
|
color="surface-variant"
|
||||||
|
href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides"
|
||||||
|
prepend-icon="mdi-star-circle-outline"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
rounded="lg"
|
||||||
|
subtitle="Explore available framework Features."
|
||||||
|
target="_blank"
|
||||||
|
title="Features"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
<v-overlay
|
||||||
|
opacity=".06"
|
||||||
|
scrim="primary"
|
||||||
|
contained
|
||||||
|
model-value
|
||||||
|
persistent
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-card
|
||||||
|
append-icon="mdi-open-in-new"
|
||||||
|
class="py-4"
|
||||||
|
color="surface-variant"
|
||||||
|
href="https://vuetifyjs.com/components/all"
|
||||||
|
prepend-icon="mdi-widgets-outline"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
rounded="lg"
|
||||||
|
subtitle="Discover components in the API Explorer."
|
||||||
|
target="_blank"
|
||||||
|
title="Components"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
<v-overlay
|
||||||
|
opacity=".06"
|
||||||
|
scrim="primary"
|
||||||
|
contained
|
||||||
|
model-value
|
||||||
|
persistent
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-card
|
||||||
|
append-icon="mdi-open-in-new"
|
||||||
|
class="py-4"
|
||||||
|
color="surface-variant"
|
||||||
|
href="https://discord.vuetifyjs.com"
|
||||||
|
prepend-icon="mdi-account-group-outline"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
rounded="lg"
|
||||||
|
subtitle="Connect with Vuetify developers."
|
||||||
|
target="_blank"
|
||||||
|
title="Community"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
<v-overlay
|
||||||
|
opacity=".06"
|
||||||
|
scrim="primary"
|
||||||
|
contained
|
||||||
|
model-value
|
||||||
|
persistent
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-responsive>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal">
|
||||||
|
<div class="head">
|
||||||
|
<p>Nuevo Movimiento</p>
|
||||||
|
<img @click="close" src="@/assets/close-icon.svg" alt="cerrar"/>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineEmits } from "vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["close"]);
|
||||||
|
|
||||||
|
const close = () => emit("close");
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<v-app-bar color="primary" prominent>
|
||||||
|
<v-app-bar-nav-icon variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
|
||||||
|
<v-toolbar-title>Menu</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<template v-if="$vuetify.display.mdAndUp">
|
||||||
|
<v-btn icon="mdi-magnify" variant="text"></v-btn>
|
||||||
|
<v-btn icon="mdi-filter" variant="text"></v-btn>
|
||||||
|
</template>
|
||||||
|
<v-btn icon="mdi-dots-vertical" variant="text"></v-btn>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-navigation-drawer v-model="drawer"
|
||||||
|
:location="$vuetify.display.mobile ? 'bottom' : undefined"
|
||||||
|
temporary>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item v-for="item in menuItems" :key="item.title" @click="navigate(item.route)">
|
||||||
|
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'NavBar',
|
||||||
|
data: () => ({
|
||||||
|
drawer: false,
|
||||||
|
group: null,
|
||||||
|
menuItems: [
|
||||||
|
{ title: 'Inicio', route: '/'},
|
||||||
|
{ title: 'Comprar', route:'/comprar'},
|
||||||
|
{ title: 'Cuadrar tarro', route: '/cuadrar_tarro'},
|
||||||
|
{ title: 'Cuadres de tarro', route: '/cuadres_de_tarro'},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
watch: {
|
||||||
|
group () {
|
||||||
|
this.drawer = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate(route) {
|
||||||
|
this.$router.push(route);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
<template>
|
||||||
|
<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"
|
||||||
|
@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
|
||||||
|
lg="2"
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="line.quantity"
|
||||||
|
label="Cantidad"
|
||||||
|
type="number"
|
||||||
|
:rules="[rules.required,rules.positive]"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
</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
|
||||||
|
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>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-btn @click="removeLine(index)" color="red">Eliminar</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</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>
|
||||||
|
<v-text-field
|
||||||
|
:value="calculateTotal"
|
||||||
|
label="Total"
|
||||||
|
prefix="$"
|
||||||
|
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-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 CasherModal from './CasherModal.vue';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
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(),
|
||||||
|
customer: null,
|
||||||
|
notes: '',
|
||||||
|
payment_method: null,
|
||||||
|
saleline_set: [{product:'', unit_price: 0, quantity: 0, unit: ''}],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
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: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
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) => {
|
||||||
|
return total + this.calculateSubtotal(saleline);
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
filteredClients() {
|
||||||
|
return this.clients.filter(client => {
|
||||||
|
if (this.client_search === '') {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return client.name.toLowerCase().includes(this.client_search.toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
filteredProducts() {
|
||||||
|
return this.products.filter(product => {
|
||||||
|
if (this.product_search === '') {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return product.name.toLowerCase().includes(this.product_search.toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.$refs.customerModal.openModal();
|
||||||
|
},
|
||||||
|
onFormChange() {
|
||||||
|
this.form_changed = true;
|
||||||
|
},
|
||||||
|
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].measuring_unit = selectedProduct.measuring_unit;
|
||||||
|
},
|
||||||
|
fetchClients() {
|
||||||
|
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() {
|
||||||
|
this.api.getProducts()
|
||||||
|
.then(data => {
|
||||||
|
this.products = data;
|
||||||
|
})
|
||||||
|
.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, measuring_unit: ''});
|
||||||
|
},
|
||||||
|
removeLine(index) {
|
||||||
|
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() {
|
||||||
|
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) {
|
||||||
|
this.$router.push(route);
|
||||||
|
},
|
||||||
|
formatPrice(price) {
|
||||||
|
return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'COP' }).format(price);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchClients();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Components
|
||||||
|
|
||||||
|
Vue template files in this folder are automatically imported.
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
|
Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it.
|
||||||
|
|
||||||
|
The following example assumes a component located at `src/components/MyComponent.vue`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MyComponent />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
When your template is rendered, the component's import will automatically be inlined, which renders to this:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MyComponent />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MyComponent from '@/components/MyComponent.vue'
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-toolbar>
|
||||||
|
<v-toolbar-title> Cuadres del Tarro </v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<v-data-table-server
|
||||||
|
v-model:items-per-page="itemsPerPage"
|
||||||
|
:headers="headers"
|
||||||
|
:items="serverItems"
|
||||||
|
:items-length="totalItems"
|
||||||
|
:loading="loading"
|
||||||
|
:search="search"
|
||||||
|
@update:options="loadItems"
|
||||||
|
>
|
||||||
|
<template v-slot:item.id="{ item }">
|
||||||
|
<v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-data-table-server>
|
||||||
|
<SummaryReconciliationModal :id="selectedReconciliationId" ref="summaryModal" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
api: inject('api'),
|
||||||
|
selectedReconciliationId: null,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
headers: [
|
||||||
|
{ title: 'Acciones', key: 'id'},
|
||||||
|
{ title: 'Fecha', key: 'date_time'},
|
||||||
|
{ title: 'Reconciliador', key: 'reconcilier'},
|
||||||
|
{ title: 'Total Compras Efectivo', key: 'total_cash_purchases'},
|
||||||
|
{ title: 'Recogido', key: 'cash_taken'},
|
||||||
|
{ title: 'Descuadre', key: 'cash_discrepancy'},
|
||||||
|
],
|
||||||
|
search: '',
|
||||||
|
serverItems: [],
|
||||||
|
loading: true,
|
||||||
|
totalItems: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadItems ({page, itemsPerPage}) {
|
||||||
|
this.loading = true;
|
||||||
|
this.api.getListReconcliations(page, itemsPerPage)
|
||||||
|
.then(data => {
|
||||||
|
this.serverItems = data['results'];
|
||||||
|
this.totalItems = data['count'];
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => console.log('Error:', error));
|
||||||
|
},
|
||||||
|
openSummaryModal(id) {
|
||||||
|
this.selectedReconciliationId = id.toString();
|
||||||
|
this.$refs.summaryModal.dialog = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-toolbar>
|
||||||
|
<v-toolbar-title> Cuadre de Tarro: {{ id }}</v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.date_time"
|
||||||
|
label="Fecha"
|
||||||
|
required
|
||||||
|
readonly
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.reconcilier"
|
||||||
|
label="Cajero"
|
||||||
|
required
|
||||||
|
readonly
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.total_cash_purchases"
|
||||||
|
label="Total Ventas en efectivo"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
readonly
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.cash_taken"
|
||||||
|
label="Dinero Recogido"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="reconciliation.cash_discrepancy"
|
||||||
|
label="Descuadre"
|
||||||
|
prefix="$"
|
||||||
|
type="number"
|
||||||
|
></v-text-field>
|
||||||
|
<v-tabs v-model="tab">
|
||||||
|
<v-tab
|
||||||
|
v-for="(elements, paymentMethod) in purchases"
|
||||||
|
:key="paymentMethod"
|
||||||
|
>
|
||||||
|
{{ paymentMethod }} <CurrencyText :value="elements.total"</CurrencyText>
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
<v-tabs-window v-model="tab">
|
||||||
|
<v-tabs-window-item
|
||||||
|
v-for="(elements, paymentMethod) in purchases"
|
||||||
|
:key="paymentMethod"
|
||||||
|
>
|
||||||
|
<v-table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<th>Fecha</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="purchase in elements.purchases" :key="purchase.id">
|
||||||
|
<td><v-btn @click="openSummaryModal(purchase.id)">{{ purchase.id }}</v-btn></td>
|
||||||
|
<td>{{ purchase.date }}</td>
|
||||||
|
<td>{{ purchase.customer }}</td>
|
||||||
|
<td><CurrencyText :value="purchase.total"</CurrencyText></td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
|
||||||
|
</v-tabs-window-item>
|
||||||
|
</v-tabs-window>
|
||||||
|
<SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ReconciliationJar View',
|
||||||
|
props: {
|
||||||
|
msg: String,
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
tab: '0',
|
||||||
|
selectedPurchaseId: null,
|
||||||
|
api: inject('api'),
|
||||||
|
valid: null,
|
||||||
|
reconciliation: {
|
||||||
|
},
|
||||||
|
purchases: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.id) {
|
||||||
|
this.fetchReconciliation(this.id);
|
||||||
|
} else {
|
||||||
|
console.error('No se proporcionó ID');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchReconciliation(reconciliationId) {
|
||||||
|
this.api.getReconciliation(reconciliationId)
|
||||||
|
.then(data => {
|
||||||
|
this.reconciliation = data;
|
||||||
|
this.groupPurchases();
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
},
|
||||||
|
groupPurchases() {
|
||||||
|
if (this.reconciliation.Sales) {
|
||||||
|
this.purchases = this.reconciliation.Sales.reduce((grouped, sale) => {
|
||||||
|
const paymentMethod = sale.payment_method;
|
||||||
|
if (!grouped[paymentMethod]) {
|
||||||
|
grouped[paymentMethod] = {
|
||||||
|
purchases: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
grouped[paymentMethod].purchases.push(sale);
|
||||||
|
grouped[paymentMethod].total += sale.total;
|
||||||
|
return grouped;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openSummaryModal(id) {
|
||||||
|
this.selectedPurchaseId = id;
|
||||||
|
this.$refs.summaryModal.dialog = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<strong class="text-red-darken-4">
|
||||||
|
<slot></slot>
|
||||||
|
</strong>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<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: Number
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
|
||||||
|
</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,30 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<SummaryPurchase :id="id"/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn text @click="dialog = false">Cerrar</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SummaryPurchase Modal',
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialog: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="400">
|
||||||
|
resumen
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<ReconciliationJarView :id="id"/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn text @click="dialog = false">Cerrar</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Summary Reconciliation Modal',
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialog: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,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>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Layouts
|
||||||
|
|
||||||
|
Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages.
|
||||||
|
|
||||||
|
Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<v-main>
|
||||||
|
<router-view />
|
||||||
|
</v-main>
|
||||||
|
|
||||||
|
<AppFooter />
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
26
tienda_ilusion/don_confiao/frontend/don-confiao/src/main.js
Normal file
26
tienda_ilusion/don_confiao/frontend/don-confiao/src/main.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* main.js
|
||||||
|
*
|
||||||
|
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import { registerPlugins } from '@/plugins'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import App from './App.vue'
|
||||||
|
import ApiImplementation from './services/api-implementation';
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
process.env.API_IMPLEMENTATION = 'django';
|
||||||
|
let apiImplementation = new ApiImplementation();
|
||||||
|
const api = apiImplementation.getApi();
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
app.provide('api', api);
|
||||||
|
|
||||||
|
registerPlugins(app)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Pages
|
||||||
|
|
||||||
|
Vue components created in this folder will automatically be converted to navigatable routes.
|
||||||
|
|
||||||
|
Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CodeDialog @code-verified="(verified) => showComponent = verified" />
|
||||||
|
</div>
|
||||||
|
<ReconciliationJarIndex v-if="showComponent" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CodeDialog from '../components/CodeDialog.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showComponent: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { CodeDialog },
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<Wellcome />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<SummaryPurchase :id="$route.query.id"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Plugins
|
||||||
|
|
||||||
|
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* plugins/index.js
|
||||||
|
*
|
||||||
|
* Automatically included in `./src/main.js`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import vuetify from './vuetify'
|
||||||
|
import pinia from '@/stores'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
export function registerPlugins (app) {
|
||||||
|
app
|
||||||
|
.use(vuetify)
|
||||||
|
.use(router)
|
||||||
|
.use(pinia)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* plugins/vuetify.js
|
||||||
|
*
|
||||||
|
* Framework documentation: https://vuetifyjs.com`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
import 'vuetify/styles'
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
|
||||||
|
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||||
|
export default createVuetify({
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'light',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* router/index.ts
|
||||||
|
*
|
||||||
|
* Automatic routes for `./src/pages/*.vue`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router/auto'
|
||||||
|
import { setupLayouts } from 'virtual:generated-layouts'
|
||||||
|
import { routes } from 'vue-router/auto-routes'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: setupLayouts(routes),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Workaround for https://github.com/vitejs/vite/issues/11804
|
||||||
|
router.onError((err, to) => {
|
||||||
|
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
||||||
|
if (!localStorage.getItem('vuetify:dynamic-reload')) {
|
||||||
|
console.log('Reloading page to fix dynamic import error')
|
||||||
|
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
||||||
|
location.assign(to.fullPath)
|
||||||
|
} else {
|
||||||
|
console.error('Dynamic import error, reloading page did not fix it', err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.isReady().then(() => {
|
||||||
|
localStorage.removeItem('vuetify:dynamic-reload')
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import DjangoApi from './django-api';
|
||||||
|
import Api from './api';
|
||||||
|
|
||||||
|
class ApiImplementation {
|
||||||
|
constructor() {
|
||||||
|
const implementation = process.env.API_IMPLEMENTATION;
|
||||||
|
let apiImplementation;
|
||||||
|
if (implementation === 'django') {
|
||||||
|
apiImplementation = new DjangoApi();
|
||||||
|
} else {
|
||||||
|
throw new Error("API implementation don't configured");
|
||||||
|
}
|
||||||
|
this.api = new Api(apiImplementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
getApi() {
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApiImplementation;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
getListReconcliations(page=1, itemsPerPage=10) {
|
||||||
|
return this.apiImplementation.getListReconcliations(page, itemsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getReconciliation(reconciliationId) {
|
||||||
|
return this.apiImplementation.getReconciliation(reconciliationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidAdminCode(code) {
|
||||||
|
return this.apiImplementation.isValidAdminCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,97 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
getListReconcliations(page, itemsPerPage) {
|
||||||
|
const url = `/don_confiao/api/reconciliate_jar/?page=${page}&page_size=${itemsPerPage}`;
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getReconciliation(reconciliationId) {
|
||||||
|
const url = `/don_confiao/api/reconciliate_jar/${reconciliationId}/`;
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidAdminCode(code) {
|
||||||
|
const url = `/don_confiao/api/admin_code/validate/${code}`
|
||||||
|
return this.getRequest(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,5 @@
|
|||||||
|
# Store
|
||||||
|
|
||||||
|
Pinia stores are used to store reactive state and expose actions to mutate it.
|
||||||
|
|
||||||
|
Full documentation for this feature can be found in the Official [Pinia](https://pinia.esm.dev/) repository.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Utilities
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', {
|
||||||
|
state: () => ({
|
||||||
|
//
|
||||||
|
}),
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Utilities
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export default createPinia()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Styles
|
||||||
|
|
||||||
|
This directory is for configuring the styles of the application.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* src/styles/settings.scss
|
||||||
|
*
|
||||||
|
* Configures SASS variables and Vuetify overwrites
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://vuetifyjs.com/features/sass-variables/`
|
||||||
|
// @use 'vuetify/settings' with (
|
||||||
|
// $color-pack: false
|
||||||
|
// );
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Plugins
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import Fonts from 'unplugin-fonts/vite'
|
||||||
|
import Layouts from 'vite-plugin-vue-layouts'
|
||||||
|
import Vue from '@vitejs/plugin-vue'
|
||||||
|
import VueRouter from 'unplugin-vue-router/vite'
|
||||||
|
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
VueRouter(),
|
||||||
|
Layouts(),
|
||||||
|
Vue({
|
||||||
|
template: { transformAssetUrls }
|
||||||
|
}),
|
||||||
|
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||||
|
Vuetify({
|
||||||
|
autoImport: true,
|
||||||
|
styles: {
|
||||||
|
configFile: 'src/styles/settings.scss',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Components(),
|
||||||
|
Fonts({
|
||||||
|
google: {
|
||||||
|
families: [{
|
||||||
|
name: 'Roboto',
|
||||||
|
styles: 'wght@100;300;400;500;700;900',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'vue-router',
|
||||||
|
],
|
||||||
|
eslintrc: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
vueTemplate: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
define: { 'process.env': {} },
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
extensions: [
|
||||||
|
'.js',
|
||||||
|
'.json',
|
||||||
|
'.jsx',
|
||||||
|
'.mjs',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.vue',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: '../../static/frontend/',
|
||||||
|
},
|
||||||
|
base: '/frontend/',
|
||||||
|
})
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-08-17 14:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0027_alter_product_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customer',
|
||||||
|
name='address',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-08-17 19:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0028_alter_customer_address'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customer',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default=None, max_length=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
22
tienda_ilusion/don_confiao/migrations/0030_paymentsale.py
Normal file
22
tienda_ilusion/don_confiao/migrations/0030_paymentsale.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-08-17 21:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0029_alter_customer_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentSale',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('payment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.payment')),
|
||||||
|
('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.sale')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-10-26 22:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0030_paymentsale'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='customer',
|
||||||
|
old_name='address',
|
||||||
|
new_name='email',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-10-26 22:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('don_confiao', '0031_rename_address_customer_email_customer_phone'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='address',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,11 +3,23 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from decimal import Decimal
|
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):
|
class Customer(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100, default=None, null=False, blank=False)
|
||||||
address = models.CharField(max_length=100)
|
address = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
email = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
phone = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class MeasuringUnits(models.TextChoices):
|
class MeasuringUnits(models.TextChoices):
|
||||||
@@ -34,21 +46,88 @@ class Product(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_list(cls):
|
||||||
|
products_list = []
|
||||||
|
all_products = cls.objects.all()
|
||||||
|
for product in all_products:
|
||||||
|
rproduct = {
|
||||||
|
"id": product.id,
|
||||||
|
"name": product.name,
|
||||||
|
"price_list": product.price,
|
||||||
|
"uom": product.measuring_unit,
|
||||||
|
"categories": [c.name for c in product.categories.all()]
|
||||||
|
}
|
||||||
|
products_list.append(rproduct)
|
||||||
|
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.DateField("Date")
|
date = models.DateTimeField("Date")
|
||||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||||
description = models.CharField(max_length=255, 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):
|
def __str__(self):
|
||||||
return f"{self.date} {self.customer}"
|
return f"{self.date} {self.customer}"
|
||||||
|
|
||||||
|
def get_total(self):
|
||||||
|
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]
|
||||||
|
|
||||||
|
return sale_header_csv
|
||||||
|
|
||||||
|
|
||||||
class SaleLine(models.Model):
|
class SaleLine(models.Model):
|
||||||
|
|
||||||
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|
sale = models.ForeignKey(Sale, on_delete=models.CASCADE)
|
||||||
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
product = models.ForeignKey(Product, null=False, blank=False, on_delete=models.CASCADE)
|
||||||
quantity = models.IntegerField(null=True)
|
quantity = models.IntegerField(null=True)
|
||||||
unit_price = models.DecimalField(max_digits=9, decimal_places=2)
|
unit_price = models.DecimalField(max_digits=9, decimal_places=2)
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(max_length=255, null=True, blank=True)
|
||||||
@@ -57,12 +136,6 @@ class SaleLine(models.Model):
|
|||||||
return f"{self.sale} - {self.product}"
|
return f"{self.sale} - {self.product}"
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethods(models.TextChoices):
|
|
||||||
CASH = 'CASH', _('Cash')
|
|
||||||
CONFIAR = 'CONFIAR', _('Confiar')
|
|
||||||
BANCOLOMBIA = 'BANCOLOMBIA', _('Bancolombia')
|
|
||||||
|
|
||||||
|
|
||||||
class ReconciliationJarSummary():
|
class ReconciliationJarSummary():
|
||||||
def __init__(self, payments):
|
def __init__(self, payments):
|
||||||
self._validate_payments(payments)
|
self._validate_payments(payments)
|
||||||
@@ -80,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(
|
||||||
@@ -137,3 +178,27 @@ class Payment(models.Model):
|
|||||||
reconciliation_jar=None
|
reconciliation_jar=None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def total_payment_from_sale(cls, payment_method, sale):
|
||||||
|
payment = cls()
|
||||||
|
payment.date_time = datetime.today()
|
||||||
|
payment.type_payment = payment_method
|
||||||
|
payment.amount = sale.get_total()
|
||||||
|
payment.clean()
|
||||||
|
payment.save()
|
||||||
|
|
||||||
|
payment_sale = PaymentSale()
|
||||||
|
payment_sale.payment = payment
|
||||||
|
payment_sale.sale = sale
|
||||||
|
payment_sale.clean()
|
||||||
|
payment_sale.save()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
103
tienda_ilusion/don_confiao/serializers.py
Normal file
103
tienda_ilusion/don_confiao/serializers.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import Sale, SaleLine, Product, Customer, ReconciliationJar
|
||||||
|
|
||||||
|
|
||||||
|
class SaleLineSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SaleLine
|
||||||
|
fields = ['id', 'sale', 'product', 'unit_price', 'quantity']
|
||||||
|
|
||||||
|
|
||||||
|
class SaleSerializer(serializers.ModelSerializer):
|
||||||
|
total = serializers.ReadOnlyField(source='get_total')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Sale
|
||||||
|
fields = ['id', 'customer', 'date', 'saleline_set',
|
||||||
|
'total', 'payment_method']
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = ['id', 'name', 'price', 'measuring_unit', 'categories']
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Customer
|
||||||
|
fields = ['id', 'name', 'address', 'email', 'phone']
|
||||||
|
|
||||||
|
|
||||||
|
class ReconciliationJarSerializer(serializers.ModelSerializer):
|
||||||
|
Sales = SaleSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ReconciliationJar
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'date_time',
|
||||||
|
'reconcilier',
|
||||||
|
'cash_taken',
|
||||||
|
'cash_discrepancy',
|
||||||
|
'total_cash_purchases',
|
||||||
|
'Sales',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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']
|
||||||
37
tienda_ilusion/don_confiao/static/css/main_menu.css
Normal file
37
tienda_ilusion/don_confiao/static/css/main_menu.css
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
nav#main_menu a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
background-color: #178E79 ;
|
||||||
|
padding: 10px 20px;
|
||||||
|
min-width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
border: solid #178E79 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#main_menu a:hover {
|
||||||
|
transition: ease-in-out 0.2s;
|
||||||
|
background-color: #7BDCB5;
|
||||||
|
color: #178E79;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#main_menu a:active {
|
||||||
|
background-color: #aaa;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#main_menu {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
li{
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page_title {
|
||||||
|
color: #04A1E4
|
||||||
|
}
|
||||||
BIN
tienda_ilusion/don_confiao/static/img/recreo_logo.png
Normal file
BIN
tienda_ilusion/don_confiao/static/img/recreo_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
@@ -25,5 +25,6 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||||||
|
|
||||||
formContainer.appendChild(newForm);
|
formContainer.appendChild(newForm);
|
||||||
totalForms.value = formCount + 1;
|
totalForms.value = formCount + 1;
|
||||||
|
setPriceListeners();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
22
tienda_ilusion/don_confiao/static/js/buy_general.js
Normal file
22
tienda_ilusion/don_confiao/static/js/buy_general.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
setPriceListeners();
|
||||||
|
|
||||||
|
function setPriceListeners() {
|
||||||
|
document.querySelectorAll('select[id^="id_saleline_set-"][id$="-product"]').forEach((input) => {
|
||||||
|
input.addEventListener('change', (e) => setLinePrice(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLinePrice(e) {
|
||||||
|
let input = e.target;
|
||||||
|
const idLine = input.id.split('-')[1];
|
||||||
|
const productId = input.value;
|
||||||
|
const priceInput = document.getElementById(`id_saleline_set-${idLine}-unit_price`);
|
||||||
|
|
||||||
|
const product = listProducts.find((product) => product.id == productId);
|
||||||
|
if (product) {
|
||||||
|
priceInput.value = product.price_list;
|
||||||
|
} else {
|
||||||
|
priceInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
const quantity_lineRegexSelector = `[id^="${idPrefix}"][id$="${quantitySuffix}"]`;
|
||||||
|
const price_lineRegexSelector = `[id^="${idPrefix}"][id$="${priceSuffix}"]`;
|
||||||
|
|
||||||
|
function insertSubtotalField() {
|
||||||
|
// Selecciona la fila de precio unitario para añadir la fila del subtotal después de ella
|
||||||
|
const unitPriceRow = document.querySelector('input[id="id_saleline_set-0-unit_price"]').closest('tr');
|
||||||
|
|
||||||
|
// Crear una nueva fila para el subtotal
|
||||||
|
const subtotalRow = document.createElement('tr');
|
||||||
|
subtotalRow.innerHTML = `
|
||||||
|
<th><label for="id_saleline_set-0-subtotal">Subtotal:</label></th>
|
||||||
|
<td><input type="number" name="saleline_set-0-subtotal" id="id_saleline_set-0-subtotal" readonly></td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insertar la fila del subtotal después de la fila del precio unitario
|
||||||
|
unitPriceRow.after(subtotalRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSubtotal(id) {
|
||||||
|
const quantityElement = document.getElementById(`id_saleline_set-${id}-quantity`);
|
||||||
|
const unitPriceElement = document.getElementById(`id_saleline_set-${id}-unit_price`);
|
||||||
|
const subtotalElement = document.getElementById(`id_saleline_set-${id}-subtotal`);
|
||||||
|
|
||||||
|
const quantity = parseFloat(quantityElement.value) || 0;
|
||||||
|
const unitPrice = parseFloat(unitPriceElement.value) || 0;
|
||||||
|
const subtotal = quantity * unitPrice;
|
||||||
|
|
||||||
|
subtotalElement.value = subtotal.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserta el campo subtotal al cargar la página
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
insertSubtotalField();
|
||||||
|
|
||||||
|
complete_form.addEventListener('change', function(event){
|
||||||
|
const quantityInputs = document.querySelectorAll(quantity_lineRegexSelector);
|
||||||
|
const ids = Array.prototype.map.call(quantityInputs, function(input) {
|
||||||
|
return input.id.match(/\d+/)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
ids.forEach(function(id) {
|
||||||
|
if (event.target.matches(quantity_lineRegexSelector)) {
|
||||||
|
calculateSubtotal(id);
|
||||||
|
}
|
||||||
|
if (event.target.matches(price_lineRegexSelector)) {
|
||||||
|
calculateSubtotal(id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
17
tienda_ilusion/don_confiao/templates/don_confiao/base.html
Normal file
17
tienda_ilusion/don_confiao/templates/don_confiao/base.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="h-full">
|
||||||
|
<head>
|
||||||
|
<title>Don Confiao - Tienda la Ilusión</title>
|
||||||
|
</head>
|
||||||
|
<body class="flex h-full w-full">
|
||||||
|
<div id="menu" class="h-full w-2/12 border bg-green-400 max-h-screen overflow-auto">
|
||||||
|
{% include 'don_confiao/menu.html' %}
|
||||||
|
</div>
|
||||||
|
<div id="content" class="w-10/12 h-screen max-h-full overflow-auto">
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.tailwindcss.com/"></script>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% if form.is_multipart %}
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
{% else %}
|
||||||
|
<form method="post">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<input type="submit" value="Importar">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
{% if form.is_multipart %}
|
{% if form.is_multipart %}
|
||||||
<form enctype="multipart/form-data" method="post">
|
<form enctype="multipart/form-data" method="post">
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -8,3 +10,4 @@
|
|||||||
{{ form }}
|
{{ form }}
|
||||||
<input type="submit" value="Importar">
|
<input type="submit" value="Importar">
|
||||||
</form>
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
<li><a href='./comprar'>Comprar</a></li>
|
<li><a href='./comprar'>Comprar</a></li>
|
||||||
<li><a href='./productos'>Productos</a></li>
|
<li><a href='./productos'>Productos</a></li>
|
||||||
<li><a href='./importar_productos'>Importar Productos</a></li>
|
<li><a href='./importar_productos'>Importar Productos</a></li>
|
||||||
|
<li><a href='./importar_terceros'>Importar Terceros</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
17
tienda_ilusion/don_confiao/templates/don_confiao/menu.html
Normal file
17
tienda_ilusion/don_confiao/templates/don_confiao/menu.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% load static %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/main_menu.css' %}">
|
||||||
|
|
||||||
|
<div class="h-full flex flex-col justify-around shadow hover:shadow-lg">
|
||||||
|
<img class="w-full px-12" src="{% static 'img/recreo_logo.png' %}" alt="Recreo">
|
||||||
|
<nav id="main_menu">
|
||||||
|
<ul class="flex flex-col m-0 p-0 justify-center shadow hover:shadow-lg gap-y-12 items-center drop-shadow-lg">
|
||||||
|
<li><a href='/don_confiao/comprar' >Comprar</a></li>
|
||||||
|
<li><a href='/don_confiao/compras'>Compras</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_terceros'>Importar Terceros</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>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.tailwindcss.com/"></script>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
<form action="" method="get">
|
<form action="" method="get">
|
||||||
<label>Filtro por nombre:</label>
|
<label>Filtro por nombre:</label>
|
||||||
<input type="text" name="name" value="{{ request.GET.name }}">
|
<input type="text" name="name" value="{{ request.GET.name }}">
|
||||||
<button type="submit">Filtrar</button>
|
<button type="submit">Filtrar</button>
|
||||||
</form>
|
</form>
|
||||||
{% block content %}
|
|
||||||
<h1>Lista de productos</h1>
|
<h1>Lista de productos</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for obj in object_list %}
|
{% for obj in object_list %}
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
<!doctype html>
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<form id="complete_form_purchase" method="POST">
|
<script>
|
||||||
|
let listProducts = JSON.parse("{{ list_products|escapejs }}");
|
||||||
|
</script>
|
||||||
|
<div class="flex h-full">
|
||||||
|
<div class="h-full w-10/12 flex flex-col p-5">
|
||||||
|
<form id="complete_form_purchase" method="POST" class="h-10/12 w-full max-h-full overflow-auto">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ sale_form }}
|
|
||||||
{{ linea_formset.management_form }}
|
{{ linea_formset.management_form }}
|
||||||
<div id="formset-container">
|
<div id="formset-container" class="w-full">
|
||||||
{% for form in linea_formset %}
|
{% for form in linea_formset %}
|
||||||
<div class="form-container">
|
<div class="form-container flex justify-center ">
|
||||||
<table style="border: solid 1px blue; margin: 10px">
|
<table class="w-3/4 my-5 shadow-inner" style="border: solid 1px #178E79;">
|
||||||
{{ form.as_table }}
|
{{ form.as_table }}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<button id="add_line" type="button" onclick="add_line">Añadir Linea</button>
|
<div class="h-2/12 flex justify-center">
|
||||||
|
<button id="add_line" type="button" class="bg-yellow-400 shadow hover:shadow-lg py-2 px-5 rounded-full font-bold hover:bg-violet-200 ease-in duration-150">Añadir Linea</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-full w-3/12 bg-green-400 p-5 shadow hover:shadow-lg flex flex-col gap-y-3 font-semibold justify-around">
|
||||||
|
<p id="sale_resume_title" class="text-center decoration-solid font-mono font-bold text-xl page_title">Resumen de Venta</p>
|
||||||
|
{{ sale_form }}
|
||||||
{{ summary_form }}
|
{{ summary_form }}
|
||||||
<br/><button name="form" type="submit" >Comprar</button>
|
<button class="font-bold my-10 py-2 px-4 rounded-full bg-yellow-400 shadow hover:shadow-lg hover:bg-violet-200 ease-in duration-150" name="form" type="submit" >Comprar</button>
|
||||||
<script src="{% static 'js/add_line.js' %}"></script>
|
</div>
|
||||||
<script src="{% static 'js/sale_summary.js' %}"></script>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.tailwindcss.com/"></script>
|
||||||
|
<script src="{% static 'js/buy_general.js' %}"></script>
|
||||||
|
<script src="{% static 'js/add_line.js' %}"></script>
|
||||||
|
<script src="{% static 'js/sale_summary.js' %}"></script>
|
||||||
|
<script src="{% static 'js/calculate_subtotal_line.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Resumen de compra</h1>
|
||||||
|
<dl>
|
||||||
|
<dt>Date</dt> <dd>{{ purchase.date }}</dd>
|
||||||
|
<dt>ID</dt> <dd>{{ purchase.id }}</dd>
|
||||||
|
<dt>Customer</dt> <dd>{{ purchase.customer.name }}</dd>
|
||||||
|
<dt>Total</dt> <dd>{{ purchase.get_total }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
{% extends 'don_confiao/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
{% if purchases %}
|
{% if purchases %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for purchase in purchases %}
|
{% for purchase in purchases %}
|
||||||
<li><a href="/don_confiao/{{ purchase.id }}/">{{ purchase.date }}, {{ purchase.customer }}</a></li>
|
<li><a href="/don_confiao/resumen_compra/{{ purchase.id }}">{{ purchase.date }}, {{ purchase.customer }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No hay Compras</p>
|
<p>No hay Compras</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
{% 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 %}
|
|
||||||
47
tienda_ilusion/don_confiao/tests/Fixtures/sales_fixture.json
Normal file
47
tienda_ilusion/don_confiao/tests/Fixtures/sales_fixture.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "don_confiao.customer",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "Alejandro Fernandez",
|
||||||
|
"address": "Avenida Siempre Viva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "don_confiao.productcategory",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "Unidad"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "don_confiao.product",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "Papaya",
|
||||||
|
"price": 2500,
|
||||||
|
"measuring_unit": "Unidad"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "don_confiao.sale",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"customer": 1,
|
||||||
|
"date": "2024-08-31",
|
||||||
|
"phone": 312201103,
|
||||||
|
"description": "Primera Venta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "don_confiao.saleline",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"sale": 1,
|
||||||
|
"product": 1,
|
||||||
|
"quantity": 10,
|
||||||
|
"unit_price": 5000,
|
||||||
|
"description": "Primer Sale Line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
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)
|
||||||
59
tienda_ilusion/don_confiao/tests/test_api.py
Normal file
59
tienda_ilusion/don_confiao/tests/test_api.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import json
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from ..models import Sale, Product, Customer
|
||||||
|
|
||||||
|
|
||||||
|
class TestAPI(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.product = Product.objects.create(
|
||||||
|
name='Panela',
|
||||||
|
price=5000,
|
||||||
|
measuring_unit='UNIT'
|
||||||
|
)
|
||||||
|
self.customer = Customer.objects.create(
|
||||||
|
name='Camilo'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_sale(self):
|
||||||
|
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.customer.name,
|
||||||
|
self.customer.name
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
sale.id,
|
||||||
|
content['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_products(self):
|
||||||
|
url = '/don_confiao/api/products/'
|
||||||
|
response = self.client.get(url)
|
||||||
|
json_response = json.loads(response.content.decode('utf-8'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(self.product.name, json_response[0]['name'])
|
||||||
|
|
||||||
|
def test_get_customers(self):
|
||||||
|
url = '/don_confiao/api/customers/'
|
||||||
|
response = self.client.get(url)
|
||||||
|
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]
|
|
||||||
22
tienda_ilusion/don_confiao/tests/test_buy_form.py
Normal file
22
tienda_ilusion/don_confiao/tests/test_buy_form.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from django.test import Client, TestCase
|
||||||
|
from ..models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuyForm(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.product = Product()
|
||||||
|
self.product.name = "Arroz"
|
||||||
|
self.product.price = 5000
|
||||||
|
self.product.save()
|
||||||
|
|
||||||
|
def test_buy_contains_products_list(self):
|
||||||
|
response = self.client.get('/don_confiao/comprar')
|
||||||
|
self.assertIn(
|
||||||
|
self.product.name,
|
||||||
|
response.context['list_products']
|
||||||
|
)
|
||||||
|
content = response.content.decode('utf-8')
|
||||||
|
self.assertIn('5000', content)
|
||||||
|
self.assertIn('Arroz', content)
|
||||||
|
self.assertIn(str(self.product.id), content)
|
||||||
50
tienda_ilusion/don_confiao/tests/test_export_sales.py
Normal file
50
tienda_ilusion/don_confiao/tests/test_export_sales.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
from io import StringIO
|
||||||
|
import csv
|
||||||
|
|
||||||
|
|
||||||
|
class TestExportSales(TestCase):
|
||||||
|
fixtures = ['sales_fixture']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_export_sales(self):
|
||||||
|
sales_response = self._export_sales_csv()
|
||||||
|
filename = sales_response.headers[
|
||||||
|
'Content-Disposition'].split('; ')[1].strip('filename=').strip("'")
|
||||||
|
content = sales_response.content
|
||||||
|
content_str = content.decode('utf-8')
|
||||||
|
csv_file = StringIO(content_str)
|
||||||
|
header = next(csv.reader(csv_file))
|
||||||
|
|
||||||
|
self.assertGreater(len(content), 0)
|
||||||
|
self.assertEqual(filename, 'sales.csv')
|
||||||
|
self.assertEqual(sales_response.headers['Content-Type'], 'text/csv')
|
||||||
|
self.assertEqual(header, self._tryton_sale_header())
|
||||||
|
|
||||||
|
def _export_sales_csv(self):
|
||||||
|
return self.client.get("/don_confiao/exportar_ventas_para_tryton")
|
||||||
|
|
||||||
|
def _tryton_sale_header(self):
|
||||||
|
return [
|
||||||
|
"Tercero",
|
||||||
|
"Dirección de facturación",
|
||||||
|
"Dirección de envío",
|
||||||
|
"Descripción",
|
||||||
|
"Referencia",
|
||||||
|
"Fecha venta",
|
||||||
|
"Plazo de pago",
|
||||||
|
"Almacén",
|
||||||
|
"Moneda",
|
||||||
|
"Líneas/Producto",
|
||||||
|
"Líneas/Cantidad",
|
||||||
|
"Líneas/Precio unitario",
|
||||||
|
"Líneas/Unidad",
|
||||||
|
"Empresa",
|
||||||
|
"Tienda",
|
||||||
|
"Terminal de venta",
|
||||||
|
"Autorecogida",
|
||||||
|
"Comentario"
|
||||||
|
]
|
||||||
264
tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py
Normal file
264
tienda_ilusion/don_confiao/tests/test_jar_reconciliation.py
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
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):
|
||||||
|
response = self._create_reconciliation_with_purchase()
|
||||||
|
|
||||||
|
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 test_list_reconciliations(self):
|
||||||
|
self._create_simple_reconciliation()
|
||||||
|
self._create_simple_reconciliation()
|
||||||
|
|
||||||
|
url = '/don_confiao/api/reconciliate_jar/'
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = json.loads(response.content.decode('utf-8'))
|
||||||
|
self.assertEqual(2, content['count'])
|
||||||
|
self.assertEqual(2, len(content['results']))
|
||||||
|
self.assertEqual('2024-07-30T00:00:00Z',
|
||||||
|
content['results'][0]['date_time'])
|
||||||
|
|
||||||
|
def test_list_reconciliations_pagination(self):
|
||||||
|
self._create_simple_reconciliation()
|
||||||
|
self._create_simple_reconciliation()
|
||||||
|
|
||||||
|
url = '/don_confiao/api/reconciliate_jar/?page=2&page_size=1'
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = json.loads(response.content.decode('utf-8'))
|
||||||
|
self.assertEqual(1, len(content['results']))
|
||||||
|
self.assertEqual('2024-07-30T00:00:00Z',
|
||||||
|
content['results'][0]['date_time'])
|
||||||
|
|
||||||
|
def test_get_single_reconciliation(self):
|
||||||
|
createResponse = self._create_reconciliation_with_purchase()
|
||||||
|
reconciliationId = json.loads(
|
||||||
|
createResponse.content.decode('utf-8')
|
||||||
|
)['id']
|
||||||
|
self.assertGreater(reconciliationId, 0)
|
||||||
|
|
||||||
|
url = f'/don_confiao/api/reconciliate_jar/{reconciliationId}/'
|
||||||
|
response = self.client.get(url, content_type='application/json')
|
||||||
|
content = json.loads(
|
||||||
|
response.content.decode('utf-8')
|
||||||
|
)
|
||||||
|
self.assertEqual(reconciliationId, content['id'])
|
||||||
|
self.assertGreater(len(content['Sales']), 0)
|
||||||
|
self.assertIn(
|
||||||
|
self.purchase.id,
|
||||||
|
[sale['id'] for sale in content['Sales']]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
'CASH',
|
||||||
|
[sale['payment_method'] for sale in content['Sales']]
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_simple_reconciliation(self):
|
||||||
|
reconciliation = ReconciliationJar()
|
||||||
|
reconciliation.date_time = "2024-07-30"
|
||||||
|
reconciliation.total_cash_purchases = 0
|
||||||
|
reconciliation.cash_taken = 0
|
||||||
|
reconciliation.cash_discrepancy = 0
|
||||||
|
reconciliation.clean()
|
||||||
|
reconciliation.save()
|
||||||
|
return reconciliation
|
||||||
|
|
||||||
|
def _create_reconciliation_with_purchase(self):
|
||||||
|
url = '/don_confiao/reconciliate_jar'
|
||||||
|
total_purchases = (11 * 72500) + (27 * 72500)
|
||||||
|
data = {
|
||||||
|
'date_time': '2024-12-02T21:07',
|
||||||
|
'reconcilier': 'carlos',
|
||||||
|
'total_cash_purchases': total_purchases,
|
||||||
|
'cash_taken': total_purchases,
|
||||||
|
'cash_discrepancy': 0,
|
||||||
|
'cash_purchases': [
|
||||||
|
self.purchase.id,
|
||||||
|
self.purchase2.id,
|
||||||
|
self.purchase.id,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return self.client.post(url, data=json.dumps(data).encode('utf-8'),
|
||||||
|
content_type='application/json')
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
from ..models import Customer
|
from ..models import Customer
|
||||||
|
|
||||||
|
|
||||||
@@ -12,3 +14,8 @@ class TestCustomer(TestCase):
|
|||||||
customer.save()
|
customer.save()
|
||||||
|
|
||||||
self.assertIsInstance(customer, Customer)
|
self.assertIsInstance(customer, Customer)
|
||||||
|
|
||||||
|
def test_don_create_customer_without_name(self):
|
||||||
|
customer = Customer()
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
customer.save()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user