diff --git a/app/mainwindow (SFConflict oscar.alvarez.montero@gmail.com 2020-06-28-10-33-14).py b/app/mainwindow (SFConflict oscar.alvarez.montero@gmail.com 2020-06-28-10-33-14).py
new file mode 100644
index 0000000..a3a124a
--- /dev/null
+++ b/app/mainwindow (SFConflict oscar.alvarez.montero@gmail.com 2020-06-28-10-33-14).py
@@ -0,0 +1,2644 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+import sys
+import os
+import logging
+from decimal import Decimal
+
+from datetime import datetime, timedelta, date
+from collections import OrderedDict
+from PyQt5.QtCore import Qt, QThread, pyqtSignal
+from PyQt5.QtGui import QTouchEvent
+from PyQt5.QtWidgets import (QLabel, QTextEdit, QHBoxLayout, QVBoxLayout,
+ QWidget, QGridLayout, QLineEdit, QDoubleSpinBox)
+
+from app.commons.action import Action
+from app.commons.forms import GridForm, FieldMoney, ComboBox
+from app.commons.messages import MessageBar
+from app.commons.image import Image
+from app.commons.dialogs import QuickDialog
+from app.commons.table import TableView
+from app.commons.model import TableModel, Modules
+from app.commons.search_window import SearchWindow
+from app.commons.frontwindow import FrontWindow
+from app.commons.menu_buttons import MenuDash
+
+from .proxy import FastModel
+from .localdb import LocalStore
+from .reporting import Receipt
+from .buttonpad import Buttonpad
+from .manage_tables import ManageTables
+from .states import STATES, RE_SIGN
+from .tools import get_icon, to_float, to_numeric
+from .constants import (PATH_PRINTERS, DELTA_LOCALE, STRETCH, alignRight,
+ alignLeft, alignCenter, alignHCenter, alignVCenter, DIALOG_REPLY_NO,
+ DIALOG_REPLY_YES, ZERO, FRACTIONS, RATE_CREDIT_LIMIT, SCREENS, FILE_BANNER,
+ CONVERSION_DIGITS)
+
+
+class MainWindow(FrontWindow):
+
+ def __init__(self, connection, params):
+ title = "PRESIK | SMART POS"
+ global CONNECTION
+ self.conn = connection
+ CONNECTION = connection
+ super(MainWindow, self).__init__(connection, params, title)
+ print('Screen Size: > ', self.screen_size)
+ self.set_style(SCREENS[self.screen_size])
+
+ self.is_clear_right_panel = True
+ self.payment_ctx = {}
+ self.set_keys()
+ self.stock_context = None
+
+ self.ctx = self._context
+ self.ctx['params'] = params
+ response = self.load_modules()
+ if response is not True:
+ d = self.dialog(response)
+ d.exec_()
+ super(MainWindow, self).close()
+ return
+ self.setup_sale_line()
+ self.setup_payment()
+ self.set_domains()
+ self.create_gui()
+ self.message_bar.load_stack(self.stack_msg)
+
+ 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()
+ if not self.tablet_mode:
+ self.create_statusbar()
+
+ self.window().showMaximized()
+ self.create_dialog_search_products()
+
+ if not self.tablet_mode:
+ self.grabKeyboard()
+ self.reader_thread = None
+ self._current_line_id = None
+ self._amount_text = ''
+ self._sign = None
+ self.create_dialogs()
+ self.createNewSale()
+ 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_cache_company()
+ self.set_cache_products()
+
+ 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 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 set_cache_company(self):
+ self.store = LocalStore()
+ self.store.create_table_config()
+ self._local_config = self.store.get_config()
+ if not self._local_config:
+ company_id = self.device['shop']['company']['id']
+ self._local_config = self.store.set_config([company_id])
+
+ def set_cache_products(self):
+ self.store.create_table_product()
+ local_products = self.store.get_local_products()
+
+ config = self.store.get_config()
+ _sync_date = config[1]
+ products = self.Product.sync_get_products({'write_date': _sync_date, 'shop_id': self.ctx['shop']})
+ self.store.update_products(products, local_products)
+ now = datetime.now()
+ self.store.set_config_sync(str(now))
+
+ def event(self, evento):
+ event_type = super(MainWindow, self).event(evento)
+ touch = QTouchEvent(event_type)
+ return event_type
+
+ 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.ModSale.find([
+ ('id', '=', self._sale['id'])
+ ])
+ if not sales:
+ return
+ return sales[0]
+
+ 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 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 delete_current_sale(self):
+ if self._sale['id']:
+ self._PosSale.cancel_sale(self._sale['id'], self._context)
+
+ def resize_window_tablet_dev(self):
+ self.resize(690, self.get_geometry()[1])
+
+ def set_stack_messages(self):
+ super(MainWindow, self).set_stack_messages()
+ self.stack_msg.update({
+ '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!')),
+ })
+
+ def load_modules(self):
+ modules = Modules(self, self.conn)
+ 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._commission_activated = self.Module.find([
+ ('name', '=', 'commission'),
+ ('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'])
+
+ _PosSale = {
+ 'name': '_PosSale',
+ 'model': 'sale.sale',
+ 'fields': ['number', 'party', 'party.name', 'salesman', 'lines',
+ 'position', 'total_amount_cache', 'salesman.party.name',
+ 'payment_term', 'payment_term.name', 'invoices',
+ 'payments', 'untaxed_amount', 'state', 'tax_amount',
+ 'total_amount', 'residual_amount', 'paid_amount', 'invoice',
+ 'invoice.state', 'invoice_number', 'invoice.number', 'invoices',
+ 'delivery_charge', 'sale_date', 'invoice_type'],
+ 'methods': (
+ 'get_printing_context', 'cancel_sale', 'get_amounts',
+ 'get_discount_total', 'process_sale', 'reconcile_invoice',
+ 'post_invoice', 'get_data', 'add_value', 'faster_add_product',
+ 'get_product_prices', 'add_payment', 'get_order2print',
+ 'get_sale_from_invoice', 'add_tax', 'check_state', 'to_quote',
+ 'to_draft', 'new_sale', 'on_change', 'get_salesman_in_party',
+ )
+ }
+
+ _Agent = {
+ 'name': '_Agent',
+ 'model': 'commission.agent',
+ 'fields': ('id', 'party.name', 'party.id_number', 'plan.percentage',
+ 'active'),
+ }
+ _Commission = {
+ 'name': '_Commission',
+ 'model': 'commission',
+ 'fields': ('id', 'origin', 'invoice_line', 'invoice_line.invoice'),
+ }
+
+ _Tables = self._Tables = None
+ if self.enviroment == 'restaurant':
+ self._sale_pos_restaurant = self.Module.find([
+ ('name', '=', 'sale_pos_frontend_rest'),
+ ('state', '=', 'activated'),
+ ])
+ if self._sale_pos_restaurant:
+ _Tables = {
+ 'name': '_Tables',
+ 'model': 'sale.shop.table',
+ 'fields': ('name', 'shop', 'capacity', 'state')
+ }
+ _PosSale['fields'].extend(['table_assigned',
+ 'table_assigned.name', 'table_assigned.state'])
+
+ if self._commission_activated:
+ _PosSale['fields'].extend(['agent', 'agent.party.name', 'commission'])
+ modules.set_models([_Agent, _Commission])
+
+ self.User = FastModel('res.user', self.ctx)
+ self._user, = self.User.find([('login', '=', self.user)])
+
+ if not self._user['sale_device']:
+ return 'user_not_permissions_device'
+
+ self.ctx['user'] = self._user['id']
+
+ self.ModSale = FastModel('sale.sale', self.ctx)
+ self.ModSaleLine = 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.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.Taxes = FastModel('account.tax', self.ctx)
+ self.ActionReport = FastModel('ir.action.report', self.ctx)
+
+ models_to_work = [_PosSale]
+
+ if _Tables:
+ models_to_work.append(_Tables)
+
+ modules.set_models(models_to_work)
+ 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.employees = self.Employee.find([
+ ('company', '=', self.company['id']),
+ ])
+ 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._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']],
+ }
+ return True
+
+ def create_dialogs(self):
+ self.create_dialog_position()
+ self.create_dialog_comment()
+ self.create_dialog_search_party()
+ self.create_dialog_payment()
+ self.create_dialog_salesman()
+ self.create_dialog_voucher()
+ self.create_dialog_print_invoice()
+ self.create_dialog_global_discount()
+ self.create_dialog_payment_term()
+ self.create_dialog_search_sales()
+ self.create_wizard_new_sale()
+ self.create_dialog_stock()
+ self.create_dialog_order()
+ self.create_dialog_force_assign()
+ self.create_dialog_taxes()
+ self.create_dialog_cancel_invoice()
+ self.create_dialog_sale_line()
+ if self._commission_activated:
+ self.create_dialog_agent()
+ if self.enviroment == 'restaurant' and self._sale_pos_restaurant:
+ self.create_dialog_manage_tables()
+
+ 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._PosSale.get_printing_context(
+ [self.device['id']], self.user, self._context)
+ ctx_printing['row_characters'] = self.row_characters
+ ctx_printing['delta_locale'] = DELTA_LOCALE
+
+ self.receipt_sale = Receipt(ctx_printing)
+
+ # Printing order context
+ if self.print_order:
+ self.receipt_order = Receipt(ctx_printing)
+ 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],
+ }
+ if printer:
+ self.receipt_sale.set_printer(printer)
+
+ def button_new_sale_pressed(self):
+ self.createNewSale()
+
+ def button_send_to_pay_pressed(self):
+ # Return sale to draft state
+ self._PosSale.to_quote(self._sale['id'], self._context)
+ if self.model_sale_lines.rowCount() > 0:
+ if self.check_salesman():
+ self.state_disabled()
+
+ def button_to_draft_pressed(self):
+ # Return sale to draft state
+ if hasattr(self, '_sale'):
+ self._PosSale.to_draft(self._sale['id'], self._context)
+ self.state_disabled()
+
+ def create_gui(self):
+ panels = QHBoxLayout()
+ panel_left = QVBoxLayout()
+ panel_right = QVBoxLayout()
+
+ left_head = QHBoxLayout()
+ left_table = None
+ left_bottom = QHBoxLayout()
+
+ self.message_bar = MessageBar()
+ self.label_input = QLabel()
+ self.label_input.setFocus()
+ self.label_input.setObjectName('label_input')
+
+ if self.enviroment == 'restaurant':
+ values = self.get_product_by_categories()
+ menu_dash = MenuDash(self, values, 'on_selected_item')
+
+ if not self.tablet_mode:
+ _label_invoice = QLabel(self.tr('INVOICE:'))
+ _label_invoice.setObjectName('label_invoice')
+ _label_invoice.setAlignment(alignRight | alignVCenter)
+
+ self.field_invoice = QLineEdit()
+ self.field_invoice.setReadOnly(True)
+ self.field_invoice.setObjectName('field_invoice')
+ if self.tablet_mode:
+ self.field_invoice.setPlaceholderText(self.tr('INVOICE'))
+
+ self.field_amount = FieldMoney(self, 'amount', {})
+ self.field_amount.setObjectName('field_amount')
+ self.field_sign = QLabel(' ')
+ self.field_sign.setObjectName('field_sign')
+
+ layout_message = QGridLayout()
+ layout_message.addLayout(self.message_bar, 1, 0, 1, 4)
+
+ if not self.tablet_mode:
+ layout_message.addWidget(self.label_input, 2, 0, 1, 2)
+ layout_message.addWidget(_label_invoice, 2, 2)
+ layout_message.addWidget(self.field_invoice, 2, 3)
+ else:
+ layout_message.addWidget(self.label_input, 2, 0, 1, 2)
+ layout_message.addWidget(self.field_invoice, 2, 3)
+
+ left_head.addLayout(layout_message, 0)
+ left_head.addWidget(self.field_sign, 0)
+ left_head.addWidget(self.field_amount, 0)
+
+ info_fields = [
+ ('party', {
+ 'name': self.tr('CUSTOMER'),
+ 'readonly': True,
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('date', {
+ 'name': self.tr('DATE'),
+ 'readonly': True,
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('salesman', {
+ 'name': self.tr('SALESMAN'),
+ 'readonly': True,
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('payment_term', {
+ 'name': self.tr('PAYMENT TERM'),
+ 'readonly': True,
+ 'invisible': self.tablet_mode,
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('order_number', {
+ 'name': self.tr('No ORDER'),
+ 'placeholder': False,
+ 'readonly': True,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('invoice_type', {
+ 'name': self.tr('INVOICE TYPE'),
+ 'placeholder': False,
+ 'type': 'selection',
+ 'on_change': 'action_invoice_type_selection_changed',
+ 'values': [
+ ('', ''),
+ ('C', self.tr('COMPUTADOR')),
+ ('M', self.tr('MANUAL')),
+ ('P', self.tr('POS')),
+ ('1', self.tr('VENTA ELECTRONICA')),
+ ('2', self.tr('VENTA DE EXPORTACION')),
+ ('3', self.tr('FACTURA POR CONTINGENCIA FACTURADOR')),
+ ('4', self.tr('FACTURA POR CONTINGENCIA DIAN')),
+ ('91', self.tr('NOTA CREDITO ELECTRONICA')),
+ ('92', self.tr('NOTA DEBITO ELECTRONICA')),
+ ],
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ]
+ self.field_invoice_type = None
+
+ if self.tablet_mode or self._config['show_position_pos']:
+ info_fields.append(('position', {
+ 'name': self.tr('POSITION'),
+ 'readonly': True,
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }))
+ if self._commission_activated and not self.tablet_mode \
+ and self._config['show_agent_pos']:
+ info_fields.append(('agent', {
+ 'name': self.tr('AGENT'),
+ 'placeholder': self.tablet_mode,
+ 'readonly': True,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }))
+
+ _cols = 2
+ if self.enviroment == 'restaurant':
+ _cols = 1
+
+ self.field_delivery_charge = None
+ if self._config['show_delivery_charge']:
+ info_fields.append(
+ ('delivery_charge', {
+ 'name': self.tr('DELIVERY CHARGE'),
+ 'placeholder': False,
+ 'type': 'selection',
+ 'on_change': 'action_delivery_charge_selection_changed',
+ 'values': [
+ ('', ''),
+ ('customer', self.tr('CUSTOMER')),
+ ('company', self.tr('COMPANY')),
+ ],
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }))
+
+ self.field_table_assigned = None
+ if self.enviroment == 'restaurant' and self._sale_pos_restaurant:
+ info_fields.append(
+ ('table_assigned', {
+ 'name': self.tr('ASSIGNED TABLE'),
+ 'placeholder': False,
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }))
+
+ self.grid_info = GridForm(self, OrderedDict(info_fields), col=_cols)
+
+ col_sizes_tlines = [field['width'] for field in self.fields_sale_line]
+ left_table = TableView('model_sale_lines', self.model_sale_lines,
+ col_sizes_tlines, method_selected_row=self.sale_line_selected)
+ self.table_sale_lines = left_table
+
+ for i, f in enumerate(self.model_sale_lines._fields, 0):
+ if f.get('invisible'):
+ self.table_sale_lines.hideColumn(i)
+
+ _fields_amounts = [
+ ('untaxed_amount', {
+ 'name': self.tr('SUBTOTAL'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'gray'
+
+ }),
+ ('taxes_amount', {
+ 'name': self.tr('TAXES'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('discount', {
+ 'name': self.tr('DISCOUNT'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('total_amount', {
+ 'name': self.tr('TOTAL'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'blue'
+ }),
+ ('paid', {
+ 'name': self.tr('PAID'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'gray'
+ }),
+ ('pending', {
+ 'name': self.tr('PENDIENTE'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'blue'
+ }),
+ ('change', {
+ 'name': self.tr('CHANGE'),
+ 'readonly': True,
+ 'type': 'money',
+ 'size': self.screen_size,
+ 'color': 'orange'
+ })
+ ]
+
+ fields_amounts = OrderedDict(_fields_amounts)
+ self.grid_amounts = GridForm(self, fields_amounts, col=1)
+
+ self.buttonpad = Buttonpad(self)
+ self.pixmap_pos = Image(self, 'pixmap_pos', FILE_BANNER)
+ self.table_payment = TableView('table_payment', self.table_payment_lines,
+ [250, STRETCH])
+
+ panel_left.addLayout(left_head, 0)
+ panel_left.addWidget(left_table, 1)
+ panel_right.addWidget(self.pixmap_pos, 0)
+
+ left_bottom.addLayout(self.grid_info, 1)
+ if self.enviroment == 'restaurant':
+ panel_left.addLayout(self.buttonpad.functions, 1)
+ left_bottom.addLayout(self.grid_amounts, 0)
+ panel_right.addLayout(menu_dash, 1)
+ panel_right.addLayout(self.buttonpad.stacked, 0)
+ else:
+ panel_right.addLayout(self.grid_amounts, 1)
+ panel_right.addLayout(self.buttonpad.functions, 1)
+ panel_right.addLayout(self.buttonpad.stacked, 0)
+ panel_right.addWidget(self.table_payment)
+
+ panel_left.addLayout(left_bottom, 0)
+
+ panels.addLayout(panel_left, 1)
+ panels.addLayout(panel_right, 0)
+
+ widget = QWidget()
+ widget.setLayout(panels)
+ self.setCentralWidget(widget)
+
+ 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)
+
+ 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 ('*', '-', '/'):
+ if hasattr(self, '_sale_line') and self._sale_line \
+ and self._sale_line.get('type') and self._state == 'add' \
+ and self.model_sale_lines.rowCount() > 0:
+ if self._sign == '*':
+ self._process_quantity(self._amount_text)
+ else:
+ error = not(self._process_price(self._amount_text))
+ elif self._state in ['add', 'cancel', 'accept']:
+ self.clear_right_panel()
+ self.add_product(code=self._input_text)
+ elif self._state == 'cash':
+ is_paid = self._process_pay(self.field_amount.text())
+ if not is_paid:
+ self.clear_input_text()
+ self.clear_amount_text()
+ return
+ else:
+ logging.warning('Unknown command/text')
+ self._clear_context(error)
+
+ 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 not in ('warning', 'cash') 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 = self._sale_line['product']
+ if _product and _product.get('quantity'):
+ if not self._check_stock_quantity(_product, quantity):
+ return
+
+ if self._current_line_id:
+ rec = self.ModSaleLine.faster_set_quantity({
+ 'id': self._current_line_id,
+ 'quantity': float(quantity)
+ })
+ except:
+ return self.message_bar.set('quantity_not_valid')
+
+ self.message_bar.set('system_ready')
+ self.model_sale_lines.update_record(rec)
+ self.update_total_amount()
+
+ def _process_price(self, text):
+ discount_valid = True
+ eval_value = text.replace(',', '')
+
+ value = float(eval_value)
+ if self._sign == '-':
+ if self.discount_method == 'percentage' and value > 90:
+ discount_valid = False
+ # Do discount
+ else:
+ discount_valid = self.set_discount(eval_value)
+ elif self._sign == '/':
+ if value <= 0:
+ return
+ sale_line, = self.ModSaleLine.find([
+ ('id', '=', self._current_line_id)
+ ])
+ price_w_tax = sale_line['unit_price_w_tax']
+ if price_w_tax <= Decimal(value):
+ # Change unit price
+ discount_valid = self.set_unit_price(value)
+ 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)
+ if not discount_valid:
+ self.message_bar.set('discount_not_valid')
+ return False
+
+ self.message_bar.set('system_ready')
+ self.update_total_amount()
+ return True
+
+ def _process_pay(self, text):
+ if not self.validate_done_sale():
+ return
+ val = Decimal(text.replace(',', ''))
+
+ if self._commission_activated:
+ if self._journals[self.field_journal_id]['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
+
+ cash_received = Decimal(val)
+ self.set_amounts()
+ residual_amount = self._sale['residual_amount']
+ if residual_amount < 0:
+ # The sale is paid
+ self._done_sale()
+ return True
+
+ change = cash_received - residual_amount
+
+ if residual_amount >= cash_received:
+ amount_to_add = cash_received
+ else:
+ amount_to_add = residual_amount
+
+ all_money = cash_received + self._sale['paid_amount']
+ # self.field_change.setText(change)
+ if change < ZERO:
+ self.field_pending.setText(abs(change))
+ else:
+ self.field_pending.setText(str(ZERO))
+
+ self.set_amount_received(all_money)
+ res = self.add_payment(amount_to_add, all_money, change)
+ if res['residual_amount'] < 0:
+ # The sale is paid
+ self._done_sale()
+ return True
+
+ self.field_journal_id = self.default_journal['id']
+
+ if res['msg'] == 'missing_money':
+ self.message_bar.set('enter_payment', self.default_journal['name'])
+ return False
+
+ if change < ZERO:
+ self.message_bar.set('enter_payment', self.default_journal['name'])
+
+ self.field_change.setText(change)
+ # self.set_amount_received(all_money)
+
+ self._sale.update({
+ 'residual_amount': res['residual_amount']
+ })
+ residual_amount = self._sale['residual_amount']
+
+ if self._sale['residual_amount'] <= 0:
+ # The sale is paid
+ 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._sale['id']:
+ res = self.ModSale.get_discount_total({
+ 'sale_id': self._sale['id']
+ })
+ self.field_discount.setText(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:
+ res = self.ModSale.faster_post_invoice({
+ 'sale_id': self.sale_to_post['id']
+ })
+ except:
+ self.dialog('invoice_done_failed')
+ return
+ if not res['result']:
+ self.dialog('send_electronic_failed')
+ return
+ if self.sale_to_post['is_credit']:
+ return
+
+ self.ModSale.reconcile_invoice({
+ 'sale_id': self.sale_to_post['id']
+ })
+
+ def _done_sale(self, is_credit=False):
+ self._sale['is_credit'] = is_credit
+ self.sale_to_post = self._sale
+ self.do_invoice.start()
+ if self._Tables and self._sale.get('table_assigned'):
+ self._Tables.write([self._sale['table_assigned']],
+ {'state': 'available'}
+ )
+
+ try:
+ if self.print_receipt == 'automatic':
+ _copies = self.device['shop']['invoice_copies']
+ if not is_credit:
+ self.print_invoice(copies=_copies)
+ if self.print_order and self.print_auto_order:
+ self.action_print_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.type_pos_user not in ('cashier', 'order') \
+ and self._config.get('new_sale_automatic'):
+ self.createNewSale()
+ else:
+ self.state_disabled()
+ return True
+
+ def print_invoice(self, sale_id=None, copies=1):
+ if not sale_id:
+ sale_id = self._sale['id']
+ data = self._PosSale.get_data(sale_id, self._context)
+ for i in range(copies):
+ self.receipt_sale.print_sale(data)
+
+ def button_accept_pressed(self):
+ if not self._sale['id'] or not self.model_sale_lines.rowCount() > 0:
+ return
+ self.set_state('accept')
+
+ def button_cash_pressed(self):
+ if not self.check_salesman():
+ return
+ if not self.validate_payment_term():
+ return
+ sale_id = self._sale['id']
+ res, msg = self.ModSale.faster_process({'sale_id': sale_id})
+ # Remove deprecation res['res']
+ if msg:
+ # msg = Mesagge error
+ self.message_bar.set(msg)
+ return
+
+ self.set_amounts(res)
+
+ self.field_invoice.setText(res['invoice_number'])
+ self.field_amount.zero()
+ if self.type_pos_user == 'salesman':
+ self.print_invoice(sale_id)
+ res = self._print_order(sale_id, 'delivery')
+ self.createNewSale()
+ return
+
+ self.message_bar.set('enter_payment', self.default_journal['name'])
+ self.set_state('cash')
+ self.buttonpad.setFocus()
+
+ def action_reservations(self):
+ logging.info('Buscando reservas.....')
+
+ def action_tables(self):
+ self.dialog_manage_tables.exec_()
+
+ def action_table_assigned(self, id, name, prev_state, new_state):
+ table_assigned = id
+
+ if self._sale.get('table_assigned') != id and prev_state == 'occupied':
+ return False
+
+ if self._sale.get('table_assigned') == id and new_state == 'available':
+ name = ''
+ table_assigned = None
+
+ self._sale['table_assigned'] = table_assigned
+ self._sale['table_assigned.state'] = new_state
+
+ self._Tables.write([id], {'state': new_state})
+
+ self._PosSale.write([self._sale['id']], {'table_assigned': table_assigned})
+ self.field_table_assigned.setText(name)
+ return True
+
+ def action_tip(self):
+ if self._config['tip_product'] and self._config['tip_rate']:
+ total_amount = int(self._get_total_amount())
+ self.add_product(code=self._config['tip_product']['code'])
+ self.button_plus_pressed()
+ eval_value = int((self._config['tip_rate'] / 100) * total_amount)
+ self.ModSaleLine.write(
+ [self._current_line_id], {'unit_price': Decimal(eval_value)}
+ )
+ self._PosSale.write([self._sale['id']], {'tip': Decimal(eval_value)})
+ self.update_total_amount()
+
+ def action_salesman(self):
+ self.dialog_salesman.exec_()
+
+ def action_tax(self):
+ self.dialog_tax.exec_()
+
+ def action_payment(self):
+ if self._state != 'cash':
+ 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'] * Decimal(RATE_CREDIT_LIMIT)) < (party['credit_amount'] + self._get_total_amount()):
+ self.dialog('credit_limit_capacity')
+ return True
+
+ def validate_payment_term(self):
+ is_credit = self._payment_terms[str(self.field_payment_term_id)]['is_credit']
+ party, = self.Party.find([('id', '=', self.party_id)])
+ if is_credit:
+ if self.party_id == self.default_party['id']:
+ self.dialog('customer_not_credit')
+ return False
+ if self._credit_limit_activated:
+ if is_credit and not party['credit_limit_amount']:
+ self.dialog('customer_not_credit')
+ return False
+
+ self._credit_amount = self.ModSale.get_credit_amount_party({'party_id': self.party_id})
+ self._credit_limit_amount = party['credit_limit_amount']
+
+ if is_credit and 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_payment_term_selection_changed(self):
+ is_credit = self._payment_terms[str(self.field_payment_term_id)]['is_credit']
+ self._PosSale.write([self._sale['id']], {'payment_term': self.field_payment_term_id})
+ if is_credit and self.type_pos_user != 'salesman':
+ if self.validate_credit_limit():
+ self._done_sale(is_credit=True)
+
+ def validate_credit_limit(self):
+ if self._credit_limit_amount and self._credit_limit_amount < (self._credit_amount + self._get_total_amount()):
+ self.dialog('credit_limit_exceed')
+ return False
+ else:
+ return True
+
+ def action_journal_selection_changed(self):
+ self.message_bar.set('enter_payment', self.field_journal_name)
+
+ def action_salesman_selection_changed(self):
+ self._PosSale.write([self._sale['id']], {'salesman': self.field_salesman_id})
+
+ def action_delivery_charge_selection_changed(self, index):
+ val = self.field_delivery_charge.get_id()
+ if val:
+ self._PosSale.write([self._sale['id']], {'delivery_charge': val})
+
+ def action_invoice_type_selection_changed(self, index):
+ val = self.field_invoice_type.get_id()
+ if val:
+ self._PosSale.write([self._sale['id']], {'invoice_type': val})
+
+ def action_tax_selection_changed(self):
+ res = self._PosSale.add_tax(self._sale['id'], self.field_tax_id, self._context)
+ self._sale.update(res)
+ self.set_amounts()
+
+ def action_print_sale(self):
+ number = self.field_invoice.text()
+ if not number:
+ number = self.field_order_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()
+ printer_id = self.field_printer_ask.get_id()
+ type_doc = self.field_type_ask.get_id()
+ sale = {}
+ if number:
+ if type_doc == 'order':
+ sales = self.ModSale.find([('number', '=', number)])
+ if sales:
+ sale = sales[0]
+ else:
+ sale = self._PosSale.get_sale_from_invoice([1], number, self._context)
+
+ if not sale:
+ return self.message_bar.set('sale_number_not_found')
+ sale_id = sale['id']
+ else:
+ sale_id = self._sale['id']
+
+ if printer_id == '1':
+ self.print_invoice(sale_id)
+ 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 print_equivalent_invoice(self, sale_id):
+ sale, = self.ModSale.find([('id', '=', sale_id)])
+
+ # if not sale['invoices']:
+ # return
+ #
+ # invoice_id = sale['invoices'][0]
+ # commissions = self._Commission.find([
+ # ('origin', '=', 'account.invoice,' + str(invoice_id))
+ # ])
+ #
+ # # if not commissions:
+ # # return
+ # # commission = commissions[0]
+ # # if not commission['invoice_line.invoice']:
+ # # return
+ # #
+ # # model = u'account.invoice'
+ # # data = {
+ # # 'model': model,
+ # # 'action_id': self._action_report_equivalent['id'],
+ # # 'id': commission['invoice_line.invoice'],
+ # # 'ids': [commission['invoice_line.invoice']],
+ # # }
+ # # 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_ask.text()
+ if comment:
+ self._PosSale.write([self._sale['id']], {'comment': comment})
+
+ def action_position(self):
+ self.dialog_position.exec_()
+ position = self.field_position_ask.text()
+ if hasattr(self, 'field_position') and position:
+ self.field_position.setText(position)
+ self._PosSale.write([self._sale['id']], {'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.ModSale.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._PosSale.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):
+ self.dialog_search_parties.clear_rows()
+ self.dialog_search_parties.execute()
+
+ def action_global_discount(self, sale_id=None):
+ self.dialog_global_discount.exec_()
+ discount = self.field_global_discount_ask.text()
+ if discount and discount.isdigit():
+ if self.model_sale_lines.rowCount() > 0:
+ lines = [line['id'] for line in self.model_sale_lines._data]
+ self.set_discount(int(discount), lines)
+
+ def _print_order(self, sale_id, kind, reversion=False):
+ result = False
+ try:
+ orders = self._PosSale.get_order2print(sale_id, reversion, False, self._context)
+ result = self.receipt_order.print_orders(orders, reversion, kind)
+ except:
+ logging.error('Printing order fail!')
+ return result
+
+ def action_print_order(self, sale_id=None, reversion=False):
+ res = self.dialog_order.exec_()
+ if res == DIALOG_REPLY_NO:
+ return
+
+ kind = 'delivery'
+ if self.enviroment == 'restaurant':
+ kind = 'command'
+ if not self.print_order:
+ return
+ if not sale_id and self._sale['id']:
+ sale_id = self._sale['id']
+
+ result = self._print_order(sale_id, kind)
+ # try:
+ # orders = self._PosSale.get_order2print(sale_id, reversion,
+ # False, self._context)
+ # result = self.receipt_order.print_orders(orders, reversion, kind)
+ # print(result, orders)
+ # except:
+ # print('Error: printing order fail!')
+ if result:
+ # Show dialog if send sale order was successful
+ self.dialog('order_successfully')
+ else:
+ self.dialog('order_failed')
+
+ def action_payment_term(self):
+ if self._state == 'cash' or self.type_pos_user == 'salesman':
+ self.dialog_payment_term.exec_()
+
+ def action_new_sale(self):
+ if not self._sale['id']:
+ return
+ if self._ask_new_sale():
+ self.createNewSale()
+ if self.enviroment == 'restaurant':
+ self.wizard_new_sale()
+
+ def wizard_new_sale(self):
+ # self.action_position()
+ # self.action_salesman()
+ pass
+
+ 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.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 == 'cash' 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
+ self._PosSale.cancel_sale(self._sale['id'], self._context)
+ self.field_password_for_cancel_ask.setText('')
+ self.set_state('cancel')
+ self.clear_right_panel()
+ self.createNewSale()
+
+ def action_search_product(self):
+ if self._state == 'cash':
+ return
+ self.dialog_search_products.show()
+
+ def action_search_sale(self):
+ delta = str(datetime.now() - timedelta(4))
+ if self.type_pos_user == 'cashier':
+ dom = ['OR', [
+ ('create_date', '>=', delta),
+ ('state', 'in', ['quotation', 'confirmed']),
+ ], [
+ ('state', '=', 'processing'),
+ ('invoice.state', '=', 'draft'),
+ ('invoice.type', '=', 'out'),
+ ]]
+ elif self.type_pos_user in ('order', 'salesman'):
+ dom = [
+ ('state', '=', 'draft'),
+ ('create_date', '>=', delta),
+ ]
+ else:
+ dom = [
+ ('state', 'in', ['draft', 'quotation', 'confirmed']),
+ ('create_date', '>=', delta),
+ ]
+
+ sales = self.ModSale.find(dom, 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.ModSale.find(dom_draft)
+ self.dialog_search_sales.set_counter_control(sales_draft)
+ response = self.dialog_search_sales.execute()
+ if response == DIALOG_REPLY_NO:
+ return
+
+ 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.grabKeyboard()
+
+ def on_selected_party(self):
+ party_id = self.dialog_search_parties.get_id()
+ if not party_id:
+ return
+
+ party, = self.Party.find([('id', '=', party_id)])
+ values = {
+ 'party': party_id,
+ 'invoice_address': party['addresses'][0]['id'],
+ 'shipment_address': party['addresses'][0]['id'],
+ }
+
+ payment_term = party.get('customer_payment_term')
+ if payment_term:
+ values['payment_term'] = payment_term['id']
+ self.field_payment_term.setText(str(payment_term['name']))
+ self.field_payment_term_id = payment_term['id']
+ else:
+ values['payment_term'] = self.default_payment_term['id']
+ self.field_payment_term_id = self.default_payment_term['id']
+ self.field_payment_term.setText(self.default_payment_term['name'])
+
+ self._PosSale.write([self._sale['id']], values)
+
+ self.party_id = party_id
+ self.field_party.setText(party['name'])
+
+ if party.get('salesman'):
+ self.field_salesman_id = party['salesman']['id']
+ self._PosSale.write([self._sale['id']], {'salesman': self.field_salesman_id})
+ self.field_salesman.setText(party['salesman']['name'])
+
+ # if self._credit_limit_activated:
+ # self._check_credit_capacity(party)
+
+ self.message_bar.set('system_ready')
+ self.setFocus()
+ self.label_input.setFocus()
+ self.grabKeyboard()
+
+ def load_sale(self, sale_id):
+ # loads only draft sales
+ self.is_clear_right_panel = True
+ self.clear_data()
+ self.clear_left_panel()
+ self.clear_right_panel()
+ sale, = self.ModSale.find([('id', '=', sale_id)])
+ self._sale.update(sale)
+ self.table_payment_lines.reset()
+ self.field_order_number.setText(sale['number'] or '')
+ self._set_sale_date()
+ if hasattr(self, 'field_position'):
+ self.field_position.setText(sale['position'] or '')
+ if sale.get('payment_term'):
+ self.field_payment_term_id = sale['payment_term']['id']
+ self.field_payment_term.setText(sale['payment_term']['name'] or '')
+ if sale.get('salesman'):
+ self.field_salesman.setText(sale['salesman']['name'] or '')
+ self.field_salesman_id = sale['salesman']['id']
+
+ res = self.ModSale.get_invoice_type({'sale_id': sale_id})
+ if res:
+ self.field_invoice_type.set_from_id(res['invoice_type'])
+
+ if sale.get('invoice_number'):
+ self.field_invoice.setText(sale['invoice_number'] or '')
+ else:
+ self.field_invoice.setText('')
+ if self.field_delivery_charge:
+ self.field_delivery_charge.set_enabled(True)
+ if self._sale.get('delivery_charge'):
+ self.field_delivery_charge.set_from_id(self._sale['delivery_charge'])
+ if sale.get('table_assigned'):
+ self.field_table_assigned.setText(sale['table_assigned.name'] or '')
+
+ 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')
+ self.field_agent.setText('[' + str(int(commission)) + ']' + ' ' + sale['agent']['name'])
+ 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.ModSaleLine.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']:
+ self.table_payment_lines.record_add(payment)
+
+ self.set_state('add')
+ self.party_id = sale['party']['id']
+ self.field_party.setText(sale['party']['name'])
+ self.set_amounts(sale)
+ self.set_amount_received()
+ self.field_amount.zero()
+ if self.type_pos_user in ('cashier', 'order', 'salesman'):
+ self.table_sale_lines.setEnabled(True)
+
+ 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):
+ residual_amount = self._sale['residual_amount']
+ if cash_received:
+ amount = cash_received
+ else:
+ amount = self._sale['paid_amount']
+ self.field_pending.setText(residual_amount)
+ self.field_paid.setText(amount)
+
+ def update_total_amount(self):
+ self.set_amounts()
+
+ def set_amounts(self, res=None):
+ if not res:
+ res = self._PosSale.get_amounts([self._sale['id']], self._context)
+
+ self._sale.update(res)
+ self.field_untaxed_amount.setText(res['untaxed_amount'])
+ self.field_taxes_amount.setText(res['tax_amount'])
+ self.field_total_amount.setText(res['total_amount'])
+ self.set_discount_amount()
+
+ def _get_products_by_category(self, cat_id):
+ records = self.Product.find([
+ ('code', '!=', None),
+ ('template.salable', '=', True),
+ ('template.categories', '=', cat_id),
+ ])
+ return [
+ [r['id'], r['code'], r['name'], r['template']['sale_price_w_tax']]
+ for r in records
+ ]
+
+ def get_product_by_categories(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'])
+ cat['items'] = self._get_products_by_category(cat['id'])
+ return self.allow_categories
+
+ 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.clear_right_panel()
+ self.add_product(product=self.dialog_search_products.current_row)
+
+ 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})
+ self.dialog_product_stock.update_values(res)
+ self.dialog_product_stock.show()
+
+ def on_selected_item(self, product_id):
+ if product_id:
+ self.clear_right_panel()
+ self.add_product(id=product_id)
+
+ def create_dialog_manage_tables(self):
+ if not self._Tables:
+ return
+ tables = self._Tables.find([
+ ('shop', '=', self.shop['id'])
+ ])
+ self.tables = ManageTables(self, tables, self.action_table_assigned)
+ self.dialog_manage_tables = QuickDialog(self, 'action', widgets=[self.tables])
+
+ def create_dialog_search_sales(self):
+ headers = OrderedDict()
+ headers['id'] = {'desc': self.tr('ID'), 'type': 'char'}
+ headers['number'] = {'desc': self.tr('NUMBER'), 'type': 'char'}
+ headers['invoice_number'] = {'desc': self.tr('INVOICE'), 'type': 'char'}
+ headers['party.name'] = {'desc': self.tr('PARTY'), 'type': 'char'}
+ headers['sale_date'] = {'desc': self.tr('DATE'), 'type': 'char'}
+ headers['salesman.name'] = {'desc': self.tr('SALESMAN'), 'type': 'char'}
+ headers['position'] = {'desc': self.tr('POSITION'), 'type': 'char'}
+ headers['total_amount_cache'] = {'desc': self.tr('TOTAL AMOUNT'), 'type': 'number'}
+
+ widths = [20, 100, 150, 100, 90, 150, 100, 100]
+ title = self.tr('SEARCH SALES...')
+ methods = {
+ 'on_selected_method': 'on_selected_sale',
+ 'on_return_method': 'on_selected_sale'
+ }
+ self.dialog_search_sales = SearchWindow(self, headers, None, methods,
+ filter_column=[1, 2, 3, 4], cols_width=widths, title=title, fill=True)
+
+ self.dialog_search_sales.activate_counter()
+
+ def create_dialog_search_products(self):
+ _cols_width = [10, 80]
+ headers = OrderedDict()
+ headers['id'] = {'desc': self.tr('ID'), 'type': 'char'}
+ headers['code'] = {'desc': self.tr('CODE'), 'type': 'char'}
+ if self._config.get('show_stock_pos') in ['icon', 'value']:
+ headers['quantity'] = {'desc': self.tr('STOCK'), 'type': 'number'}
+ if self._config['show_stock_pos'] == 'icon':
+ headers['quantity']['icon'] = 'stock'
+ headers['quantity']['type'] = 'icon'
+ _cols_width.append(60)
+
+ headers['name'] = {'desc': self.tr('NAME'), 'type': 'char'}
+ _cols_width.append(350)
+
+ if self._config.get('show_description_pos'):
+ headers['description'] = {'desc': self.tr('DESCRIPTION'), 'type': 'char'}
+ _cols_width.append(300)
+
+ if self._config.get('show_brand'):
+ headers['template.brand'] = {'desc': self.tr('BRAND'), 'type': 'char'}
+ _cols_width.append(100)
+
+ price = {'desc': self.tr('PRICE'), 'type': 'number'}
+ if not self._config.get('encoded_sale_price'):
+ headers['template.sale_price_w_tax'] = price
+ else:
+ price['type'] = 'char'
+ headers['encoded_sale_price'] = price
+
+ _cols_width.append(100)
+
+ if self._config.get('show_location_pos'):
+ headers['location.name'] = {'desc': self.tr('LOCATION'), 'type': 'char'}
+ _cols_width.append(100)
+
+ if self._config['show_product_image']:
+ headers['image'] = {'desc': self.tr('IMAGE'), 'icon': 'image', 'type': 'icon'}
+ _cols_width.append(30)
+
+ methods = {
+ 'on_selected_method': 'on_selected_product',
+ 'on_return_method': 'on_search_product',
+ 'image': self.on_selected_icon_product,
+ 'quantity': self.on_selected_stock_product
+ }
+
+ self.dialog_search_products = SearchWindow(self, headers, None,
+ methods, cols_width=_cols_width, fill=True)
+
+ def create_dialog_search_party(self):
+ headers = OrderedDict()
+ headers['id'] = {'desc': self.tr('ID'), 'type': 'char'}
+ headers['id_number'] = {'desc': self.tr('ID NUMBER'), 'type': 'char'}
+ headers['name'] = {'desc': self.tr('NAME'), 'type': 'char'}
+ headers['street'] = {'desc': self.tr('ADDRESS'), 'type': 'char'}
+ headers['phone'] = {'desc': self.tr('PHONE'), 'type': 'char'}
+
+ title = self.tr('SEARCH CUSTOMER')
+ methods = {
+ 'on_selected_method': 'on_selected_party',
+ 'on_return_method': 'on_search_party',
+ }
+ self.dialog_search_parties = SearchWindow(self, headers, None,
+ methods, filter_column=[], cols_width=[60, 120, 270, 190, 90],
+ title=title, fill=True)
+
+ def create_dialog_payment(self):
+ data = {
+ 'name': 'journal',
+ 'values': sorted([(str(j), self._journals[j]['name'])
+ for j in self._journals]),
+ 'heads': [self.tr('ID'), self.tr('PAYMENT MODE:')],
+ }
+ string = self.tr('SELECT PAYMENT MODE:')
+ self.dialog_payment = QuickDialog(self, 'selection', string, data)
+
+ def create_dialog_stock(self):
+ data = {
+ 'name': 'stock',
+ 'values': [],
+ 'heads': [self.tr('WAREHOUSE'), self.tr('QUANTITY')],
+ }
+ label = self.tr('STOCK BY PRODUCT:')
+ self.dialog_product_stock = QuickDialog(self.dialog_search_products,
+ 'selection', label, data, readonly=True)
+
+ def create_dialog_salesman(self):
+ data = {
+ 'name': 'salesman',
+ 'values': [(str(e['id']), e['party']['name'])
+ for e in self.employees],
+ 'heads': [self.tr('Id'), self.tr('Salesman')],
+ }
+ string = self.tr('CHOOSE SALESMAN')
+ self.dialog_salesman = QuickDialog(self, 'selection', string, data)
+
+ def create_dialog_taxes(self):
+ if self.shop_taxes:
+ taxes = [(str(e['id']), e['name']) for e in self.shop_taxes]
+ else:
+ taxes = []
+ data = {
+ 'name': 'tax',
+ 'values': taxes,
+ 'heads': [self.tr('Id'), self.tr('Salesman')],
+ }
+ string = self.tr('CHOOSE TAX')
+ self.dialog_tax = QuickDialog(self, 'selection', string, data)
+
+ def create_dialog_payment_term(self):
+ data = {
+ 'name': 'payment_term',
+ 'values': [(p_id, self._payment_terms[p_id]['name'])
+ for p_id in self._payment_terms],
+ 'heads': [self.tr('ID'), self.tr('PAYMENT TERM')],
+ }
+ string = self.tr('SELECT PAYMENT TERM')
+ self.dialog_payment_term = QuickDialog(self, 'selection', string, data)
+
+ 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),
+ ]
+
+ for tw in target_words:
+ if len(tw) <= 1:
+ continue
+ clause = ['OR',
+ ('template.name', 'ilike', '%{:}%'.format(tw)),
+ ('description', 'ilike', '%{:}%'.format(tw)),
+ ]
+ domain.append(clause)
+ if self.shop.get('product_categories'):
+ categories_id = [ct['id'] for ct in self.shop['product_categories']]
+ domain.append(('template.account_category', 'in', categories_id))
+
+ if not domain:
+ return
+ if self.cache_local:
+ products = self.store.find_product_elastic(domain, limit=100)
+ else:
+ products = self.Product.find(domain, 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)
+
+ parties = self.Party.find(domain)
+ self.dialog_search_parties.set_from_values(parties)
+
+ def create_dialog_print_invoice(self):
+ view = [
+ ('invoice_number_ask', {'name': self.tr('INVOICE NUMBER')}),
+ ('printer_ask', {
+ 'name': self.tr('PRINTER'),
+ 'type': 'selection',
+ 'values': [
+ (1, 'POS'),
+ (2, 'LASER')
+ ],
+ }),
+ ('type_ask', {
+ 'name': self.tr('TYPE'),
+ 'type': 'selection',
+ 'values': [
+ ('invoice', self.tr('INVOICE')),
+ ('order', self.tr('ORDER'))
+ ],
+ }),
+ ]
+ self.dialog_print_invoice = QuickDialog(self, 'action', data=view)
+
+ def create_dialog_cancel_invoice(self):
+ view = [
+ ('password_for_cancel_ask', {
+ 'name': self.tr('INSERT PASSWORD FOR CANCEL'),
+ 'password': True
+ }),
+ ]
+ self.dialog_cancel_invoice = QuickDialog(self, 'action', data=view)
+
+ def create_dialog_global_discount(self):
+ field = 'global_discount_ask'
+ data = {'name': self.tr('GLOBAL DISCOUNT')}
+ self.dialog_global_discount = QuickDialog(self, 'action', data=[(field, data)])
+
+ def create_dialog_force_assign(self):
+ field = 'password_force_assign_ask'
+ data = {'name': self.tr('PASSWORD FORCE ASSIGN')}
+ self.dialog_force_assign = QuickDialog(self, 'action', data=[(field, data)])
+ self.field_password_force_assign_ask.setEchoMode(QLineEdit.Password)
+
+ def create_dialog_voucher(self):
+ field = 'voucher_ask'
+ data = {'name': self.tr('VOUCHER NUMBER')}
+ self.dialog_voucher = QuickDialog(self, 'action', data=[(field, data)])
+
+ def create_dialog_order(self):
+ # field = 'Send Order'
+ # data = {'name': self.tr('COPY NUMBER')}
+ string = self.tr('DO YOU WANT TO CONFIRM THE SEND ORDER?')
+ self.dialog_order = QuickDialog(self, 'action', string, data=[])
+
+ def create_dialog_position(self):
+ field = 'position_ask'
+ data = {'name': self.tr('POSITION')}
+ self.dialog_position = QuickDialog(self, 'action', data=[(field, data)])
+
+ def create_dialog_agent(self):
+ view = [
+ ('agent_ask', {
+ 'name': self.tr('AGENT'),
+ 'type': 'relation',
+ 'model': self._Agent,
+ 'domain': [('active', '=', True)],
+ 'fields': [
+ ('id', self.tr('ID')),
+ ('party.name', self.tr('NAME')),
+ ('party.id_number', self.tr('ID NUMBER')),
+ ]}),
+ ('commission_ask', {'name': self.tr('COMMISSION')}),
+ ('commission_amount', {'name': self.tr('AMOUNT'),
+ 'readonly': True}),
+ ]
+ self.dialog_agent = QuickDialog(self, 'action', data=view, size=(600, 400))
+
+ def create_wizard_new_sale(self):
+ pass
+
+ def create_dialog_comment(self):
+ field = 'comment_ask'
+ data = {'name': self.tr('COMMENTS'), 'widget': 'text'}
+ self.dialog_comment = QuickDialog(self, 'action', data=[(field, data)])
+
+ def clear_data(self):
+ self._sale = {
+ 'total_amount': 0
+ }
+ self.party_name = None
+ self._sale_line = {'id': None}
+ self._total_amount = {}
+ self._sale_lines_taxes = {}
+ self.field_journal_id = self.default_journal['id']
+
+ def clear_left_panel(self):
+ self.message_bar.set('system_ready')
+ self.field_party.setText('')
+ self.field_salesman.setText('')
+ self.field_salesman_id = None
+ self.field_invoice_type.set_from_id('')
+ 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_date.setText('')
+ self.field_global_discount_ask.setText('')
+ self.field_amount.zero()
+ self.field_order_number.setText('')
+ self.current_comment = ''
+ 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_ask'):
+ # self.field_comment_ask.document().clear()
+ pass
+ if hasattr(self, 'field_points'):
+ self.field_points.setText('')
+ 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('')
+ if hasattr(self, 'field_position'):
+ self.field_position.setText('')
+ self.field_position_ask.setText('')
+ self.model_sale_lines.reset()
+ self.clear_input_text()
+ self.clear_amount_text()
+
+ def clear_right_panel(self):
+ if self.is_clear_right_panel:
+ return
+ self.field_invoice.setText('')
+ self.field_untaxed_amount.zero()
+ self.field_taxes_amount.zero()
+ self.field_total_amount.zero()
+ self.field_change.zero()
+ self.field_paid.zero()
+ self.field_discount.zero()
+ self.table_payment_lines.reset()
+ self.is_clear_right_panel = True
+
+ 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 createNewSale(self):
+ self.check_empty_sale()
+ if self.type_pos_user == 'cashier':
+ self.state_disabled()
+ return
+ self.set_state('add')
+ self.input_text_changed('')
+ self.amount_text_changed('0')
+ self.clear_sign()
+ self.global_timer = 0
+ self.payment_ctx = {}
+ self.is_clear_right_panel = False
+ self.table_sale_lines.setEnabled(True)
+ self.clear_data()
+ self.clear_left_panel()
+ self._sale = self._PosSale.new_sale([], {
+ 'shop': self.shop['id'],
+ 'invoice_type': 'P',
+ 'company': self.company['id'],
+ 'party': self.default_party['id'],
+ 'sale_device': self.device['id'],
+ 'payment_term': self.default_payment_term['id']
+ }, self._context)
+
+ self.field_invoice_type.set_from_id(self._sale['invoice_type'])
+ # FIXME ADD MORE
+ self._sale.update({
+ 'total_amount': 0
+ })
+ self.party_id = self.default_party['id']
+ if self._sale.get('id'):
+ self._set_sale_date()
+ self.field_order_number.setText(self._sale['number'])
+ if self.field_delivery_charge:
+ self.field_delivery_charge.set_enabled(True)
+
+ 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_date.setText(local_date)
+
+ def _search_product(self, code):
+ domain = [
+ ('template.salable', '=', True),
+ ('template.account_category', '!=', None),
+ ]
+ domain.append(['OR',
+ ('barcode', '=', code),
+ ('code', '=', code),
+ ])
+ products = self.Product.find(domain)
+ if not products or len(products) > 1:
+ self.message_bar.set('product_not_found')
+ return False
+ else:
+ product = products[0]
+ return product
+
+ def check_salesman(self):
+ if self.salesman_required and not self.field_salesman_id:
+ dialog = self.dialog('missing_salesman')
+ dialog.exec_()
+ return False
+ return True
+
+ def add_product(self, id=None, code=None, product=None):
+ if self._state == 'disabled':
+ self.message_bar.set('must_load_or_create_sale')
+ return
+
+ product_id = None
+ if id:
+ product_id = 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
+ }
+ res = self.ModSale.faster_add_product(data)
+ self._sale_line = res
+ self._current_line_id = res['id']
+ self.add_sale_line(res)
+ self._sale_line['product'] = product
+
+ self.update_total_amount()
+ self.set_state('add')
+
+ def _check_stock_quantity(self, product, request_qty):
+ if self._password_admin and product['quantity'] < request_qty:
+ self.dialog_force_assign.exec_()
+ password = self.field_password_force_assign_ask.text()
+ self.field_password_force_assign_ask.setText('')
+ if password != self._password_admin:
+ self.message_bar.set('not_can_force_assign')
+ return False
+ 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']
+ rec = self.model_sale_lines.add_record(record)
+ self.field_amount.setText(rec['amount_w_tax'])
+
+ def sale_line_selected(self, product):
+ if self._state == 'cash':
+ return
+ self._current_line_id = product['id']
+ self.label_product.setText(product['product.template.name'])
+ self.row_field_description.setText(product['description'])
+ self.row_field_qty.setValue(float(product['quantity']))
+ self.row_field_price.setText(str(product['unit_price_w_tax']))
+ # self.row_field_note.setText(str(product['note']))
+
+ self.dialog_product_edit.show()
+ self.row_field_note.setFocus()
+
+ def create_dialog_sale_line(self):
+ self.state_line = {}
+
+ vbox_product = QVBoxLayout()
+ grid = QGridLayout()
+ qty = 2
+
+ self.label_product = QLabel()
+ self.label_product.setAlignment(alignCenter)
+ self.label_product.setObjectName('label_product')
+ vbox_product.addWidget(self.label_product)
+ self.row_field_description = QLineEdit()
+ self.row_field_description.setObjectName('row_field_description')
+ self.row_field_description.textChanged.connect(
+ lambda: self.update_sale_line('description')
+ )
+ grid.addWidget(self.row_field_description, 1, 1, 1, 2)
+
+ if self._config.get('show_fractions'):
+ label_fraction = QLabel(self.tr('FRACTION:'))
+ label_fraction.setObjectName('label_fraction')
+ grid.addWidget(label_fraction, 2, 1)
+ self.field_combobox_fraction = ComboBox(self, 'fraction',
+ {'values': FRACTIONS})
+ grid.addWidget(self.field_combobox_fraction, 2, 2)
+ self.field_combobox_fraction.currentIndexChanged.connect(
+ lambda: self.update_sale_line('qty_fraction')
+ )
+
+ label_qty = QLabel(self.tr('QUANTITY:'))
+ label_qty.setObjectName('label_qty')
+ grid.addWidget(label_qty, 3, 1)
+ self.row_field_qty = QDoubleSpinBox()
+ self.row_field_qty.setObjectName('row_field_qty')
+ self.row_field_qty.setMinimum(0)
+ self.row_field_qty.setMaximum(100000)
+ if self._config.get('decimals_digits_quantity'):
+ qty = self._config['decimals_digits_quantity']
+
+ self.row_field_qty.setDecimals(qty)
+ self.row_field_qty.setAlignment(alignCenter)
+ grid.addWidget(self.row_field_qty, 3, 2)
+ self.row_field_qty.valueChanged.connect(
+ lambda: self.update_sale_line('quantity')
+ )
+
+ label_price = QLabel(self.tr('UNIT PRICE:'))
+ label_price.setObjectName('label_price')
+ grid.addWidget(label_price, 4, 1)
+ self.row_field_price = FieldMoney(self, 'row_field_price', {}, readonly=False)
+ self.row_field_price.setObjectName('row_field_price')
+ grid.addWidget(self.row_field_price, 4, 2)
+ self.row_field_price.textChanged.connect(
+ lambda: self.update_sale_line('unit_price')
+ )
+
+ self.row_field_note = QTextEdit('')
+ self.row_field_note.setObjectName('row_field_note')
+ grid.addWidget(self.row_field_note, 5, 1, 5, 2)
+ self.row_field_note.textChanged.connect(
+ lambda: self.update_sale_line('note')
+ )
+
+ vbox_product.addLayout(grid)
+ self.dialog_product_edit = QuickDialog(self, 'action', widgets=[vbox_product])
+ self.dialog_product_edit.accepted.connect(self.dialog_product_edit_accepted)
+
+ def update_sale_line(self, field):
+ value = None
+ self.state_line['id'] = self._current_line_id
+ if field == 'quantity':
+ value = Decimal(self.row_field_qty.value())
+ if field == 'unit_price':
+ value = self.row_field_price.text()
+ if field == 'qty_fraction':
+ qty = self.field_combobox_fraction.get_id()
+ self.row_field_qty.setValue(float(qty))
+ value = self.field_combobox_fraction.get_label()
+ self.state_line['quantity'] = qty
+
+ price_ = self.ModSale.get_product_prices({
+ 'ids': [self._sale_line['product']['id']],
+ 'quantity': float(qty),
+ 'sale_id': self._sale['id'],
+ })
+ if price_ and price_.get('unit_price_w_tax'):
+ price_list = str(price_['unit_price_w_tax'])
+ self.row_field_price.setText(price_list)
+ self.state_line['unit_price'] = price_list
+
+ if field == 'description':
+ value = self.row_field_description.text()
+ if field == 'note':
+ value = self.row_field_note.toPlainText()
+
+ if value:
+ self.state_line[field] = value
+
+ def dialog_product_edit_accepted(self):
+ if not self.state_line:
+ return
+
+ _record = None
+
+ if self.state_line.get('quantity'):
+ quantity = self.state_line.pop('quantity')
+ _record = self.ModSaleLine.faster_set_quantity({
+ 'id': self._current_line_id,
+ 'quantity': to_float(quantity, 2)
+ })
+
+ if self.state_line.get('unit_price'):
+ unit_price = self.state_line.pop('unit_price')
+ self._sign = '/'
+ self._process_price(unit_price)
+ self._sign = None
+ _record = self.ModSaleLine.write([self._current_line_id], {})
+
+ if self.state_line.get('description'):
+ _record = self.ModSaleLine.write([self._current_line_id], {
+ 'description': self.state_line['description']
+ })
+
+ if self.state_line.get('note'):
+ _record = self.ModSaleLine.write([self._current_line_id], {
+ 'note': self.state_line['note']
+ })
+
+ if _record:
+ if not _record.get('unit.symbol'):
+ _record['unit.symbol'] = _record['unit']['symbol']
+ 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']
+ self.model_sale_lines.update_record(_record)
+
+ self.update_total_amount()
+ self.state_line = {}
+ # self.field_combobox_fraction.set_from_id(1)
+
+ def setup_sale_line(self):
+ product_code = {
+ 'name': 'product.code',
+ 'align': alignRight,
+ 'description': self.tr('COD'),
+ 'width': 80
+ }
+ product = {
+ 'name': 'product.template.name',
+ 'align': alignLeft,
+ 'description': self.tr('NAME'),
+ 'width': STRETCH
+ }
+ description = {
+ 'name': 'description',
+ 'align': alignLeft,
+ 'description': self.tr('DESCRIPTION'),
+ 'width': 180
+ }
+ uom = {
+ 'name': 'unit.symbol',
+ 'align': alignHCenter,
+ 'description': self.tr('UNIT'),
+ 'width': 50
+ }
+ qty = {
+ 'name': 'quantity',
+ 'format': '{:3,.%sf}',
+ 'align': alignRight,
+ 'description': self.tr('QTY'),
+ 'digits': ('unit.symbol', CONVERSION_DIGITS),
+ 'width': 80
+ }
+ discount = {
+ 'name': 'discount',
+ 'format': '{0:.0%}',
+ 'align': alignRight,
+ 'description': self.tr('DISC'),
+ 'width': 50
+ }
+ amount = {
+ 'name': 'amount_w_tax',
+ 'format': '{:5,.1f}',
+ 'align': alignRight,
+ 'description': self.tr('SUBTOTAL'),
+ 'width': 100
+ }
+ note = {
+ 'name': 'note',
+ 'align': alignLeft,
+ 'description': self.tr('NOTE'),
+ 'invisible': True,
+ 'width': 100
+ }
+ unit_price = {
+ 'name': 'unit_price_w_tax',
+ 'format': '{:5,.1f}',
+ 'align': alignLeft,
+ 'description': self.tr('UNIT PRICE W TAX'),
+ 'invisible': True,
+ 'width': 100
+ }
+
+ qty_fraction = {
+ 'name': 'qty_fraction',
+ 'align': alignHCenter,
+ 'description': self.tr('FRAC'),
+ 'width': 50
+ }
+
+ 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)
+
+ self.model_sale_lines = TableModel('sale.line', self.fields_sale_line)
+
+ def setup_payment(self):
+ pay_fields = [{
+ 'name': 'statement',
+ 'align': alignLeft,
+ 'description': self.tr('STATEMENT JOURNAL'),
+ }, {
+ 'name': 'amount',
+ 'align': alignRight,
+ 'format': '{:5,.1f}',
+ 'description': self.tr('AMOUNT'),
+ }, {
+ 'name': 'voucher',
+ 'align': alignCenter,
+ 'description': self.tr('VOUCHER'),
+ }]
+ self.table_payment_lines = TableModel('account.statement.line', pay_fields)
+
+ def action_table(self):
+ self.table_sale_lines.setFocus()
+
+ def on_change_line_selected(self, key):
+ self.table_sale_lines.moved_selection(key)
+
+ def action_delete_line(self):
+ if self.model_sale_lines.rowCount() <= 0 or self._state == 'cash':
+ return
+
+ self.table_sale_lines.setFocus()
+
+ removed_item = self.table_sale_lines.delete_item()
+ self.ModSaleLine.delete([removed_item['id']])
+ self.set_amounts()
+ self.update_total_amount()
+ # self.clear_right_panel()
+
+ self._current_line_id = None
+ self.setFocus()
+ self.label_input.setFocus()
+
+ if self.enviroment == 'restaurant':
+ if removed_item and self.print_order:
+ self.action_print_order(self._sale['id'], removed_item)
+ if self._config['tip_product']['code'] == removed_item['product.code']:
+ self._PosSale.write([self._sale['id']], {'tip': None})
+
+ def set_discount(self, eval_value, lines_ids=[]):
+ res = False
+ try:
+ value = round(float(str(eval_value)), 6)
+ except ValueError:
+ logging.warning('ValueError > ', ValueError)
+ return
+
+ if float(value) <= 0:
+ return
+
+ if not lines_ids:
+ target_lines = [self._current_line_id]
+ else:
+ target_lines = lines_ids
+
+ records = self.ModSaleLine.faster_set_discount({
+ 'line_ids': target_lines,
+ 'value': value
+ })
+
+ for rec in records:
+ self.model_sale_lines.update_record(rec)
+
+ if records:
+ res = True
+ self.set_amounts()
+ return res
+
+ def set_unit_price(self, value):
+ rec = self.ModSaleLine.set_faster_unit_price({
+ 'id': self._current_line_id,
+ 'value': value,
+ })
+
+ if rec:
+ self.model_sale_lines.update_record(rec)
+ self.update_total_amount()
+ return True
+ return False
+
+ def add_payment(self, amount, cash_received=0, change=0):
+ voucher_number = None
+ if self._journals[self.field_journal_id]['require_voucher']:
+ self.dialog_voucher.exec_()
+ voucher_number = self.field_voucher_ask.text()
+ if voucher_number is None or voucher_number == '':
+ return self.add_payment(amount)
+
+ res = self.ModSale.faster_add_payment({
+ 'sale_id': self._sale['id'],
+ 'journal_id': self.field_journal_id,
+ 'amount': to_numeric(amount),
+ 'voucher_number': voucher_number,
+ 'cash_received': to_numeric(cash_received),
+ 'change': to_numeric(change)
+ })
+
+ if res.get('msg') not in ('missing_money', 'ok'):
+ self.dialog(res['msg'])
+ return res
+
+ self.table_payment_lines.add_record(res)
+ return res
+
+ def create_dialog_help(self):
+ from .help import Help
+ help = Help(self)
+ help.show()
+
+ 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.buttonpad.stacked.stacked.currentWidget():
+ return
+ if state['button']:
+ self.buttonpad.stacked.stacked.setCurrentWidget(
+ getattr(self.buttonpad.stacked, state['button'])
+ )
+ if not self.tablet_mode:
+ self.buttonpad.stacked.stacked.currentWidget().setVisible(True)
+ else:
+ self.buttonpad.stacked.stacked.currentWidget().setVisible(False)
+
+ def key_pressed(self, text):
+ if not self._sign and self._state != 'cash':
+ 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
+ self.sign_text_changed(value)
+
+ def key_backspace_pressed(self):
+ if self._sign or self._state == 'cash':
+ self._amount_text = self._amount_text[:-1]
+ self.amount_text_changed()
+ else:
+ self._input_text = self._input_text[:-1]
+ self.input_text_changed()
+
+ def set_text(self, text):
+ if not self._state == 'cash':
+ 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):
+ self._keyStates[event.key()] = True
+ key = event.key()
+
+ 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 == 'cash' and key not in self.keys_numbers:
+ return
+ self.key_pressed(event.text())
+ elif key in self.keys_special:
+ if self._state == 'cash' 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:
+ self.close()
+ elif key == Qt.Key_F1:
+ self.create_dialog_help()
+ 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 ['accept', 'cash']:
+ self.button_cash_pressed()
+ elif key == Qt.Key_F2:
+ self.action_search_product()
+ elif key == Qt.Key_F3:
+ self.action_payment()
+ elif key == Qt.Key_F4:
+ self.action_party()
+ elif key == Qt.Key_F5:
+ self.action_global_discount()
+ elif key == Qt.Key_F6:
+ self.action_print_order()
+ elif key == Qt.Key_F8:
+ self.action_payment_term()
+ elif key == Qt.Key_F10:
+ self.action_table()
+ elif key == Qt.Key_F12:
+ self.action_cancel()
+ elif key == Qt.Key_Home:
+ self.action_salesman()
+ elif key == Qt.Key_Down or key == Qt.Key_Up:
+ self.on_change_line_selected(key)
+ elif key == Qt.Key_Delete:
+ self.action_delete_line()
+ 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 ['accept', 'cash']:
+ 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
+
+
+class DoInvoice(QThread):
+ """
+ Process invoices using a thread
+ """
+ sigDoInvoice = pyqtSignal()
+
+ def __init__(self, main, context):
+ QThread.__init__(self)
+
+ def run(self):
+ self.sigDoInvoice.emit()
diff --git a/app/mainwindow.py b/app/mainwindow.py
index fd10713..9c01ae9 100644
--- a/app/mainwindow.py
+++ b/app/mainwindow.py
@@ -382,7 +382,6 @@ class MainWindow(FrontWindow):
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(),
@@ -1911,7 +1910,8 @@ class MainWindow(FrontWindow):
('commission_amount', {'name': self.tr('AMOUNT'),
'readonly': True}),
]
- self.dialog_agent = QuickDialog(self, 'action', data=view, size=(600, 400))
+ print()
+ self.dialog_agent = QuickDialog(self, 'action', data=view)
def create_wizard_new_sale(self):
pass
diff --git a/app/share/icon.png b/app/share/icon.png
new file mode 100644
index 0000000..0487e14
Binary files /dev/null and b/app/share/icon.png differ
diff --git a/app/share/pos-icon.svg b/app/share/pos-icon.svg
index 8a60771..cb36bf6 100644
--- a/app/share/pos-icon.svg
+++ b/app/share/pos-icon.svg
@@ -14,9 +14,9 @@
height="96"
id="svg5453"
version="1.1"
- inkscape:version="0.91 r13725"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="pos-icon.svg"
- inkscape:export-filename="/mnt/DataX/Dev/TRYTON/TRYTON-3.6/PRESIK-DEV/POS/pos_client_qt5/pos/interface/pos-icon.png"
+ inkscape:export-filename="/media/DATA/Seafile/DEVELOPMENT/TRYTON/TRYTON-5.0/FRONTENDS/psk_pos/app/share/icon.png"
inkscape:export-xdpi="86"
inkscape:export-ydpi="86">
+ id="guide4146"
+ inkscape:locked="false" />
+ id="guide4148"
+ inkscape:locked="false" />
+ id="guide4150"
+ inkscape:locked="false" />
+ id="guide4152"
+ inkscape:locked="false" />
+ id="guide4154"
+ inkscape:locked="false" />
+ id="guide4156"
+ inkscape:locked="false" />
+ id="guide4158"
+ inkscape:locked="false" />
+ id="guide4160"
+ inkscape:locked="false" />
+ id="guide4162"
+ inkscape:locked="false" />
+ id="guide4164"
+ inkscape:locked="false" />
+ id="guide4166"
+ inkscape:locked="false" />
+ id="guide4168"
+ inkscape:locked="false" />
+ id="guide4170"
+ inkscape:locked="false" />
+ id="guide4172"
+ inkscape:locked="false" />
+ id="guide4174"
+ inkscape:locked="false" />
+ id="guide4176"
+ inkscape:locked="false" />