presik_pos/app/frontwindow.py

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()