From 80e89c7a11d16fad6248e7b1cc4bebeea9e71545 Mon Sep 17 00:00:00 2001 From: Mansi Gandhi Date: Wed, 11 Jan 2017 18:48:19 +0530 Subject: [PATCH] [IMP] point_of_sale: Add new settings form per POS Purpose: Replace POS APP settings with a clean POS Form settings, one per POS - add state button for (active/inactive) archive/unarchive and remove boolean field. - remove 'Reprint Receipt' field from pos_reprint module and related code. - remove 'Multi-currencies' field and related code. - put 'Company' field under the 'Taxes' block and 'Sales Channel' under the 'Pricing' block. - add 'save' buttons to install needed modules manually. - removed constrains and set default fiscal value on fiscal position ids even if that not selected. - change the skip Receipt Screen label to a more appropriate one. - Tax help only appears when Tax-included is selected. * [IMP] point_of_sale: Removing multiple prices prod Purpose: The choice of the method for "Multiple price per product" was not appropriate for the Point Of Sale. Only the choice of the Pricelist make sense. So, only the Pricelist can be choosen in the POS config. * [IMP] point_of_sale, pos_discount, pos_restaurant: Generic module install Purpose: Before this improvement, some fields were used in pos.config to automatically install some modules. This fix tries to mimic the ResConfigSettings transient so that when a field starts with 'module_', it's automaticaly installed. --- addons/point_of_sale/__manifest__.py | 1 - .../point_of_sale/data/point_of_sale_data.xml | 5 + addons/point_of_sale/models/__init__.py | 1 - addons/point_of_sale/models/pos_config.py | 197 ++++-- .../models/pos_config_settings.py | 72 --- addons/point_of_sale/static/src/js/models.js | 4 +- .../views/account_journal_view.xml | 28 +- .../views/point_of_sale_view.xml | 5 - .../views/pos_config_settings_views.xml | 170 ------ .../point_of_sale/views/pos_config_view.xml | 559 +++++++++++++++--- addons/pos_cache/views/pos_cache_views.xml | 6 +- addons/pos_discount/models/pos_config.py | 16 +- addons/pos_discount/static/src/js/discount.js | 2 +- .../pos_discount/views/pos_discount_views.xml | 24 +- .../views/pos_config_setting_views.xml | 17 +- .../pos_mercury/views/pos_mercury_views.xml | 2 +- addons/pos_reprint/__init__.py | 1 - addons/pos_reprint/__manifest__.py | 2 +- addons/pos_reprint/models/__init__.py | 4 - addons/pos_reprint/models/pos_config.py | 10 - addons/pos_reprint/static/src/js/reprint.js | 4 +- addons/pos_reprint/views/pos_config_views.xml | 18 +- addons/pos_restaurant/__manifest__.py | 2 +- addons/pos_restaurant/models/pos_config.py | 40 +- .../pos_restaurant/views/pos_config_views.xml | 118 +++- .../views/pos_restaurant_views.xml | 16 +- addons/pos_sale/views/pos_config_views.xml | 21 +- addons/web/static/src/less/form_view.less | 3 + 28 files changed, 877 insertions(+), 471 deletions(-) delete mode 100644 addons/point_of_sale/models/pos_config_settings.py delete mode 100644 addons/point_of_sale/views/pos_config_settings_views.xml delete mode 100644 addons/pos_reprint/models/__init__.py delete mode 100644 addons/pos_reprint/models/pos_config.py diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index 713c990330e..7689d082fc7 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -36,7 +36,6 @@ 'views/account_statement_report.xml', 'views/res_users_view.xml', 'views/res_partner_view.xml', - 'views/pos_config_settings_views.xml', 'views/report_statement.xml', 'views/report_userlabel.xml', 'views/report_saledetails.xml', diff --git a/addons/point_of_sale/data/point_of_sale_data.xml b/addons/point_of_sale/data/point_of_sale_data.xml index 0c97ed89258..13059b39a0c 100644 --- a/addons/point_of_sale/data/point_of_sale_data.xml +++ b/addons/point_of_sale/data/point_of_sale_data.xml @@ -24,6 +24,11 @@ reload + + + Tips + + Main diff --git a/addons/point_of_sale/models/__init__.py b/addons/point_of_sale/models/__init__.py index 9df58f2c5fe..c65760ad7fc 100644 --- a/addons/point_of_sale/models/__init__.py +++ b/addons/point_of_sale/models/__init__.py @@ -6,7 +6,6 @@ from . import account_journal from . import barcode_rule from . import pos_category from . import pos_config -from . import pos_config_settings from . import pos_order from . import pos_session from . import product diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 05259bd89a3..65a082d9f65 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -3,7 +3,7 @@ import uuid from odoo import api, fields, models, _ -from odoo.exceptions import UserError +from odoo.exceptions import ValidationError class AccountCashboxLine(models.Model): @@ -11,9 +11,16 @@ class AccountCashboxLine(models.Model): default_pos_id = fields.Many2one('pos.config', string='This cashbox line is used by default when opening or closing a balance for this point of sale') + @api.multi + def name_get(self): + result = [] + for cashbox_line in self: + result.append((cashbox_line.id, "%s * %s"%(cashbox_line.coin_value, cashbox_line.number))) + return result + class AccountBankStmtCashWizard(models.Model): _inherit = 'account.bank.statement.cashbox' - + @api.model def default_get(self, fields): vals = super(AccountBankStmtCashWizard, self).default_get(fields) @@ -44,9 +51,6 @@ class PosConfig(models.Model): def _get_default_location(self): return self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id - def _get_default_nomenclature(self): - return self.env['barcode.nomenclature'].search([], limit=1) - def _get_group_pos_manager(self): return self.env.ref('point_of_sale.group_pos_manager') @@ -56,7 +60,8 @@ class PosConfig(models.Model): def _compute_default_customer_html(self): return self.env['ir.qweb'].render('point_of_sale.customer_facing_display_html') - name = fields.Char(string='Point of Sale Name', index=True, required=True, help="An internal identification of the point of sale") + name = fields.Char(string='Point of Sale Name', index=True, required=True, help="An internal identification of the point of sale.") + is_installed_account_accountant = fields.Boolean(compute="_compute_is_installed_account_accountant") journal_ids = fields.Many2many( 'account.journal', 'pos_config_journal_rel', 'pos_config_id', 'journal_id', string='Available Payment Methods', @@ -77,37 +82,36 @@ class PosConfig(models.Model): help="Accounting journal used to create invoices.", default=_default_invoice_journal) currency_id = fields.Many2one('res.currency', compute='_compute_currency', string="Currency") - iface_cashdrawer = fields.Boolean(string='Cashdrawer', help="Automatically open the cashdrawer") - iface_payment_terminal = fields.Boolean(string='Payment Terminal', help="Enables Payment Terminal integration") - iface_electronic_scale = fields.Boolean(string='Electronic Scale', help="Enables Electronic Scale integration") - iface_vkeyboard = fields.Boolean(string='Virtual KeyBoard', help="Enables an integrated Virtual Keyboard") - iface_customer_facing_display = fields.Boolean(string='Customer Facing Display', help="Enables a remotely connected customer facing display") - iface_print_via_proxy = fields.Boolean(string='Print via Proxy', help="Bypass browser printing and prints via the hardware proxy") - iface_scan_via_proxy = fields.Boolean(string='Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner") - iface_invoicing = fields.Boolean(string='Invoicing', help='Enables invoice generation from the Point of Sale', default=True) - iface_big_scrollbars = fields.Boolean('Large Scrollbars', help='For imprecise industrial touchscreens') + iface_cashdrawer = fields.Boolean(string='Cashdrawer', help="Automatically open the cashdrawer.") + iface_payment_terminal = fields.Boolean(string='Payment Terminal', help="Enables Payment Terminal integration.") + iface_electronic_scale = fields.Boolean(string='Electronic Scale', help="Enables Electronic Scale integration.") + iface_vkeyboard = fields.Boolean(string='Virtual KeyBoard', help=u"Don’t turn this option on if you take orders on smartphones or tablets. \n Such devices already benefit from a native keyboard.") + iface_customer_facing_display = fields.Boolean(string='Customer Facing Display', help="Show checkout to customers with a remotely-connected screen.") + iface_print_via_proxy = fields.Boolean(string='Print via Proxy', help="Bypass browser printing and prints via the hardware proxy.") + iface_scan_via_proxy = fields.Boolean(string='Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner.") + iface_invoicing = fields.Boolean(string='Invoicing', help='Enables invoice generation from the Point of Sale.') + iface_big_scrollbars = fields.Boolean('Large Scrollbars', help='For imprecise industrial touchscreens.') iface_print_auto = fields.Boolean(string='Automatic Receipt Printing', default=False, - help='The receipt will automatically be printed at the end of each order') - iface_print_skip_screen = fields.Boolean(string='Skip Receipt Screen', default=True, + help='The receipt will automatically be printed at the end of each order.') + iface_print_skip_screen = fields.Boolean(string='Skip Preview Screen', default=True, help='The receipt screen will be skipped if the receipt can be printed automatically.') iface_precompute_cash = fields.Boolean(string='Prefill Cash Payment', - help='The payment input will behave similarily to bank payment input, and will be prefilled with the exact due amount') - iface_tax_included = fields.Boolean(string='Include Taxes in Prices', - help='The displayed prices will always include all taxes, even if the taxes have been setup differently') - iface_start_categ_id = fields.Many2one('pos.category', string='Start Category', - help='The point of sale will display this product category by default. If no category is specified, all available products will be shown') + help='The payment input will behave similarily to bank payment input, and will be prefilled with the exact due amount.') + iface_tax_included = fields.Selection([('subtotal', 'Tax-Excluded Prices'), ('total', 'Tax-Included Prices')], "Tax Display", default='subtotal', required=True) + iface_start_categ_id = fields.Many2one('pos.category', string='Initial Category', + help='The point of sale will display this product category by default. If no category is specified, all available products will be shown.') iface_display_categ_images = fields.Boolean(string='Display Category Pictures', help="The product categories will be displayed with pictures.") restrict_price_control = fields.Boolean(string='Restrict Price Modifications to Managers', - help="Check to box to restrict the price control to managers only on point of sale orders.") + help="Only users with Manager access rights for PoS app can modify the product prices on orders.") cash_control = fields.Boolean(string='Cash Control', help="Check the amount of the cashbox at opening and closing.") - receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt") - receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt") + receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt.") + receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt.") proxy_ip = fields.Char(string='IP Address', size=45, - help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty') + help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty.') active = fields.Boolean(default=True) uuid = fields.Char(readonly=True, default=lambda self: str(uuid.uuid4()), - help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data') + help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data.') sequence_id = fields.Many2one('ir.sequence', string='Order IDs Sequence', readonly=True, help="This sequence is automatically created by Odoo but you can change it " "to customize the reference numbers of your orders.", copy=False) @@ -121,21 +125,39 @@ class PosConfig(models.Model): last_session_closing_date = fields.Date(compute='_compute_last_session') pos_session_username = fields.Char(compute='_compute_current_session_user') group_by = fields.Boolean(string='Group Journal Items', default=True, - help="Check this if you want to group the Journal Items by Product while closing a Session") + help="Check this if you want to group the Journal Items by Product while closing a Session.") pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, default=_default_pricelist) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id) - barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', string='Barcodes', required=True, default=_get_default_nomenclature, - help='Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers') + barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', string='Barcode Nomenclature', + help='Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers.') group_pos_manager_id = fields.Many2one('res.groups', string='Point of Sale Manager Group', default=_get_group_pos_manager, - help='This field is there to pass the id of the pos manager group to the point of sale client') + help='This field is there to pass the id of the pos manager group to the point of sale client.') group_pos_user_id = fields.Many2one('res.groups', string='Point of Sale User Group', default=_get_group_pos_user, - help='This field is there to pass the id of the pos user group to the point of sale client') + help='This field is there to pass the id of the pos user group to the point of sale client.') + iface_tipproduct = fields.Boolean(string="Product tips") tip_product_id = fields.Many2one('product.product', string='Tip Product', - help="The product used to encode the customer tip. Leave empty if you do not accept tips.") - fiscal_position_ids = fields.Many2many('account.fiscal.position', string='Fiscal Positions') + help="This product is used as reference on customer receipts.") + fiscal_position_ids = fields.Many2many('account.fiscal.position', string='Fiscal Positions', help='This is useful for restaurants with onsite and take-away services that imply specific tax rates.') default_fiscal_position_id = fields.Many2one('account.fiscal.position', string='Default Fiscal Position') default_cashbox_lines_ids = fields.One2many('account.cashbox.line', 'default_pos_id', string='Default Balance') customer_facing_display_html = fields.Html(string='Customer facing display content', translate=True, default=_compute_default_customer_html) + use_pricelist = fields.Boolean("Use a pricelist.") + tax_regime = fields.Boolean("Tax Regime") + tax_regime_selection = fields.Boolean("Tax Regime Selection value") + barcode_scanner = fields.Boolean("Barcode Scanner") + start_category = fields.Boolean("Set Start Category") + module_pos_restaurant = fields.Boolean("Is a Bar/Restaurant") + module_pos_discount = fields.Boolean("Global Discounts") + module_pos_loyalty = fields.Boolean("Loyalty Program") + module_pos_mercury = fields.Boolean(string="Integrated Card Payments") + module_pos_reprint = fields.Boolean(string="Reprint Receipt") + is_posbox = fields.Boolean("PosBox") + is_header_or_footer = fields.Boolean("Header & Footer") + + def _compute_is_installed_account_accountant(self): + account_accountant = self.env['ir.module.module'].sudo().search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]) + for pos_config in self: + pos_config.is_installed_account_accountant = account_accountant and account_accountant.id @api.depends('journal_id.currency_id', 'journal_id.company_id.currency_id') def _compute_currency(self): @@ -179,27 +201,22 @@ class PosConfig(models.Model): @api.constrains('company_id', 'stock_location_id') def _check_company_location(self): if self.stock_location_id.company_id and self.stock_location_id.company_id.id != self.company_id.id: - raise UserError(_("The company of the stock location is different than the one of point of sale")) + raise ValidationError(_("The company of the stock location is different than the one of point of sale")) @api.constrains('company_id', 'journal_id') def _check_company_journal(self): if self.journal_id and self.journal_id.company_id.id != self.company_id.id: - raise UserError(_("The company of the sales journal is different than the one of point of sale")) + raise ValidationError(_("The company of the sales journal is different than the one of point of sale")) @api.constrains('company_id', 'invoice_journal_id') - def _check_company_journal(self): + def _check_company_invoice_journal(self): if self.invoice_journal_id and self.invoice_journal_id.company_id.id != self.company_id.id: - raise UserError(_("The invoice journal and the point of sale must belong to the same company")) + raise ValidationError(_("The invoice journal and the point of sale must belong to the same company")) @api.constrains('company_id', 'journal_ids') def _check_company_payment(self): if self.env['account.journal'].search_count([('id', 'in', self.journal_ids.ids), ('company_id', '!=', self.company_id.id)]): - raise UserError(_("The company of a payment method is different than the one of point of sale")) - - @api.constrains('fiscal_position_ids', 'default_fiscal_position_id') - def _check_default_fiscal_position(self): - if self.default_fiscal_position_id and self.default_fiscal_position_id not in self.fiscal_position_ids: - raise UserError(_("The default fiscal position must be included in the available fiscal positions of the point of sale")) + raise ValidationError(_("The company of a payment method is different than the one of point of sale")) @api.onchange('iface_print_via_proxy') def _onchange_iface_print_via_proxy(self): @@ -210,6 +227,60 @@ class PosConfig(models.Model): if self.picking_type_id.default_location_src_id.usage == 'internal' and self.picking_type_id.default_location_dest_id.usage == 'customer': self.stock_location_id = self.picking_type_id.default_location_src_id.id + @api.onchange('use_pricelist') + def _onchange_use_pricelist(self): + """ + If the 'pricelist' box is unchecked, we reset the pricelist_id to stop + using a pricelist for this posbox. + """ + if not self.use_pricelist: + self.pricelist_id = self._default_pricelist() + + @api.onchange('iface_scan_via_proxy') + def _onchange_iface_scan_via_proxy(self): + if self.iface_scan_via_proxy: + self.barcode_scanner = True + else: + self.barcode_scanner = False + + @api.onchange('barcode_scanner') + def _onchange_barcode_scanner(self): + if self.barcode_scanner: + self.barcode_nomenclature_id = self.env['barcode.nomenclature'].search([], limit=1) + else: + self.barcode_nomenclature_id = False + + @api.onchange('is_posbox') + def _onchange_is_posbox(self): + if not self.is_posbox: + self.proxy_ip = False + self.iface_scan_via_proxy = False + self.iface_electronic_scale = False + self.iface_cashdrawer = False + self.iface_print_via_proxy = False + self.iface_customer_facing_display = False + + @api.onchange('tax_regime') + def _onchange_tax_regime(self): + if not self.tax_regime: + self.default_fiscal_position_id = False + + @api.onchange('tax_regime_selection') + def _onchange_tax_regime_selection(self): + if not self.tax_regime_selection: + self.fiscal_position_ids = [(5, 0, 0)] + + @api.onchange('start_category') + def _onchange_start_category(self): + if not self.start_category: + self.iface_start_categ_id = False + + @api.onchange('is_header_or_footer') + def _onchange_header_footer(self): + if not self.is_header_or_footer: + self.receipt_header = False + self.receipt_footer = False + @api.multi def name_get(self): result = [] @@ -235,7 +306,17 @@ class PosConfig(models.Model): val.update(name=_('POS order line %s') % values['name'], code='pos.order.line') values['sequence_line_id'] = IrSequence.create(val).id - return super(PosConfig, self).create(values) + pos_config = super(PosConfig, self).create(values) + pos_config.sudo()._check_modules_to_install() + # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install. + return pos_config + + @api.multi + def write(self, vals): + result = super(PosConfig, self).write(vals) + self.sudo()._set_fiscal_position() + self.sudo()._check_modules_to_install() + return result @api.multi def unlink(self): @@ -244,6 +325,32 @@ class PosConfig(models.Model): pos_config.sequence_line_id.unlink() return super(PosConfig, self).unlink() + def _set_fiscal_position(self): + for config in self: + if config.tax_regime and config.default_fiscal_position_id.id not in config.fiscal_position_ids.ids: + config.fiscal_position_ids = [(4, config.default_fiscal_position_id.id)] + elif not config.tax_regime_selection and not config.tax_regime and config.fiscal_position_ids.ids: + config.fiscal_position_ids = [(5, 0, 0)] + + def _check_modules_to_install(self): + module_installed = False + for pos_config in self: + for field_name in [f for f in pos_config.fields_get_keys() if f.startswith('module_')]: + module_name = field_name.split('module_')[1] + module_to_install = self.env['ir.module.module'].sudo().search([('name', '=', module_name)]) + if getattr(pos_config, field_name) and module_to_install.state not in ('installed', 'to install', 'to upgrade'): + module_to_install.button_immediate_install() + module_installed = True + # just in case we want to do something if we install a module. (like a refresh ...) + return module_installed + + def execute(self): + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + 'params': {'wait': True} + } + # Methods to open the POS @api.multi def open_ui(self): diff --git a/addons/point_of_sale/models/pos_config_settings.py b/addons/point_of_sale/models/pos_config_settings.py deleted file mode 100644 index db36ef0ddd6..00000000000 --- a/addons/point_of_sale/models/pos_config_settings.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models - - -class PosConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' - _name = 'pos.config.settings' - - group_multi_currency = fields.Boolean("Multi-Currencies", implied_group='base.group_multi_currency') - group_product_variant = fields.Boolean("Attributes & Variants", implied_group='product.group_product_variant') - group_product_pricelist = fields.Boolean("Show pricelists On Products", - implied_group='product.group_product_pricelist') - group_pricelist_item = fields.Boolean("Show pricelists to customers", - implied_group='product.group_pricelist_item') - group_sale_pricelist = fields.Boolean("Use pricelists to adapt your price per customers", - implied_group='product.group_sale_pricelist', - help="""Allows to manage different prices based on rules per category of customers. - Example: 10% for retailers, promotion of 5 EUR on this product, etc.""") - use_pos_sale_price = fields.Boolean("A single sale price per product", oldname='default_sale_price') - pos_pricelist_setting = fields.Selection([ - ('percentage', 'Multiple prices per product (e.g. quantity, shop-specific)'), - ('formula', 'Price computed from formulas (discounts, margins, rounding)') - ], string="Multiple Prices per Products", - oldname='default_pricelist_setting') - module_pos_restaurant = fields.Boolean(string="Restaurant") - module_pos_loyalty = fields.Boolean(string="Loyalty Program") - module_pos_discount = fields.Boolean(string="Global Discounts") - module_pos_mercury = fields.Boolean(string="Credit Cards") - module_pos_reprint = fields.Boolean(string="Reprint Receipt") - module_pos_data_drinks = fields.Boolean(string="Import common drinks data") - - @api.onchange('use_pos_sale_price') - def _onchange_fix_sale_price(self): - if not self.use_pos_sale_price: - self.pos_pricelist_setting = False - if self.use_pos_sale_price and not self.pos_pricelist_setting: - self.pos_pricelist_setting = 'percentage' - - @api.onchange('pos_pricelist_setting') - def _onchange_sale_price(self): - if self.pos_pricelist_setting == 'percentage': - self.update({ - 'group_product_pricelist': True, - 'group_sale_pricelist': True, - 'group_pricelist_item': False, - }) - elif self.pos_pricelist_setting == 'formula': - self.update({ - 'group_product_pricelist': False, - 'group_sale_pricelist': True, - 'group_pricelist_item': True, - }) - else: - self.update({ - 'group_product_pricelist': False, - 'group_sale_pricelist': False, - 'group_pricelist_item': False, - }) - - def get_values(self): - res = super(PosConfigSettings, self).get_values() - res.update( - use_pos_sale_price=self.env['ir.config_parameter'].sudo().get_param('pos.use_pos_sale_price'), - pos_pricelist_setting=self.env['ir.config_parameter'].sudo().get_param('pos.pos_pricelist_setting'), - ) - return res - - def set_values(self): - super(PosConfigSettings, self).set_values() - self.env['ir.config_parameter'].sudo().set_param('pos.use_pos_sale_price', self.use_pos_sale_price) - self.env['ir.config_parameter'].sudo().set_param('pos.pos_pricelist_setting', self.pos_pricelist_setting) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index dfd4b5b9b29..9d06aeca317 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1442,7 +1442,7 @@ exports.Orderline = Backbone.Model.extend({ return parseFloat(round_di(this.price || 0, digits).toFixed(digits)); }, get_unit_display_price: function(){ - if (this.pos.config.iface_tax_included) { + if (this.pos.config.iface_tax_included === 'total') { var quantity = this.quantity; this.quantity = 1.0; var price = this.get_all_prices().priceWithTax; @@ -1457,7 +1457,7 @@ exports.Orderline = Backbone.Model.extend({ return round_pr(this.get_unit_price() * this.get_quantity() * (1 - this.get_discount()/100), rounding); }, get_display_price: function(){ - if (this.pos.config.iface_tax_included) { + if (this.pos.config.iface_tax_included === 'total') { return this.get_price_with_tax(); } else { return this.get_base_price(); diff --git a/addons/point_of_sale/views/account_journal_view.xml b/addons/point_of_sale/views/account_journal_view.xml index a4d69f7f64a..7255a81cf0a 100644 --- a/addons/point_of_sale/views/account_journal_view.xml +++ b/addons/point_of_sale/views/account_journal_view.xml @@ -20,7 +20,29 @@ + action="action_account_journal_form" + parent="point_of_sale.menu_point_config_product" + groups="base.group_no_one" + sequence="30"/> + + account.journal.filter + account.journal + + + + + + + + + + POS Journals + ir.actions.act_window + account.journal + tree,form,kanban + + [('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])] + {'search_default_available_in_pos': 1, 'default_type': 'cash'} + + diff --git a/addons/point_of_sale/views/point_of_sale_view.xml b/addons/point_of_sale/views/point_of_sale_view.xml index 9d12f0bce7c..dd4d48c6ef8 100644 --- a/addons/point_of_sale/views/point_of_sale_view.xml +++ b/addons/point_of_sale/views/point_of_sale_view.xml @@ -28,9 +28,4 @@ sequence="100" groups="group_pos_manager"/> - - diff --git a/addons/point_of_sale/views/pos_config_settings_views.xml b/addons/point_of_sale/views/pos_config_settings_views.xml deleted file mode 100644 index 8edc687273a..00000000000 --- a/addons/point_of_sale/views/pos_config_settings_views.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - pos.config.settings.form - pos.config.settings - -
-
-
- -

Point of Sale

-
-
-
- -
-
-
-
-
-

Pricing

-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-

Product Catalog

-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-

Payments

-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- - Configure Point of Sale - pos.config.settings - form - inline - - - -
diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index 31c7e0526dc..9dca1ea3e8c 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -1,87 +1,480 @@ - + + + + + account.cashbox.line.tree + account.cashbox.line + + + + + + + + + + + Opening/Closing Values + ir.actions.act_window + account.cashbox.line + form + tree,form + + + + pos.config.form.view pos.config
- @@ -129,9 +522,11 @@ + groups="group_pos_manager"/> Point of Sale @@ -149,12 +544,6 @@

- - -
diff --git a/addons/pos_cache/views/pos_cache_views.xml b/addons/pos_cache/views/pos_cache_views.xml index 9b4454c45d7..553c1df1f84 100644 --- a/addons/pos_cache/views/pos_cache_views.xml +++ b/addons/pos_cache/views/pos_cache_views.xml @@ -1,10 +1,10 @@ - - pos.config.form.view + + pos.config.form.inherit.pos.cache pos.config - + diff --git a/addons/pos_discount/models/pos_config.py b/addons/pos_discount/models/pos_config.py index 6ff0c325157..0c21d482f17 100644 --- a/addons/pos_discount/models/pos_config.py +++ b/addons/pos_discount/models/pos_config.py @@ -1,12 +1,24 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import api, fields, models class PosConfig(models.Model): _inherit = 'pos.config' + def _get_default_discount_product(self): + return self.env.ref('point_of_sale.product_product_consumable') + iface_discount = fields.Boolean(string='Order Discounts', help='Allow the cashier to give discounts on the whole order.') discount_pc = fields.Float(string='Discount Percentage', default=10, help='The default discount percentage') - discount_product_id = fields.Many2one('product.product', string='Discount Product', domain="[('available_in_pos', '=', True)]", help='The product used to model the discount') + discount_product_id = fields.Many2one('product.product', string='Discount Product', domain="[('available_in_pos', '=', True)]", help='The product used to model the discount.', default=_get_default_discount_product) + + @api.onchange('module_pos_discount') + def _onchange_module_pos_discount(self): + if self.module_pos_discount: + self.discount_product_id = self.env['product.product'].search([('available_in_pos', '=', True)], limit=1) + self.discount_pc = 10.0 + else: + self.discount_product_id = False + self.discount_pc = 0.0 diff --git a/addons/pos_discount/static/src/js/discount.js b/addons/pos_discount/static/src/js/discount.js index 497573f5107..eadad806fd1 100644 --- a/addons/pos_discount/static/src/js/discount.js +++ b/addons/pos_discount/static/src/js/discount.js @@ -44,7 +44,7 @@ screens.define_action_button({ 'name': 'discount', 'widget': DiscountButton, 'condition': function(){ - return this.pos.config.iface_discount && this.pos.config.discount_product_id; + return this.pos.config.module_pos_discount && this.pos.config.discount_product_id; }, }); diff --git a/addons/pos_discount/views/pos_discount_views.xml b/addons/pos_discount/views/pos_discount_views.xml index d8141bdb9a0..e5e8c2a2704 100644 --- a/addons/pos_discount/views/pos_discount_views.xml +++ b/addons/pos_discount/views/pos_discount_views.xml @@ -1,18 +1,20 @@ - - pos.config.form.inherit + + pos.config.form.inherit.pos.discount pos.config - + - - - - - +
+
+
+
+
+
diff --git a/addons/pos_mercury/views/pos_config_setting_views.xml b/addons/pos_mercury/views/pos_config_setting_views.xml index 4d9677344a9..6a2331d0079 100644 --- a/addons/pos_mercury/views/pos_config_setting_views.xml +++ b/addons/pos_mercury/views/pos_config_setting_views.xml @@ -1,13 +1,16 @@ - - pos.config.settings.form.inherit.mercury - pos.config.settings - + + pos.config.form.inherit.mercury + pos.config + -
-
-
+
diff --git a/addons/pos_mercury/views/pos_mercury_views.xml b/addons/pos_mercury/views/pos_mercury_views.xml index a201a02070f..d9f668b6c0d 100644 --- a/addons/pos_mercury/views/pos_mercury_views.xml +++ b/addons/pos_mercury/views/pos_mercury_views.xml @@ -104,6 +104,6 @@
- +
diff --git a/addons/pos_reprint/__init__.py b/addons/pos_reprint/__init__.py index be9f4fab00a..67dee8c60db 100644 --- a/addons/pos_reprint/__init__.py +++ b/addons/pos_reprint/__init__.py @@ -1,3 +1,2 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import models diff --git a/addons/pos_reprint/__manifest__.py b/addons/pos_reprint/__manifest__.py index 2f79db26a87..bda15eae0a9 100644 --- a/addons/pos_reprint/__manifest__.py +++ b/addons/pos_reprint/__manifest__.py @@ -15,7 +15,7 @@ Allow cashier to reprint receipts 'depends': ['point_of_sale'], 'data': [ 'views/pos_reprint_templates.xml', - 'views/pos_config_views.xml', + 'views/pos_config_views.xml' ], 'qweb': [ 'static/src/xml/reprint.xml', diff --git a/addons/pos_reprint/models/__init__.py b/addons/pos_reprint/models/__init__.py deleted file mode 100644 index eaa81f34b8a..00000000000 --- a/addons/pos_reprint/models/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import pos_config diff --git a/addons/pos_reprint/models/pos_config.py b/addons/pos_reprint/models/pos_config.py deleted file mode 100644 index 0cbda70075a..00000000000 --- a/addons/pos_reprint/models/pos_config.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import api, fields, models - -class PosConfig(models.Model): - _inherit = 'pos.config' - - iface_reprint = fields.Boolean( - string='Receipt Reprinting', help="This allows you to reprint a previously printed receipt.") diff --git a/addons/pos_reprint/static/src/js/reprint.js b/addons/pos_reprint/static/src/js/reprint.js index 532f45f6bbe..f133cf4a2d2 100644 --- a/addons/pos_reprint/static/src/js/reprint.js +++ b/addons/pos_reprint/static/src/js/reprint.js @@ -45,8 +45,8 @@ var ReprintButton = screens.ActionButtonWidget.extend({ screens.define_action_button({ 'name': 'reprint', 'widget': ReprintButton, - 'condition': function() { - return this.pos.config.iface_reprint; + 'condition': function(){ + return this.pos.config.iface_print_via_proxy; }, }); diff --git a/addons/pos_reprint/views/pos_config_views.xml b/addons/pos_reprint/views/pos_config_views.xml index 65c61ec3d53..d5861bf3eb4 100644 --- a/addons/pos_reprint/views/pos_config_views.xml +++ b/addons/pos_reprint/views/pos_config_views.xml @@ -1,13 +1,11 @@ - - pos.config.form.view - pos.config - - - - - - - + + pos.config.form.inherit.reprint + pos.config + + +
+ + diff --git a/addons/pos_restaurant/__manifest__.py b/addons/pos_restaurant/__manifest__.py index 1ab693367ba..9db1a3c50bc 100644 --- a/addons/pos_restaurant/__manifest__.py +++ b/addons/pos_restaurant/__manifest__.py @@ -20,9 +20,9 @@ This module adds several restaurant features to the Point of Sale: 'website': 'https://www.odoo.com/page/point-of-sale', 'data': [ 'security/ir.model.access.csv', - 'views/pos_config_views.xml', 'views/pos_order_views.xml', 'views/pos_restaurant_views.xml', + 'views/pos_config_views.xml', 'views/pos_restaurant_templates.xml', ], 'qweb': [ diff --git a/addons/pos_restaurant/models/pos_config.py b/addons/pos_restaurant/models/pos_config.py index 2a0312b3fdb..8a396f9ff01 100644 --- a/addons/pos_restaurant/models/pos_config.py +++ b/addons/pos_restaurant/models/pos_config.py @@ -1,14 +1,44 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import api, fields, models class PosConfig(models.Model): _inherit = 'pos.config' - iface_splitbill = fields.Boolean(string='Bill Splitting', help='Enables Bill Splitting in the Point of Sale') - iface_printbill = fields.Boolean(string='Bill Printing', help='Allows to print the Bill before payment') - iface_orderline_notes = fields.Boolean(string='Orderline Notes', help='Allow custom notes on Orderlines') - floor_ids = fields.One2many('restaurant.floor', 'pos_config_id', string='Restaurant Floors', help='The restaurant floors served by this point of sale') + iface_splitbill = fields.Boolean(string='Bill Splitting', help='Enables Bill Splitting in the Point of Sale.') + iface_printbill = fields.Boolean(string='Bill Printing', help='Allows to print the Bill before payment.') + iface_orderline_notes = fields.Boolean(string='Orderline Notes', help='Allow custom notes on Orderlines.') + floor_ids = fields.One2many('restaurant.floor', 'pos_config_id', string='Restaurant Floors', help='The restaurant floors served by this point of sale.') printer_ids = fields.Many2many('restaurant.printer', 'pos_config_printer_rel', 'config_id', 'printer_id', string='Order Printers') + is_table_management = fields.Boolean('Table Management') + is_order_printer = fields.Boolean('Order Printers') + module_pos_restaurant = fields.Boolean(default=True) + + @api.onchange('iface_tipproduct') + def _onchange_tipproduct(self): + if self.iface_tipproduct: + self.tip_product_id = self.env.ref('point_of_sale.product_product_tip', False) + else: + self.tip_product_id = False + + @api.onchange('module_pos_restaurant') + def _onchange_module_pos_restaurant(self): + if not self.module_pos_restaurant: + self.update({'iface_printbill': False, + 'iface_splitbill': False, + 'iface_tipproduct': False, + 'is_order_printer': False, + 'is_table_management': False, + 'iface_orderline_notes': False}) + + @api.onchange('is_table_management') + def _onchange_is_table_management(self): + if not self.is_table_management: + self.floor_ids = [(5, 0, 0)] + + @api.onchange('is_order_printer') + def _onchange_is_order_printer(self): + if not self.is_order_printer: + self.printer_ids = [(5, 0, 0)] diff --git a/addons/pos_restaurant/views/pos_config_views.xml b/addons/pos_restaurant/views/pos_config_views.xml index 5837e89364d..be94d8fd3ff 100644 --- a/addons/pos_restaurant/views/pos_config_views.xml +++ b/addons/pos_restaurant/views/pos_config_views.xml @@ -1,20 +1,114 @@ - - pos.config.form.view.inherit + + pos.config.form.inherit.restaurant pos.config - + - - - - - - - - - +
+ + + + +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
diff --git a/addons/pos_restaurant/views/pos_restaurant_views.xml b/addons/pos_restaurant/views/pos_restaurant_views.xml index 7f2719f3234..fa68041285e 100644 --- a/addons/pos_restaurant/views/pos_restaurant_views.xml +++ b/addons/pos_restaurant/views/pos_restaurant_views.xml @@ -94,10 +94,10 @@ + parent="point_of_sale.menu_point_config_product" + action="action_restaurant_floor_form" + sequence="10" + groups="base.group_no_one"/> @@ -149,9 +149,9 @@ + parent="point_of_sale.menu_point_config_product" + action="action_restaurant_printer_form" + sequence="15" + groups="base.group_no_one"/>
diff --git a/addons/pos_sale/views/pos_config_views.xml b/addons/pos_sale/views/pos_config_views.xml index c4cc9d65264..caa000eacd9 100644 --- a/addons/pos_sale/views/pos_config_views.xml +++ b/addons/pos_sale/views/pos_config_views.xml @@ -1,16 +1,21 @@ - - pos.config.form + + pos.config.form.pos.sale pos.config - + - - - - - +
+
+
+
+
+
+
+
diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less index bcea630b152..28909ed87e4 100644 --- a/addons/web/static/src/less/form_view.less +++ b/addons/web/static/src/less/form_view.less @@ -647,6 +647,9 @@ label.o_light_label, .o_light_label label { button.btn-link { padding: 0; } + a.oe-link { + font-size: 12px; + } } } }