presik_pos/app/main.py

3284 lines
117 KiB
Python

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys
import os
import logging
import base64
import platform
from packaging.version import parse as parse_version
from decimal import Decimal
from datetime import datetime, timedelta, date
from collections import OrderedDict
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (QLabel, QHBoxLayout, QVBoxLayout, QWidget)
from paramiko import Agent
from app.commons.image import Image
from .frontwindow import FrontWindow
from .tools import get_icon, to_float, to_numeric
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 .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, DIALOG_REPLY_YES,
)
from .dialogs import DialogDeliveryParty
invoice_type = [('P', ('POS'))]
_SALE_HISTORIC = [
'party', 'sale_date', 'number', 'invoice_number', 'consumer',
'total_amount_cache', 'lines'
]
class AppWindow(FrontWindow):
def __init__(self, connection, params, mode_conn):
title = "PRESIK | SMART POS (○ " + mode_conn + ")"
global CONNECTION
CONNECTION = connection
self.data_expenses = None
self.mode_conn = mode_conn
super(AppWindow, self).__init__(connection, params, title)
self.payment_ctx = {}
self.set_keys()
StackMessages(self)
self.stock_context = None
self._sale = {}
self.ctx = self._context
self.ctx['params'] = params
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()
self.setup_sale_consumer()
self.setup_payment()
self.set_domains()
self.create_gui()
self.message_bar.load_stack(self.stack_msg)
self.journal = {}
if not hasattr(self, 'auto_print_commission'):
self.auto_print_commission = False
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 not hasattr(self, 'active_weighing'):
self.active_weighing = False
elif self.active_weighing is True:
from .electronic_scale import ScaleReader
self.reader_thread = ScaleReader()
self.reader_thread.sigSetWeight.connect(self.set_weight_readed)
self.do_invoice = DoInvoice(self, self._context)
self.do_invoice.sigDoInvoice.connect(self.__do_invoice_thread)
self.set_state('disabled')
self.change_view_to('start_front')
if self.cache_local:
self.set_cache_company()
self.set_cache_products()
for j in self._journals:
if j['id'] == self.default_journal['id']:
self.default_journal = j
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.device['shop']['company']['id']
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):
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):
if hasattr(self, '_sale') and self._sale['id']:
sales = self.Sale.find([
('id', '=', self._sale['id'])
])
if not sales:
return
return sales[0]
def get_price_lists(self):
price_lists = self.Pricelist.find([])
price_lists = [(l['id'], l['name']) for l 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):
# Printing invoice context
if self.printer_sale_name:
if ">" in self.printer_sale_name:
self.printer_sale_name = str(
str("\\") + self.printer_sale_name.replace('>', str('\\')))
ctx_printing = self.Sale.get_printing_context({
'device_id': self.device['id'],
'user': self.user,
'user_id': self._user['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")
platform_py = platform.python_version()
# if float(platform_py[:4]) >= 3.9:
if parse_version(platform_py) >= parse_version('3.9'):
f.write(base64.decodebytes(self.logo.encode()))
else:
f.write(base64.decodestring(self.logo.encode()))
f.close()
self.receipt_sale = Receipt(
ctx_printing,
logo=locale_logo,
environment=self.enviroment
)
# Printing order context
if self.print_order:
self.receipt_order = Receipt(ctx_printing, environment=self.enviroment)
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
args = {'sale_id': self._sale['id']}
self.Sale.to_quote(args)
if self.model_sale_lines.rowCount() > 0:
if self.check_salesman():
# self.state_disabled()
pass
try:
self.Sale.generate_shipment({
'sale_id': self._sale['id'],
})
self.dialog('order_dispatched')
self.see_start_front()
except:
self.dialog('error_order_dispatched')
def button_to_draft_pressed(self):
# Return sale to draft state
if hasattr(self, '_sale'):
args = {'sale_id': self._sale['id']}
self.Sale.to_draft(args)
self.see_start_front()
# self.state_disabled()
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)
if self.mode_conn == 'offline':
self.WidgetOffline = QWidget()
self.WidgetOffline.setObjectName('label_offline')
vbox_label = QVBoxLayout()
vbox_label.setAlignment(Qt.AlignBottom)
LabelOffline = QLabel(self.mode_conn)
LabelOffline.setObjectName('LabelOffline')
LabelOffline.setAlignment(Qt.AlignCenter)
LabelOffline.setStyleSheet(
"""background-color:red;
font: 30px; color: white;
font-weight: bold;
min-height: 60px;
max-height: 90px""")
vbox_label.addWidget(LabelOffline)
self.WidgetOffline.setLayout(vbox_label)
board.addWidget(self.WidgetOffline, 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('electronic_authorization'):
invoice_type.append(('1', ('VENTA ELECTRONICA')))
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
button_functions = ButtonsFunction(self)
info_fields = [
('party', {
'name': 'CLIENTE',
'readonly': True,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
}),
('kind', {
'name': 'CLASE',
'placeholder': False,
'type': 'selection',
'on_change': 'action_kind_selection_changed',
'values': KIND,
'size': self.screen_size,
'color': self.label_color
}),
('salesman', {
'name': 'VENDEDOR',
'readonly': True,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
}),
('number', {
'name': 'No ORDEN',
'placeholder': False,
'readonly': True,
'size': self.screen_size,
'color': self.label_color
}),
('payment_term', {
'name': 'FORMA DE PAGO',
'readonly': True,
'invisible': False,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
}),
('delivery_party', {
'name': 'DOMICILIARIO',
'readonly': True,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
})
]
self.sources = self.Source.find([])
if self.enviroment != 'restaurant':
info_fields.append(
('list_price', {
'name': 'LISTA DE PRECIOS',
'placeholder': False,
'type': 'selection',
'values': self.get_price_lists(),
'default': self.shop['price_list'],
'size': self.screen_size,
'color': self.label_color
}))
info_fields.append(
('source', {
'name': 'CANAL',
'readonly': True,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
}))
if self._config['show_position_pos']:
info_fields.append(('position', {
'name': 'POSICION',
'readonly': True,
'placeholder': False,
'size': self.screen_size,
'color': self.label_color
}))
if self._commission_activated and self._config['show_agent_pos']:
info_fields.append(('agent', {
'name': 'AGENTE',
'placeholder': False,
'readonly': True,
'size': self.screen_size,
'color': self.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': self.screen_size,
'color': self.label_color
}))
self.field_table_assigned = None
if self._sale_pos_restaurant:
info_fields.append(
('consumer', {
'name': 'CONSUMIDOR',
'placeholder': False,
'size': self.screen_size,
'color': self.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': self.screen_size,
'color': self.label_color
}))
info_fields.append(
('table_assigned', {
'name': 'MESA',
'placeholder': False,
'size': self.screen_size,
'color': self.label_color,
'readonly': True,
}))
info_fields.append(
('order_status', {
'name': 'ESTADO',
'placeholder': False,
'size': self.screen_size,
'color': self.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.enviroment == 'restaurant':
invisible = True
_fields_amounts = [
('untaxed_amount', {
'name': 'SUBTOTAL',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
'invisible': invisible,
}),
('tax_amount', {
'name': 'IMPUESTOS',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
'invisible': invisible,
}),
('discount', {
'name': 'DISCUENTOS',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
'invisible': invisible,
}),
('total_amount', {
'name': 'TOTAL',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': 'black',
'font_size': 'big',
}),
('paid_amount', {
'name': 'PAGADO',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
}),
('change', {
'name': 'PENDIENTE',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'color': self.label_color_2 or 'orange'
})
]
if self.enviroment == 'restaurant':
_fields_amounts.insert(3, ('tip_amount', {
'name': 'VR. PROPINA',
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
})
)
_fields_amounts.insert(4, ('delivery_amount', {
'name': 'VR. DOMIC',
'type': 'money',
'size': self.screen_size,
'color': self.label_color,
})
)
_fields_amounts.insert(6, ('net_amount', {
'name': 'NETO TOTAL',
'readonly': True,
'type': 'money',
'size': self.screen_size,
'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=self.screen_size
)
vbox_order_front.addWidget(self.table_sale_lines, 1)
vbox_left.addLayout(board, 0)
if self.enviroment == 'restaurant':
self.panel_right_box = QWidget()
values = self.get_product_by_categories_dash()
self.menu_dash = MenuDash(self, values, 'on_selected_item')
self.order_front.hide()
vbox_order_front.addLayout(button_functions, 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(button_functions, 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.enviroment == '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.text())
if not is_paid:
self.set_state('payment')
self.clear_input_text()
self.clear_amount_text()
return
if self.enviroment == '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, 'WidgetOffline'):
self.WidgetOffline.show()
self.set_state('disabled')
self.buttons_stacked.hide()
self._sale = {}
else:
self.start_front.hide()
if hasattr(self, 'WidgetOffline'):
self.WidgetOffline.hide()
self.order_front.show()
self.buttons_stacked.show()
def action_read_weight(self):
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'):
ctx = {'price_list': self.field_list_price.get_id()}
rec = self.SaleLine.faster_set_quantity(args, ctx=ctx)
except:
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 = (1 - (value / price_w_tax)) * 100
# if self.discount_method == 'fixed':
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.replace(',', ''))
if self._commission_activated and self.enviroment == '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 = j
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._context['exception'] = True
res = self.Sale.faster_post_invoice({
'sale_id': self.sale_to_post_id,
})
except:
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.device['shop']['invoice_copies']
if self.enviroment == 'restaurant':
self.action_print_sale()
else:
if not is_credit:
self.print_invoice(copies=_copies, open_box=True)
if self.enviroment == 'restaurant' and self.print_order and not self.check_all_commanded():
self.action_send_order()
except:
logging.error(sys.exc_info()[0])
# if not is_credit and self._commission_activated:
# agent_id = self.field_agent_ask.get_id()
# if self.auto_print_commission and agent_id:
# self.print_equivalent_invoice(self._sale['id'])
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):
self.receipt_sale.print_sale(data, type_doc, open_box)
# 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.enviroment != '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):
kind = self.store.get('kind')
if self.enviroment == 'restaurant' and kind == 'delivery':
if not self.store.get('consumer'):
self.action_consumer()
return
if not self._check_quantity() or not self._sale.get('id'):
return
if not self._sale['id'] or not self.model_sale_lines.rowCount() > 0:
return
if self.enviroment == 'retail':
self.Sale.quote({'id': self._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.enviroment == '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._context['exception'] = True
res, msg, exception = self.Sale.faster_process({'sale_id': sale_id})
if msg:
self.message_bar.set(msg)
return
if exception:
self.dialog('invoice_validated_failed', extra_message=exception)
return
self.set_amounts(res)
self._sale['number'] = res['number']
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'])
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.enviroment == '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_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))
else:
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)
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)
# 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))
# else:
# self.dialog_tip_amount.exec_()
# value = self.field_tip_amount_ask.text()
# value = int(value)
# 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
res = self.Sale.faster_close_statement({
'device': self.device['id'],
'data': values,
'salesman': salesman['id'],
})
# 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['id'],
'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()
if val and self._sale.get('id'):
self.Sale.write([self._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()
if val and self._sale and self._sale.get('id'):
self.Sale.write([self._sale['id']], {'invoice_type': val})
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_print_sale(self):
number = self.field_invoice_number.text()
if not number:
number = self.field_number.text()
if number:
self.field_invoice_number_ask.setText(number)
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
if number:
if type_doc in ['order', 'delivery']:
if number == self._sale.get('number'):
sale_id = self._sale['id']
else:
sales = self.Sale.find([('number', '=', number)])
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_invoice(sale)
def print_odt_invoice(self, sale):
if not sale.get('invoices'):
return
invoice_id = sale['invoices'][0]
model = u'account.invoice'
data = {
'model': model,
'action_id': self._action_report['id'],
'id': invoice_id,
'ids': [invoice_id],
}
ctx = {'date_format': u'%d/%m/%Y'}
ctx.update(self._context)
Action.exec_report(self.conn, u'account.invoice',
data, direct_print=True, context=ctx)
def action_comment(self):
self.dialog_comment.exec_()
comment = self.field_comment.toPlainText()
self.store.update({'comment': comment})
self._sale.update({'comment': comment})
def action_position(self):
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()
if not res:
return
commission = float(res)
sale, = self.Sale.find([
('id', '=', self._sale['id']),
])
self.field_agent_id = self.field_agent_ask.get_id()
if self.field_agent_id and commission:
agent, = self.Agent.find([
('id', '=', self.field_agent_id),
])
if commission <= agent['plan']['percentage']:
self.Sale.write([self._sale['id']], {
'agent': self.field_agent_id,
'commission': int(commission),
})
else:
self.message_bar.set('invalid_commission')
return
self.message_bar.set('system_ready')
comm_string = str('[' + str(commission) + ']'
+ ' ') + (str(self.field_agent_ask.text()))
self.field_agent.setText(comm_string)
self._set_commission_amount(sale['untaxed_amount'], commission)
def _set_commission_amount(self, untaxed_amount, commission):
untaxed_amount = int(untaxed_amount)
commission = int(commission)
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
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)
# if res:
# if line:
# sale_line, = self.SaleLine.find([
# ('id', '=', line_id)
# ])
# newnote = line['note'] + '\n' if line.get('note') else ''
# new_note = newnote + rec['name']
# sale_line['note'] = new_note
# self.model_sale_lines.update_record(sale_line)
# else:
# comment = self._sale['comment'] + \
# '\n' if self._sale.get('comment') else ''
# comment = comment + rec['name']
# sale_lines = self.SaleLine.find([
# ('id', 'in', lines)
# ])
# for line in sale_lines:
# self.model_sale_lines.update_record(line)
# self.store.update({'comment': comment})
# self._sale.update({'comment': comment})
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, reversion=True)
except:
logging.error('Printing order reversion fail!')
def _print_order(self, sale_id, kind, reversion=False):
result = False
try:
args = {
'sale_id': sale_id,
'repeat': False,
}
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)
self.store.set({'number': sale_number})
self.order_number.setText(sale_number)
result = self.receipt_order.print_orders(orders, reversion, kind)
except:
logging.error('Printing order fail!')
return result
def action_delivery_report(self):
_parties = {dp['id']: dp['rec_name'] for dp in self.delivery_parties}
add_fields = ('delivery_party', {
'name': 'DOMICILIARIO',
'type': 'selection',
'values': _parties.items(),
})
args = self.dialog_reports.open_wizard(
'delivery_report', add_fields, True
)
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']
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']['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):
if self.model_sale_lines.rowCount() == 0:
return
res = self.dialog_order.exec_()
if res == DIALOG_REPLY_NO:
return False
# For retail
kind = 'dispatch'
if self.enviroment == 'restaurant':
kind = 'command'
if not sale_id and self._sale['id']:
sale_id = self._sale['id']
result = self._print_order(sale_id, kind)
if result:
self.dialog('order_successfully', widgets=[self.order_number])
else:
self.dialog('order_failed')
if self.enviroment == 'restaurant':
self.Sale.write([sale_id], {'order_status': 'commanded'})
if init_view:
self.change_view_to('start_front')
return True
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()
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):
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 self._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 \
self._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
print('va a cancelar')
self.Sale.cancel_sale({'id': self._sale['id']})
print('paso')
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(30))
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):
self.print_no_commanded()
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'):
delta = str(datetime.now() - timedelta(1))
shop_id = self.shop['id']
if self.type_pos_user == 'cashier':
dom = [['OR',
[
('create_date', '>=', delta),
('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 = [
('state', '=', 'draft'),
('shop', '=', shop_id),
('create_date', '>=', delta),
]
elif self.type_pos_user == 'frontend':
dom = [
('state', 'in', ['draft', 'quotation',
'confirmed', 'processing']),
('sale_device', '=', self.device['id']),
('shop', '=', shop_id),
('create_date', '>=', delta),
]
else:
dom = [
('state', 'in', ['draft', 'quotation',
'confirmed', 'processing']),
('shop', '=', shop_id),
('create_date', '>=', delta),
]
if _type == 'cash':
dom.append(['payment_term', '=', self.default_payment_term['id']])
else:
dom.append(['payment_term', '!=', self.default_payment_term['id']])
# 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.enviroment == 'retail':
dom_draft = [
('create_date', '>=', delta),
('state', '=', 'draft'),
('invoice_number', '!=', None),
]
sales_draft = self.Sale.find(dom_draft)
self.dialog_search_sales.set_counter_control(sales_draft)
def on_selected_sale(self):
sale_id = self.dialog_search_sales.get_id()
if not sale_id:
return
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:
party_id = self.dialog_search_parties.get_id()
if not party_id:
return
party, = self.Party.find([('id', '=', party_id)])
address_id = ''
if party['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
sale, = self.Sale.find([('id', '=', sale_id)])
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.update(sale)
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'])
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.field_change.zero()
if self._commission_activated:
if hasattr(self, 'field_agent') and sale.get('agent') \
and sale.get('commission'):
commission = sale.get('commission')
sale['agent']['name'] = '[' + str(commission) + ']' + ' ' + sale['agent']['name']
self.store.set(sale['agent'])
self.field_agent_id = sale['agent']['id']
self.field_agent_ask.setText(sale['agent']['name'])
self.field_commission_ask.setText(str(commission))
self._set_commission_amount(sale['untaxed_amount'], commission)
self.line_ids = [l['id'] for l in sale.get('lines')]
if sale.get('lines'):
lines = self.SaleLine.find([
('id', 'in', self.line_ids),
])
sale['lines'] = 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.enviroment == 'restaurant':
self.table_sale_lines.setEnabled(True)
self.menu_dash.setDisabled(False)
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.enviroment == '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 _get_products_by_category_dash(self, cat_id):
records = self.Product.find([
('code', '!=', None),
('template.salable', '=', True),
('template.categories', '=', cat_id),
], fields=['id', 'name', 'code', 'categories', 'rec_name', 'list_price'],
order=[('template.name', 'ASC')])
return records
def get_product_by_categories_dash(self):
self.allow_categories = [
pc for pc in self.product_categories
if not pc['parent'] and not pc['accounting']
]
for cat in self.allow_categories:
cat['icon'] = get_icon(cat['name_icon'])
if cat.get('childs'):
for child in cat.get('childs'):
child['items'] = self._get_products_by_category_dash(
child['id'])
else:
cat['items'] = self._get_products_by_category_dash(cat['id'])
return self.allow_categories
def action_square_box_report(self):
try:
values, report_name = self.dialog_reports.open_wizard('square_box_report', open_print=False)
except:
values = {}
if not values.get('turn'):
return
statements = self.Statement.find([
('turn', '=', values['turn']),
('state', '=', 'draft'),
('date', '=', values['date']),
('company', '=', values['company']),
('sale_device.shop', '=', values['shop'])
])
if statements:
return
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 get_category_items(self, records):
records_by_category = {}
def _get_tree_categories(cat):
sub_categories = {}
if not cat['childs']:
sub_categories[cat['name']] = records_by_category.get(
cat['id']) or []
else:
for child in cat['childs']:
sub_categories.update(_get_tree_categories(
self.target_categories[child]['obj']))
return sub_categories
for record in records:
cat_id = record.get('template.account_category')
if cat_id not in records_by_category.keys():
records_by_category[cat_id] = []
records_by_category[cat_id].append(record)
res = {}
for ac in self.allow_categories:
res[ac['name']] = _get_tree_categories(ac)
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)
])
if not products:
return
product = products[0]
image = Image(name='product_icon')
if not product['image']:
return
b64image = product['image'].encode()
image.set_image(b64image, kind='bytes')
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', 'template.positions.position'])
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):
# line = self.dialog_product_edit.get()
# _text = ''
# current_note = line['note']
# if current_note:
# _text = current_note + ' \n'
# else:
# _text = 'CON '
# new_note = _text + record['template']['rec_name']
# self.dialog_product_edit.field_note.setText(new_note)
# self.dialog_product_edit.active_line['note'] = new_note
# self.SaleLine.write([line['id']], {'note': new_note})
list_price = 0
self.on_selected_item(record, list_price)
def on_selected_item_mix(self, record):
line = self.dialog_product_edit.get()
# list_price = line['product'].get('list_price')
code = line['product']['code']
product = self.Product.find([('code', '=', code)])
list_price = product[0].get('list_price')
code_r = record['code']
product_r = self.Product.find([('code', '=', code_r)])
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):
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)
self.dialog_source.close()
def on_selected_payment(self, journal):
self.journal = journal
if journal.get('id'):
self.field_journal_id = journal['id']
self.dialog_payment.close()
self.message_bar.set('enter_payment', journal['rec_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))
]
domain.append(clause)
if not self._sale_pos_restaurant and self.shop.get('product_categories'):
categories_id = [ct['id']
for ct in self.shop['product_categories']]
domain.append([
'OR',
('template.categories', 'in', categories_id),
('template.account_category', 'in', categories_id),
])
if not domain:
return
if self.cache_local:
domain = [clause]
products = self.local_db.find_product_elastic(domain, limit=100)
else:
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('')
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('P')
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 hasattr(self, 'field_agent'):
self.field_agent.setText('')
self.field_agent_ask.setText('')
self.field_commission_ask.setText('')
self.field_commission_amount.setText('')
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.enviroment == 'restaurant':
self.field_tip_amount.setText('')
self.field_delivery_amount.setText('')
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(self, kind='take_away'):
# self.check_empty_sale()
if self.type_pos_user == 'cashier':
self.message_bar.set('not_sale')
# self.state_disabled()
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.enviroment == '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()
self.clear_right_panel()
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': 'P',
'company': self.company['id'],
'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']})
self._sale = self.Sale.new_sale(to_create)
self.store.set(self._sale)
# FIXME ADD MORE
self._sale.update({'total_amount': 0})
self.store.set({'invoice_number': last_invoice_number})
self.label_input.setFocus()
self.party_id = self.default_party['id']
if self._sale.get('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.enviroment == '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.enviroment == 'restaurant':
self.action_source()
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)
])
if self.cache_local:
clause = [
'OR',
('barcode', '=', '%{:}%'.format(code)),
('code', '=', '%{:}%'.format(code)),
]
domain = [clause]
products = self.local_db.find_product_elastic(domain, limit=100)
else:
products = self.Product.find(domain, ctx=self.stock_context,
fields=['name', 'code', 'categories', 'description', 'encoded_sale_price'
'id', 'image', 'image_icon', 'list_price', 'location'
'products_mix', 'quantity', 'rec_name', 'template',
'extra_tax', 'template.sale_price_w_tax', 'uom',
'barcode', 'write_date'])
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 isinstance(line['product'], int):
product_id = line['product']
elif isinstance(line['product'], dict):
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(line['quantity'])
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
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 = None
if record:
product_id = record['id']
elif code:
# REMOVE ME
product = self._search_product(code)
if product:
product_id = product['id']
elif product:
product_id = product['id']
if not product_id:
self._state = 'warning'
return
data = {
'sale_id': self._sale['id'],
'product_id': product_id,
'qty': 1,
}
if notes:
# data['notes'] = notes
pass
if list_price is not None:
data['list_price'] = list_price
ctx = None
if hasattr(self, 'field_list_price'):
ctx = {'price_list': self.field_list_price.get_id()}
res = self.Sale.faster_add_product(data, ctx=ctx)
self._sale_line = res
self._current_line_id = res['id']
self.add_sale_line(res)
self._sale_line['product'] = record
self.product_id = product_id
self.message_bar.set('system_ready')
self.set_amounts()
self.set_state('add')
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['product'], dict):
if not record.get('product.template.name'):
record['product.template.name'] = record['product']['template']['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([
# ('turn', '=', self.turn),
('state', '=', 'draft'),
('sale_device', '=', self.device['id']),
('journal.kind', '=', 'cash'),
])
if statements:
self.statement_cash = statements[0]
self.data_expenses = self.Expenses.find([
('statement', '=', self.statement_cash['id']),
], fields=['reference', 'invoice_number', 'description', 'amount'])
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.template.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.enviroment == '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.enviroment == '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
}
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, 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):
self.dialog_fixed_discount.exec_()
if self.enviroment == 'restaurant':
self.dialog_combine_product.close()
def action_discount_bono_line(self, record):
self.field_bono_discount_manual.setText('')
self.dialog_fixed_discount_manual.exec_()
if self.enviroment == '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):
line = self.dialog_product_edit.get()
code = line['product']['code']
product = self.Product.find([('code', '=', code)])
products_mix = product[0].get('products_mix')
if not products_mix:
return
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()
code = line['product']['code']
product = self.Product.find([('code', '=', code)])
categories = product[0].get('categories')
if not categories:
return
categories_ids = [cat['id'] for cat in categories]
mixables = self.Product.find([
('categories', 'in', categories_ids),
], 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.close()
def action_print_count_money(self, data):
try:
self.receipt_sale.print_count_money(data)
except:
pass
def action_delete_line(self):
"""Delete Product """
if self.model_sale_lines.rowCount() <= 0 or self._state == 'checkout':
return
self.table_sale_lines.setFocus()
# if not removed_item:
removed_item = self.table_sale_lines.delete_item(ignore_focus=True)
self._print_reversion(self._sale['id'], removed_item['id'])
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()
if self.enviroment == 'restaurant':
prd_code = removed_item.get('product.code', None)
if isinstance(removed_item['product'], dict):
prd_code = removed_item['product']['code']
if prd_code and self._config['tip_product']['code'] == prd_code:
self.Sale.write([self._sale['id']], {'tip': None})
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,
})
print(value, discount, 'set_unit_price')
if rec:
self.model_sale_lines.update_record(rec)
self.set_amounts()
return True
return False
def add_payment(self, amount):
voucher_number = None
print('ingresa a este add_payment', self.journal)
if not self.journal:
journal = self.default_journal
else:
journal = self.journal
print('ingresa a este add_payment', 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,
})
if res.get('msg') not in ('missing_money', 'ok'):
self.dialog(res['msg'])
return res
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_input.extend(self.keys_special)
self.keys_input.extend(self.show_keys)
self.keys_input.extend(self.keys_numbers)
self.keys_input.extend([Qt.Key_Return, Qt.Key_Plus])
def set_state(self, state='add'):
self._state = state
state = STATES[state]
self._re = state['re']
if not self.type_pos_user == 'order':
if not self.buttons_stacked.stacked.currentWidget():
return
if state['button']:
self.buttons_stacked.stacked.setCurrentWidget(
getattr(self.buttons_stacked, state['button'])
)
self.buttons_stacked.stacked.currentWidget().setVisible(True)
else:
self.buttons_stacked.stacked.currentWidget().setVisible(False)
if self._state == 'payment':
self.table_sale_lines.setDisabled(True)
self.field_invoice_type.set_enabled(False)
if self.enviroment == '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 hasattr(self, '_sale_line') and 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:
qty_data = {
'id': data['id'],
'quantity': to_float(v, 2)
}
if self._config.get('use_price_list'):
qty_data['use_price_list'] = True
ctx = None
if hasattr(self, 'field_list_price'):
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
for k, v in consumer.items():
if k != 'notes' and v != '':
consumer[k] = str(v).splitlines()[0]
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=''):
if field == 'delivery_party':
self.state_delivery_party['party'] = self.row_delivery_party.text()
if field == 'row_phone':
self.state_delivery_party['phone'] = self.row_phone.text()
if field == 'id_number':
self.state_delivery_party['id_number'] = self.row_id_number.text()
if field == 'number_plate':
self.state_delivery_party['number_plate'] = self.row_number_plate.text(
)
if field == 'type_vehicle':
self.state_delivery_party['type_vehicle'] = self.row_type_vehicle.get_id(
)
if field == 'delivery_party_active':
self.state_delivery_party['active'] = self.row_delivery_party_active.isChecked(
)
def search_consumer(self, phone):
if phone:
consumers = self.PartyConsumer.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,
}
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['id'])
self.dialog_manage_tables.hide()
self.dialog_manage_tables.hide()