425 lines
18 KiB
Python
425 lines
18 KiB
Python
# -*- coding: UTF-8 -*-
|
|
import os
|
|
import time
|
|
import logging
|
|
from pathlib import Path
|
|
from collections import OrderedDict
|
|
from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QLabel, QLineEdit
|
|
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, Qt
|
|
|
|
from .commons.dialogs import QuickDialog
|
|
from .commons.dblogin import safe_reconnect
|
|
from .proxy import FastModel
|
|
from .constants import SCREENS
|
|
from .dialogs import (
|
|
Help, ControlPanel, SearchSale, SearchParty, SearchProduct, Position,
|
|
Comment, DialogPayment, DialogSalesman, DialogVoucher, DialogPrintInvoice,
|
|
DialogGlobalDiscount, DialogPaymentTerm, DialogStock, DialogOrder,
|
|
DialogForceAssign, DialogTaxes, DialogCancelInvoice, SaleLine, DialogAgent,
|
|
DialogConsumer, DialogManageTables, DialogDeliveryMen, DialogChannel,
|
|
DialogTableMoneyCount, DialogGlobalDiscountTable, DeliveryPartySelected,
|
|
DialogTableDeliveryParty, DialogTableSaleConsumer, SaleConsumerSelected
|
|
)
|
|
from .constants import DIALOG_REPLY_YES
|
|
|
|
__all__ = ['FrontWindow', 'ClearUi']
|
|
|
|
parent = Path(__file__).parent
|
|
|
|
file_base_css = os.path.join(str(parent), 'css', 'base.css')
|
|
_DEFAULT_TIMEOUT = 60000 # on ms (100 minutes)
|
|
path_trans = os.path.join(os.path.abspath(
|
|
os.path.dirname(__file__)), 'locale', 'i18n_es.qm')
|
|
|
|
|
|
class FrontWindow(QMainWindow):
|
|
|
|
def __init__(self, connection, params, title=None, show_mode=None):
|
|
super(FrontWindow, self).__init__()
|
|
if not title:
|
|
title = self.tr('APPLICATION')
|
|
|
|
self._state = None
|
|
self._keyStates = {}
|
|
self.window().setWindowTitle(title)
|
|
self.setObjectName('WinMain')
|
|
self.conn = connection
|
|
self._context = connection.context
|
|
self.set_params(params)
|
|
self.logger = logging.getLogger('app_logger')
|
|
|
|
"""
|
|
We need get the size of screen (display)
|
|
--------------- -------------------
|
|
name width (px)
|
|
--------------- -------------------
|
|
small screen =< 1024
|
|
medium screen > 1024 and =< 1366
|
|
large screen > 1366
|
|
"""
|
|
|
|
screen = QDesktopWidget().screenGeometry()
|
|
self.setGeometry(0, 0, screen.width(), screen.height())
|
|
screen_width = screen.width()
|
|
self.screen_size = 'large'
|
|
if screen_width <= 1024:
|
|
self.screen_size = 'small'
|
|
elif screen_width <= 1366:
|
|
self.screen_size = 'medium'
|
|
|
|
self.timeout = _DEFAULT_TIMEOUT
|
|
self.set_stack_messages()
|
|
|
|
self.setFocus()
|
|
self.global_timer = 0
|
|
print('Screen Size: > ', self.screen_size)
|
|
_theme = params['theme'] if params.get('theme') else None
|
|
self.profile_printer = params['profile_printer'] if params.get('profile_printer') else 'TM-P80'
|
|
self.set_style(SCREENS[self.screen_size], _theme)
|
|
self.label_color = 'gray'
|
|
self.label_color_2 = ''
|
|
if _theme == 'dark':
|
|
self.label_color = 'light'
|
|
self.label_color_2 = 'orange'
|
|
self.window().showMaximized()
|
|
|
|
def filter_cache(self, data, filter, target):
|
|
res = []
|
|
for d in data:
|
|
for t in target:
|
|
if t in d[filter]:
|
|
res.append(d)
|
|
return res
|
|
|
|
def get_geometry(self):
|
|
screen = QDesktopWidget().screenGeometry()
|
|
return screen.width(), screen.height()
|
|
|
|
def set_statusbar(self, values):
|
|
status_bar = self.statusBar()
|
|
status_bar.setSizeGripEnabled(False)
|
|
|
|
for k, v in values.items():
|
|
_label = QLabel(v['name'] + ':')
|
|
_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
_label.setObjectName('label_status_bar')
|
|
status_bar.addWidget(_label, 1)
|
|
setattr(self, k, QLabel(str(v['value'])))
|
|
_field_info = getattr(self, k)
|
|
_field_info.setObjectName('field_status_bar')
|
|
_field_info.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
status_bar.addWidget(_field_info)
|
|
|
|
def set_style(self, file_css, theme_css=None):
|
|
styles = []
|
|
if theme_css:
|
|
theme_css = os.path.join(str(parent), 'css', theme_css + '.css')
|
|
for style in [theme_css or file_base_css, file_css]:
|
|
with open(style, 'r') as infile:
|
|
styles.append(infile.read())
|
|
self.setStyleSheet(''.join(styles))
|
|
|
|
def set_timeout(self):
|
|
if self.active_timeout != 'True':
|
|
return
|
|
|
|
self.timeout = eval(self.timeout)
|
|
if not self.timeout:
|
|
self.timeout = _DEFAULT_TIMEOUT
|
|
timer = QTimer(self)
|
|
timer.timeout.connect(self.count_time)
|
|
timer.start(1000)
|
|
|
|
def count_time(self):
|
|
self.global_timer += 1
|
|
if self.global_timer > self.timeout:
|
|
self.global_timer = 0
|
|
safe_reconnect()
|
|
|
|
def close(self):
|
|
dialog = self.dialog('confirm_exit', response=True)
|
|
response = dialog.exec_()
|
|
if response == DIALOG_REPLY_YES:
|
|
self.check_empty_sale()
|
|
if self.active_weighing and self.reader_thread:
|
|
self.reader_thread.onClose()
|
|
super(MainWindow, self).close()
|
|
|
|
def dialog(self, name, response=False):
|
|
res = QuickDialog(
|
|
parent=self,
|
|
kind=self.stack_msg[name][0],
|
|
string=self.stack_msg[name][1],
|
|
)
|
|
return res
|
|
|
|
def set_params(self, values):
|
|
for k, v in values.items():
|
|
if v in ('False', 'True'):
|
|
v = eval(v)
|
|
setattr(self, k, v)
|
|
|
|
def action_block(self):
|
|
safe_reconnect(self)
|
|
|
|
def dialog_password_accept(self):
|
|
self.connection()
|
|
|
|
def dialog_password_rejected(self):
|
|
self.connection()
|
|
|
|
def keyReleaseEvent(self, event):
|
|
self._keyStates[event.key()] = False
|
|
|
|
def set_stack_messages(self):
|
|
messages = {
|
|
'system_ready': ('info', self.tr('SYSTEM READY...')),
|
|
'confirm_exit': ('warning', self.tr('DO YOU WANT TO EXIT?')),
|
|
'confirm_credit': ('question', self.tr('PLEASE CONFIRM YOUR PAYMENT TERM AS CREDIT?')),
|
|
'sale_number_not_found': ('warning', self.tr('SALE ORDER / INVOICE NUMBER NOT FOUND!')),
|
|
'sale_closed': ('error', self.tr('THIS SALE IS CLOSED, YOU CAN NOT TO MODIFY!')),
|
|
'discount_not_valid': ('warning', self.tr('DISCOUNT VALUE IS NOT VALID!')),
|
|
'add_payment_sale_draft': ('info', self.tr('YOU CAN NOT ADD PAYMENTS TO SALE ON DRAFT STATE!')),
|
|
'enter_quantity': ('question', self.tr('ENTER QUANTITY...')),
|
|
'enter_discount': ('question', self.tr('ENTER DISCOUNT...')),
|
|
'enter_payment': ('question', self.tr('ENTER PAYMENT AMOUNT BY: %s')),
|
|
'enter_new_price': ('question', self.tr('ENTER NEW PRICE...')),
|
|
'order_successfully': ('info', self.tr('ORDER SUCCESUFULLY SENT.')),
|
|
'order_failed': ('warning', self.tr('FAILED SEND ORDER!')),
|
|
'missing_agent': ('warning', self.tr('MISSING AGENT!')),
|
|
'missing_salesman': ('warning', self.tr('THERE IS NOT SALESMAN FOR THE SALE!')),
|
|
'sale_without_products': ('warning', self.tr('YOU CAN NOT CONFIRM A SALE WITHOUT PRODUCTS!')),
|
|
'user_without_permission': ('error', self.tr('USER WITHOUT PERMISSION FOR SALE POS!')),
|
|
'quantity_not_valid': ('error', self.tr('THE QUANTITY IS NOT VALID...!')),
|
|
'user_not_permissions_device': ('error', self.tr('THE USER HAVE NOT PERMISSIONS FOR ACCESS TO DEVICE!')),
|
|
'missing_party_configuration': ('warning', self.tr('MISSING THE DEFAULT PARTY ON SHOP CONFIGURATION!')),
|
|
'missing_journal_device': ('error', self.tr('MISSING SET THE JOURNAL ON DEVICE!')),
|
|
'statement_closed': ('error', self.tr('THERE IS NOT A STATEMENT OPEN FOR THIS DEVICE!')),
|
|
'product_not_found': ('warning', self.tr('PRODUCT NOT FOUND!')),
|
|
'must_load_or_create_sale': ('warning', self.tr('FIRST YOU MUST CREATE/LOAD A SALE!')),
|
|
'new_sale': ('warning', self.tr('DO YOU WANT CREATE NEW SALE?')),
|
|
'cancel_sale': ('question', self.tr('ARE YOU WANT TO CANCEL SALE?')),
|
|
'not_permission_delete_sale': ('info', self.tr('YOU HAVE NOT PERMISSIONS FOR DELETE THIS SALE!')),
|
|
'not_permission_for_cancel': ('info', self.tr('YOU HAVE NOT PERMISSIONS FOR CANCEL THIS SALE!')),
|
|
'customer_not_credit': ('info', self.tr('THE CUSTOMER HAS NOT CREDIT!')),
|
|
'agent_not_found': ('warning', self.tr('AGENT NOT FOUND!')),
|
|
'invalid_commission': ('warning', self.tr('COMMISSION NOT VALID!')),
|
|
'credit_limit_exceed': ('info', self.tr('CREDIT LIMIT FOR CUSTOMER EXCEED!')),
|
|
'credit_limit_capacity': ('info', self.tr('THE CUSTOMER CREDIT CAPACITY IS ABOVE 80%')),
|
|
'not_can_force_assign': ('warning', self.tr('YOU CAN NOT FORCE ASSIGN!')),
|
|
'send_electronic_failed': ('info', self.tr('FALLO EL ENVIO DE FACTURA!')),
|
|
'invoice_done_failed': ('info', self.tr('FALLO FINALIZACIÓN DE FACTURA!')),
|
|
'without_stock_quantity': ('info', self.tr('PRODUCT WITHOUT STOCK: %s')),
|
|
'not_sale': ('info', self.tr('NOT SALE!...')),
|
|
'statement_created': ('info', self.tr('STATEMENTS CREATED!')),
|
|
'statement_finish': ('info', self.tr('STATEMENTS CLOSED!')),
|
|
'order_dispatched': ('info', self.tr('ORDER DISPATCHED!')),
|
|
'error_order_dispatched': ('error', self.tr('ERROR TO DISPATCHED ORDER!')),
|
|
}
|
|
self.stack_msg = messages
|
|
|
|
def create_dialogs(self):
|
|
# self.create_wizard_new_sale()
|
|
self.dialog_control_panel = ControlPanel(self).get(self.menu_control_panel)
|
|
self.dialog_money_count = DialogTableMoneyCount(self).get()
|
|
self.dialog_search_sales = SearchSale(self).get()
|
|
self.dialog_search_parties = SearchParty(self).get()
|
|
self.dialog_search_products = SearchProduct(self).get()
|
|
self.dialog_position = Position(self).get()
|
|
self.dialog_comment = Comment(self).get()
|
|
self.dialog_payment = DialogPayment(self).get()
|
|
self.dialog_salesman = DialogSalesman(self).get()
|
|
self.dialog_voucher = DialogVoucher(self).get()
|
|
self.dialog_print_invoice = DialogPrintInvoice(self).get()
|
|
self.dialog_global_discount = DialogGlobalDiscount(self).get()
|
|
self.dialog_global_discount_table = DialogGlobalDiscountTable(self).get()
|
|
self.dialog_payment_term = DialogPaymentTerm(self).get()
|
|
self.dialog_search_sales.activate_counter()
|
|
self.dialog_product_stock = DialogStock(self.dialog_search_products).get()
|
|
self.dialog_order = DialogOrder(self).get()
|
|
self.dialog_force_assign = DialogForceAssign(self).get()
|
|
self.field_password_force_assign_ask.setEchoMode(QLineEdit.Password)
|
|
self.dialog_tax = DialogTaxes(self).get()
|
|
self.dialog_cancel_invoice = DialogCancelInvoice(self).get()
|
|
self.dialog_product_edit = SaleLine(self).get()
|
|
self.dialog_table_delivery_party = DialogTableDeliveryParty(self).get()
|
|
self.dialog_delivery_party_selected = DeliveryPartySelected(self).get()
|
|
self.dialog_delivery_men = DialogDeliveryMen(self).get()
|
|
if self._web_channel:
|
|
self.dialog_channel = DialogChannel(self).get()
|
|
if self._commission_activated:
|
|
self.dialog_agent = DialogAgent(self).get()
|
|
if self.enviroment == 'restaurant' and self._sale_pos_restaurant:
|
|
self.dialog_table_sale_consumer = DialogTableSaleConsumer(self).get()
|
|
self.dialog_sale_consumer_selected = SaleConsumerSelected(self).get()
|
|
self.dialog_search_consumer = DialogConsumer(self).get()
|
|
self.manage_tables = DialogManageTables(self)
|
|
self.dialog_manage_tables = self.manage_tables.get()
|
|
|
|
def action_help(self):
|
|
Help(self).show()
|
|
|
|
def resize_window_tablet_dev(self):
|
|
self.resize(690, self.get_geometry()[1])
|
|
|
|
def load_modules(self):
|
|
self._sale_pos_restaurant = None
|
|
self.Module = FastModel('ir.module', self.ctx)
|
|
self.Config = FastModel('sale.configuration', self.ctx)
|
|
self._config, = self.Config.find([('id', '=', 1)])
|
|
self.discount_method = self._config.get('discount_pos_method')
|
|
self.sale_automatic = self._config.get('new_sale_automatic')
|
|
self._commission_activated = self.Module.find([
|
|
('name', '=', 'commission'),
|
|
('state', '=', 'activated'),
|
|
])
|
|
self._web_channel = self.Module.find([
|
|
('name', '=', 'sale_web_channel'),
|
|
('state', '=', 'activated'),
|
|
])
|
|
self._credit_limit_activated = self.Module.find([
|
|
('name', '=', 'account_credit_limit'),
|
|
('state', '=', 'activated'),
|
|
])
|
|
|
|
_product = {
|
|
'name': 'product.product',
|
|
'fields': [
|
|
'template.name', 'code', 'barcode', 'write_date',
|
|
'description', 'template.sale_price_w_tax',
|
|
'template.account_category'
|
|
]
|
|
}
|
|
self.cache_local = self._config.get('cache_products_local')
|
|
|
|
if self._config['show_location_pos']:
|
|
_product['fields'].append('location_')
|
|
|
|
if self._config['show_stock_pos'] in ('value', 'icon'):
|
|
if self._config['show_stock_pos'] == 'value':
|
|
_product['fields'].append('quantity')
|
|
if self._config['show_brand']:
|
|
_product['fields'].append('brand.name')
|
|
|
|
if self._config['encoded_sale_price']:
|
|
_product['fields'].extend(['image', 'image_icon', 'encoded_sale_price'])
|
|
|
|
if self.enviroment == 'restaurant':
|
|
self._sale_pos_restaurant = self.Module.find([
|
|
('name', '=', 'sale_pos_frontend_rest'),
|
|
('state', '=', 'activated'),
|
|
])
|
|
if self._sale_pos_restaurant:
|
|
self.PartyConsumer = FastModel('party.consumer', self.ctx)
|
|
if self._config['delivery_product']:
|
|
self._delivery_product = 0
|
|
|
|
self.User = FastModel('res.user', self.ctx)
|
|
self._user, = self.User.find([('login', '=', self.user)])
|
|
self.Company = FastModel('company.company', self.ctx)
|
|
self._company, = self.Company.find([('id', '=', 1)])
|
|
self.logo = self._company['logo']
|
|
|
|
if not self._user['sale_device']:
|
|
return 'user_not_permissions_device'
|
|
|
|
self.ctx['user'] = self._user['id']
|
|
|
|
self.Sale = FastModel('sale.sale', self.ctx)
|
|
self.SaleLine = FastModel('sale.line', self.ctx)
|
|
self.Product = FastModel('product.product', self.ctx)
|
|
self.Journal = FastModel('account.statement.journal', self.ctx)
|
|
self.Employee = FastModel('company.employee', self.ctx)
|
|
self.Shop = FastModel('sale.delivery_party', self.ctx)
|
|
self.SaleDiscont = FastModel('sale.discount', self.ctx)
|
|
self.Device = FastModel('sale.device', self.ctx)
|
|
self.Category = FastModel('product.category', self.ctx)
|
|
self.PaymentTerm = FastModel('account.invoice.payment_term', self.ctx)
|
|
self.Party = FastModel('party.party', self.ctx)
|
|
self.DeliveryParty = FastModel('sale.delivery_party', self.ctx)
|
|
self.Taxes = FastModel('account.tax', self.ctx)
|
|
self.ActionReport = FastModel('ir.action.report', self.ctx)
|
|
if self._commission_activated:
|
|
self.Agent = FastModel('commission.agent', self.ctx)
|
|
self.Comission = FastModel('commission', self.ctx)
|
|
if self._sale_pos_restaurant:
|
|
self.RestTables = FastModel('sale.shop.table', self.ctx)
|
|
self.Consumer = FastModel('party.consumer', self.ctx)
|
|
|
|
self.device, = self.Device.find([
|
|
('id', '=', self._user['sale_device']['id']),
|
|
])
|
|
|
|
self.shop = self.device['shop']
|
|
self.shop_taxes = self.shop['taxes']
|
|
self.company = self.shop['company']
|
|
self._journals = dict([(j['id'], j) for j in self.device['journals']])
|
|
self.salesman_ids = [s['id'] for s in self.shop.get('salesmans', [])]
|
|
|
|
dom_salesman = [
|
|
('company', '=', self.company['id']),
|
|
]
|
|
if self.salesman_ids:
|
|
dom_salesman.append(('id', 'in', self.salesman_ids))
|
|
self.employees = self.Employee.find(dom_salesman)
|
|
|
|
self.discounts = self.shop.get('discounts', [])
|
|
self.delivery_man_table = self.shop.get('delivery_man', [])
|
|
self.delivery_man = [d for d in self.delivery_man_table if d['active']]
|
|
self._payment_terms = self.PaymentTerm.get_payment_term_pos()
|
|
self.type_pos_user = self._context.get('type_pos_user')
|
|
|
|
if not self.type_pos_user:
|
|
return 'user_without_permission'
|
|
self.user_can_delete = self.type_pos_user in ('frontend_admin', 'cashier')
|
|
|
|
self.product_categories = self.device['shop']['product_categories']
|
|
self.salesman_required = self.device['shop']['salesman_pos_required']
|
|
|
|
self.default_party = self.shop['party']
|
|
if not self.default_party:
|
|
return 'missing_party_configuration'
|
|
|
|
self.default_journal = self.device['journal']
|
|
if not self.default_journal:
|
|
return 'missing_journal_device'
|
|
|
|
self.default_payment_term = self.shop['payment_term']
|
|
self._password_admin = self._config.get('password_admin_pos')
|
|
self._password_force_assign = self._config.get('password_force_assign')
|
|
|
|
self._action_report, = self.ActionReport.find([
|
|
('report_name', '=', 'account.invoice'),
|
|
])
|
|
if self._config['show_stock_pos'] in ('value', 'icon'):
|
|
self.stock_context = {
|
|
'stock_date_end': date.today(),
|
|
'locations': [self.shop['warehouse']['id']],
|
|
}
|
|
if self._web_channel:
|
|
self.Channel = FastModel('sale.web_channel', self.ctx)
|
|
return True
|
|
|
|
def create_statusbar(self):
|
|
values = OrderedDict([
|
|
('stb_shop', {'name': self.tr('SHOP'), 'value': self.shop['name']}),
|
|
('stb_device', {'name': self.tr('DEVICE'), 'value': self.device['name']}),
|
|
('stb_database', {'name': self.tr('DATABASE'), 'value': self.database}),
|
|
('stb_user', {'name': self.tr('USER'), 'value': self.user}),
|
|
('stb_printer', {'name': self.tr('PRINTER'), 'value': self.printer_sale_name})
|
|
])
|
|
self.set_statusbar(values)
|
|
|
|
|
|
class ClearUi(QThread):
|
|
sigActionClear = pyqtSignal()
|
|
state = None
|
|
|
|
def __init__(self, wait_time):
|
|
QThread.__init__(self)
|
|
self.wait_time = wait_time
|
|
|
|
def run(self):
|
|
time.sleep(self.wait_time)
|
|
self.sigActionClear.emit()
|