mirror of
https://bitbucket.org/presik/presik_pos.git
synced 2023-12-14 06:03:00 +01:00
2871 lines
109 KiB
Python
2871 lines
109 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: UTF-8 -*-
|
|
import sys
|
|
import os
|
|
import logging
|
|
import base64
|
|
from decimal import Decimal
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDate
|
|
from .tools import get_icon, to_float, to_numeric
|
|
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 datetime import datetime, timedelta, date
|
|
from collections import OrderedDict
|
|
# from PyQt5.QtGui import QTouchEvent
|
|
from PyQt5.QtWidgets import (QLabel, QHBoxLayout, QVBoxLayout,
|
|
QWidget, QGridLayout, QLineEdit)
|
|
from app.commons.action import Action
|
|
from app.commons.forms import GridForm, ComboBox, FieldNumeric
|
|
from app.commons.messages import MessageBar
|
|
from app.commons.image import Image
|
|
from app.commons.table import TableView
|
|
from app.commons.model import TableModel, Modules, TableModelEdit
|
|
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, ButtonsFunction
|
|
from .states import STATES, RE_SIGN
|
|
from .constants import (PATH_PRINTERS, DELTA_LOCALE, STRETCH, alignRight,
|
|
alignLeft, alignCenter, alignHCenter, alignVCenter,
|
|
DIALOG_REPLY_NO, DIALOG_REPLY_YES, ZERO,
|
|
RATE_CREDIT_LIMIT, SCREENS, FILE_BANNER,
|
|
CONVERSION_DIGITS)
|
|
|
|
INVOICE_TYPE = [('', '')]
|
|
DELIVERY_WAY = [
|
|
('', ''),
|
|
('take_away', 'TAKE AWAY'),
|
|
('delivery', 'DELIVERY'),
|
|
('table', 'TABLE')
|
|
]
|
|
MINIMUM_DATE = QDate(0, 1, 1)
|
|
|
|
|
|
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)
|
|
_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.is_clear_right_panel = True
|
|
self.payment_ctx = {}
|
|
self.set_keys()
|
|
self.stock_context = None
|
|
|
|
self.ctx = self._context
|
|
self.ctx['params'] = params
|
|
self.label_color = 'gray'
|
|
self.label_color_2 = ''
|
|
if _theme == 'dark':
|
|
self.label_color = 'light'
|
|
self.label_color_2 = 'orange'
|
|
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_delivery_party()
|
|
self.setup_sale_consumer()
|
|
self.setup_payment()
|
|
self.set_domains()
|
|
self.setup_money_count()
|
|
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()
|
|
|
|
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)
|
|
|
|
if self.cache_local:
|
|
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!')),
|
|
'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!')),
|
|
})
|
|
|
|
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._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'])
|
|
|
|
_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',
|
|
)
|
|
}
|
|
|
|
_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:
|
|
self.PartyConsumer = FastModel('party.consumer', self.ctx)
|
|
_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'])
|
|
|
|
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.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.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)
|
|
|
|
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.salesman_ids = [s['id'] for s in self.shop['salesmans']] \
|
|
if self.shop.get('salesmans') else []
|
|
|
|
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['discounts'] if self.shop.get('discounts') else []
|
|
self.delivery_man = self.shop['delivery_man'] if self.shop.get('delivery_man') else []
|
|
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_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.dialog_manage_tables = DialogManageTables(self).get()
|
|
|
|
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
|
|
|
|
locale_logo = ''
|
|
if self.logo:
|
|
locale_logo = os.path.join(os.path.abspath(
|
|
os.path.dirname(__file__)), 'logo.png')
|
|
f = open(locale_logo, "wb")
|
|
f.write(base64.decodestring(self.logo.encode()))
|
|
f.close()
|
|
|
|
self.receipt_sale = Receipt(ctx_printing, logo=locale_logo)
|
|
|
|
# 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],
|
|
'profile': self.profile_printer,
|
|
}
|
|
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
|
|
if not self._check_quantity():
|
|
return
|
|
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')
|
|
|
|
values = self.get_menu_control_panel()
|
|
self.menu_control_panel = ButtonsFunction(self, panel=True, values=values)
|
|
|
|
if self.enviroment == 'restaurant':
|
|
values = self.get_product_by_categories()
|
|
menu_dash = MenuDash(self, values, 'on_selected_item')
|
|
|
|
if not self.tablet_mode:
|
|
_label_type_invoice = QLabel(self.tr(' TYPE INVOICE:'))
|
|
_label_type_invoice.setObjectName('label_invoice')
|
|
_label_type_invoice.setAlignment(alignRight | alignVCenter)
|
|
|
|
self.field_amount = FieldNumeric(self, 'amount', {})
|
|
self.field_amount.setObjectName('field_amount')
|
|
self.field_sign = QLabel(' ')
|
|
self.field_sign.setObjectName('field_sign')
|
|
|
|
layout_message = QGridLayout()
|
|
layout_message.setColumnStretch(0, 1)
|
|
layout_message.addLayout(self.message_bar, 0, 0, 2, 0)
|
|
|
|
INVOICE_TYPE.append(('P', ('POS')))
|
|
if self.shop.get('credit_note_electronic_authorization'):
|
|
INVOICE_TYPE.append(('91', ('NOTA CREDITO ELECTRONICA')))
|
|
if self.shop.get('debit_note_electronic_authorization'):
|
|
INVOICE_TYPE.append(('92', ('NOTA DEBITO ELECTRONICA')))
|
|
if self.shop.get('manual_authorization'):
|
|
INVOICE_TYPE.append(('M', 'MANUAL'))
|
|
if self.shop.get('computer_authorization'):
|
|
INVOICE_TYPE.append(('C', ('COMPUTADOR')))
|
|
if self.shop.get('electronic_authorization'):
|
|
INVOICE_TYPE.append(('1', ('VENTA ELECTRONICA')))
|
|
|
|
self.field_invoice_type = ComboBox(self, 'invoice_type',
|
|
{'values': INVOICE_TYPE,
|
|
'on_change': 'action_invoice_type_selection_changed'
|
|
})
|
|
|
|
if not self.tablet_mode:
|
|
layout_message.addWidget(self.label_input, 2, 0)
|
|
layout_message.addWidget(_label_type_invoice, 2, 1)
|
|
layout_message.addWidget(self.field_invoice_type, 2, 2)
|
|
else:
|
|
layout_message.addWidget(self.label_input, 2, 0, 1, 2)
|
|
layout_message.addWidget(self.field_invoice_type, 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': self.label_color
|
|
}),
|
|
('date', {
|
|
'name': self.tr('DATE'),
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('salesman', {
|
|
'name': self.tr('SALESMAN'),
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('order_number', {
|
|
'name': self.tr('No ORDER'),
|
|
'placeholder': False,
|
|
'readonly': True,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('payment_term', {
|
|
'name': self.tr('PAYMENT TERM'),
|
|
'readonly': True,
|
|
'invisible': self.tablet_mode,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('delivery_men', {
|
|
'name': self.tr('DELIVERY MEN'),
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
})
|
|
]
|
|
self.field_state = QLineEdit('STATE')
|
|
self.field_state.setReadOnly(True)
|
|
self.field_state.setObjectName('field_state')
|
|
|
|
if self._web_channel:
|
|
self.Channel = FastModel('sale.web_channel', self.ctx)
|
|
self.channels = self.Channel.find([])
|
|
|
|
info_fields.append(
|
|
('channel', {
|
|
'name': self.tr('CHANNEL'),
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}))
|
|
|
|
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': self.label_color
|
|
}))
|
|
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': self.label_color
|
|
}))
|
|
|
|
_cols = 2
|
|
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': self.label_color
|
|
}))
|
|
|
|
self.field_table_assigned = None
|
|
if self._sale_pos_restaurant:
|
|
# info_fields.append(
|
|
# ('delivery_time', {
|
|
# 'name': self.tr('DELIVERY TIME'),
|
|
# 'placeholder': False,
|
|
# 'type': 'money',
|
|
# 'size': self.screen_size,
|
|
# 'color': self.label_color
|
|
# }))
|
|
info_fields.append(
|
|
('delivery_way', {
|
|
'name': self.tr('DELIVERY WAY'),
|
|
'placeholder': False,
|
|
'type': 'selection',
|
|
'on_change': 'action_delivery_way_selection_changed',
|
|
'values': DELIVERY_WAY,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}))
|
|
info_fields.append(
|
|
('table_assigned', {
|
|
'name': self.tr('ASSIGNED TABLE'),
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}))
|
|
|
|
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': self.label_color
|
|
|
|
}),
|
|
('taxes_amount', {
|
|
'name': self.tr('TAXES'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('discount', {
|
|
'name': self.tr('DISCOUNT'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('total_amount', {
|
|
'name': self.tr('TOTAL'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('paid', {
|
|
'name': self.tr('PAID'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('pending', {
|
|
'name': self.tr('PENDIENTE'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color_2 or 'blue'
|
|
}),
|
|
('change', {
|
|
'name': self.tr('CHANGE'),
|
|
'readonly': True,
|
|
'type': 'money',
|
|
'size': self.screen_size,
|
|
'color': self.label_color_2 or '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)
|
|
|
|
_other_fields = [
|
|
('invoice', {
|
|
'name': self.tr('INVOICE'),
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
('state', {
|
|
'name': self.tr('STATE'),
|
|
'readonly': True,
|
|
'placeholder': False,
|
|
'size': self.screen_size,
|
|
'color': self.label_color
|
|
}),
|
|
]
|
|
# if self.enviroment == 'restaurant':
|
|
# info_fields.extend(_other_fields)
|
|
|
|
self.grid_info = GridForm(self, OrderedDict(info_fields), col=_cols)
|
|
other_fields = OrderedDict(_other_fields)
|
|
self.grid_other_fields = GridForm(self, other_fields, col=2)
|
|
left_bottom.addLayout(self.grid_info, 1)
|
|
|
|
if self.enviroment == 'restaurant':
|
|
panel_left.addLayout(self.buttonpad.functions, 1)
|
|
panel_right.addLayout(menu_dash, 1)
|
|
panel_right.addLayout(self.grid_amounts, 0)
|
|
panel_right.addLayout(self.buttonpad.stacked, 0)
|
|
panel_right.addLayout(self.grid_other_fields, 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.addLayout(self.grid_other_fields, 1)
|
|
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 == 'disabled':
|
|
self.message_bar.set('not_sale')
|
|
elif 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:
|
|
if 1:
|
|
quantity = Decimal(eval_value)
|
|
# _product = self._sale_line['product']
|
|
product_id = self.product_id
|
|
if product_id:
|
|
_product, = self.Product.find([('id', '=', product_id)], ctx=self.stock_context)
|
|
if _product and _product.get('quantity'):
|
|
if not self._check_stock_quantity(_product, quantity):
|
|
return False
|
|
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
|
|
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['product']['sale_price_w_tax']
|
|
if price_w_tax <= Decimal(value):
|
|
# Change unit price
|
|
discount_valid = self.set_unit_price(value)
|
|
self.set_discount(0)
|
|
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']
|
|
res = self.add_payment(amount_to_add, all_money, change)
|
|
if res.get('msg') == 'statement_closed':
|
|
self.dialog('statement_closed')
|
|
return False
|
|
if change < ZERO:
|
|
self.field_pending.setText(abs(change))
|
|
else:
|
|
self.field_pending.setText(str(ZERO))
|
|
self.set_amount_received(all_money)
|
|
|
|
if res['residual_amount'] < 0:
|
|
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._sale.update({
|
|
'residual_amount': res['residual_amount']
|
|
})
|
|
residual_amount = self._sale['residual_amount']
|
|
|
|
if self._sale['residual_amount'] <= 0:
|
|
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']
|
|
})
|
|
# self.set_state('finished')
|
|
|
|
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()
|
|
self.message_bar.set('system_ready')
|
|
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_duplicate_sale(self):
|
|
if self.sale_customer_selected:
|
|
res = self.ModSale.duplicate_sale({
|
|
'sale_id': self.sale_customer_selected,
|
|
})
|
|
self.load_sale(res['sale_id'])
|
|
self.dialog_search_consumer.close()
|
|
self.dialog_table_sale_consumer.close()
|
|
self.dialog_sale_consumer_selected.close()
|
|
|
|
def button_create_delivery_party(self):
|
|
self.clear_delivery_party()
|
|
self.dialog_delivery_party_selected.show()
|
|
|
|
def button_sale_consumer_history(self):
|
|
if self._consumer:
|
|
delta = str(datetime.now() - timedelta(180))
|
|
dom = [
|
|
('create_date', '>=', delta),
|
|
('state', 'in', ['processing', 'done']),
|
|
('consumer', '=', self._consumer['id']),
|
|
]
|
|
self.model_sale_consumer.reset()
|
|
sales = self.ModSale.find(dom, limit=10)
|
|
for record in sales:
|
|
self.model_sale_consumer.add_record(record)
|
|
self.dialog_table_sale_consumer.exec_()
|
|
self.dialog_search_consumer.close()
|
|
else:
|
|
pass
|
|
|
|
def button_accept_pressed(self):
|
|
if not self._check_quantity():
|
|
return
|
|
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})
|
|
if msg:
|
|
self.message_bar.set(msg)
|
|
return
|
|
|
|
self.set_amounts(res)
|
|
|
|
self.field_invoice.setText(res['invoice_number'])
|
|
self.field_amount.setText('')
|
|
if self.type_pos_user == 'salesman':
|
|
self.print_invoice(sale_id)
|
|
res = self._print_order(sale_id, 'delivery')
|
|
self.createNewSale()
|
|
return
|
|
if self.type_pos_user != 'cashier':
|
|
self.field_invoice_type.set_enabled(False)
|
|
|
|
self.message_bar.set('enter_payment', self.default_journal['name'])
|
|
self.set_state('cash')
|
|
self.buttonpad.setFocus()
|
|
self.label_input.setFocus()
|
|
|
|
def action_reservations(self):
|
|
logging.info('Buscando reservas.....')
|
|
|
|
def action_tables(self):
|
|
self.dialog_manage_tables.exec_()
|
|
|
|
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):
|
|
if self._state in ['cash']:
|
|
return
|
|
self.dialog_salesman.exec_()
|
|
|
|
def action_delivery_men(self):
|
|
self.dialog_delivery_men.exec_()
|
|
|
|
def action_delivery_men_panel(self):
|
|
self.dialog_control_panel.close()
|
|
self.dialog_table_delivery_party.exec_()
|
|
|
|
def action_control_panel(self):
|
|
self.dialog_control_panel.exec_()
|
|
|
|
def action_help(self):
|
|
Help(self).show()
|
|
|
|
def dialog_money_count_accepted(self):
|
|
new_data = [[d[0], 0, 0] for d in self.model_money_count._data]
|
|
self.model_money_count._data = new_data
|
|
return True
|
|
|
|
def action_open_statement(self):
|
|
if not self.dialog_money_count.exec_():
|
|
return
|
|
res = self.ModSale.faster_open_statement({
|
|
'device': self.device['id'],
|
|
'total_money': Decimal(
|
|
self.row_field_total_money.text().replace(',', '')
|
|
),
|
|
})
|
|
if res['result']:
|
|
self.dialog('statement_created')
|
|
self.row_field_total_money.setText('0')
|
|
|
|
def action_closed_statement(self):
|
|
if not self.dialog_money_count.exec_():
|
|
return
|
|
res = self.ModSale.faster_close_statement({
|
|
'device': self.device['id'],
|
|
'data': self.model_money_count._data,
|
|
})
|
|
if res['result']:
|
|
self.dialog('statement_finish')
|
|
self.row_field_total_money.setText('0')
|
|
|
|
def action_table_discount(self):
|
|
self.dialog_global_discount_table.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})
|
|
self.field_payment_term.setText(self._payment_terms[str(self.field_payment_term_id)]['name'])
|
|
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_delivery_way_selection_changed(self, index):
|
|
val = self.field_delivery_way.get_id()
|
|
if val:
|
|
self._PosSale.write([self._sale['id']], {'delivery_way': 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_consumer(self):
|
|
self.clear_dialog_consumer()
|
|
self.dialog_search_consumer.show()
|
|
self.row_field_phone.setFocus()
|
|
|
|
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 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):
|
|
if self._state in ['cash']:
|
|
return
|
|
self.dialog_search_parties.clear_rows()
|
|
self.dialog_search_parties.execute()
|
|
|
|
def action_global_discount(self, sale_id=None):
|
|
if self._state in ['accept', 'cash']:
|
|
return
|
|
self.dialog_global_discount.exec_()
|
|
discount = self.field_global_discount_ask.text()
|
|
if discount and discount.isdigit():
|
|
self.validate_discount(discount)
|
|
|
|
def validate_discount(self, discount):
|
|
if self.model_sale_lines.rowCount() > 0:
|
|
lines = [line['id'] for line in self.model_sale_lines._data]
|
|
res = self.set_discount(int(discount), lines)
|
|
if not res:
|
|
self.message_bar.set('discount_not_valid')
|
|
return False
|
|
|
|
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_channel(self):
|
|
if self._state != 'cash':
|
|
self.dialog_channel.exec_()
|
|
|
|
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 self._state in ['cash']:
|
|
return
|
|
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._state == 'disabled':
|
|
return
|
|
if self.type_pos_user == 'cashier':
|
|
self.dialog_cancel_invoice.exec_()
|
|
password = self.field_password_for_cancel_ask.text()
|
|
if password != self._password_admin:
|
|
return self.dialog('not_permission_for_cancel')
|
|
|
|
if not self._sale['id']:
|
|
return
|
|
if self._state == '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):
|
|
if self._state in ['cash']:
|
|
return
|
|
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()
|
|
self.field_invoice_type.set_enabled(True)
|
|
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=None):
|
|
if not party_id:
|
|
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.field_state.setText(sale['state'] 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 sale.get('delivery_charge'):
|
|
self.field_delivery_charge.set_from_id(sale['delivery_charge'])
|
|
if sale.get('table_assigned'):
|
|
self.field_table_assigned.setText(sale['table_assigned.name'] or '')
|
|
|
|
if sale.get('channel'):
|
|
self.field_channel.setText(sale['channel']['rec_name'].upper())
|
|
|
|
if sale.get('delivery_way'):
|
|
self.field_delivery_way.set_from_id(sale['delivery_way'])
|
|
|
|
if sale.get('delivery_party'):
|
|
self.field_delivery_men.setText(sale['delivery_party']['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.setText('')
|
|
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)
|
|
residual_amount = 0
|
|
if res.get('residual_amount'):
|
|
residual_amount = res['residual_amount']
|
|
self.field_pending.setText(residual_amount)
|
|
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_menu_control_panel(self):
|
|
menu_dash = [
|
|
['button_open', self.tr('OPEN STATEMENTS'), 'action_open_statement'],
|
|
['button_closed', self.tr('CLOSED STATEMENTS'), 'action_closed_statement'],
|
|
# ['button_money', self.tr('COUNT MONEY'), 'action_count_money'],
|
|
['button_discount', self.tr('GLOBAL DISCOUNT'), 'action_table_discount'],
|
|
['button_delivery_men', self.tr('DELIVERY MEN'), 'action_delivery_men_panel'],
|
|
['button_help', self.tr('HELP'), 'action_help'],
|
|
]
|
|
return menu_dash
|
|
|
|
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 on_selected_salesman(self, salesman_id):
|
|
if salesman_id:
|
|
self._PosSale.write([self._sale['id']], {'salesman': salesman_id})
|
|
sale, = self.ModSale.find([('id', '=', self._sale['id'])])
|
|
self.field_salesman.setText(sale['salesman']['name'] or '')
|
|
self.field_salesman_id = salesman_id
|
|
self.dialog_salesman.close()
|
|
|
|
def on_selected_discount(self, discount):
|
|
if discount:
|
|
self.validate_discount(discount)
|
|
self.dialog_global_discount_table.close()
|
|
self.dialog_control_panel.close()
|
|
|
|
def on_selected_delivery_men(self, delivery_men_id):
|
|
if delivery_men_id:
|
|
self._PosSale.write([self._sale['id']], {'delivery_party': delivery_men_id})
|
|
sale, = self.ModSale.find([('id', '=', self._sale['id'])])
|
|
self.field_delivery_men.setText(sale['delivery_party']['name'] or '')
|
|
self.field_delivery_men_id = delivery_men_id
|
|
self.dialog_delivery_men.close()
|
|
|
|
def on_selected_payment_term(self, payterm_id):
|
|
if payterm_id:
|
|
self.field_payment_term_id = payterm_id
|
|
self.action_payment_term_selection_changed()
|
|
self.dialog_payment_term.close()
|
|
|
|
def on_selected_channel(self, channel_id):
|
|
if channel_id:
|
|
channel, = self.channels = self.Channel.find([
|
|
('id', '=', int(channel_id))
|
|
])
|
|
self.field_channel.setText(channel['rec_name'].upper())
|
|
self._PosSale.write([self._sale['id']], {'channel': channel_id})
|
|
self.dialog_channel.close()
|
|
|
|
def on_selected_payment(self, payment_id):
|
|
if payment_id:
|
|
self.field_journal_id = payment_id
|
|
self.dialog_payment.close()
|
|
name_ = self._journals[payment_id]['name']
|
|
self.message_bar.set('enter_payment', name_)
|
|
|
|
def on_search_product(self):
|
|
target = self.dialog_search_products.filter_field.text()
|
|
if not target:
|
|
return
|
|
target_words = target.split(' ')
|
|
domain = [
|
|
('template.active', '=', True),
|
|
('active', '=', True),
|
|
('template.salable', '=', True),
|
|
]
|
|
for tw in target_words:
|
|
if len(tw) <= 1:
|
|
continue
|
|
clause = ['OR',
|
|
('template.name', 'ilike', '%{:}%'.format(tw)),
|
|
('description', 'ilike', '%{:}%'.format(tw))]
|
|
domain.append(clause)
|
|
if not self._sale_pos_restaurant and self.shop.get('product_categories'):
|
|
categories_id = [ct['id'] for ct in self.shop['product_categories']]
|
|
domain.append(('template.account_category', 'in', categories_id))
|
|
|
|
if not domain:
|
|
return
|
|
if self.cache_local:
|
|
domain = [clause]
|
|
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 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.setText('')
|
|
self.field_order_number.setText('')
|
|
self.field_state.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.field_pending.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):
|
|
if self.enviroment == 'restaurant' and self._sale_pos_restaurant:
|
|
self.clear_dialog_consumer()
|
|
self.check_empty_sale()
|
|
if self.type_pos_user == 'cashier':
|
|
self.message_bar.set('not_sale')
|
|
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.consumer_id = None
|
|
self.payment_ctx = {}
|
|
self.is_clear_right_panel = False
|
|
self.table_sale_lines.setEnabled(True)
|
|
self.field_invoice_type.set_enabled(True)
|
|
self.clear_data()
|
|
self.clear_left_panel()
|
|
self.message_bar.set('system_ready')
|
|
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_party.setText(self._sale['party_name'])
|
|
self.field_invoice_type.set_from_id(self._sale['invoice_type'])
|
|
self.field_state.setText(self._sale['state'])
|
|
# FIXME ADD MORE
|
|
self._sale.update({
|
|
'total_amount': 0
|
|
})
|
|
if hasattr(self, 'field_channel'):
|
|
self.field_channel.setText('')
|
|
if hasattr(self, 'field_delivery_way'):
|
|
self.field_delivery_way.set_from_id('')
|
|
if hasattr(self, 'field_delivery_men'):
|
|
self.field_delivery_men.setText('')
|
|
|
|
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)])
|
|
if self.cache_local:
|
|
clause = ['OR',
|
|
('barcode', '=', '%{:}%'.format(code)),
|
|
('code', '=', '%{:}%'.format(code))]
|
|
domain = [clause]
|
|
products = self.store.find_product_elastic(domain, limit=100)
|
|
else:
|
|
products = self.Product.find(domain, ctx=self.stock_context)
|
|
# 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_quantity(self):
|
|
for line in self.model_sale_lines._data:
|
|
product_id = None
|
|
if isinstance(line['product'], int):
|
|
product_id = line['product']
|
|
elif isinstance(line['product'], dict):
|
|
product_id = line['product']['id']
|
|
if product_id:
|
|
product, = self.Product.find([('id', '=', product_id)], ctx=self.stock_context)
|
|
if product.get('quantity') and not self._check_stock_quantity(product):
|
|
return False
|
|
return True
|
|
|
|
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.product_id = product_id
|
|
|
|
self.update_total_amount()
|
|
self.set_state('add')
|
|
|
|
def _check_stock_quantity(self, product, request_qty=None):
|
|
if not isinstance(product['quantity'], (str, int, float, Decimal)):
|
|
return True
|
|
qty_ = Decimal(product['quantity'])
|
|
if self._password_admin:
|
|
if (request_qty and qty_ < request_qty) or (qty_ <= 0):
|
|
self.message_bar.set('without_stock_quantity', product['name'])
|
|
self.dialog_force_assign.exec_()
|
|
password = self.field_password_force_assign_ask.text()
|
|
self.field_password_force_assign_ask.setText('')
|
|
if password != self._password_admin:
|
|
self.message_bar.set('not_can_force_assign')
|
|
return False
|
|
else:
|
|
self.message_bar.set('system_ready')
|
|
return True
|
|
|
|
def add_sale_line(self, record):
|
|
if not record.get('unit.symbol'):
|
|
record['unit.symbol'] = record['unit']['symbol']
|
|
if isinstance(record['product'], dict):
|
|
if not record.get('product.template.name'):
|
|
record['product.template.name'] = record['product']['template']['name']
|
|
if not record.get('product.code'):
|
|
record['product.code'] = record['product']['code']
|
|
rec = self.model_sale_lines.add_record(record)
|
|
self.field_amount.setText(rec['amount_w_tax'])
|
|
|
|
def sale_line_selected(self, line):
|
|
if self._state == 'cash':
|
|
return
|
|
self._current_line_id = line['id']
|
|
product_id = None
|
|
if isinstance(line['product'], int):
|
|
product_id = line['product']
|
|
elif isinstance(line['product'], dict):
|
|
product_id = line['product']['id']
|
|
self.product_id = product_id
|
|
self.label_product.setText(line['product.template.name'])
|
|
self.row_field_description.setText(line['description'])
|
|
self.row_field_qty.setValue(float(line['quantity']))
|
|
self.row_field_price.setText(str(line['unit_price_w_tax']))
|
|
# self.row_field_note.setText(str(line['note']))
|
|
self.dialog_product_edit.show()
|
|
self.row_field_note.setFocus()
|
|
|
|
def sale_consumer_selected(self, data):
|
|
self.sale_customer_selected = data['id']
|
|
self.row_sale_party.setText(data['party']['name'])
|
|
self.row_sale_number.setText(data['number'])
|
|
self.row_sale_date.setText(data['sale_date'])
|
|
self.row_sale_tax_amount.setText(str(data['tax_amount']))
|
|
self.row_sale_untaxed.setText(str(data['untaxed_amount']))
|
|
self.row_sale_total.setText(str(data['total_amount_cache']))
|
|
self.model_sale_customer_lines.reset()
|
|
for line in data['lines']:
|
|
self.model_sale_customer_lines.add_record(line)
|
|
self.dialog_sale_consumer_selected.exec_()
|
|
|
|
def clear_delivery_party(self):
|
|
self.row_delivery_men.setText('')
|
|
self.row_id_number.setText('')
|
|
self.row_number_plate.setText('')
|
|
self.row_type_vehicle.set_from_id('')
|
|
self.row_delivery_men_active.setChecked(True)
|
|
|
|
def delivery_party_selected(self, data):
|
|
return
|
|
self._delivery_party_selected = data['id']
|
|
self.row_delivery_men.setText(data['rec_name'])
|
|
# self.row_id_number.setText(data['party']['id_number'])
|
|
self.row_number_plate.setText(data['number_plate'])
|
|
self.row_type_vehicle.setText(data['type_vehicle'])
|
|
self.row_delivery_men_active.setChecked(data['active'])
|
|
self.dialog_delivery_party_selected.show()
|
|
self.row_delivery_men_active.setFocus()
|
|
|
|
def setup_money_count(self):
|
|
money = [[100000, 0, 0],
|
|
[50000, 0, 0],
|
|
[20000, 0, 0],
|
|
[10000, 0, 0],
|
|
[5000, 0, 0],
|
|
[2000, 0, 0],
|
|
[1000, 0, 0],
|
|
[500, 0, 0],
|
|
[200, 0, 0],
|
|
[100, 0, 0],
|
|
[50, 0, 0],
|
|
]
|
|
|
|
self.model_money_count = TableModelEdit(self, money)
|
|
|
|
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)
|
|
self.model_sale_customer_lines = TableModel('sale.line', self.fields_sale_line)
|
|
|
|
def setup_sale_consumer(self):
|
|
number = {
|
|
'name': 'number',
|
|
'align': alignCenter,
|
|
'description': self.tr('NUMBER'),
|
|
'width': 200
|
|
}
|
|
invoice_number = {
|
|
'name': 'invoice_number',
|
|
'align': alignCenter,
|
|
'description': self.tr('INVOICE NUMBER'),
|
|
'width': 200
|
|
}
|
|
consumer = {
|
|
'name': 'consumer.name',
|
|
'align': alignCenter,
|
|
'description': self.tr('CONSUMER'),
|
|
'width': 300
|
|
}
|
|
sale_date = {
|
|
'name': 'sale_date',
|
|
'align': alignCenter,
|
|
'description': self.tr('DATE'),
|
|
'width': 300
|
|
}
|
|
total_amount_cache = {
|
|
'name': 'total_amount_cache',
|
|
'align': alignCenter,
|
|
'description': self.tr('TOTAL AMOUNT'),
|
|
'width': 300
|
|
}
|
|
|
|
self.fields_sale_consumer = [number, invoice_number, consumer, sale_date,
|
|
total_amount_cache]
|
|
self.model_sale_consumer = TableModel('sale.sale', self.fields_sale_consumer)
|
|
|
|
def setup_delivery_party(self):
|
|
party = {
|
|
'name': 'rec_name',
|
|
'align': alignLeft,
|
|
'description': self.tr('PARTY'),
|
|
'width': 380
|
|
}
|
|
number_plate = {
|
|
'name': 'number_plate',
|
|
'align': alignCenter,
|
|
'description': self.tr('NUMBER PLATE'),
|
|
'width': 200
|
|
}
|
|
type_vehicle = {
|
|
'name': 'type_vehicle',
|
|
'align': alignCenter,
|
|
'description': self.tr('TYPE VEHICLE'),
|
|
'width': 300
|
|
}
|
|
|
|
self.fields_delivery_party = [party, number_plate, type_vehicle]
|
|
self.model_delivery_party = TableModel('sale.delivery_party', self.fields_delivery_party)
|
|
for record in self.delivery_man:
|
|
self.model_delivery_party.add_record(record)
|
|
|
|
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()
|
|
|
|
"""Delete product """
|
|
|
|
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 False
|
|
|
|
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 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:
|
|
if self._state not in ['cash']:
|
|
self._clear_context()
|
|
# self.close()
|
|
elif key == Qt.Key_F1:
|
|
# help = Help(self).show()
|
|
self.action_control_panel()
|
|
elif key == Qt.Key_F9:
|
|
self.action_search_sale()
|
|
elif key == Qt.Key_F11:
|
|
self.action_new_sale()
|
|
elif key == Qt.Key_F7:
|
|
self.action_print_sale()
|
|
elif self._state == 'disabled':
|
|
self.message_bar.set('must_load_or_create_sale')
|
|
return
|
|
elif key in (Qt.Key_Enter, Qt.Key_End):
|
|
if self.type_pos_user in ['order', 'salesman']:
|
|
return
|
|
if self._state == 'add':
|
|
self.button_accept_pressed()
|
|
elif self._state in ['cash', 'accept']:
|
|
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 ['cash']:
|
|
self.action_agent()
|
|
elif key == Qt.Key_QuoteDbl:
|
|
self.action_comment()
|
|
elif key == Qt.Key_Question:
|
|
self.action_tax()
|
|
|
|
@property
|
|
def state(self):
|
|
return self._state
|
|
|
|
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('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('quantity'):
|
|
quantity = self.state_line.pop('quantity')
|
|
if self._process_quantity(str(quantity)):
|
|
_record = self.ModSaleLine.faster_set_quantity({
|
|
'id': self._current_line_id,
|
|
'quantity': to_float(quantity, 2)
|
|
})
|
|
|
|
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 = {}
|
|
|
|
def dialog_search_consumer_accepted(self):
|
|
if not self.state_consumer:
|
|
return
|
|
if self._consumer:
|
|
res = self.ModSale.update_consumer({
|
|
'id': self._consumer['id'],
|
|
'fields': self.state_consumer,
|
|
})
|
|
else:
|
|
res = self.ModSale.create_consumer({
|
|
'fields': self.state_consumer,
|
|
})
|
|
if res['msg'] == 'ok':
|
|
self.consumer_id = res['consumer']
|
|
self._PosSale.write([self._sale['id']], {'consumer': self.consumer_id})
|
|
if res.get('party'):
|
|
self.on_selected_party(res['party'])
|
|
|
|
def dialog_delivery_men_accepted(self):
|
|
if not self.state_delivery_party:
|
|
return
|
|
# if hasattr(self, '_delivery_party_selected'):
|
|
# res = self.ModSale.update_delivery_party({
|
|
# 'id': self._delivery_party_selected,
|
|
# 'data': self.state_delivery_party
|
|
# })
|
|
# print(res)
|
|
# else:
|
|
res = self.ModSale.create_delivery_party({
|
|
'data': self.state_delivery_party,
|
|
'shop_id': self.ctx['shop']
|
|
})
|
|
self.model_delivery_party.add_record(res)
|
|
self.delivery_man.append(res)
|
|
self.dialog_delivery_men = DialogDeliveryMen(self).get()
|
|
|
|
def update_delivery_party(self, field=''):
|
|
if field == 'delivery_men':
|
|
self.state_delivery_party['party'] = self.row_delivery_men.text()
|
|
if field == 'id_number':
|
|
self.state_delivery_party['id_number'] = self.row_id_number.text()
|
|
if field == 'number_plate':
|
|
self.state_delivery_party['number_plate'] = self.row_number_plate.text()
|
|
if field == 'type_vehicle':
|
|
self.state_delivery_party['type_vehicle'] = self.row_type_vehicle.get_id()
|
|
if field == 'delivery_men_active':
|
|
self.state_delivery_party['active'] = self.row_delivery_men_active.isChecked()
|
|
|
|
def update_consumer_data(self, field=''):
|
|
if field == 'preferences':
|
|
self.state_consumer['preferences'] = self.row_field_consumer_preference.toPlainText()
|
|
if field == 'address':
|
|
self.state_consumer['address'] = self.row_field_address.text()
|
|
elif field == 'id_number':
|
|
self.state_consumer['id_number'] = self.row_field_id_number.text()
|
|
elif field == 'name':
|
|
self.state_consumer['name'] = self.row_field_consumer.text()
|
|
elif field == 'birthday':
|
|
self.state_consumer['birthday'] = self.row_field_birthday.date().toPyDate()
|
|
elif field == 'phone':
|
|
self._text_field_phone = self.row_field_phone.text()
|
|
if self._text_field_phone:
|
|
self.state_consumer['phone'] = self._text_field_phone
|
|
consumers = self.PartyConsumer.find([
|
|
('phone', '=', self._text_field_phone),
|
|
])
|
|
if not consumers:
|
|
self._consumer = {}
|
|
self.row_field_address.setText('')
|
|
self.row_field_consumer.setText('')
|
|
self.row_field_id_number.setText('')
|
|
self.row_field_birthday.setDate(MINIMUM_DATE)
|
|
self.row_field_consumer_preference.clear()
|
|
else:
|
|
self.button_history_customer.setVisible(True)
|
|
self._consumer = consumers[0]
|
|
self.row_field_address.setText(self._consumer['address'])
|
|
self.row_field_consumer.setText(self._consumer['name'])
|
|
self.row_field_id_number.setText(self._consumer['id_number'])
|
|
self.row_field_consumer_preference.insertPlainText(
|
|
self._consumer['preferences'])
|
|
if self._consumer.get('birthday'):
|
|
y, m, d = self._consumer['birthday'].split('-')
|
|
self.row_field_birthday.setDate(QDate(int(y), int(m), int(d)))
|
|
|
|
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 clear_dialog_consumer(self):
|
|
self._consumer = {}
|
|
self.row_field_phone.setText('')
|
|
self.row_field_address.setText('')
|
|
self.row_field_consumer.setText('')
|
|
self.row_field_id_number.setText('')
|
|
self.row_field_birthday.setDate(QDate(2000, 1, 1))
|
|
self.row_field_consumer_preference.clear()
|
|
self.button_history_customer.setVisible(False)
|
|
|
|
# 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)
|
|
|
|
|
|
class DoInvoice(QThread):
|
|
"""
|
|
Process invoices using a thread
|
|
"""
|
|
sigDoInvoice = pyqtSignal()
|
|
|
|
def __init__(self, main, context):
|
|
QThread.__init__(self)
|
|
|
|
def run(self):
|
|
self.sigDoInvoice.emit()
|