3756 lines
137 KiB
Python
3756 lines
137 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: UTF-8 -*-
|
|
import sys
|
|
import os
|
|
import logging
|
|
import base64
|
|
import time
|
|
import traceback
|
|
from decimal import Decimal
|
|
from datetime import datetime, timedelta
|
|
from collections import OrderedDict
|
|
from PySide6 import QtGui
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from PySide6.QtWidgets import (
|
|
QLabel, QHBoxLayout, QVBoxLayout,
|
|
QWidget)
|
|
from app.commons.image import Image
|
|
from .frontwindow import FrontWindow
|
|
from .tools import get_icon, to_float, to_numeric, compare_versions
|
|
from .status_bar import StatusBar
|
|
from .stack_messages import StackMessages
|
|
# from app.commons.action import Action
|
|
from app.commons.forms import GridForm, ComboBox, FieldNumeric
|
|
from app.commons.messages import MessageBar
|
|
from app.commons.table import TableView
|
|
from app.commons.model import TableModel
|
|
from app.commons.menu_buttons import MenuDash
|
|
# from .localdb import LocalDB
|
|
from .reporting import Receipt
|
|
from .proxy import Report
|
|
from .buttonpad import ButtonsStacked, ButtonsFunction, StartButtons
|
|
from .states import STATES, RE_SIGN
|
|
from .commons.custom_button import CustomButton
|
|
from .threads import DoInvoice
|
|
from .store import StoreView
|
|
from .constants import (
|
|
PATH_PRINTERS, DELTA_LOCALE, STRETCH, alignRight, alignLeft, alignCenter,
|
|
alignHCenter, DIALOG_REPLY_NO, ZERO, RATE_CREDIT_LIMIT, CONVERSION_DIGITS,
|
|
SALE_FIELDS, KIND_REST, KIND_RETAIL, DIALOG_REPLY_YES,
|
|
)
|
|
from .dialogs import DialogDeliveryParty
|
|
|
|
from sync_scale import read_files_scale
|
|
|
|
invoice_type = []
|
|
|
|
_SALE_HISTORIC = [
|
|
'party.name', 'sale_date', 'number', 'invoice_number', 'consumer.name',
|
|
'total_amount_cache', 'lines.quantity', 'lines.product.name', 'lines.unit.symbol',
|
|
'lines.amount', 'lines.unit_price_w_tax', 'lines.amount_w_tax',
|
|
'lines.note'
|
|
]
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AppWindow(FrontWindow):
|
|
|
|
def __init__(self, context, params):
|
|
# default_params of configpos.ini is defined in app/commons/config.py
|
|
title = "PRESIK | SMART POS"
|
|
self.data_expenses = None
|
|
super(AppWindow, self).__init__(context, params, title)
|
|
self.payment_ctx = {}
|
|
self.set_keys()
|
|
StackMessages(self)
|
|
self.stock_context = None
|
|
self.sale_id = None
|
|
self.ctx = context
|
|
self.ctx['params'] = params
|
|
self.get_defaults_variables()
|
|
response = self.load_modules()
|
|
if response is not True:
|
|
d = self.dialog(response)
|
|
d.exec_()
|
|
super(AppWindow, self).close()
|
|
return
|
|
self.setup_sale_line()
|
|
self.setup_delivery_party()
|
|
if self.environment == 'restaurant':
|
|
self.setup_sale_consumer()
|
|
self.setup_payment()
|
|
# self.set_domains()
|
|
|
|
self.create_gui()
|
|
self.message_bar.load_stack(self.stack_msg)
|
|
self.journal = {}
|
|
self.active_usb_printers = []
|
|
|
|
if os.name == 'posix' and os.path.exists(PATH_PRINTERS):
|
|
self.set_printers_usb(PATH_PRINTERS)
|
|
|
|
self.set_printing_context()
|
|
self.statusbar = StatusBar(self)
|
|
# self._clear_invoice_number = False
|
|
# self.reader_thread = None
|
|
# self._current_line_id = None
|
|
# self._amount_text = ''
|
|
# self._input_text = ''
|
|
# self._sign = None
|
|
self.create_dialogs()
|
|
self.store = StoreView(self, SALE_FIELDS)
|
|
if self.active_weighing:
|
|
from .electronic_scale import ScaleReader
|
|
self.reader_thread = ScaleReader()
|
|
self.reader_thread.sigSetWeight.connect(self.set_weight_readed)
|
|
|
|
if self.server_printer and self.environment == 'restaurant':
|
|
timer = QTimer()
|
|
timer.timeout.connect(self.verify_print_order_automatic)
|
|
timer.start(30000)
|
|
|
|
self.do_invoice = DoInvoice(self, self.ctx)
|
|
self.do_invoice.sigDoInvoice.connect(self.__do_invoice_thread)
|
|
self.set_state('disabled')
|
|
self.change_view_to('start_front')
|
|
|
|
matching_journal = next((j for j in self._journals if j['id'] == self.default_journal['id']), None)
|
|
if matching_journal:
|
|
self.default_journal = matching_journal
|
|
|
|
def get_defaults_variables(self):
|
|
self._clear_invoice_number = False
|
|
self.reader_thread = None
|
|
self._current_line_id = None
|
|
self._amount_text = ''
|
|
self._input_text = ''
|
|
self._sign = None
|
|
self._sale_line = None
|
|
self.field_agent = None
|
|
|
|
def verify_print_order_automatic(self):
|
|
args = {'shop': self.shop['id']}
|
|
orders = []
|
|
tasks = []
|
|
version = compare_versions(self.modules['sale_pos_frontend_rest']['version'], '6.0.7')
|
|
print('pasa por esta new query')
|
|
if version in ('equal', 'higher'):
|
|
if self.tasks_station:
|
|
result = self.SaleLine.get_data_command_and_task(args)
|
|
orders = result['orders']
|
|
tasks = result['tasks']
|
|
else:
|
|
orders = self.SaleLine.get_data_command(args)
|
|
|
|
# TODO: for remove this option
|
|
else:
|
|
logger.warning('The backend sale_pos_frontend_rest is deprecation, update version')
|
|
records = self.Sale.get_orders_to_command(args)
|
|
for record in records:
|
|
orders += record['orders'].values()
|
|
tasks += record['tasks'].values()
|
|
# print('ingresa a impresion orders')
|
|
# pprint(orders)
|
|
# print('ingresa a impresion tasks')
|
|
# pprint(tasks)
|
|
try:
|
|
result = self.receipt_order.print_orders(orders)
|
|
if result:
|
|
self.Sale.mark_commanded({'lines_ids': result})
|
|
except Exception:
|
|
traceback.print_exc()
|
|
|
|
try:
|
|
receipt = Receipt(context={}, environment='restaurant')
|
|
result_print = receipt.print_tasks(tasks)
|
|
if any(result_print):
|
|
print(result_print, 'validate print')
|
|
self.SaleLine.mark_tasks_printed({'task_ids': result_print})
|
|
except Exception:
|
|
traceback.print_exc()
|
|
# timer = QTimer()
|
|
# timer.singleShot(30000, self.verify_print_order_automatic)
|
|
# self.verify_command_order_th.exit(0)
|
|
|
|
# def set_domains(self):
|
|
# self.domain_search_product = [
|
|
# ('code', '!=', None),
|
|
# ('active', '=', True),
|
|
# ('template.salable', '=', True),
|
|
# ('template.account_category', '!=', None),
|
|
# ]
|
|
|
|
# if self.shop['product_categories.']:
|
|
# self.domain_search_product.append(
|
|
# ('account_category', 'in', self.shop['product_categories.'])
|
|
# )
|
|
|
|
# def set_cache_company(self):
|
|
# self.local_db = LocalDB()
|
|
# self.local_db.create_table_config()
|
|
# self._local_config = self.local_db.get_config()
|
|
# if not self._local_config:
|
|
# company_id = self.company
|
|
# self._local_config = self.local_db.set_config([company_id])
|
|
|
|
# def set_cache_products(self):
|
|
# self.local_db.create_table_product()
|
|
# local_products = self.local_db.get_local_products()
|
|
|
|
# config = self.local_db.get_config()
|
|
# _sync_date = config[1]
|
|
# products = self.Product.sync_get_products({
|
|
# 'write_date': _sync_date,
|
|
# 'shop_id': self.ctx['shop']
|
|
# })
|
|
# self.local_db.update_products(products, local_products)
|
|
# now = datetime.now()
|
|
# self.local_db.set_config_sync(str(now))
|
|
|
|
def set_printers_usb(self, PATH_PRINTERS):
|
|
for usb_dev in os.listdir(PATH_PRINTERS):
|
|
# # fix me return for set printer default usb
|
|
# return
|
|
if 'lp' not in usb_dev:
|
|
continue
|
|
path_device = os.path.join(PATH_PRINTERS, usb_dev)
|
|
self.active_usb_printers.append(['usb', path_device])
|
|
|
|
def get_current_sale(self):
|
|
sale_id = self.sale_id
|
|
if sale_id:
|
|
sales = self.Sale.find([('id', '=', sale_id)],
|
|
fields=['state', 'number'])
|
|
if not sales:
|
|
return
|
|
return sales[0]
|
|
|
|
def get_price_lists(self):
|
|
price_lists = self.Pricelist.find([])
|
|
price_lists = [(ln['id'], ln['name']) for ln in price_lists]
|
|
return price_lists
|
|
|
|
def check_empty_sale(self):
|
|
sale = self.get_current_sale()
|
|
if sale and self.model_sale_lines.rowCount() == 0 \
|
|
and sale['state'] == 'draft' and not sale['number']:
|
|
self.delete_current_sale()
|
|
|
|
def delete_current_sale(self):
|
|
if self.sale_id:
|
|
args = {'id': self.sale_id}
|
|
self.Sale.cancel_sale(args)
|
|
|
|
def set_printing_context(self):
|
|
|
|
ctx_printing = self.Sale.get_printing_context(
|
|
{'device_id': self.device['id']})
|
|
ctx_printing['row_characters'] = self.row_characters
|
|
ctx_printing['delta_locale'] = DELTA_LOCALE
|
|
locale_logo = ''
|
|
if self.logo:
|
|
locale_logo = os.path.join(os.path.abspath(
|
|
os.path.dirname(__file__)), 'logo.png')
|
|
f = open(locale_logo, "wb")
|
|
f.write(base64.decodebytes(self.logo.encode()))
|
|
f.close()
|
|
|
|
self.receipt_sale = Receipt(
|
|
ctx_printing,
|
|
logo=locale_logo,
|
|
environment=self.environment
|
|
)
|
|
self.receipt_order = None
|
|
# Printing order context
|
|
if self.print_order:
|
|
self.receipt_order = Receipt(ctx_printing, environment=self.environment)
|
|
self.set_default_printer()
|
|
|
|
def set_default_printer(self, printer=None):
|
|
if self.active_usb_printers:
|
|
self.printer_sale_name = self.active_usb_printers[0]
|
|
if not printer and self.printer_sale_name:
|
|
printer = {
|
|
'interface': self.printer_sale_name[0],
|
|
'device': self.printer_sale_name[1],
|
|
'profile': self.profile_printer,
|
|
}
|
|
if printer:
|
|
self.receipt_sale.config_printer(printer)
|
|
|
|
def button_new_sale_pressed(self):
|
|
self.create_new_sale()
|
|
|
|
def button_send_to_pay_pressed(self):
|
|
# Return sale to draft state
|
|
if not self._check_quantity():
|
|
return
|
|
if not self.validate_payment_term():
|
|
return
|
|
sale_id = self.sale_id
|
|
# sale, = self.Sale.find([['id', '=', sale_id]], fields=['invoice_type', 'total_amount', 'reservation'])
|
|
sale = self.store.store
|
|
if sale['invoice_type'] == 'P' and self._config.get('uvt_pos') and sale['untaxed_amount'] > self._config['uvt_pos']:
|
|
return self.dialog('base_uvt_pos')
|
|
|
|
args = {'sale_id': sale_id}
|
|
result = self.Sale.to_quote(args)
|
|
self.store.set(result)
|
|
if sale.get('reservation'):
|
|
self.Sale.write([sale_id], {'reservation': False})
|
|
if self.field_agent and self.field_agent.text():
|
|
msg = 'Agente: ' + result.get('agent.').get('rec_name') + \
|
|
"\n Comision: " + str(result['commission']) + "%"
|
|
dialog = self.dialog('confirm_agent', response=True, widgets=[], extra_message=msg)
|
|
response = dialog.exec_()
|
|
if response == DIALOG_REPLY_NO:
|
|
self.Sale.write([sale_id], {'state': 'draft'})
|
|
return
|
|
if self.model_sale_lines.rowCount() > 0:
|
|
self.check_salesman()
|
|
res = self.action_send_order(sale_id=sale_id, init_view=False)
|
|
|
|
try:
|
|
if res:
|
|
self.Sale.generate_shipment({'sale_id': sale_id})
|
|
self.dialog('order_dispatched')
|
|
self.see_start_front()
|
|
else:
|
|
self.Sale.write([sale_id], {'state': 'draft'})
|
|
|
|
except Exception:
|
|
self.dialog('error_order_dispatched')
|
|
|
|
def button_to_force_draft_pressed(self):
|
|
# Return sale to draft state force draft
|
|
if self.sale_id:
|
|
args = {'sale_id': self.sale_id}
|
|
self.Sale.to_draft(args)
|
|
self.see_start_front()
|
|
|
|
def button_to_draft_pressed(self):
|
|
# Return sale to draft state when state quotation
|
|
self.store.update({'state': 'draft'})
|
|
# self.Sale.button_method('draft', [self.sale_id])
|
|
# self._sale['state'] = 'draft'
|
|
self.buttons_function.button_to_draft.hide()
|
|
self.buttons_function.button_to_quote.show()
|
|
|
|
def button_to_quote_pressed(self):
|
|
sale_id = self.sale_id
|
|
self.Sale.button_method('quote', [sale_id])
|
|
self.Sale.write([sale_id], {'reservation': True})
|
|
self.store.set({'state': 'quotation'})
|
|
self.buttons_function.button_to_quote.hide()
|
|
self.buttons_function.button_to_draft.show()
|
|
|
|
def create_gui(self):
|
|
panels = QHBoxLayout()
|
|
panels.setSpacing(0)
|
|
panels.setContentsMargins(0, 0, 0, 0)
|
|
|
|
panel_left = QWidget()
|
|
panel_left.setObjectName('panel_left')
|
|
vbox_left = QVBoxLayout()
|
|
vbox_left.setSpacing(0)
|
|
vbox_left.setContentsMargins(0, 0, 0, 0)
|
|
|
|
if self.screen_size == 'small':
|
|
max_size = 620
|
|
else:
|
|
max_size = 1300
|
|
panel_left.setMaximumWidth(max_size)
|
|
panel_left.setLayout(vbox_left)
|
|
panel_left.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.panel_right = QVBoxLayout()
|
|
self.panel_right.setSpacing(4)
|
|
self.panel_right.setObjectName('panel_right')
|
|
self.panel_right.setContentsMargins(3, 0, 3, 0)
|
|
|
|
left_head = QHBoxLayout()
|
|
left_head.setSpacing(4)
|
|
left_invoice = QHBoxLayout()
|
|
left_invoice.setSpacing(4)
|
|
|
|
board = QVBoxLayout()
|
|
board.setSpacing(0)
|
|
|
|
# CREATE START FRONT
|
|
self.start_front = QWidget()
|
|
self.start_front.setObjectName('start_front')
|
|
start_front = StartButtons(self)
|
|
self.start_front.setLayout(start_front)
|
|
|
|
# CREATE ORDER FRONT
|
|
self.order_front = QWidget()
|
|
self.order_front.setObjectName('order_front')
|
|
vbox_order_front = QVBoxLayout()
|
|
vbox_order_front.setSpacing(4)
|
|
vbox_order_front.addLayout(left_head)
|
|
vbox_order_front.addLayout(left_invoice)
|
|
self.order_front.setLayout(vbox_order_front)
|
|
|
|
# CONFIGURE BOARD
|
|
board.addWidget(self.start_front, 0)
|
|
self.WidgetShop = QWidget()
|
|
self.WidgetShop.setObjectName('label_shop')
|
|
vbox_label = QVBoxLayout()
|
|
vbox_label.setAlignment(Qt.AlignBottom)
|
|
LabelShop = QLabel(self.WidgetShop)
|
|
LabelShop.setObjectName('LabelShop')
|
|
LabelShop.setText(self.shop['name'])
|
|
LabelShop.setStyleSheet(
|
|
"""
|
|
font: 30px; color: grey;
|
|
font-weight: bold;
|
|
min-height: 60px;
|
|
max-height: 90px""")
|
|
vbox_label.addWidget(LabelShop)
|
|
self.WidgetShop.setLayout(vbox_label)
|
|
board.addWidget(self.WidgetShop, 2)
|
|
board.addWidget(self.order_front, 0)
|
|
|
|
# LEFT HEAD COMPONENTS
|
|
start_front = CustomButton(
|
|
self,
|
|
id='button_start',
|
|
size=self.screen_size,
|
|
icon=get_icon('start_front'),
|
|
title='INICIO',
|
|
method='see_start_front',
|
|
name_style='start',
|
|
)
|
|
self.message_bar = MessageBar()
|
|
self.field_amount = FieldNumeric(self, 'amount', {})
|
|
self.field_amount.setObjectName('field_amount')
|
|
self.field_sign = QLabel(' ')
|
|
self.field_sign.setObjectName('field_sign')
|
|
|
|
left_head.addWidget(start_front, 0)
|
|
left_head.addLayout(self.message_bar, 1)
|
|
left_head.addWidget(self.field_sign, 0)
|
|
left_head.addWidget(self.field_amount, 1)
|
|
|
|
# LEFT INVOICE COMPONENTS
|
|
self.label_input = QLabel()
|
|
self.label_input.setFocus()
|
|
self.label_input.setObjectName('label_input')
|
|
size_input = 100
|
|
if self.screen_size == 'large':
|
|
size_input = 300
|
|
self.label_input.setMaximumWidth(size_input)
|
|
|
|
if self.shop.get('pos_authorization'):
|
|
invoice_type.append(('P', ('POS')))
|
|
|
|
if self.shop.get('electronic_authorization'):
|
|
invoice_type.append(('1', ('VENTA ELECTRONICA')))
|
|
|
|
if self.shop.get('manual_authorization'):
|
|
invoice_type.append(('M', ('MANUAL')))
|
|
|
|
self.field_invoice_type = ComboBox(self, 'invoice_type', {
|
|
'values': invoice_type,
|
|
'on_change': 'action_invoice_type_selection_changed'
|
|
})
|
|
|
|
left_invoice.addWidget(self.label_input, 1)
|
|
left_invoice.addWidget(self.field_invoice_type, 1)
|
|
|
|
self.field_invoice_number = QLabel('')
|
|
self.field_invoice_number.setObjectName('field_invoice_number')
|
|
left_invoice.addWidget(self.field_invoice_number, 1)
|
|
|
|
label_date = QLabel('FECHA:')
|
|
label_date.setObjectName('label_sale_date')
|
|
self.field_sale_date = QLabel('')
|
|
self.field_sale_date.setObjectName('field_sale_date')
|
|
left_invoice.addWidget(label_date)
|
|
left_invoice.addWidget(self.field_sale_date, 0)
|
|
|
|
# LEFT TABLE COMPONENTS
|
|
self.buttons_function = ButtonsFunction(self)
|
|
|
|
if self.environment == 'restaurant':
|
|
KIND = KIND_REST
|
|
else:
|
|
KIND = KIND_RETAIL
|
|
screen = self.screen_size
|
|
label_color = self.label_color
|
|
|
|
info_fields = [
|
|
('party', {
|
|
'name': 'CLIENTE',
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
}),
|
|
('kind', {
|
|
'name': 'CLASE',
|
|
'placeholder': False,
|
|
'type': 'selection',
|
|
'on_change': 'action_kind_selection_changed',
|
|
'values': KIND,
|
|
'size': screen,
|
|
'color': label_color
|
|
}),
|
|
('salesman', {
|
|
'name': 'VENDEDOR',
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
}),
|
|
('number', {
|
|
'name': 'No ORDEN',
|
|
'placeholder': False,
|
|
'readonly': True,
|
|
'size': screen,
|
|
'color': label_color
|
|
}),
|
|
('payment_term', {
|
|
'name': 'FORMA DE PAGO',
|
|
'readonly': True,
|
|
'invisible': False,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
}),
|
|
('delivery_party', {
|
|
'name': 'DOMICILIARIO',
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
})
|
|
]
|
|
info_fields_append = info_fields.append
|
|
if self.environment != 'restaurant' and self._config.get('use_price_list'):
|
|
info_fields_append(
|
|
('list_price', {
|
|
'name': 'LISTA DE PRECIOS',
|
|
'placeholder': False,
|
|
'type': 'selection',
|
|
'values': self.get_price_lists(),
|
|
'default': self.shop['price_list.'],
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
|
|
self.sources = self.Source.find([])
|
|
info_fields_append(
|
|
('source', {
|
|
'name': 'CANAL',
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
|
|
if self._config['show_position_pos']:
|
|
info_fields_append(('position', {
|
|
'name': 'POSICION',
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
if self._commission_activated and self._config['show_agent_pos']:
|
|
info_fields_append(('agent', {
|
|
'name': 'AGENTE',
|
|
'placeholder': False,
|
|
'readonly': True,
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
|
|
_cols = 2
|
|
self.field_delivery_charge = None
|
|
if self._config['show_delivery_charge']:
|
|
info_fields_append(
|
|
('delivery_charge', {
|
|
'name': 'PAGO DOMICILIO',
|
|
'placeholder': False,
|
|
'type': 'selection',
|
|
'on_change': 'action_delivery_charge_selection_changed',
|
|
'values': [
|
|
('', ''),
|
|
('customer', 'CLIENTE'),
|
|
('company', 'COMPAÑIA'),
|
|
],
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
|
|
self.field_table_assigned = None
|
|
if self._sale_pos_restaurant:
|
|
info_fields_append(
|
|
('consumer', {
|
|
'name': 'CONSUMIDOR',
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color,
|
|
'readonly': True,
|
|
}))
|
|
|
|
PAYMENT_METHODS = [
|
|
('', ''),
|
|
('cash', 'EFECTIVO'),
|
|
('terminal', 'DATAFONO'),
|
|
('all_paid', 'TODO PAGO'),
|
|
('partial_paid', 'PAGO PARCIAL'),
|
|
]
|
|
info_fields_append(
|
|
('payment_method', {
|
|
'name': 'MEDIO DE PAGO',
|
|
'placeholder': False,
|
|
'type': 'selection',
|
|
'on_change': 'action_payment_method_selection_changed',
|
|
'values': PAYMENT_METHODS,
|
|
'size': screen,
|
|
'color': label_color
|
|
}))
|
|
|
|
info_fields_append(
|
|
('table_assigned', {
|
|
'name': 'MESA',
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color,
|
|
'readonly': True,
|
|
}))
|
|
|
|
info_fields_append(
|
|
('order_status', {
|
|
'name': 'ESTADO',
|
|
'placeholder': False,
|
|
'size': screen,
|
|
'color': label_color,
|
|
'translate': True,
|
|
'readonly': True,
|
|
}))
|
|
|
|
col_sizes_tlines = [field['width'] for field in self.fields_sale_line]
|
|
self.table_sale_lines = TableView(
|
|
'model_sale_lines',
|
|
self.model_sale_lines, col_sizes_tlines,
|
|
method_selected_row=self.sale_line_selected
|
|
)
|
|
|
|
for i, f in enumerate(self.model_sale_lines._fields, 0):
|
|
if f.get('invisible'):
|
|
self.table_sale_lines.hideColumn(i)
|
|
|
|
invisible = False
|
|
if self.environment == 'restaurant':
|
|
invisible = True
|
|
_fields_amounts = [
|
|
('untaxed_amount', {
|
|
'name': 'SUBTOTAL',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': label_color,
|
|
'invisible': invisible,
|
|
}),
|
|
('tax_amount', {
|
|
'name': 'IMPUESTOS',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': label_color,
|
|
'invisible': invisible,
|
|
}),
|
|
('discount', {
|
|
'name': 'DESCUENTOS',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': label_color,
|
|
'invisible': invisible,
|
|
}),
|
|
('total_amount', {
|
|
'name': 'TOTAL',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': 'black',
|
|
'font_size': 'big',
|
|
}),
|
|
('paid_amount', {
|
|
'name': 'PAGADO',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': label_color,
|
|
}),
|
|
('change', {
|
|
'name': 'PENDIENTE',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': self.label_color_2 or 'orange'
|
|
})
|
|
]
|
|
_fields_amounts_insert = _fields_amounts.insert
|
|
if self.environment == 'restaurant':
|
|
_fields_amounts_insert(3, ('tip_amount', {
|
|
'name': 'VR. PROPINA',
|
|
'type': 'money',
|
|
'readonly': True,
|
|
'size': screen,
|
|
'color': label_color,
|
|
}))
|
|
_fields_amounts_insert(4, ('delivery_amount', {
|
|
'name': 'VR. DOMIC',
|
|
'type': 'money',
|
|
'readonly': True,
|
|
'size': screen,
|
|
'color': label_color,
|
|
}))
|
|
_fields_amounts_insert(6, ('net_amount', {
|
|
'name': 'NETO TOTAL',
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': screen,
|
|
'color': 'black',
|
|
'font_size': 'big',
|
|
}))
|
|
fields_amounts = OrderedDict(_fields_amounts)
|
|
self.grid_amounts = GridForm(self, fields_amounts, col=1)
|
|
self.buttons_stacked = ButtonsStacked(self)
|
|
|
|
self.table_payment = TableView(
|
|
'table_payment', self.table_payment_lines, [250, STRETCH])
|
|
|
|
self.grid_info = GridForm(
|
|
self,
|
|
OrderedDict(info_fields),
|
|
col=_cols,
|
|
size=screen
|
|
)
|
|
|
|
vbox_order_front.addWidget(self.table_sale_lines, 1)
|
|
vbox_left.addLayout(board, 0)
|
|
|
|
if self.environment == 'restaurant':
|
|
self.panel_right_box = QWidget()
|
|
# values = self.get_product_by_categories_dash()
|
|
values = self.product_categories
|
|
|
|
self.menu_dash = MenuDash(self, values, 'on_selected_item')
|
|
self.order_front.hide()
|
|
vbox_order_front.addLayout(self.buttons_function, 0)
|
|
vbox_order_front.addLayout(self.grid_info, 0)
|
|
self.panel_right.addWidget(self.menu_dash, 1)
|
|
_panel_right_box = QVBoxLayout()
|
|
_panel_right_box.addLayout(self.grid_amounts, 0)
|
|
_panel_right_box.addWidget(self.buttons_stacked, 0)
|
|
self.panel_right_box.setLayout(_panel_right_box)
|
|
self.panel_right.addWidget(self.panel_right_box)
|
|
else:
|
|
vbox_left.addLayout(self.grid_info, 1)
|
|
self.panel_right.addLayout(self.grid_amounts, 1)
|
|
self.panel_right.addLayout(self.buttons_function, 1)
|
|
self.panel_right.addWidget(self.buttons_stacked, 0)
|
|
self.panel_right.addWidget(self.table_payment)
|
|
|
|
panels.addWidget(panel_left, 1)
|
|
panels.addLayout(self.panel_right, 0)
|
|
widget = QWidget()
|
|
widget.setLayout(panels)
|
|
self.setCentralWidget(widget)
|
|
|
|
self.order_number = QLabel('')
|
|
self.order_number.setObjectName('label_number_notification')
|
|
self.order_number.setAlignment(Qt.AlignCenter | Qt.AlignVCenter)
|
|
|
|
def show_right_panel(self, show):
|
|
if self.environment == 'restaurant':
|
|
if show:
|
|
self.panel_right_box.show()
|
|
else:
|
|
self.panel_right_box.hide()
|
|
|
|
def button_plus_pressed(self):
|
|
error = False
|
|
if self._input_text == '' and self._amount_text == '0':
|
|
return
|
|
if self._state in ('paid', 'disabled'):
|
|
return
|
|
if self._sign in ('*', '-', '/'):
|
|
self._sale_line = self.table_sale_lines.get_selected_clicked()
|
|
if self._sale_line and self._sale_line.get('type') \
|
|
and self._state == 'add' \
|
|
and self.model_sale_lines.rowCount() > 0:
|
|
self._current_line_id = self._sale_line['id']
|
|
if self._sign == '*':
|
|
self._process_quantity(self._amount_text)
|
|
else:
|
|
error = not (self._process_price(self._amount_text))
|
|
sale_line, = self.SaleLine.find([
|
|
('id', '=', self._sale_line['id'])
|
|
])
|
|
self.model_sale_lines.update_record(sale_line)
|
|
elif self._state in ['add', 'cancel', 'accept']:
|
|
self.clear_right_panel()
|
|
self.add_product(code=self._input_text)
|
|
elif self._state == 'payment':
|
|
is_paid = self._process_pay(self.field_amount.get_value())
|
|
if not is_paid:
|
|
self.set_state('payment')
|
|
self.clear_input_text()
|
|
self.clear_amount_text()
|
|
return
|
|
if self.environment == 'restaurant':
|
|
self.set_state('finished')
|
|
else:
|
|
self.set_state('add')
|
|
else:
|
|
logging.warning('Unknown command/text')
|
|
self._clear_context(error)
|
|
|
|
def see_start_front(self):
|
|
self.change_view_to('start_front')
|
|
|
|
def change_view_to(self, to_view):
|
|
self.field_salesman_code_ask.setText('')
|
|
if to_view == 'start_front':
|
|
self.print_no_commanded()
|
|
self.salesman = {}
|
|
self.order_front.hide()
|
|
self.start_front.show()
|
|
if hasattr(self, 'WidgetShop'):
|
|
self.WidgetShop.show()
|
|
self.set_state('disabled')
|
|
self.buttons_stacked.hide()
|
|
self.buttons_function.hide_buttons()
|
|
self._sale = {}
|
|
else:
|
|
self.start_front.hide()
|
|
if hasattr(self, 'WidgetShop'):
|
|
self.WidgetShop.hide()
|
|
self.order_front.show()
|
|
self.buttons_stacked.show()
|
|
self.buttons_function.show_buttons()
|
|
if self.environment == 'retail' and self.type_pos_user in ('order', 'salesman'):
|
|
self.buttons_function.button_change_salesman.hide()
|
|
|
|
def action_read_weight(self):
|
|
print('si hace action_read_weight')
|
|
self.reader_thread.start()
|
|
|
|
def set_weight_readed(self):
|
|
if not self.reader_thread or not self.reader_thread.best_weight:
|
|
return
|
|
|
|
if self.reader_thread.best_weight:
|
|
self.amount_text_changed(self.reader_thread.best_weight)
|
|
self._process_quantity(self._amount_text)
|
|
self._clear_context(False)
|
|
self.reader_thread.fZERO()
|
|
|
|
def _clear_context(self, error=False):
|
|
self.clear_input_text()
|
|
self.clear_amount_text()
|
|
self.clear_sign()
|
|
|
|
if self.state == 'disabled':
|
|
self.message_bar.set('not_sale')
|
|
elif self._state not in ('warning', 'checkout', 'payment') and not error:
|
|
self.message_bar.set('system_ready')
|
|
else:
|
|
self.set_state('add')
|
|
|
|
def _process_quantity(self, text):
|
|
eval_value = text.replace(',', '.')
|
|
rec = {}
|
|
try:
|
|
quantity = Decimal(eval_value)
|
|
product_id = self._sale_line['product.']['id']
|
|
if product_id and self.stock_context:
|
|
_product, = self.Product.find(
|
|
[('id', '=', product_id)],
|
|
ctx=self.stock_context
|
|
)
|
|
if _product and 'quantity' in _product:
|
|
if not self._check_stock_quantity(_product, quantity):
|
|
return False
|
|
if self._current_line_id:
|
|
self._sale_line['quantity'] = float(quantity)
|
|
args = {
|
|
'id': self._sale_line['id'],
|
|
'quantity': quantity,
|
|
}
|
|
# if self._config.get('use_price_list'):
|
|
# args['use_price_list'] = True
|
|
ctx = None
|
|
if hasattr(self, 'field_list_price') and self._config.get('use_price_list'):
|
|
ctx = {'price_list': self.field_list_price.get_id()}
|
|
rec = self.SaleLine.faster_set_quantity(args, ctx=ctx)
|
|
except Exception as e:
|
|
print(e)
|
|
return self.message_bar.set('quantity_not_valid')
|
|
|
|
self.message_bar.set('system_ready')
|
|
self.model_sale_lines.update_record(rec)
|
|
self.set_amounts()
|
|
|
|
def _process_price(self, text, line=None):
|
|
discount_valid = True
|
|
eval_value = text.replace(',', '')
|
|
value = float(eval_value)
|
|
if not line:
|
|
line_id = self._current_line_id
|
|
else:
|
|
line_id = line['id']
|
|
|
|
if not self.allow_discount_handle:
|
|
discount_valid = False
|
|
else:
|
|
if self._sign == '-':
|
|
if self.discount_method == 'percentage' and value > 90:
|
|
discount_valid = False
|
|
else:
|
|
discount_valid = self.set_discount(eval_value)
|
|
elif self._sign == '/':
|
|
if value <= 0:
|
|
return
|
|
sale_line, = self.SaleLine.find([
|
|
('id', '=', line_id)
|
|
])
|
|
price_w_tax = float(str(round(sale_line['product.']['sale_price_taxed'], 0)))
|
|
if price_w_tax <= value:
|
|
discount_valid = self.set_unit_price(value, discount=False)
|
|
else:
|
|
eval_value = price_w_tax - value
|
|
discount_valid = self.set_discount(eval_value, type_='fixed')
|
|
if not discount_valid:
|
|
self.message_bar.set('discount_not_valid')
|
|
return False
|
|
|
|
self.message_bar.set('system_ready')
|
|
self.set_amounts()
|
|
return True
|
|
|
|
def _process_pay(self, text):
|
|
if not self.validate_done_sale():
|
|
return
|
|
cash_received = Decimal(text)
|
|
|
|
if self._commission_activated and self.environment == 'retail':
|
|
if not self.journal:
|
|
self.journal = self.default_journal
|
|
if self.journal['kind'] == 'payment':
|
|
agent_id = None
|
|
if self.field_agent_id:
|
|
agent_id = self.field_agent_id
|
|
else:
|
|
agent_id = self.field_agent_ask.get_id()
|
|
|
|
if not agent_id:
|
|
self.message_bar.set('missing_agent')
|
|
return False
|
|
|
|
res = self.add_payment(cash_received)
|
|
if not res:
|
|
return
|
|
if res.get('msg') == 'statement_closed':
|
|
self.dialog('statement_closed')
|
|
return False
|
|
|
|
if res['residual_amount'] <= 0:
|
|
self.grid_amounts.label_change.setText('CAMBIO')
|
|
self._done_sale()
|
|
return True
|
|
|
|
self.set_amounts()
|
|
# for j in self._journals:
|
|
# if j['id'] == self.default_journal['id']:
|
|
self.journal = self.default_journal
|
|
|
|
if res['msg'] == 'missing_money':
|
|
self.message_bar.set('enter_payment', self.default_journal['name'])
|
|
return False
|
|
|
|
if res['change'] < ZERO:
|
|
self.message_bar.set('enter_payment', self.default_journal['name'])
|
|
|
|
residual_amount = res['residual_amount']
|
|
self.store.set({
|
|
'residual_amount': residual_amount,
|
|
'change': res['change'],
|
|
})
|
|
|
|
# if residual_amount <= 0:
|
|
# self.grid_amounts.label_change.setText('CAMBIO')
|
|
# self._done_sale()
|
|
# return True
|
|
|
|
def validate_done_sale(self):
|
|
if self.model_sale_lines.rowCount() == 0:
|
|
self.dialog('sale_without_products')
|
|
self.set_state('add')
|
|
self.message_bar.set('system_ready')
|
|
return
|
|
return True
|
|
|
|
def _get_total_amount(self):
|
|
return self.model_sale_lines.get_sum('amount_w_tax')
|
|
|
|
def set_discount_amount(self):
|
|
res = 0
|
|
if self.store.sale_id:
|
|
res = self.Sale.get_discount_total({
|
|
'sale_id': self.store.sale_id
|
|
})
|
|
self.store.set({'discount': res})
|
|
|
|
def amount_text_changed(self, text=None):
|
|
if text:
|
|
self._amount_text += text
|
|
self.field_amount.setText(self._amount_text)
|
|
|
|
def input_text_changed(self, text=None):
|
|
if text:
|
|
self._input_text += text
|
|
elif text == '':
|
|
self._input_text = ''
|
|
self.label_input.setText(self._input_text)
|
|
|
|
def __do_invoice_thread(self):
|
|
try:
|
|
self.ctx['exception'] = True
|
|
res = self.Sale.faster_post_invoice({
|
|
'sale_id': self.sale_to_post_id,
|
|
})
|
|
except Exception:
|
|
self.dialog('invoice_done_failed')
|
|
return
|
|
|
|
if res['result'] is not True:
|
|
self.dialog('invoice_done_failed', res['result'])
|
|
return False
|
|
payment_term = self.store.get('payment_term')
|
|
# Type payment 2 is credit
|
|
if payment_term.get('payment_type') == '2':
|
|
return True
|
|
|
|
self.Sale.reconcile_invoice({'sale_id': self.sale_to_post_id})
|
|
|
|
def _done_sale(self, is_credit=False):
|
|
# We need create this temp variable called sale_to_post_id for this
|
|
# thread, because self.store.sale_id is deleted inmediately,
|
|
# so post invoice and reconcile it will fail if reference to sale_id
|
|
# is missing or None
|
|
self.sale_to_post_id = self.store.sale_id
|
|
table_assigned = self.store.get('table_assigned')
|
|
if table_assigned:
|
|
self.RestTables.write(
|
|
[table_assigned['id']], {
|
|
'state': 'available',
|
|
'sale': None,
|
|
}
|
|
)
|
|
self.do_invoice.start()
|
|
try:
|
|
if self.print_receipt == 'automatic':
|
|
_copies = self.shop['invoice_copies']
|
|
if self.environment == 'restaurant':
|
|
for n in range(_copies):
|
|
self.action_print_sale()
|
|
else:
|
|
if not is_credit:
|
|
self.print_invoice(copies=_copies, open_box=True)
|
|
except Exception as e:
|
|
print('Error', e)
|
|
logging.error(sys.exc_info()[0])
|
|
|
|
if self.sale_automatic and self.type_pos_user not in ('cashier', 'order'):
|
|
self.create_new_sale()
|
|
else:
|
|
self.change_view_to('start_front')
|
|
|
|
self.message_bar.set('system_ready')
|
|
return True
|
|
|
|
def print_invoice(self, sale_id=None, type_doc='invoice', open_box=False, copies=1):
|
|
if not sale_id:
|
|
sale_id = self.store.sale_id
|
|
args = {
|
|
'sale_id': sale_id,
|
|
'type_doc': type_doc,
|
|
}
|
|
data = self.Sale.get_data(args)
|
|
if not data:
|
|
return
|
|
for i in range(copies):
|
|
msg = self.receipt_sale.print_sale(data, type_doc, open_box)
|
|
if isinstance(msg, dict) and msg.get('error'):
|
|
self.dialog('print_error', extra_message=msg['error'])
|
|
break
|
|
|
|
# def button_duplicate_sale(self):
|
|
# if self.sale_customer_selected:
|
|
# res = self.Sale.duplicate_sale({
|
|
# 'sale_id': self.sale_customer_selected,
|
|
# })
|
|
# self.load_sale(res['sale_id'])
|
|
# self.dialog_consumer.close()
|
|
# self.dialog_table_sale_consumer.close()
|
|
# self.dialog_sale_consumer_selected.close()
|
|
|
|
def button_create_delivery_party(self):
|
|
self.clear_delivery_party()
|
|
self.dialog_delivery_party_selected.show()
|
|
|
|
def button_sale_consumer_history(self):
|
|
if self._consumer:
|
|
delta = str(datetime.now() - timedelta(180))
|
|
dom = [
|
|
('create_date', '>=', delta),
|
|
('state', 'in', ['processing', 'done']),
|
|
('consumer', '=', self._consumer['id']),
|
|
]
|
|
self.model_sale_historic.reset()
|
|
sales = self.Sale.find(dom, fields=_SALE_HISTORIC, limit=10)
|
|
for record in sales:
|
|
self.model_sale_historic.add_record(record)
|
|
self.dialog_historic_sales.exec_()
|
|
|
|
def check_all_commanded(self):
|
|
return all([
|
|
line.get('order_sended') for line in self.model_sale_lines._data
|
|
])
|
|
|
|
def print_no_commanded(self):
|
|
if self.environment != 'restaurant':
|
|
return True
|
|
|
|
all_commmanded = self.check_all_commanded()
|
|
if not all_commmanded:
|
|
res = self.action_send_order(init_view=False)
|
|
if res:
|
|
return True
|
|
return True
|
|
|
|
def button_accept_pressed(self):
|
|
sale = self.store.store
|
|
environment = self.environment
|
|
sale_id = self.sale_id
|
|
if not sale_id or not self.model_sale_lines.rowCount() > 0:
|
|
return
|
|
limit_uvt = self._config.get('uvt_pos')
|
|
# sale, = self.Sale.find([['id', '=', sale_id]], fields=['invoice_type', 'total_amount'])
|
|
if sale['invoice_type'] == 'P' and limit_uvt and sale['untaxed_amount'] > limit_uvt:
|
|
return self.dialog('base_uvt_pos')
|
|
if environment == 'restaurant':
|
|
if self.print_order:
|
|
self.print_no_commanded()
|
|
if not sale.get('consumer') and sale['kind'] == 'delivery':
|
|
self.action_consumer()
|
|
return
|
|
elif environment == 'retail':
|
|
if not self._check_quantity():
|
|
return
|
|
self.Sale.button_method('quote', [sale_id])
|
|
self.load_sale(sale_id)
|
|
self.set_state('accept')
|
|
|
|
def button_checkout_pressed(self):
|
|
if not self.check_salesman():
|
|
return
|
|
if not self.validate_payment_term():
|
|
return
|
|
if self.environment == 'restaurant':
|
|
# if not self.print_no_commanded():
|
|
# return
|
|
res = self.check_delivery_party()
|
|
if not res:
|
|
return
|
|
self.store.update({'order_status': 'dispatched'})
|
|
|
|
sale_id = self.sale_id
|
|
self.ctx['exception'] = True
|
|
res = self.Sale.faster_process({'sale_id': sale_id})
|
|
if isinstance(res, list):
|
|
res, msg, exception = res
|
|
if msg:
|
|
self.message_bar.set(msg)
|
|
return
|
|
if exception:
|
|
self.dialog('invoice_validated_failed', extra_message=exception)
|
|
return
|
|
else:
|
|
self.dialog('process_invoice_failed', extra_message=res['error'])
|
|
return
|
|
self.set_amounts(res)
|
|
# self.field_number.setText(self._sale['number'])
|
|
self.field_invoice_number.setText(res['invoice_number'])
|
|
self.field_amount.setText('')
|
|
self.set_state('payment')
|
|
if self.type_pos_user == 'salesman':
|
|
self.print_invoice(sale_id)
|
|
if self.sale_automatic:
|
|
self.create_new_sale()
|
|
else:
|
|
self.message_bar.set('enter_payment', self.default_journal['name'])
|
|
self.setFocus()
|
|
|
|
def check_delivery_party(self):
|
|
kind = self.store.get('kind')
|
|
delivery_party = self.store.get('delivery_party')
|
|
if kind == 'delivery' and not delivery_party:
|
|
self.dialog('missing_delivery_party')
|
|
return False
|
|
return True
|
|
|
|
def action_reservations(self):
|
|
logging.info('Buscando reservas.....')
|
|
|
|
def action_tables(self):
|
|
if self.environment == 'restaurant':
|
|
self.dialog_manage_tables.open_tables()
|
|
self.dialog_manage_tables.exec_()
|
|
|
|
def action_release_table(self, table_id):
|
|
dialog = self.dialog('release_table', response=True)
|
|
response = dialog.exec_()
|
|
if response == DIALOG_REPLY_NO:
|
|
return
|
|
to_drop = {'state': 'available', 'sale': None}
|
|
self.RestTables.write([table_id], to_drop)
|
|
return to_drop
|
|
|
|
def action_change_salesman(self):
|
|
salesman = self.action_salesman_code()
|
|
if salesman == 0:
|
|
self.dialog_salesman_code.hide()
|
|
return
|
|
if not salesman:
|
|
return self.dialog('error_salesman_wrong')
|
|
else:
|
|
self.salesman = salesman
|
|
self.Sale.write([self.sale_id], {'salesman': salesman['id']})
|
|
self.store.update({'salesman': salesman})
|
|
|
|
def action_tip(self):
|
|
if self._state in ['finished']:
|
|
return
|
|
|
|
value = self.field_tip_amount_ask.text()
|
|
|
|
if not value and self._config['tip_rate']:
|
|
total_amount = int(self._get_total_amount())
|
|
value = int((self._config['tip_rate'] / 100) * total_amount)
|
|
self.field_tip_amount_ask.setText(str(value))
|
|
|
|
result = self.dialog_tip_amount.exec_()
|
|
if result == 0:
|
|
self.field_tip_amount_invoice.setChecked(False)
|
|
return
|
|
value = self.field_tip_amount_ask.text()
|
|
value = int(value) if value else 0
|
|
tip_invoice = self.field_tip_amount_invoice.isChecked()
|
|
self.field_tip_amount_invoice.setChecked(False)
|
|
if tip_invoice:
|
|
tip_product = self._config.get('tip_product.')
|
|
self.add_product(record=tip_product, list_price=value)
|
|
res = self.store.update({'tip_amount': 0})
|
|
self.set_amounts(res)
|
|
else:
|
|
res = self.store.update({'tip_amount': value})
|
|
self.set_amounts(res)
|
|
|
|
def action_delivery(self):
|
|
if self._state in ['finished']:
|
|
return
|
|
result = self.dialog_delivery_amount.exec_()
|
|
if result == 0:
|
|
self.field_delivery_amount_invoice.setChecked(False)
|
|
return
|
|
value = self.field_delivery_amount_ask.text()
|
|
value = int(value)
|
|
delivery_invoice = self.field_delivery_amount_invoice.isChecked()
|
|
self.field_delivery_amount_invoice.setChecked(False)
|
|
if delivery_invoice:
|
|
delivery_product = self._config.get('delivery_product.')
|
|
self.add_product(record=delivery_product, list_price=value)
|
|
res = self.store.update({'delivery_amount': 0})
|
|
self.set_amounts(res)
|
|
else:
|
|
res = self.store.update({'delivery_amount': value})
|
|
self.set_amounts(res)
|
|
|
|
def action_salesman_code(self):
|
|
if self._state in ['checkout']:
|
|
return
|
|
result = self.dialog_salesman_code.exec_()
|
|
code = self.field_salesman_code_ask.text()
|
|
if result == 0:
|
|
# Cancelled the action
|
|
return result
|
|
if not code:
|
|
return
|
|
salesman_list = self.Employee.find([
|
|
('code', '=', code)
|
|
])
|
|
if salesman_list:
|
|
return salesman_list[0]
|
|
|
|
def action_delivery_party(self):
|
|
self.dialog_delivery_party.exec_()
|
|
|
|
def action_delivery_party_panel(self):
|
|
self.dialog_control_panel.close()
|
|
self.dialog_table_delivery_party.exec_()
|
|
|
|
def action_control_panel(self):
|
|
self.dialog_control_panel.exec_()
|
|
|
|
def action_reports(self):
|
|
self.dialog_reports.exec_()
|
|
|
|
def dialog_money_count_accepted(self):
|
|
return True
|
|
|
|
def action_expenses(self):
|
|
if self.set_expenses():
|
|
self.dialog_expenses.exec()
|
|
|
|
def action_open_statement(self):
|
|
self.dialog_money_count.exec('open')
|
|
|
|
def open_statement_accepted(self, value):
|
|
res = self.Sale.faster_open_statement({
|
|
'device': self.device['id'],
|
|
'total_money': Decimal(value),
|
|
})
|
|
if res['result']:
|
|
self.dialog('statement_created')
|
|
self.dialog_money_count.clear()
|
|
self.set_expenses()
|
|
|
|
def action_close_statement(self, values):
|
|
salesman = self.action_salesman_code()
|
|
self.field_salesman_code_ask.setText('')
|
|
self.salesman_statement = salesman
|
|
if not salesman:
|
|
return self.dialog('error_salesman_wrong')
|
|
|
|
self.dialog_money_count.exec('close')
|
|
|
|
def close_statement_accepted(self, values):
|
|
if self.salesman_statement:
|
|
salesman = self.salesman_statement
|
|
send_mail = self.field_send_mail.isChecked()
|
|
self.field_send_mail.setChecked(False)
|
|
res = self.Sale.faster_close_statement({
|
|
'device': self.device['id'],
|
|
'data': values,
|
|
'salesman': salesman['id'],
|
|
'send_mail': send_mail,
|
|
})
|
|
|
|
# FIXME: Check if account statement is closed previously.
|
|
if res and res['result']:
|
|
self.dialog('statement_finish')
|
|
else:
|
|
# self.dialog('statement_previously_closed')
|
|
pass
|
|
self.dialog_expenses.clear()
|
|
self.data_expenses = None
|
|
self.dialog_money_count.clear()
|
|
|
|
def action_split_sale(self):
|
|
if self.table_sale_lines.is_active_selection():
|
|
res = self.dialog_split_sale.ask()
|
|
if res == DIALOG_REPLY_YES:
|
|
selectedItems = self.table_sale_lines.get_selected_rows()
|
|
if selectedItems:
|
|
self.new_sale_from_lines(selectedItems)
|
|
self.table_sale_lines.delete_selected()
|
|
self.table_sale_lines.clear_selected()
|
|
self.table_sale_lines.active_selection(False)
|
|
self.message_bar.set('system_ready')
|
|
else:
|
|
self.table_sale_lines.clear_selected()
|
|
self.message_bar.set('select_products_to_split')
|
|
self.table_sale_lines.active_selection(True)
|
|
|
|
def new_sale_from_lines(self, lines):
|
|
_sale = self.store.store
|
|
lines_ids = [line['id'] for line in lines]
|
|
consumer_id = None
|
|
source_id = None
|
|
turn = None
|
|
if _sale['consumer']:
|
|
consumer_id = _sale['consumer']['id']
|
|
if _sale.get('source'):
|
|
source_id = _sale['source']['id']
|
|
if _sale.get('turn'):
|
|
turn = _sale['turn']
|
|
shipment_address = _sale['shipment_address']
|
|
if isinstance(shipment_address, dict):
|
|
shipment_address_id = shipment_address['id']
|
|
else:
|
|
shipment_address_id = shipment_address
|
|
|
|
to_create = {
|
|
'shop': self.shop['id'],
|
|
'invoice_type': 'P',
|
|
'company': self.company,
|
|
'party': _sale['party']['id'],
|
|
'sale_device': self.device['id'],
|
|
'payment_method': 'cash',
|
|
'payment_term': self.default_payment_term['id'],
|
|
'kind': _sale['kind'],
|
|
'salesman': _sale['salesman']['id'],
|
|
'shipment_address': shipment_address_id,
|
|
'invoice_address': shipment_address_id,
|
|
'turn': turn,
|
|
'sale_date': _sale['sale_date'],
|
|
'consumer': consumer_id,
|
|
'source': source_id,
|
|
'state': 'draft',
|
|
'comment': _sale['comment'],
|
|
}
|
|
sale = self.Sale.new_sale(to_create)
|
|
|
|
self.SaleLine.write(lines_ids, {'sale': sale['id']})
|
|
number = self.Sale.set_sale_number({'sale_id': sale['id']})
|
|
self.dialog_split_sale.info(number)
|
|
self.set_amounts()
|
|
|
|
def action_table_discount(self):
|
|
self.dialog_auth_discount.exec_()
|
|
|
|
def action_tax(self):
|
|
self.dialog_tax.exec_()
|
|
|
|
def button_payment_pressed(self):
|
|
if self._state not in ('checkout', 'payment'):
|
|
self.dialog('add_payment_sale_draft')
|
|
return
|
|
self.dialog_payment.exec_()
|
|
|
|
def _check_credit_capacity(self, party):
|
|
if party['credit_limit_amount']:
|
|
if (party['credit_limit_amount'] * RATE_CREDIT_LIMIT) < (
|
|
party['credit_amount'] + self._get_total_amount()):
|
|
self.dialog('credit_limit_capacity')
|
|
return True
|
|
|
|
def validate_payment_term(self, payment_term=None):
|
|
if not payment_term:
|
|
payment_term = self.store.get('payment_term')
|
|
payment_type = payment_term.get('payment_type')
|
|
# Type payment 2 is credit
|
|
if payment_type != '2':
|
|
return True
|
|
party, = self.Party.find([('id', '=', self.party_id)])
|
|
if self._credit_limit_activated:
|
|
if self.party_id == self.default_party['id']:
|
|
self.dialog('customer_not_credit')
|
|
return False
|
|
elif not party['credit_limit_amount']:
|
|
self.dialog('customer_not_credit')
|
|
return False
|
|
|
|
self._credit_amount = self.Sale.get_credit_amount_party(
|
|
{'party_id': self.party_id})
|
|
self._credit_limit_amount = party['credit_limit_amount']
|
|
if self._credit_limit_amount and \
|
|
self._credit_limit_amount < (self._credit_amount + self._get_total_amount()):
|
|
self.dialog('credit_limit_exceed')
|
|
return False
|
|
return True
|
|
|
|
def action_delivery_charge_selection_changed(self, index):
|
|
val = self.field_delivery_charge.get_id()
|
|
sale_id = self.store.store.get('id')
|
|
if val and sale_id:
|
|
self.store.update({'delivery_charge': val})
|
|
# self.Sale.write([sale_id], {'delivery_charge': val})
|
|
|
|
def action_kind_selection_changed(self, index):
|
|
val = self.field_kind.get_id()
|
|
self.store.update({'kind': val})
|
|
|
|
def action_payment_method_selection_changed(self, index):
|
|
val = self.field_payment_method.get_id()
|
|
self.store.update({'payment_method': val})
|
|
|
|
def action_invoice_type_selection_changed(self, index):
|
|
val = self.field_invoice_type.get_id()
|
|
sale_id = self.store.store.get('id')
|
|
if val and sale_id:
|
|
args = {'invoice_type': val}
|
|
if val == 'M':
|
|
args['invoice_method'] = 'manual'
|
|
self.Sale.write([sale_id], args)
|
|
self.label_input.setFocus()
|
|
|
|
def action_tax_selection_changed(self):
|
|
args = {'id': self.store.sale_id, 'tax_id': self.field_tax_id}
|
|
res = self.Sale.add_tax(args)
|
|
self.set_amounts(res)
|
|
|
|
def action_consumer(self):
|
|
consumer = self.store.get('consumer')
|
|
if consumer and consumer.get('id'):
|
|
self.dialog_consumer.fill(consumer)
|
|
else:
|
|
self.dialog_consumer.clear()
|
|
self.dialog_consumer.show()
|
|
|
|
def action_info_product(self):
|
|
self.dialog_info_product.show()
|
|
|
|
def action_collection(self):
|
|
self.dialog_collection.show()
|
|
|
|
def action_add_advance(self):
|
|
if self.type_pos_user not in ('cashier', 'frontend_admin'):
|
|
self.dialog('user_without_permission')
|
|
return
|
|
|
|
sale = self.store.store
|
|
sale_id = sale['id']
|
|
if sale.get('state') == 'draft':
|
|
self.Sale.button_method('quote', [sale_id])
|
|
# self.Sale.quote({'id': self._sale['id']})
|
|
self.load_sale(sale_id)
|
|
|
|
if sale.get('state') == 'quotation':
|
|
self.dialog_payment.exec_()
|
|
flag = True
|
|
# dialog_advance.addAction
|
|
while flag:
|
|
self.dialog_advance.clean()
|
|
res = self.dialog_advance.exec_()
|
|
amount = self.field_amount_ask.text()
|
|
reservation = self.field_reservation_ask.isChecked()
|
|
if res == 0 or (res == 1 and amount.isdigit()):
|
|
flag = False
|
|
if res == 1:
|
|
journal = self.journal
|
|
voucher_number = None
|
|
if journal.get('require_voucher'):
|
|
res = self.dialog_voucher.exec_()
|
|
if res == 0:
|
|
self.dialog_voucher.close()
|
|
return
|
|
voucher_number = self.field_voucher_ask.text()
|
|
if voucher_number is None or voucher_number == '':
|
|
return self.add_payment(amount)
|
|
self.field_voucher_ask.setText('')
|
|
|
|
res = self.Sale.faster_add_payment({
|
|
'sale_id': sale_id,
|
|
'journal_id': journal['id'],
|
|
'cash_received': to_numeric(float(amount)),
|
|
'voucher_number': voucher_number,
|
|
'extra_paid': False,
|
|
}, ctx={'advance': True, 'reservation': reservation})
|
|
|
|
if res.get('msg') not in ('missing_money', 'ok'):
|
|
self.dialog(res['msg'])
|
|
return
|
|
residual_amount = res.get('residual_amount')
|
|
change = res.get('change')
|
|
paid_amount = res.pop('paid_amount')
|
|
self.store.set({
|
|
'residual_amount': residual_amount,
|
|
'change': change,
|
|
'paid_amount': paid_amount,
|
|
})
|
|
if res['id']:
|
|
self.table_payment_lines.add_record(res)
|
|
|
|
def action_print_sale(self):
|
|
number = self.field_invoice_number.text()
|
|
type_doc = 'invoice'
|
|
if not number:
|
|
type_doc = 'order'
|
|
number = self.field_number.text()
|
|
if number:
|
|
self.field_invoice_number_ask.setText(number)
|
|
self.field_type_ask.set_from_id(type_doc)
|
|
res = self.dialog_print_invoice.exec_()
|
|
if res == DIALOG_REPLY_NO:
|
|
return
|
|
number = self.field_invoice_number_ask.text()
|
|
resend_dian = self.field_resend_dian_ask.isChecked()
|
|
printer_id = self.field_printer_ask.get_id()
|
|
type_doc = self.field_type_ask.get_id()
|
|
sale_id = None
|
|
sale = self.store.store
|
|
if number:
|
|
if type_doc in ['order', 'delivery', 'quotation']:
|
|
if number != sale.get('number'):
|
|
sale_id = sale['id']
|
|
else:
|
|
sales = self.Sale.find([('number', '=', number)], fields=['id'])
|
|
sale = sales[0]
|
|
if sales:
|
|
sale_id = sales[0]['id']
|
|
else:
|
|
args = {'number': number}
|
|
sale = self.Sale.get_sale_from_invoice(args)
|
|
if sale:
|
|
sale_id = sale['id']
|
|
else:
|
|
sale_id = self.sale_id
|
|
if not sale_id:
|
|
self.dialog('sale_number_not_found')
|
|
|
|
if resend_dian:
|
|
self.field_resend_dian_ask.setChecked(False)
|
|
res = self.Sale.resend_invoice({'id': sale_id, 'number': number})
|
|
if not res:
|
|
self.dialog('fail_send_invoice')
|
|
if printer_id == '1':
|
|
self.print_invoice(sale_id, type_doc=type_doc)
|
|
else:
|
|
self.print_odt_report(sale, type_doc)
|
|
|
|
def print_odt_report(self, sale, type_doc):
|
|
if type_doc == 'order':
|
|
model = 'sale.sale'
|
|
sale_id = sale['id']
|
|
action_id = self._action_report_sale[-1]['id']
|
|
data = {
|
|
'model': model,
|
|
'action_id': action_id,
|
|
}
|
|
ctx = {'date_format': '%d/%m/%Y'}
|
|
ctx.update(self.ctx)
|
|
report = Report(ctx)
|
|
values = {
|
|
'report_name': model,
|
|
'args': data,
|
|
'record': sale_id,
|
|
'records': [sale_id],
|
|
}
|
|
result = report.get(values)
|
|
report.open(result)
|
|
elif type_doc == 'invoice':
|
|
if not sale.get('invoices'):
|
|
return
|
|
invoice_id = sale['invoices'][0]
|
|
model = 'account.invoice'
|
|
action_id = self._action_report_invoice['id']
|
|
if sale['invoice_type'] == '1':
|
|
action_id = self._action_report_invoice_e['id']
|
|
model = 'electronic_invoice_co.invoice_face'
|
|
data = {
|
|
'model': model,
|
|
'action_id': action_id,
|
|
}
|
|
ctx = {'date_format': u'%d/%m/%Y'}
|
|
ctx.update(self.ctx)
|
|
report = Report(ctx)
|
|
values = {
|
|
'report_name': model,
|
|
'args': data,
|
|
'record': invoice_id,
|
|
'records': [invoice_id],
|
|
}
|
|
result = report.get(values)
|
|
report.open(result)
|
|
|
|
def action_comment(self):
|
|
self.dialog_comment.exec_()
|
|
comment = self.field_comment.toPlainText()
|
|
self.store.update({'comment': comment})
|
|
|
|
def action_position(self):
|
|
self.field_position_ask.setText(self.store.get('position'))
|
|
self.dialog_position.exec_()
|
|
position = self.field_position_ask.text()
|
|
self.store.update({'position': position})
|
|
|
|
def action_agent(self):
|
|
self.dialog_agent.exec_()
|
|
res = self.field_commission_ask.text()
|
|
commission = float(res or 0)
|
|
self.field_agent_id = self.field_agent_ask.get_id()
|
|
|
|
if self.field_agent_id:
|
|
self.Sale.write([self.sale_id], {
|
|
'agent': self.field_agent_id,
|
|
# 'commission': int(commission),
|
|
})
|
|
self.message_bar.set('system_ready')
|
|
comm_string = str('[' + str(commission) + ']'
|
|
+ ' ') + (str(self.field_agent_ask.text()))
|
|
self.field_agent.setText(comm_string)
|
|
|
|
def _set_commission_amount(self, untaxed_amount, commission):
|
|
untaxed_amount = int(untaxed_amount)
|
|
commission = int(commission if commission else 0)
|
|
total = ((untaxed_amount * commission) / 100)
|
|
self.field_commission_amount.setText(str(total))
|
|
|
|
def action_party(self):
|
|
if self._state in ['checkout']:
|
|
return
|
|
self.dialog_search_parties.clear_rows()
|
|
self.dialog_search_parties.execute()
|
|
|
|
def action_global_discount(self, sale_id=None):
|
|
if self._state in ('accept', 'checkout', 'payment'):
|
|
return
|
|
if self.environment == 'restaurant' and self.type_pos_user not in (
|
|
'cashier', 'frontend_admin'):
|
|
self.dialog('user_without_permission')
|
|
return
|
|
self.dialog_global_discount.exec_()
|
|
discount = self.field_global_discount_ask.text()
|
|
if discount and discount.isdigit():
|
|
rec = {
|
|
'type_discount': 'percentage',
|
|
'discount': discount,
|
|
'name': 'DESCUENTO GLOBAL'
|
|
}
|
|
self.validate_discount(rec)
|
|
|
|
def validate_discount(self, rec, line=False):
|
|
if self.model_sale_lines.rowCount() == 0:
|
|
return
|
|
|
|
type_ = rec['type_discount']
|
|
discount = int(rec['discount'])
|
|
line_id = None
|
|
if line:
|
|
line_id = line.get('id')
|
|
self._current_line_id = line_id
|
|
if type_ == 'fixed' and line_id:
|
|
sale_line, = self.SaleLine.find([
|
|
('id', '=', line_id)
|
|
])
|
|
price_w_tax = sale_line['product.']['sale_price_taxed']
|
|
price = Decimal(price_w_tax) - (discount / Decimal(line['quantity']))
|
|
if price < 0:
|
|
price = 0
|
|
res = self.set_unit_price(price)
|
|
else:
|
|
lines = []
|
|
if line_id:
|
|
lines.append(line_id)
|
|
else:
|
|
# If type is percentage will apply to all lines
|
|
lines = [ln['id'] for ln in self.model_sale_lines._data]
|
|
if lines:
|
|
res = self.set_discount(discount, lines, type_)
|
|
|
|
if not res:
|
|
self.message_bar.set('discount_not_valid')
|
|
return False
|
|
|
|
def _print_reversion(self, sale_id, sale_line_id):
|
|
try:
|
|
args = {
|
|
'sale_id': sale_id,
|
|
'sale_line_id': sale_line_id,
|
|
}
|
|
orders, sale_number = self.Sale.get_reversion(args)
|
|
self.receipt_order.print_orders(orders.values(), reversion=True)
|
|
except Exception:
|
|
print(self.print_order, 'validar variable en opcion print_order=True en config_pos.ini')
|
|
logging.error('Printing order reversion fail!')
|
|
|
|
def _print_order(self, sale_id, reversion=False):
|
|
result = False
|
|
try:
|
|
args = {
|
|
'sale_id': sale_id,
|
|
'repeat': False,
|
|
}
|
|
if self.environment == 'restaurant' and self.tasks_station:
|
|
data_station = self.Sale.method_instance('get_data_for_stations', sale_id)
|
|
receipt = Receipt(context={}, environment='restaurant')
|
|
receipt.print_tasks(data_station)
|
|
orders, sale_number = self.Sale.get_order2print(args)
|
|
for line in self.model_sale_lines._data:
|
|
line['order_sended'] = '✔'
|
|
self.model_sale_lines.update_record(line)
|
|
result = self.receipt_order.print_orders(orders.values(), reversion)
|
|
if result:
|
|
self.Sale.mark_commanded({'sale_id': sale_id, 'lines_ids': result})
|
|
except Exception as e:
|
|
print(e, 'error')
|
|
traceback.print_exc()
|
|
logging.error('Printing order fail!')
|
|
return result
|
|
|
|
def action_delivery_report(self):
|
|
_parties = {dp['id']: dp['rec_name'] for dp in self.delivery_parties}
|
|
default = self.delivery_parties[0]
|
|
add_fields = ('delivery_party', {
|
|
'name': 'DOMICILIARIO',
|
|
'type': 'selection',
|
|
'values': _parties.items(),
|
|
'default': default,
|
|
})
|
|
args, report_name = self.dialog_reports.open_wizard(
|
|
'delivery_report', add_fields, open_print=False
|
|
)
|
|
domain = [
|
|
('sale_date', '=', args['date']),
|
|
('delivery_party', '=', int(args['delivery_party'])),
|
|
('shop', '=', self.shop['id']),
|
|
('state', '=', 'processing'),
|
|
('payment_term.payment_type', '=', '1'),
|
|
]
|
|
fields = ['number', 'invoice_number', 'total_amount', 'consumer.rec_name']
|
|
sales = self.Sale.find(domain, fields=fields)
|
|
data = {
|
|
'name': _parties[int(args['delivery_party'])],
|
|
'sale_date': str(args['date']),
|
|
'sales': [],
|
|
}
|
|
total = 0
|
|
for sale in sales:
|
|
total_amount = sale['total_amount']
|
|
consumer_address = sale['consumer.']['rec_name'].split('| ')
|
|
data['sales'].append({
|
|
'order': sale['number'],
|
|
'number': sale['invoice_number'],
|
|
'consumer_address': consumer_address[2],
|
|
'total': total_amount,
|
|
})
|
|
total += total_amount
|
|
data['total'] = total
|
|
self.receipt_sale.print_delivery_report(data)
|
|
|
|
def action_send_order(self, sale_id=None, reversion=False, init_view=True):
|
|
sale = self.store.store
|
|
sale_id = sale_id or sale['id']
|
|
if self.model_sale_lines.rowCount() == 0 or not sale_id:
|
|
return
|
|
|
|
res = self.dialog_order.exec_()
|
|
if res == DIALOG_REPLY_NO:
|
|
return False
|
|
result = None
|
|
number = sale.get('number')
|
|
if not number:
|
|
number = self.Sale.set_sale_number({'sale_id': sale_id})
|
|
self.store.set({'number': number})
|
|
self.order_number.setText(number)
|
|
if self.environment == 'restaurant':
|
|
if self.print_order:
|
|
result = self.print_command(sale)
|
|
else:
|
|
result = self.print_dispatch(sale)
|
|
if result:
|
|
self.dialog('order_successfully', widgets=[self.order_number])
|
|
else:
|
|
self.dialog('order_failed')
|
|
if init_view:
|
|
self.change_view_to('start_front')
|
|
return True
|
|
|
|
def get_header_sale(self, sale):
|
|
order = {
|
|
'id': self.sale_id,
|
|
'sale_number': sale['number'],
|
|
'number': sale.get('invoice_number'),
|
|
'turn': sale.get('turn'),
|
|
'position': sale['position'],
|
|
'party': sale['party']['name'],
|
|
'kind': sale.get('kind'),
|
|
'delivery_amount': sale.get('delivery_amount'),
|
|
'salesman': sale['salesman']['rec_name'] if sale.get('salesman') else '',
|
|
'comment': sale['comment'],
|
|
'payment_term': sale['payment_term']['rec_name'] if sale.get('payment_term') else '',
|
|
'delivery_charge': sale.get('delivery_charge'),
|
|
'total_amount': sale['total_amount'],
|
|
'shop': self.shop['name'],
|
|
'consumer': sale.get('consumer'),
|
|
'table_assigned': sale['table_assigned']['name'] if sale.get('table_assigned') else ''
|
|
}
|
|
return order
|
|
|
|
def print_dispatch(self, sale):
|
|
order = self.get_header_sale(sale)
|
|
lines = self.model_sale_lines._data
|
|
order['lines'] = [
|
|
{'name': ln['product.']['name'],
|
|
'quantity': str(ln['quantity']),
|
|
'sale_price_taxed': '',
|
|
'amount_w_tax': ln['amount_w_tax'],
|
|
'note': ln['note']}
|
|
for ln in lines]
|
|
order['lines_ids']: [ln['id'] for ln in lines]
|
|
result = self.receipt_order.print_orders([order])
|
|
return result
|
|
|
|
def print_command(self, sale, line_reversion=None):
|
|
version = compare_versions(self.modules['sale_pos_frontend_rest']['version'], '6.0.7')
|
|
reversion = False if not line_reversion else True
|
|
orders = {}
|
|
lines_ids = []
|
|
lines = [line_reversion] if reversion else self.model_sale_lines._data
|
|
if version in ('equal', 'higher'):
|
|
order = self.get_header_sale(sale)
|
|
for ln in lines:
|
|
if not reversion and ln['order_sended'] in (True, '✔'):
|
|
continue
|
|
pd_id = ln['product.']['id']
|
|
print(pd_id, 'njnj')
|
|
try:
|
|
printers, cat_id = self.products_printers.get(str(pd_id))
|
|
cat_id = str(cat_id[0])
|
|
except Exception:
|
|
traceback.print_exc()
|
|
printers, cat_id = None, None
|
|
if printers:
|
|
for printer_id in printers:
|
|
printer = self.printers_shop.get(str(printer_id))
|
|
if printer_id not in orders.keys():
|
|
orders[printer_id] = {
|
|
**order,
|
|
**printer,
|
|
'lines_ids': []
|
|
}
|
|
if printer.get('categories'):
|
|
orders[printer_id]['lines'] = {**printer['lines']}
|
|
orders[printer_id]['categories'] = {**printer['categories']}
|
|
else:
|
|
orders[printer_id]['lines'] = []
|
|
value_line = {
|
|
'name': ln['product.']['name'],
|
|
'quantity': str(ln['quantity']),
|
|
'sale_price_taxed': '',
|
|
'amount_w_tax': ln['amount_w_tax'],
|
|
'note': ln['note']
|
|
}
|
|
if isinstance(orders[printer_id]['lines'], list):
|
|
orders[printer_id]['lines'].append(value_line)
|
|
orders[printer_id]['lines_ids'].append(ln['id'])
|
|
else:
|
|
try:
|
|
key_id = orders[printer_id]['categories'][cat_id]
|
|
except Exception:
|
|
if 'others' not in orders[printer_id]['lines'].keys():
|
|
orders[printer_id]['lines']['others'] = {'name': 'OTROS', 'lines': []}
|
|
key_id = 'others'
|
|
try:
|
|
orders[printer_id]['lines'][key_id]['lines'].append(value_line)
|
|
orders[printer_id]['lines_ids'].append(ln['id'])
|
|
except Exception:
|
|
orders[printer_id]['lines'][key_id]['lines'] = [value_line]
|
|
orders[printer_id]['lines_ids'].append(ln['id'])
|
|
else:
|
|
lines_ids.append(ln['id'])
|
|
else:
|
|
# for remove this code
|
|
if reversion:
|
|
args = {'sale_id': self.sale_id, 'sale_line_id': line_reversion['id']}
|
|
orders, sale_number = self.Sale.get_reversion(args)
|
|
else:
|
|
args = {'sale_id': self.sale_id}
|
|
orders, sale_number = self.Sale.get_order2print(args)
|
|
|
|
if self.environment == 'restaurant' and self.tasks_station:
|
|
if version in ('equal', 'higher'):
|
|
args = {
|
|
'shop': self.shop['id'],
|
|
'sale_id': self.sale_id
|
|
}
|
|
tasks = self.SaleLine.get_data_tasks(args)
|
|
receipt = Receipt(context={}, environment='restaurant')
|
|
result_print = receipt.print_tasks(tasks)
|
|
if any(result_print):
|
|
self.SaleLine.mark_tasks_printed(result_print)
|
|
else:
|
|
data_station = self.Sale.method_instance('get_data_for_stations', self.sale_id)
|
|
receipt = Receipt(context={}, environment='restaurant')
|
|
receipt.print_tasks(data_station.values())
|
|
print(orders, 'validate')
|
|
return
|
|
# result = self.receipt_order.print_orders(orders.values(), reversion)
|
|
# lines_sended = result + lines_ids
|
|
# print(lines_sended, 'this is lines printed')
|
|
# if not reversion and lines_sended:
|
|
# for line in lines:
|
|
# if line['id'] in lines_sended:
|
|
# line['order_sended'] = '✔'
|
|
# self.model_sale_lines.update_record(line)
|
|
# self.Sale.mark_commanded({'lines_ids': lines_sended})
|
|
# return result
|
|
|
|
def action_source(self):
|
|
if self._state != 'checkout' and self.sources:
|
|
self.dialog_source.exec_()
|
|
|
|
def action_payment_term(self):
|
|
if self._state == 'payment' or self.type_pos_user == 'salesman':
|
|
self.dialog_payment_term.exec_()
|
|
|
|
def action_new_sale(self):
|
|
if self._state in ['checkout']:
|
|
return
|
|
if not self.sale_id:
|
|
return
|
|
if self._ask_new_sale():
|
|
self.create_new_sale()
|
|
|
|
# for remove
|
|
# def numpad_price_clicked(self):
|
|
# code = self.label_input.text()
|
|
# product = self._search_product(code)
|
|
# if not product:
|
|
# return
|
|
|
|
def _ask_new_sale(self):
|
|
dialog = self.dialog('new_sale', response=True)
|
|
res = dialog.exec_()
|
|
if res == DIALOG_REPLY_NO:
|
|
return False
|
|
return True
|
|
|
|
def action_cancel(self):
|
|
sale = self.store.store
|
|
if self._state == 'disabled':
|
|
return
|
|
if self.type_pos_user == 'cashier':
|
|
self.dialog_cancel_invoice.exec_()
|
|
password = self.field_password_for_cancel_ask.text()
|
|
if password != self._password_admin:
|
|
return self.dialog('not_permission_for_cancel')
|
|
|
|
if not sale['id']:
|
|
return
|
|
if self._state in ('checkout', 'payment') and not self.user_can_delete:
|
|
return self.dialog('not_permission_delete_sale')
|
|
if self.type_pos_user in ('order', 'salesman') and \
|
|
sale.get('invoice_number'):
|
|
return self.dialog('not_permission_delete_sale')
|
|
dialog = self.dialog('cancel_sale', response=True)
|
|
response = dialog.exec_()
|
|
if response == DIALOG_REPLY_NO:
|
|
return
|
|
self.Sale.cancel_sale({'id': sale['id']})
|
|
self.field_password_for_cancel_ask.setText('')
|
|
self.set_state('cancel')
|
|
self.clear_right_panel()
|
|
if self.sale_automatic:
|
|
self.create_new_sale()
|
|
|
|
def action_search_product(self):
|
|
if self._state in ('checkout', 'disabled'):
|
|
return
|
|
self.dialog_search_products.show()
|
|
|
|
def action_start_delivery(self):
|
|
self.create_new_sale(kind='delivery')
|
|
|
|
def action_start_catering(self):
|
|
self.create_new_sale(kind='catering')
|
|
|
|
def action_start_table(self):
|
|
self.create_new_sale(kind='to_table')
|
|
|
|
def action_start_take_away(self):
|
|
self.create_new_sale(kind='take_away')
|
|
|
|
def action_historic_sales(self):
|
|
if not hasattr(self, '_sale_historic'):
|
|
self._sale_historic = []
|
|
|
|
if not self._sale_historic:
|
|
|
|
delta = str(datetime.now() - timedelta(2))
|
|
dom = [
|
|
('create_date', '>=', delta),
|
|
('state', 'in', ['processing', 'done']),
|
|
('shop', '=', self.shop['id']),
|
|
]
|
|
sales = self.Sale.find(dom, fields=_SALE_HISTORIC,
|
|
order=[('sale_date', 'DESC')])
|
|
self._sale_historic = sales
|
|
for record in sales:
|
|
self.model_sale_historic.add_record(record)
|
|
self.dialog_historic_sales.exec_()
|
|
|
|
def action_search_sale(self):
|
|
if hasattr(self, 'activate_scale_sync') and self.activate_scale_sync:
|
|
files = read_files_scale()
|
|
for file in files:
|
|
self.create_new_sale_from_scale(file)
|
|
|
|
self.dialog_search_sales.show()
|
|
self.search_sales_by_domain()
|
|
self.field_invoice_type.set_enabled(True)
|
|
|
|
def search_sales_by_domain(self, _type='cash'):
|
|
if _type == 'reservation':
|
|
delta = timedelta(days=30)
|
|
elif _type == 'quotation':
|
|
delta = timedelta(days=8)
|
|
else:
|
|
delta = timedelta(days=1)
|
|
|
|
delta_date = str(datetime.now() - delta)
|
|
shop_id = self.shop['id']
|
|
|
|
if self.type_pos_user == 'cashier':
|
|
dom = [['OR',
|
|
[
|
|
('create_date', '>=', delta_date),
|
|
('state', 'in', ['quotation', 'confirmed']),
|
|
('shop', '=', shop_id),
|
|
], [
|
|
('state', '=', 'processing'),
|
|
('invoice.state', '=', 'draft'),
|
|
('invoice.type', '=', 'out'),
|
|
('shop', '=', shop_id),
|
|
]]]
|
|
elif self.type_pos_user in ('order', 'salesman'):
|
|
dom = [
|
|
('shop', '=', shop_id),
|
|
('create_date', '>=', delta_date),
|
|
]
|
|
if _type != 'quotation':
|
|
dom.append(('state', '=', 'draft'))
|
|
elif self.type_pos_user == 'frontend':
|
|
dom = [
|
|
('state', 'in', ['draft', 'quotation',
|
|
'confirmed', 'processing']),
|
|
('sale_device', '=', self.device['id']),
|
|
('shop', '=', shop_id),
|
|
('create_date', '>=', delta_date),
|
|
]
|
|
else:
|
|
dom = [
|
|
('state', 'in', ['draft', 'quotation',
|
|
'confirmed', 'processing']),
|
|
('shop', '=', shop_id),
|
|
('create_date', '>=', delta_date),
|
|
]
|
|
|
|
if _type == 'cash':
|
|
dom.extend([
|
|
[
|
|
'OR',
|
|
('payment_term', '=', self.default_payment_term['id']),
|
|
('payment_term', '=', None),
|
|
],
|
|
('reservation', '!=', True)
|
|
])
|
|
elif _type == 'credit':
|
|
dom.extend([
|
|
['reservation', '!=', True],
|
|
['payment_term', '!=', self.default_payment_term['id']]
|
|
])
|
|
elif _type == 'reservation':
|
|
dom.append(('reservation', '=', True))
|
|
else:
|
|
self.dialog_search_sales.set_from_values([])
|
|
return
|
|
dom.append(('state', '=', 'quotation'))
|
|
|
|
# dom.append(['payment_method', '!=', 'all_paid'])
|
|
fields = self.dialog_search_sales.fields_names
|
|
sales = self.Sale.find(dom, fields=fields, order=[('id', 'DESC')])
|
|
self.dialog_search_sales.set_from_values(sales)
|
|
if self.environment == 'retail':
|
|
dom_draft = [
|
|
('create_date', '>=', delta_date),
|
|
('state', '=', 'draft'),
|
|
('invoice_number', '!=', None),
|
|
]
|
|
sales_draft = self.Sale.search_count(dom_draft)
|
|
self.dialog_search_sales.set_counter_control(sales_draft)
|
|
|
|
def on_search_sale_by_number(self):
|
|
target = self.dialog_search_sales.filter_field.text()
|
|
if not target:
|
|
return
|
|
dom = [
|
|
('state', '=', 'quotation'),
|
|
('number', '=', target)
|
|
]
|
|
fields = self.dialog_search_sales.fields_names
|
|
sales = self.Sale.find(dom, fields=fields, order=[('id', 'DESC')])
|
|
self.dialog_search_sales.set_from_values(sales)
|
|
|
|
def on_selected_sale(self):
|
|
sale_id = self.dialog_search_sales.get_id()
|
|
if not sale_id:
|
|
return
|
|
if self.environment != 'restaurant' and self.type_pos_user in ['order', 'salesman']:
|
|
sale = self.Sale.find([('id', '=', sale_id)], fields=['salesman.code'])
|
|
salesman = self.action_salesman_code()
|
|
self.field_salesman_code_ask.setText('')
|
|
if not salesman:
|
|
return self.dialog('error_salesman_incorrect')
|
|
elif salesman['code'] != sale[0]['salesman.']['code']:
|
|
return self.dialog('error_salesman_incorrect')
|
|
self.load_sale(sale_id)
|
|
self.setFocus()
|
|
self.label_input.setFocus()
|
|
self.change_view_to('order_front')
|
|
|
|
def on_selected_party(self, party_id=None):
|
|
if self._state in ('payment'):
|
|
self.message_bar.set('sale_closed')
|
|
return
|
|
if not party_id:
|
|
self.label_input.setFocus()
|
|
party_id = self.dialog_search_parties.get_id()
|
|
if not party_id:
|
|
self.label_input.setFocus()
|
|
return
|
|
|
|
party, = self.Party.find([('id', '=', party_id)])
|
|
address_id = ''
|
|
if party.get('addresses.'):
|
|
address_id = party['addresses.'][0]['id']
|
|
invoice_type = 'P'
|
|
if party['invoice_type']:
|
|
invoice_type = party['invoice_type']
|
|
values = {
|
|
'party': {'id': party_id, 'name': party['name']},
|
|
'invoice_address': address_id,
|
|
'shipment_address': address_id,
|
|
'invoice_type': invoice_type,
|
|
}
|
|
|
|
payment_term = party.get('customer_payment_term.')
|
|
if payment_term:
|
|
values['payment_term'] = payment_term
|
|
|
|
self.field_invoice_type.set_from_id(invoice_type)
|
|
self.store.update(values)
|
|
self.party_id = party_id
|
|
|
|
if self._credit_limit_activated:
|
|
if not self._check_credit_capacity(party):
|
|
return
|
|
self.message_bar.set('system_ready')
|
|
self.setFocus()
|
|
self.label_input.setFocus()
|
|
|
|
def load_sale(self, sale_id):
|
|
# loads only draft sales
|
|
self.clear_data()
|
|
self.clear_left_panel()
|
|
self.clear_right_panel()
|
|
self.store.clear()
|
|
self._clear_invoice_number = False
|
|
fields_sale_line = ['lines.' + f for f in self.SaleLine.fields if f != 'sale']
|
|
fields = self.Sale.fields + fields_sale_line
|
|
if self._commission_activated and self.environment == 'retail':
|
|
fields.extend(['agent.rec_name', 'commission'])
|
|
sale, = self.Sale.find([('id', '=', sale_id)], fields=fields)
|
|
else:
|
|
sale, = self.Sale.find([('id', '=', sale_id)], fields=fields)
|
|
|
|
self.store.set_sale(sale['id'])
|
|
# See option only get invoice_number not all invoice data
|
|
sale['invoice'] = sale['invoice_number']
|
|
self.store.set(sale)
|
|
self.sale_id = sale['id']
|
|
self.table_payment_lines.reset()
|
|
# self._set_sale_date()
|
|
self.journal = {}
|
|
if self.field_delivery_charge:
|
|
self.field_delivery_charge.set_enabled(True)
|
|
if sale.get('delivery_charge'):
|
|
self.field_delivery_charge.set_from_id(sale['delivery_charge'])
|
|
|
|
self.field_change.zero()
|
|
|
|
lines = sale.get('lines.', [])
|
|
for line in lines:
|
|
self.add_sale_line(line)
|
|
|
|
if sale.get('payments'):
|
|
for payment in sale['payments']:
|
|
# FIXME
|
|
# self.table_payment_lines.record_add(payment)
|
|
pass
|
|
|
|
self.party_id = sale['party.']['id']
|
|
self.set_amounts(sale)
|
|
self.set_amount_received()
|
|
self.field_amount.setText('')
|
|
self.message_bar.set('system_ready')
|
|
if self.type_pos_user in ('cashier', 'order', 'salesman'):
|
|
self.table_sale_lines.setEnabled(True)
|
|
|
|
if self.environment == 'restaurant':
|
|
if sale.get('table_assigned.'):
|
|
self.field_table_assigned.setText(
|
|
sale['table_assigned.']['name'] or '')
|
|
consumer = sale.get('consumer.', None)
|
|
if consumer:
|
|
self.field_consumer.setText(consumer['name'] or '')
|
|
self._consumer, = self.Consumer.find([
|
|
('id', '=', consumer['id']),
|
|
])
|
|
|
|
self.table_sale_lines.setEnabled(True)
|
|
self.menu_dash.setDisabled(False)
|
|
|
|
else:
|
|
if self._commission_activated:
|
|
if self.field_agent and sale.get('agent.'):
|
|
commission = sale.get('commission')
|
|
sale['agent.']['rec_name'] = '[' + str(commission) + ']' + ' ' + sale['agent.']['rec_name']
|
|
self.store.set({'agent': sale['agent.']})
|
|
self.field_agent_id = sale['agent.']['id']
|
|
self.field_agent_ask.setText(sale['agent.']['rec_name'])
|
|
self.field_commission_ask.setText(str(commission))
|
|
self._set_commission_amount(sale['untaxed_amount'], commission)
|
|
if self.type_pos_user in ('order', 'salesman'):
|
|
if sale['state'] == 'draft':
|
|
self.buttons_function.button_to_quote.show()
|
|
elif sale['state'] == 'quotation':
|
|
self.buttons_function.button_to_draft.show()
|
|
# else:
|
|
# self.buttons_function.button_change_salesman.show()
|
|
|
|
if sale['state'] in ('draft', 'quotation'):
|
|
self.set_state('add')
|
|
elif sale['state'] in ('confirmed', 'processing', 'done'):
|
|
self.table_sale_lines.setEnabled(False)
|
|
if self.environment == 'restaurant':
|
|
self.menu_dash.setDisabled(False)
|
|
self.set_state('payment')
|
|
|
|
def set_change_amount(self):
|
|
amount_paid = self.table_payment_lines.get_sum('amount')
|
|
res = amount_paid - self._get_total_amount()
|
|
self.field_change.setText(res)
|
|
|
|
def set_amount_received(self, cash_received=ZERO):
|
|
pass
|
|
|
|
# def set_amounts(self, res=None):
|
|
# if not res:
|
|
# res = self.Sale.get_amounts({'sale_id': self.sale_id})
|
|
# self.store.set(res)
|
|
|
|
def set_amounts(self, res=None):
|
|
if not res:
|
|
if self._state == 'add':
|
|
sale = self.store.store
|
|
lines = self.model_sale_lines._data
|
|
res = {
|
|
'untaxed_amount': 0,
|
|
'tax_amount': 0,
|
|
'total_amount': 0,
|
|
'net_amount': 0,
|
|
'residual_amount': None,
|
|
'paid_amount': None,
|
|
'change': None,
|
|
}
|
|
for ln in lines:
|
|
res['untaxed_amount'] += ln['amount']
|
|
res['total_amount'] += ln['amount_w_tax']
|
|
tip_amount = sale.get('tip_amount', 0) or 0
|
|
delivery_amount = sale.get('delivery_amount', 0) or 0
|
|
res['tax_amount'] = res['total_amount'] - res['untaxed_amount']
|
|
res['net_amount'] = res['total_amount'] + tip_amount + delivery_amount
|
|
else:
|
|
res = self.Sale.get_amounts({'sale_id': self.sale_id})
|
|
self.store.set(res)
|
|
|
|
def _get_products_by_category_dash(self, child, find):
|
|
child['items'] = self.Product.find([
|
|
('code', '!=', None),
|
|
('template.salable', '=', True),
|
|
('template.categories', '=', child['id']),
|
|
], fields=['id', 'name', 'code', 'categories', 'rec_name', 'list_price'],
|
|
order=[('template.name', 'ASC')])
|
|
# return records
|
|
|
|
def get_product_by_categories_dash(self):
|
|
values = self.Sale.get_product_by_categories(
|
|
{
|
|
'categories': self.allow_categories_ids
|
|
})
|
|
return values
|
|
|
|
def action_square_box_report(self):
|
|
values, report_name = self.dialog_reports.open_wizard('square_box_report', open_print=False)
|
|
if not values.get('turn'):
|
|
return
|
|
shop, company = self.shop['id'], self.company
|
|
statements = self.Statement.find([
|
|
('turn', '=', values['turn']),
|
|
('state', '=', 'draft'),
|
|
('date', '=', values['date']),
|
|
('company', '=', company),
|
|
('sale_device.shop', '=', shop)
|
|
], fields=['id'])
|
|
if statements:
|
|
return
|
|
values['company'] = company
|
|
values['shop'] = shop
|
|
self.dialog_reports.open_report(report_name, values)
|
|
|
|
def action_terminal_journal_report(self):
|
|
pass
|
|
|
|
def _get_childs(self, parent_cat):
|
|
res = {}
|
|
for cat_id in parent_cat['childs']:
|
|
sub_categories = self.Category.find([
|
|
('parent', '=', parent_cat['id'])
|
|
])
|
|
|
|
for sub_cat in sub_categories:
|
|
res.update(self._get_childs(sub_cat))
|
|
res = {
|
|
'id': parent_cat['id'],
|
|
'name': parent_cat['name'],
|
|
'childs': parent_cat['childs'],
|
|
'records': [],
|
|
'obj': parent_cat,
|
|
}
|
|
return res
|
|
|
|
def on_selected_product(self):
|
|
if self.dialog_search_products.current_row:
|
|
self._current_line_id = None
|
|
self.add_product(record=self.dialog_search_products.current_row)
|
|
|
|
def on_selected_product_info(self):
|
|
if self.dialog_search_product_info.current_row:
|
|
self._current_line_id = None
|
|
record = self.dialog_search_product_info.current_row
|
|
self.dialog_info_product.search(record)
|
|
self.dialog_info_product.show()
|
|
|
|
def on_selected_icon_product(self):
|
|
if self.dialog_search_products.current_row:
|
|
code = self.dialog_search_products.current_row['code']
|
|
products = self.Product.find([
|
|
('code', '=', code)
|
|
], fields=['image'])
|
|
if not products:
|
|
return
|
|
product = products[0]
|
|
image = Image(name='product_icon')
|
|
if not product['image']:
|
|
return
|
|
|
|
image_64_decode = base64.b64decode(product['image'])
|
|
image_ = QtGui.QImage()
|
|
image_.loadFromData(image_64_decode)
|
|
image.set_image(image_, kind='qimage')
|
|
image.activate()
|
|
|
|
def on_selected_stock_product(self):
|
|
if self.dialog_search_products.current_row:
|
|
code = self.dialog_search_products.current_row['code']
|
|
res = self.Product.get_stock_by_locations({'code': code})
|
|
products = self.Product.find([
|
|
('code', '=', code),
|
|
('template.salable', '=', True),
|
|
], fields=['template.positions.warehouse.name', 'template.positions.position.name'])
|
|
if products and products[0]['template.']['positions.']:
|
|
warehouses = {w[0]: w + [''] for w in res}
|
|
for p in products[0]['template.']['positions.']:
|
|
warehouses[p['warehouse.']['name']][2] = p['position.']['name']
|
|
res = warehouses.values()
|
|
self.dialog_product_stock.update_values(res)
|
|
self.dialog_product_stock.show()
|
|
|
|
def on_selected_item_combo(self, record):
|
|
list_price = 0
|
|
qty_text = self.dialog_combo_product.label_qty_add.text()
|
|
try:
|
|
qty = int(qty_text) + 1
|
|
except Exception:
|
|
qty = 1
|
|
self.dialog_combo_product.label_qty_add.setText(str(qty))
|
|
self.on_selected_item(record, list_price)
|
|
|
|
def on_selected_item_mix(self, record):
|
|
line = self.dialog_product_edit.get()
|
|
self._current_line_id = line['id']
|
|
# list_price = line['product'].get('list_price')
|
|
try:
|
|
code = line['product.']['code']
|
|
except:
|
|
code = line['product']['code']
|
|
product = self.Product.find([('code', '=', code)], fields=["list_price"])
|
|
list_price = product[0].get('list_price')
|
|
|
|
code_r = record['code']
|
|
product_r = self.Product.find([('code', '=', code_r)], fields=["template.sale_price_w_tax", "list_price"])
|
|
list_price_r = product_r[0].get('list_price')
|
|
|
|
if list_price < list_price_r:
|
|
# Update price in selected_line
|
|
self.set_unit_price(product_r[0]['template.']['sale_price_w_tax'], discount=False)
|
|
list_price = None
|
|
self.on_selected_item(record, list_price)
|
|
self.dialog_combine_product.close()
|
|
self.dialog_product_edit.close()
|
|
|
|
def on_selected_item(self, record, list_price=None, notes=None):
|
|
if not record:
|
|
return
|
|
self.add_product(record=record, list_price=list_price, notes=notes)
|
|
|
|
def on_selected_discount(self, discount):
|
|
self.dialog_auth_discount.close()
|
|
self.dialog_control_panel.close()
|
|
self.dialog_fixed_discount.close()
|
|
|
|
# If dialog edit product is active just it will
|
|
# apply discount to one product
|
|
_line = self.dialog_product_edit.get() or False
|
|
|
|
if self._state == 'payment':
|
|
dialog = self.dialog('cant_add_discount')
|
|
dialog.exec_()
|
|
return
|
|
if discount:
|
|
self.validate_discount(discount, _line)
|
|
self.dialog_product_edit.close()
|
|
|
|
def on_selected_delivery_party(self, delivery_party_id):
|
|
if delivery_party_id:
|
|
self.store.update({
|
|
'delivery_party': delivery_party_id,
|
|
})
|
|
self.dialog_delivery_party.close()
|
|
|
|
def on_selected_payment_term(self, record):
|
|
if record:
|
|
# payment_type = 2 is credit according electronic_invoice_co
|
|
payment_type = record['payment_type']
|
|
if payment_type == '2' and self.type_pos_user != 'salesman':
|
|
if not self.validate_payment_term(record):
|
|
return
|
|
self.store.update({'payment_term': record})
|
|
self._done_sale(is_credit=True)
|
|
else:
|
|
if not self.validate_payment_term(record):
|
|
return
|
|
self.store.update({'payment_term': record})
|
|
self.dialog_payment_term.close()
|
|
|
|
def on_selected_source(self, source):
|
|
self.dialog_source.close()
|
|
data = {'source': source}
|
|
# if source.get('party'):
|
|
# party = source.get('party')
|
|
# self.on_selected_party(party['id'])
|
|
# else:
|
|
# self.on_selected_party(self.default_party['id'])
|
|
self.store.update(data)
|
|
|
|
def on_selected_payment(self, journal):
|
|
self.journal = journal
|
|
self.setFocus()
|
|
if journal.get('id'):
|
|
self.field_journal_id = journal['id']
|
|
self.dialog_payment.close()
|
|
self.message_bar.set('enter_payment', journal['name'])
|
|
|
|
def on_search_product(self):
|
|
target = self.dialog_search_products.filter_field.text()
|
|
if not target:
|
|
return
|
|
target_words = target.split(' ')
|
|
domain = [
|
|
('template.active', '=', True),
|
|
('active', '=', True),
|
|
('template.salable', '=', True),
|
|
]
|
|
for tw in target_words:
|
|
if len(tw) <= 1:
|
|
continue
|
|
clause = [
|
|
'OR',
|
|
('template.name', 'ilike', '%{:}%'.format(tw)),
|
|
('description', 'ilike', '%{:}%'.format(tw)),
|
|
('code', 'ilike', '%{:}%'.format(tw)),
|
|
]
|
|
if self._onebarcode_activated:
|
|
clause.append(('barcode', '=', '%{:}%'.format(tw)))
|
|
if self.environment == 'retail':
|
|
clause.append(('template.reference', 'ilike', '%{:}%'.format(tw)))
|
|
domain.append(clause)
|
|
|
|
if self.shop.get('product_categories'):
|
|
categories_id = self.allow_categories_ids
|
|
domain.append([
|
|
'OR',
|
|
('template.categories', 'in', categories_id),
|
|
('template.account_category', 'in', categories_id),
|
|
])
|
|
if not domain:
|
|
return
|
|
|
|
ctx = self.stock_context
|
|
if not self.stock_context:
|
|
ctx = {'price_list': self.shop['price_list.']['id']}
|
|
else:
|
|
ctx['price_list'] = self.shop['price_list.']['id']
|
|
fields = self.dialog_search_products.fields_names
|
|
products = self.Product.find(domain, fields=fields,
|
|
limit=100, ctx=self.stock_context)
|
|
self.dialog_search_products.set_from_values(products)
|
|
|
|
def on_search_party(self):
|
|
target = self.dialog_search_parties.filter_field.text()
|
|
if not target:
|
|
return
|
|
target_words = target.split(' ')
|
|
domain = [('id_number', '!=', None)]
|
|
for tw in target_words:
|
|
if len(tw) <= 2:
|
|
continue
|
|
or_clause = [
|
|
'OR',
|
|
('name', 'ilike', '%' + tw + '%'),
|
|
('contact_mechanisms.value', 'like', tw + '%'),
|
|
('id_number', 'like', tw + '%')
|
|
]
|
|
domain.append(or_clause)
|
|
fields_names = self.dialog_search_parties.fields_names
|
|
parties = self.Party.find(domain, fields=fields_names)
|
|
self.dialog_search_parties.set_from_values(parties)
|
|
|
|
def clear_data(self):
|
|
# self._sale = {'total_amount': 0, 'tip_amount': 0, 'delivery_amount': 0}
|
|
self.party_name = None
|
|
self._sale_line = {}
|
|
self._total_amount = {}
|
|
self._sale_lines_taxes = {}
|
|
self.field_journal_id = self.default_journal['id']
|
|
self.field_voucher_ask.setText('')
|
|
self._consumer = None
|
|
|
|
def clear_left_panel(self):
|
|
self.field_party.setText('')
|
|
self.field_salesman.setText('')
|
|
self.field_salesman_id = None
|
|
self.field_invoice_type.set_from_id(invoice_type[0][0])
|
|
self.field_party_id = None
|
|
self.field_agent_id = None
|
|
self.field_payment_term_id = self.default_payment_term['id']
|
|
self.field_payment_term.setText(self.default_payment_term['name'])
|
|
self.field_sale_date.setText('')
|
|
self.field_global_discount_ask.setText('')
|
|
self.field_bono_discount_manual.setText('')
|
|
self.field_amount.setText('')
|
|
self.field_number.setText('')
|
|
self.current_comment = ''
|
|
if hasattr(self, 'field_consumer'):
|
|
self.field_consumer.setText('')
|
|
if self.field_delivery_charge:
|
|
self.field_delivery_charge.set_from_id('')
|
|
if self.field_table_assigned:
|
|
self.field_table_assigned.setText('')
|
|
if hasattr(self, 'field_comment'):
|
|
self.field_comment.document().clear()
|
|
if self._commission_activated and self.field_agent:
|
|
self.field_agent.setText('')
|
|
self.field_agent_ask.setText('')
|
|
self.field_commission_ask.setText('')
|
|
self.field_commission_ask.setReadOnly(True)
|
|
self.field_commission_amount.setText('')
|
|
self.field_commission_amount.setReadOnly(True)
|
|
self.model_sale_lines.reset()
|
|
self.clear_input_text()
|
|
self.clear_amount_text()
|
|
|
|
def clear_right_panel(self):
|
|
self.field_untaxed_amount.zero()
|
|
self.field_tax_amount.zero()
|
|
self.field_total_amount.zero()
|
|
self.field_change.zero()
|
|
self.field_paid_amount.zero()
|
|
self.field_discount.zero()
|
|
self.table_payment_lines.reset()
|
|
if self.environment == 'restaurant':
|
|
self.field_tip_amount.zero()
|
|
self.field_delivery_amount.zero()
|
|
self.field_net_amount.zero()
|
|
|
|
def state_enabled(self):
|
|
pass
|
|
|
|
def state_disabled(self):
|
|
self.payment_ctx = {}
|
|
self.clear_left_panel()
|
|
self.table_sale_lines.setDisabled(True)
|
|
self.set_state('disabled')
|
|
if self.field_delivery_charge:
|
|
self.field_delivery_charge.set_enabled(False)
|
|
|
|
def create_new_sale_from_scale(self, file):
|
|
to_create = {
|
|
'shop': self.shop['id'],
|
|
'invoice_type': invoice_type[0][0],
|
|
'company': self.company,
|
|
'party': self.default_party['id'],
|
|
'sale_device': self.device['id'],
|
|
'payment_method': 'cash',
|
|
'payment_term': self.default_payment_term['id'],
|
|
'kind': 'take_away',
|
|
'reference': file['ticket'],
|
|
'turn': 1,
|
|
}
|
|
|
|
_sale = self.Sale.new_sale(to_create)
|
|
|
|
def _add_products():
|
|
for _line in file['lines']:
|
|
_code = _line['product']
|
|
dom = [('code', '=', _code)]
|
|
products = self.Product.find(
|
|
dom,
|
|
fields=['id', 'name', 'code', 'description']
|
|
)
|
|
if not products:
|
|
print('Producto no encontrado ...', _code)
|
|
return
|
|
else:
|
|
product = products[0]
|
|
|
|
qty = Decimal(int(_line['weight']) / 1000)
|
|
print('sale qty...', qty, product)
|
|
|
|
data = {
|
|
'sale_id': _sale['id'],
|
|
'product_id': product['id'],
|
|
'qty': qty
|
|
}
|
|
|
|
res = self.Sale.faster_add_product(data)
|
|
print('res ....producto creado ...', res)
|
|
_add_products()
|
|
|
|
def create_new_sale(self, kind='take_away'):
|
|
if self.type_pos_user == 'cashier':
|
|
self.message_bar.set('not_sale')
|
|
return
|
|
|
|
if self.salesman_required and not self.salesman:
|
|
salesman = self.action_salesman_code()
|
|
if salesman == 0:
|
|
self.dialog_salesman_code.hide()
|
|
return
|
|
if not salesman:
|
|
return self.dialog('error_salesman_wrong')
|
|
else:
|
|
self.salesman = salesman
|
|
|
|
if self.environment == 'restaurant' and self._sale_pos_restaurant:
|
|
self.dialog_consumer.clear()
|
|
self._clear_invoice_number = True
|
|
last_invoice_number = self.store.get('invoice_number')
|
|
self.store.clear(
|
|
exception=['change', 'total_amount', 'net_amount', 'paid_amount']
|
|
)
|
|
self.journal = {}
|
|
self._consumer = {}
|
|
self.set_state('add')
|
|
self.input_text_changed('')
|
|
self.amount_text_changed('0')
|
|
self.clear_sign()
|
|
change = self.field_change.text()
|
|
self.clear_right_panel()
|
|
self.field_change.setText(change.replace(',', ''))
|
|
self.show_right_panel(True)
|
|
self.payment_ctx = {}
|
|
self.table_sale_lines.setEnabled(True)
|
|
self.field_invoice_type.set_enabled(True)
|
|
self.clear_data()
|
|
self.clear_left_panel()
|
|
self.message_bar.set('system_ready')
|
|
to_create = {
|
|
'shop': self.shop['id'],
|
|
'invoice_type': invoice_type[0][0],
|
|
'company': self.company,
|
|
'party': self.default_party['id'],
|
|
'sale_device': self.device['id'],
|
|
'payment_method': 'cash',
|
|
'payment_term': self.default_payment_term['id'],
|
|
'kind': kind,
|
|
'turn': 1,
|
|
}
|
|
if self.salesman:
|
|
to_create.update({'salesman': self.salesman['id']})
|
|
if self.environment != 'restaurant' and self._config.get('use_price_list'):
|
|
self.field_list_price.set_from_id(self.shop['price_list.']['id'])
|
|
sale = self.Sale.new_sale(to_create)
|
|
self.store.set(sale)
|
|
self.sale_id = sale['id']
|
|
|
|
# FIXME ADD MORE
|
|
self.store.set({'invoice_number': last_invoice_number})
|
|
self.party_id = self.default_party['id']
|
|
if self.sale_id and self.field_delivery_charge:
|
|
if self.field_delivery_charge:
|
|
self.field_delivery_charge.set_enabled(True)
|
|
|
|
self.change_view_to('order_front')
|
|
if self.environment == 'restaurant':
|
|
self.menu_dash.setDisabled(False)
|
|
if kind == 'delivery':
|
|
self.action_consumer()
|
|
elif kind == 'take_away':
|
|
self.action_position()
|
|
elif kind == 'to_table':
|
|
self.action_tables()
|
|
else:
|
|
self.set_state('add')
|
|
if self.type_pos_user in ('order', 'salesman'):
|
|
self.buttons_function.button_to_quote.show()
|
|
self.buttons_function.button_to_draft.hide()
|
|
if self.environment == 'restaurant':
|
|
self.action_source()
|
|
self.label_input.setFocus()
|
|
|
|
# def _set_sale_date(self):
|
|
# if self._sale.get('sale_date'):
|
|
# local_date = self._sale['sale_date']
|
|
# if isinstance(local_date, date):
|
|
# local_date = local_date.isoformat()
|
|
# self.field_sale_date.setText(local_date)
|
|
|
|
def _search_product(self, code):
|
|
domain = [
|
|
('template.salable', '=', True),
|
|
('template.account_category', '!=', None),
|
|
]
|
|
domain.append([
|
|
'OR',
|
|
('barcode', '=', code),
|
|
('code', '=', code)
|
|
])
|
|
# 'image', 'image_icon', 'write_date' fields remove from domain
|
|
products = self.Product.find(
|
|
domain,
|
|
fields=[
|
|
'name', 'code', 'categories', 'description',
|
|
'id', 'list_price', 'quantity', 'rec_name', 'template',
|
|
'extra_tax', 'template.sale_price_w_tax',
|
|
'template.default_uom'])
|
|
|
|
if not products or len(products) > 1:
|
|
self.message_bar.set('product_not_found')
|
|
return False
|
|
product = products[0]
|
|
return product
|
|
|
|
def _check_quantity(self):
|
|
if self.type_pos_user == 'cashier':
|
|
return True
|
|
for line in self.model_sale_lines._data:
|
|
product_id = None
|
|
if line.get('product'):
|
|
if isinstance(line['product'], dict):
|
|
product_id = line['product']['id']
|
|
else:
|
|
product_id = line['product']
|
|
elif line.get('product.'):
|
|
product_id = line['product.']['id']
|
|
if product_id:
|
|
product, = self.Product.find(
|
|
[('id', '=', product_id)],
|
|
fields=['id', 'name', 'code', 'rec_name', 'quantity'],
|
|
ctx=self.stock_context)
|
|
quantity = Decimal(str(round(float(line['quantity']), 5)))
|
|
if 'quantity' in product and not self._check_stock_quantity(
|
|
product, quantity):
|
|
return False
|
|
return True
|
|
|
|
def check_salesman(self):
|
|
salesman = self.store.get('salesman')
|
|
if self.salesman_required and not salesman:
|
|
dialog = self.dialog('missing_salesman')
|
|
dialog.exec_()
|
|
return False
|
|
return True
|
|
|
|
def add_product(self, record=None, code=None, list_price=None, notes=None):
|
|
if self._state == 'disabled':
|
|
return
|
|
sale_id = self.sale_id
|
|
if self._state in ('payment'):
|
|
self.message_bar.set('sale_closed')
|
|
return
|
|
|
|
if self._clear_invoice_number:
|
|
self.field_invoice_number.setText('')
|
|
self._clear_invoice_number = False
|
|
self.grid_amounts.label_change.setText('PENDIENTE')
|
|
product_id = record['id'] if record else None
|
|
if not product_id and code:
|
|
# REMOVE ME THIS OPTION IS FOR BARCODE
|
|
# product = self._search_product(code)
|
|
product = self._search_product(code)
|
|
if product:
|
|
product_id = product['id']
|
|
|
|
if not product_id:
|
|
self._state = 'warning'
|
|
return
|
|
data = {
|
|
'sale_id': sale_id,
|
|
'product_id': product_id,
|
|
'qty': 1,
|
|
}
|
|
if notes:
|
|
pass
|
|
if list_price is not None:
|
|
data['list_price'] = list_price
|
|
|
|
if hasattr(self, 'field_list_price'):
|
|
data['price_list'] = self.field_list_price.get_id()
|
|
res = self.Sale.faster_add_product(data)
|
|
res['sale'] = sale_id
|
|
self._sale_line = res
|
|
self._current_line_id = res['id']
|
|
self.add_sale_line(res)
|
|
self.product_id = product_id
|
|
self.message_bar.set('system_ready')
|
|
self.set_amounts()
|
|
self.set_state('add')
|
|
if record:
|
|
self._sale_line['product'] = record
|
|
if record.get('products_mix') and len(record.get('products_mix')) > 0:
|
|
self.action_combo(record['code'])
|
|
|
|
def _check_stock_quantity(self, product, request_qty=None):
|
|
if not isinstance(product['quantity'], (str, int, float, Decimal)):
|
|
return True
|
|
qty_ = round(Decimal(product['quantity']), 2)
|
|
if self._password_force_assign:
|
|
if (request_qty and qty_ < request_qty) or (qty_ <= 0):
|
|
self.message_bar.set('without_stock_quantity', product['name'])
|
|
self.dialog_force_assign.exec_()
|
|
password = self.field_password_force_assign_ask.text()
|
|
self.field_password_force_assign_ask.setText('')
|
|
if password != self._password_force_assign:
|
|
self.message_bar.set('not_can_force_assign')
|
|
return False
|
|
else:
|
|
self.message_bar.set('system_ready')
|
|
return True
|
|
|
|
def add_sale_line(self, record):
|
|
# if not record.get('unit.symbol'):
|
|
# record['unit.symbol'] = record['unit.']['symbol']
|
|
# if isinstance(record.get('product.'), dict):
|
|
# if not record.get('product.name'):
|
|
# record['product.name'] = record['product.']['name']
|
|
# if not record.get('product.code'):
|
|
# record['product.code'] = record['product.']['code']
|
|
if record.get('order_sended'):
|
|
record['order_sended'] = '✔'
|
|
rec = self.model_sale_lines.add_record(record)
|
|
self.field_amount.setText(rec['amount_w_tax'])
|
|
|
|
def sale_line_selected(self, line):
|
|
if self._state in ('checkout', 'payment'):
|
|
return
|
|
self.dialog_product_edit.set_line(line)
|
|
|
|
def sale_form_selected(self, data):
|
|
self.dialog_sale_form.start(data)
|
|
|
|
def clear_delivery_party(self):
|
|
self._delivery_party_selected = None
|
|
self.row_delivery_party.setText('')
|
|
self.row_id_number.setText('')
|
|
self.row_phone.setText('')
|
|
self.row_number_plate.setText('')
|
|
self.row_type_vehicle.set_from_id('')
|
|
self.row_delivery_party_active.setChecked(True)
|
|
|
|
def delivery_party_selected(self, data):
|
|
self._delivery_party_selected = data['id']
|
|
self.row_delivery_party.setText(data['rec_name'])
|
|
self.row_id_number.setText(data['party']['id_number'])
|
|
self.row_number_plate.setText(data['number_plate'])
|
|
if data.get('type_vehicle'):
|
|
self.row_type_vehicle.set_from_id(data['type_vehicle'])
|
|
self.row_delivery_party_active.setChecked(data['active'])
|
|
self.dialog_delivery_party_selected.show()
|
|
self.row_delivery_party_active.setFocus()
|
|
|
|
def set_expenses(self):
|
|
statements = self.Statement.find([
|
|
('state', '=', 'draft'),
|
|
('sale_device', '=', self.device['id']),
|
|
('journal.kind', '=', 'cash'),
|
|
])
|
|
if statements:
|
|
self.statement_cash = statements[0]
|
|
expenses = self.Expenses.find([
|
|
('statement', '=', self.statement_cash['id']),
|
|
], fields=['reference', 'invoice_number', 'description', 'amount'])
|
|
self.data_expenses = expenses
|
|
return True
|
|
else:
|
|
self.statement_cash = None
|
|
self.dialog('statement_closed')
|
|
|
|
def setup_sale_line(self):
|
|
product_code = {
|
|
'name': 'product.code',
|
|
'align': alignRight,
|
|
'description': 'COD',
|
|
'width': 80
|
|
}
|
|
product = {
|
|
'name': 'product.name',
|
|
'align': alignLeft,
|
|
'description': 'NOMBRE',
|
|
'width': STRETCH
|
|
}
|
|
description = {
|
|
'name': 'description',
|
|
'align': alignLeft,
|
|
'description': 'DESCRIPCION',
|
|
'width': 180
|
|
}
|
|
uom = {
|
|
'name': 'unit.symbol',
|
|
'align': alignHCenter,
|
|
'description': 'UNID.',
|
|
'width': 50
|
|
}
|
|
qty = {
|
|
'name': 'quantity',
|
|
'format': '{:3,.%sf}',
|
|
'align': alignRight,
|
|
'description': 'CANT.',
|
|
'digits': ('unit.symbol', CONVERSION_DIGITS),
|
|
'width': 80
|
|
}
|
|
discount = {
|
|
'name': 'discount_rate',
|
|
'format': '{0:.0%}',
|
|
'align': alignRight,
|
|
'description': 'DESC.',
|
|
'width': 50
|
|
}
|
|
amount = {
|
|
'name': 'amount_w_tax',
|
|
'format': '{:,d}',
|
|
'align': alignRight,
|
|
'description': 'SUBTOTAL',
|
|
'width': 100
|
|
}
|
|
note = {
|
|
'name': 'note',
|
|
'align': alignLeft,
|
|
'description': 'NOTA',
|
|
'invisible': True,
|
|
'width': 100
|
|
}
|
|
unit_price = {
|
|
'name': 'unit_price_w_tax',
|
|
'format': '{:5,.2f}',
|
|
'align': alignLeft,
|
|
'description': 'PRECIO',
|
|
'invisible': True,
|
|
'width': 100
|
|
}
|
|
|
|
qty_fraction = {
|
|
'name': 'qty_fraction',
|
|
'align': alignHCenter,
|
|
'description': 'FRAC',
|
|
'width': 50
|
|
}
|
|
|
|
commanded = {
|
|
'name': 'order_sended',
|
|
'align': alignHCenter,
|
|
'description': 'COMAN.',
|
|
'width': 70
|
|
}
|
|
|
|
self.fields_sale_line = [product, uom, qty, discount,
|
|
amount, note, unit_price]
|
|
|
|
if self.environment == 'retail':
|
|
self.fields_sale_line.insert(0, product_code)
|
|
|
|
if self._config.get('show_description_pos'):
|
|
self.fields_sale_line.insert(2, description)
|
|
|
|
if self._config.get('show_fractions'):
|
|
self.fields_sale_line.insert(4, qty_fraction)
|
|
|
|
if self.environment == 'restaurant':
|
|
self.fields_sale_line.insert(4, commanded)
|
|
|
|
self.model_sale_lines_simple = TableModel(
|
|
'sale.line', [product, qty, unit_price, amount, note]
|
|
)
|
|
|
|
self.model_sale_lines = TableModel('sale.line', self.fields_sale_line)
|
|
|
|
def setup_sale_consumer(self):
|
|
number = {
|
|
'name': 'number',
|
|
'align': alignCenter,
|
|
'description': 'ORDEN',
|
|
'width': 130
|
|
}
|
|
invoice_number = {
|
|
'name': 'invoice_number',
|
|
'align': alignCenter,
|
|
'description': 'FACTURA',
|
|
'width': 140
|
|
}
|
|
consumer = {
|
|
'name': 'consumer.name',
|
|
'align': alignLeft,
|
|
'description': 'CONSUMIDOR',
|
|
'width': 330
|
|
}
|
|
party = {
|
|
'name': 'party.name',
|
|
'align': alignLeft,
|
|
'description': 'CLIENTE',
|
|
'width': 330
|
|
}
|
|
sale_date = {
|
|
'name': 'sale_date',
|
|
'align': alignCenter,
|
|
'description': 'FECHA',
|
|
'width': 170
|
|
}
|
|
total_amount_cache = {
|
|
'name': 'total_amount_cache',
|
|
'align': alignRight,
|
|
'format': '{:5,.1f}',
|
|
'description': 'VALOR TOTAL',
|
|
'width': 180
|
|
}
|
|
|
|
self.fields_sales_query = [
|
|
number, invoice_number, sale_date, consumer, party, total_amount_cache
|
|
]
|
|
self.model_sale_historic = TableModel(
|
|
'sale.sale', self.fields_sales_query)
|
|
|
|
def setup_delivery_party(self):
|
|
party = {
|
|
'name': 'rec_name',
|
|
'align': alignLeft,
|
|
'description': 'CLIENTE',
|
|
'width': 380
|
|
}
|
|
id_number = {
|
|
'name': 'party.id_number',
|
|
'align': alignLeft,
|
|
'description': 'NUM. ID',
|
|
'width': 200
|
|
}
|
|
phone = {
|
|
'name': 'party.phone',
|
|
'align': alignLeft,
|
|
'description': 'TELEFONO',
|
|
'width': 200
|
|
}
|
|
number_plate = {
|
|
'name': 'number_plate',
|
|
'align': alignCenter,
|
|
'description': 'PLACA',
|
|
'width': 200
|
|
}
|
|
type_vehicle = {
|
|
'name': 'type_vehicle',
|
|
'align': alignCenter,
|
|
'description': 'TIPO DE VEHICULO',
|
|
'width': 300
|
|
}
|
|
|
|
self.fields_delivery_party = [
|
|
party, id_number, phone, number_plate, type_vehicle]
|
|
self.model_delivery_party = TableModel(
|
|
'sale.delivery_party', self.fields_delivery_party)
|
|
for record in self.delivery_man_table:
|
|
self.model_delivery_party.add_record(record)
|
|
self.delivery_parties = [
|
|
d for d in self.model_delivery_party._data if d['active']]
|
|
|
|
def setup_payment(self):
|
|
pay_fields = [{
|
|
'name': 'statement',
|
|
'align': alignLeft,
|
|
'description': 'DIARIO',
|
|
}, {
|
|
'name': 'amount',
|
|
'align': alignRight,
|
|
'format': '{:5,.1f}',
|
|
'description': 'VALOR',
|
|
}, {
|
|
'name': 'voucher',
|
|
'align': alignCenter,
|
|
'description': 'VOUCHER',
|
|
}]
|
|
self.table_payment_lines = TableModel(
|
|
'account.statement.line', pay_fields)
|
|
|
|
def on_change_line_selected(self, key):
|
|
self.table_sale_lines.moved_selection(key)
|
|
|
|
def action_discount_line(self, record):
|
|
if self.environment == 'restaurant' and self.type_pos_user not in (
|
|
'cashier', 'frontend_admin'):
|
|
self.dialog('user_without_permission')
|
|
return
|
|
self.dialog_fixed_discount.exec_()
|
|
if self.environment == 'restaurant':
|
|
self.dialog_combine_product.close()
|
|
|
|
def action_discount_bono_line(self, record):
|
|
if self.environment == 'restaurant' and self.type_pos_user not in (
|
|
'cashier', 'frontend_admin'):
|
|
self.dialog('user_without_permission')
|
|
return
|
|
self.field_bono_discount_manual.setText('')
|
|
self.dialog_fixed_discount_manual.exec_()
|
|
if self.environment == 'restaurant':
|
|
self.dialog_combine_product.close()
|
|
discount = self.field_bono_discount_manual.text()
|
|
_line = self.dialog_product_edit.get() or False
|
|
if discount and discount.isdigit() and _line:
|
|
rec = {
|
|
'type_discount': 'fixed',
|
|
'discount': discount,
|
|
'name': 'BONO ABIERTO',
|
|
}
|
|
self.validate_discount(rec, _line)
|
|
self.dialog_product_edit.close()
|
|
|
|
def action_combo(self, code=None):
|
|
if not code:
|
|
line = self.dialog_product_edit.get()
|
|
try:
|
|
code = line['product.']['code']
|
|
except Exception:
|
|
code = line['product']['code']
|
|
product = self.Product.find(
|
|
[('code', '=', code)],
|
|
fields=[
|
|
"code", "description", "extra_tax", "name", "sale_uom",
|
|
"quantity", "sale_price_w_tax", "template.account_category",
|
|
"template.sale_price_w_tax", "template.rec_name",
|
|
'products_mix.code', 'products_mix.name',
|
|
"quantity_mix_required"
|
|
])
|
|
products_mix = product[0].get('products_mix.')
|
|
if not products_mix:
|
|
return
|
|
qty_min_req = product[0].get('quantity_mix_required')
|
|
if qty_min_req:
|
|
self.dialog_combo_product.label_qty_min_req.setText(str(qty_min_req))
|
|
self.dialog_combo_product.set_buttons(products_mix)
|
|
self.dialog_combo_product.exec_()
|
|
self.dialog_combo_product.close()
|
|
|
|
def action_combine_line(self):
|
|
line = self.dialog_product_edit.get()
|
|
try:
|
|
code = line['product.']['code']
|
|
except Exception:
|
|
code = line['product']['code']
|
|
|
|
product = self.Product.find([('code', '=', code)])
|
|
categories = product[0].get('categories')
|
|
if not categories:
|
|
return
|
|
mixables = self.Product.find([
|
|
('categories', 'in', categories),
|
|
], fields=['id', 'name', 'code', 'categories', 'rec_name', 'list_price'])
|
|
self.dialog_combine_product.set_buttons(mixables)
|
|
self.dialog_combine_product.exec_()
|
|
self.dialog_combine_product.filter_field.clear()
|
|
self.dialog_combine_product.close()
|
|
|
|
def action_print_count_money(self, data):
|
|
try:
|
|
self.receipt_sale.print_count_money(data)
|
|
except Exception:
|
|
pass
|
|
|
|
def action_delete_line(self):
|
|
"""Delete Product """
|
|
if self.model_sale_lines.rowCount() <= 0 or self._state == 'checkout':
|
|
return
|
|
sale = self.store.store
|
|
self.table_sale_lines.setFocus()
|
|
line = self.table_sale_lines.get_selected_clicked()
|
|
state = sale.get('state')
|
|
note = None
|
|
if self.environment == 'restaurant':
|
|
order_sended = line.get('order_sended') and line['order_sended'] in (True, '✔')
|
|
not_delete = self._config.get('no_remove_commanded', False)
|
|
if order_sended and (not self.user_can_delete or not_delete):
|
|
return self.dialog('user_without_permission')
|
|
|
|
if order_sended:
|
|
res = self.dialog_delete_product.exec_()
|
|
if res == 0:
|
|
self.dialog_delete_product.close()
|
|
return
|
|
note = self.field_note_ask.toPlainText()
|
|
if note is None or note == '':
|
|
return self.dialog('missing_note_for_delete_product')
|
|
self.field_note_ask.clear()
|
|
elif self.environment == 'retail' and state and state != 'draft':
|
|
return self.dialog('dont_delete_product')
|
|
# if not removed_item:
|
|
removed_item = self.table_sale_lines.delete_item(ignore_focus=True)
|
|
if self.environment == 'restaurant' and self.print_order and order_sended:
|
|
self.print_command(sale, removed_item)
|
|
if note:
|
|
self.SaleLine.write([removed_item['id']], {'note': note})
|
|
prd_code = removed_item['product.']['code']
|
|
tip_product = self._config.get('tip_product.')
|
|
if prd_code and tip_product and tip_product['code'] == prd_code:
|
|
self.Sale.write([self.sale_id], {'tip': None})
|
|
self.SaleLine.delete([removed_item['id']])
|
|
self.set_amounts()
|
|
|
|
self._current_line_id = None
|
|
self.setFocus()
|
|
self.label_input.setFocus()
|
|
self.dialog_product_edit.close()
|
|
|
|
def set_discount(self, eval_value, lines_ids=[], type_=None):
|
|
res = False
|
|
try:
|
|
value = round(float(str(eval_value)), 6)
|
|
except ValueError:
|
|
logging.warning('ValueError > ', ValueError)
|
|
return
|
|
if float(value) < 0:
|
|
return False
|
|
|
|
if not lines_ids:
|
|
target_lines = [self._current_line_id]
|
|
else:
|
|
target_lines = lines_ids
|
|
response = self.SaleLine.faster_set_discount({
|
|
'line_ids': target_lines,
|
|
'value': value,
|
|
'type': type_
|
|
})
|
|
if response.get('records'):
|
|
for rec in response['records']:
|
|
self.model_sale_lines.update_record(rec)
|
|
|
|
res = True
|
|
self.set_amounts()
|
|
return res
|
|
|
|
def set_unit_price(self, value, discount=True):
|
|
rec = self.SaleLine.set_faster_unit_price({
|
|
'id': self._current_line_id,
|
|
'value': value,
|
|
'discount': discount,
|
|
})
|
|
if rec:
|
|
self.model_sale_lines.update_record(rec)
|
|
self.set_amounts()
|
|
return True
|
|
return False
|
|
|
|
def add_payment(self, amount):
|
|
voucher_number = None
|
|
if not self.journal:
|
|
journal = self.default_journal
|
|
else:
|
|
journal = self.journal
|
|
|
|
if journal.get('require_voucher'):
|
|
res = self.dialog_voucher.exec_()
|
|
if res == 0:
|
|
self.dialog_voucher.close()
|
|
return
|
|
voucher_number = self.field_voucher_ask.text()
|
|
if voucher_number is None or voucher_number == '':
|
|
return self.add_payment(amount)
|
|
self.field_voucher_ask.setText('')
|
|
|
|
# FIXME add extra_paid bool
|
|
res = self.Sale.faster_add_payment({
|
|
'sale_id': self.sale_id,
|
|
'journal_id': journal['id'],
|
|
'cash_received': to_numeric(amount),
|
|
'voucher_number': voucher_number,
|
|
'extra_paid': False,
|
|
}, ctx={'advance': False, 'reservation': False})
|
|
if res.get("msg") and res.get('msg') not in ('missing_money', 'ok') or res.get('error'):
|
|
if res.get("msg"):
|
|
self.dialog(res['msg'])
|
|
return
|
|
|
|
residual_amount = res.get('residual_amount')
|
|
change = res.get('change')
|
|
paid_amount = res.pop('paid_amount')
|
|
self.store.set({
|
|
'residual_amount': residual_amount,
|
|
'change': change,
|
|
'paid_amount': paid_amount,
|
|
})
|
|
if res['id']:
|
|
self.table_payment_lines.add_record(res)
|
|
return res
|
|
|
|
def set_keys(self):
|
|
self.keys_numbers = list(range(Qt.Key_0, Qt.Key_9 + 1))
|
|
self.keys_alpha = list(range(Qt.Key_A, Qt.Key_Z + 1))
|
|
self.keys_period = [Qt.Key_Period]
|
|
self.show_keys = self.keys_numbers + self.keys_alpha + self.keys_period
|
|
self.keys_special = [Qt.Key_Asterisk,
|
|
Qt.Key_Comma, Qt.Key_Minus, Qt.Key_Slash]
|
|
self.keys_input = [Qt.Key_Backspace, *self.keys_special, *self.show_keys]
|
|
self.keys_input.extend([Qt.Key_Return, Qt.Key_Plus])
|
|
# self.keys_input.extend(self.keys_special)
|
|
# self.keys_input.extend(self.show_keys)
|
|
# self.keys_input.extend(self.keys_numbers)
|
|
|
|
def set_state(self, state='add'):
|
|
self._state = state
|
|
state_info = STATES[state]
|
|
self._re = state_info['re']
|
|
if not self.type_pos_user == 'order':
|
|
stacked = self.buttons_stacked.stacked
|
|
if not stacked.currentWidget():
|
|
return
|
|
if state_info['button']:
|
|
stacked.setCurrentWidget(
|
|
getattr(self.buttons_stacked, state_info['button'])
|
|
)
|
|
stacked.currentWidget().setVisible(True)
|
|
else:
|
|
stacked.currentWidget().setVisible(False)
|
|
|
|
if state == 'payment':
|
|
self.table_sale_lines.setDisabled(True)
|
|
self.field_invoice_type.set_enabled(False)
|
|
if self.environment == 'restaurant':
|
|
self.menu_dash.setDisabled(True)
|
|
|
|
def key_pressed(self, text):
|
|
if self._state == 'disabled':
|
|
return
|
|
if not self._sign and self._state not in ('checkout', 'payment'):
|
|
if self._re.match(self._input_text + text):
|
|
self.input_text_changed(text)
|
|
else:
|
|
if RE_SIGN['quantity'].match(self._amount_text + text):
|
|
self.amount_text_changed(text)
|
|
|
|
def clear_sign(self):
|
|
self._sign = None
|
|
self.field_sign.setText(' {0} '.format(' '))
|
|
|
|
def sign_text_changed(self, sign):
|
|
self._sign = sign
|
|
self.field_sign.setText(' {0} '.format(sign))
|
|
if self._sale_line:
|
|
if sign == '-':
|
|
self.message_bar.set('enter_discount')
|
|
elif sign == '/':
|
|
self.message_bar.set('enter_new_price')
|
|
elif sign == '*':
|
|
self.message_bar.set('enter_quantity')
|
|
if self.active_weighing and self._sale_line['unit_symbol'] != 'u':
|
|
self.action_read_weight()
|
|
|
|
def key_special_pressed(self, value):
|
|
self.clear_amount_text()
|
|
self.clear_input_text()
|
|
if value not in ['-', '/', '*']:
|
|
return
|
|
if self._state in ['payment', 'checkout']:
|
|
return
|
|
self.sign_text_changed(value)
|
|
|
|
def key_backspace_pressed(self):
|
|
if self._sign or self._state in ('checkout', 'payment'):
|
|
self._amount_text = self._amount_text[:-1]
|
|
self.amount_text_changed()
|
|
self.label_input.setFocus()
|
|
else:
|
|
self._input_text = self._input_text[:-1]
|
|
self.input_text_changed()
|
|
|
|
def set_text(self, text):
|
|
if not self._state == 'checkout':
|
|
self.input_text_changed(text)
|
|
else:
|
|
self.amount_text_changed(text)
|
|
|
|
def clear_input_text(self):
|
|
self.input_text_changed('')
|
|
|
|
def clear_amount_text(self):
|
|
self._amount_text = '0'
|
|
self.amount_text_changed()
|
|
|
|
def keyPressEvent(self, event):
|
|
key = event.key()
|
|
self._keyStates[key] = True
|
|
if self._state == 'add' and key not in self.keys_input and \
|
|
key not in (Qt.Key_Enter, Qt.Key_End):
|
|
# Clear ui context when keys function are pressed
|
|
self._clear_context()
|
|
if key in (Qt.Key_Return, Qt.Key_Plus):
|
|
self.button_plus_pressed()
|
|
elif key in self.show_keys:
|
|
# No allow change quantity o discount in state == cash
|
|
if self._state in ('checkout', 'payment') and key not in self.keys_numbers:
|
|
return
|
|
self.key_pressed(event.text())
|
|
elif key in self.keys_special:
|
|
if self._state in ['checkout', 'payment'] or not self._current_line_id:
|
|
return
|
|
self.key_special_pressed(event.text())
|
|
elif key == Qt.Key_Backspace:
|
|
self.key_backspace_pressed()
|
|
elif key == Qt.Key_Escape:
|
|
if self._state not in ('checkout', 'payment'):
|
|
self._clear_context()
|
|
# self.close()
|
|
elif key == Qt.Key_F1:
|
|
if self.type_pos_user in ('cashier', 'frontend', 'frontend_admin'):
|
|
self.action_control_panel()
|
|
elif key == Qt.Key_F9:
|
|
self.action_search_sale()
|
|
elif key == Qt.Key_F11:
|
|
self.action_new_sale()
|
|
elif key == Qt.Key_F7:
|
|
self.action_print_sale()
|
|
elif self._state == 'disabled':
|
|
self.message_bar.set('must_load_or_create_sale')
|
|
return
|
|
elif key in (Qt.Key_Enter, Qt.Key_End):
|
|
if self.type_pos_user in ['order', 'salesman']:
|
|
return
|
|
if self._state == 'add':
|
|
self.button_accept_pressed()
|
|
elif self._state in ['checkout', 'accept']:
|
|
self.button_checkout_pressed()
|
|
elif key == Qt.Key_F2:
|
|
self.action_search_product()
|
|
elif key == Qt.Key_F3:
|
|
self.button_payment_pressed()
|
|
elif key == Qt.Key_F4:
|
|
self.action_party()
|
|
elif key == Qt.Key_F5:
|
|
self.action_global_discount()
|
|
elif key == Qt.Key_F6:
|
|
self.action_send_order()
|
|
elif key == Qt.Key_F8:
|
|
self.action_payment_term()
|
|
elif key == Qt.Key_F10:
|
|
# self.action_check_product_price()
|
|
pass
|
|
elif key == Qt.Key_F12:
|
|
# self.action_cancel()
|
|
pass
|
|
elif key == Qt.Key_Home:
|
|
pass
|
|
elif key == Qt.Key_Down or key == Qt.Key_Up:
|
|
self.on_change_line_selected(key)
|
|
elif key == Qt.Key_Insert:
|
|
self.action_position()
|
|
elif key == Qt.Key_Semicolon and self._commission_activated:
|
|
sale = self.get_current_sale()
|
|
if sale['state'] == 'draft' and self._state not in ['checkout', 'payment']:
|
|
self.action_agent()
|
|
elif key == Qt.Key_QuoteDbl:
|
|
self.action_comment()
|
|
elif key == Qt.Key_Question:
|
|
self.action_tax()
|
|
else:
|
|
pass
|
|
|
|
@property
|
|
def state(self):
|
|
return self._state
|
|
|
|
def get_product_fraction_prices(self, product_id, sale_id, qty):
|
|
price = self.Sale.get_product_prices({
|
|
'ids': [product_id],
|
|
'quantity': float(qty),
|
|
'sale_id': sale_id,
|
|
})
|
|
return price
|
|
|
|
def dialog_product_edit_accepted(self, data, line):
|
|
if not data:
|
|
return
|
|
_record = None
|
|
update_base_price = self.dialog_product_edit.checkbox_base.isChecked()
|
|
if update_base_price:
|
|
data['update_base_price'] = update_base_price
|
|
if data:
|
|
_record = self.edit_line_sale(data, line)
|
|
if _record:
|
|
self.model_sale_lines.update_record(_record)
|
|
self.set_amounts()
|
|
self.dialog_product_edit.clear()
|
|
|
|
def edit_line_sale(self, data, line):
|
|
current_unit_price = float(str(round(line['unit_price_w_tax'], 0)))
|
|
sale_line, = self.SaleLine.find([
|
|
('id', '=', line['id'])
|
|
])
|
|
base_price = float(str(round(sale_line['product.']['sale_price_taxed'], 0)))
|
|
previous_qty = Decimal(line['quantity'])
|
|
self._current_line_id = line['id']
|
|
to_write = {}
|
|
for k, v in data.items():
|
|
if k in ['description', 'note']:
|
|
to_write[k] = v
|
|
elif k == 'unit_price' and float(v) != current_unit_price:
|
|
if float(v) > base_price:
|
|
self.set_unit_price(v, discount=False)
|
|
else:
|
|
self._sign = '/'
|
|
value = current_unit_price - float(v)
|
|
self.set_discount(value, type_='fixed')
|
|
self._sign = None
|
|
elif k == 'quantity' and v != previous_qty:
|
|
if data.get('qty_fraction'):
|
|
qty = to_float(float(v), 5)
|
|
else:
|
|
qty = to_float(v, 2)
|
|
qty_data = {
|
|
'id': data['id'],
|
|
'quantity': qty
|
|
}
|
|
# if self._config.get('use_price_list'):
|
|
# qty_data['use_price_list'] = True
|
|
ctx = None
|
|
if hasattr(self, 'field_list_price') and self._config.get('use_price_list'):
|
|
ctx = {'price_list': self.field_list_price.get_id()}
|
|
self.SaleLine.faster_set_quantity(qty_data, ctx=ctx)
|
|
elif k == 'update_base_price':
|
|
self.set_unit_price(base_price, discount=False)
|
|
|
|
self.SaleLine.write([data['id']], to_write)
|
|
_record, = self.SaleLine.find([
|
|
('id', '=', data['id'])
|
|
])
|
|
return _record
|
|
|
|
def save_consumer(self, consumer):
|
|
if consumer is None:
|
|
return
|
|
consumer = {k: str(v).splitlines()[0] for k, v in consumer.items()
|
|
if k != 'notes' and v != ''}
|
|
if consumer.get('id'):
|
|
res = self.Consumer.write([consumer['id']], consumer)
|
|
else:
|
|
res = self.Sale.create_consumer(consumer)
|
|
|
|
if res:
|
|
self.store.update({'consumer': res})
|
|
if res.get('party'):
|
|
self.on_selected_party(res['party'])
|
|
|
|
def dialog_delivery_party_accepted(self):
|
|
if not self.state_delivery_party:
|
|
return
|
|
if hasattr(self, '_delivery_party_selected') and self._delivery_party_selected:
|
|
res = self.Sale.update_delivery_party({
|
|
'id': self._delivery_party_selected,
|
|
'data': self.state_delivery_party
|
|
})
|
|
self._delivery_party_selected = None
|
|
self.model_delivery_party.update_record(res)
|
|
else:
|
|
res = self.Sale.create_delivery_party({
|
|
'data': self.state_delivery_party,
|
|
'shop_id': self.ctx['shop']
|
|
})
|
|
self.model_delivery_party.add_record(res)
|
|
|
|
self.delivery_parties = [
|
|
d for d in self.model_delivery_party._data if d['active']]
|
|
self.dialog_delivery_party = DialogDeliveryParty(self).get()
|
|
|
|
def update_delivery_party(self, field=''):
|
|
state_delivery = self.state_delivery_party
|
|
field_text = ['party', 'phone', 'id_number', 'number_plate']
|
|
if field in field_text:
|
|
state_delivery[field] = self.row_delivery_party.text()
|
|
elif field == 'active':
|
|
state_delivery['active'] = self.row_delivery_party_active.isChecked()
|
|
elif field == 'type_vehicle':
|
|
state_delivery['type_vehicle'] = self.row_type_vehicle.get_id()
|
|
|
|
def search_consumer(self, phone):
|
|
if phone:
|
|
consumers = self.Consumer.find([('phone', '=', phone)])
|
|
if consumers:
|
|
self._consumer = consumers[0]
|
|
self.dialog_consumer.fill(self._consumer)
|
|
self.store.update({'consumer': self._consumer})
|
|
return True
|
|
|
|
def action_assign_table(self, table_id, table_name):
|
|
current_table = self.store.get('table_assigned')
|
|
to_drop = {}
|
|
if current_table:
|
|
# We must drop current assigned table to sale
|
|
to_drop = {
|
|
'state': 'available',
|
|
'sale': None,
|
|
}
|
|
if isinstance(current_table, int):
|
|
self.RestTables.write([current_table], to_drop)
|
|
to_drop['id'] = current_table
|
|
elif isinstance(current_table, dict):
|
|
self.RestTables.write([current_table['id']], to_drop)
|
|
to_drop['id'] = current_table['id']
|
|
|
|
vals = {
|
|
'table_assigned': {
|
|
'id': table_id,
|
|
'name': table_name,
|
|
}
|
|
}
|
|
self.store.update(vals)
|
|
|
|
to_add = {
|
|
'state': 'occupied',
|
|
'sale': self.store.sale_id,
|
|
}
|
|
self.RestTables.write([table_id], to_add)
|
|
return {'to_add': to_add, 'to_drop': to_drop}
|
|
|
|
def action_see_table(self, table_id):
|
|
table_, = self.RestTables.find([
|
|
('id', '=', table_id),
|
|
], limit=1)
|
|
sale = table_['sale']
|
|
if sale:
|
|
self.change_view_to('order_front')
|
|
self.load_sale(sale)
|
|
self.dialog_manage_tables.hide()
|