point_of_sale/models/res_config_settings.py

332 lines
21 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
from odoo import api, fields, models
import logging
_logger = logging.getLogger(__name__)
class ResConfigSettings(models.TransientModel):
"""
NOTES
1. Fields with name starting with 'pos_' are removed from the vals before super call to `create`.
Values of these fields are written to `pos_config_id` record after the super call.
This is done so that these fields are written at the same time to the active pos.config record.
2. During `creation` of this record, each related field is written to the source record
*one after the other*, so constraints on the source record that are based on multiple
fields might not work properly. However, only the *modified* related fields are written
to the source field. But the identification of modified fields happen during the super
call, not before `create` is called. Because of this, vals contains a lot of field before
super call, then the number of fields is reduced after.
"""
_inherit = 'res.config.settings'
def _default_pos_config(self):
# Default to the last modified pos.config.
active_model = self.env.context.get('active_model', '')
if active_model == 'pos.config':
return self.env.context.get('active_id')
return self.env['pos.config'].search([('company_id', '=', self.env.company.id)], order='write_date desc', limit=1)
pos_config_id = fields.Many2one('pos.config', string="Point of Sale", default=lambda self: self._default_pos_config())
sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", related='company_id.account_sale_tax_id', readonly=False)
module_pos_mercury = fields.Boolean(string="Vantiv Payment Terminal", help="The transactions are processed by Vantiv. Set your Vantiv credentials on the related payment method.")
module_pos_adyen = fields.Boolean(string="Adyen Payment Terminal", help="The transactions are processed by Adyen. Set your Adyen credentials on the related payment method.")
module_pos_stripe = fields.Boolean(string="Stripe Payment Terminal", help="The transactions are processed by Stripe. Set your Stripe credentials on the related payment method.")
module_pos_six = fields.Boolean(string="Six Payment Terminal", help="The transactions are processed by Six. Set the IP address of the terminal on the related payment method.")
module_pos_paytm = fields.Boolean(string="PayTM Payment Terminal", help="The transactions are processed by PayTM. Set your PayTM credentials on the related payment method.")
module_pos_preparation_display = fields.Boolean(string="Preparation Display", help="Show orders on the preparation display screen.")
update_stock_quantities = fields.Selection(related="company_id.point_of_sale_update_stock_quantities", readonly=False)
account_default_pos_receivable_account_id = fields.Many2one(string='Default Account Receivable (PoS)', related='company_id.account_default_pos_receivable_account_id', readonly=False)
barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', related='company_id.nomenclature_id', readonly=False)
is_kiosk_mode = fields.Boolean(string="Is Kiosk Mode", default=False)
# pos.config fields
pos_module_pos_discount = fields.Boolean(related='pos_config_id.module_pos_discount', readonly=False)
pos_module_pos_hr = fields.Boolean(related='pos_config_id.module_pos_hr', readonly=False)
pos_module_pos_restaurant = fields.Boolean(related='pos_config_id.module_pos_restaurant', readonly=False)
pos_is_order_printer = fields.Boolean(compute='_compute_pos_printer', store=True, readonly=False)
pos_printer_ids = fields.Many2many(related='pos_config_id.printer_ids', readonly=False)
pos_allowed_pricelist_ids = fields.Many2many('product.pricelist', compute='_compute_pos_allowed_pricelist_ids')
pos_amount_authorized_diff = fields.Float(related='pos_config_id.amount_authorized_diff', readonly=False)
pos_available_pricelist_ids = fields.Many2many('product.pricelist', string='Available Pricelists', compute='_compute_pos_pricelist_id', readonly=False, store=True)
pos_cash_control = fields.Boolean(related='pos_config_id.cash_control')
pos_cash_rounding = fields.Boolean(related='pos_config_id.cash_rounding', readonly=False, string="Cash Rounding (PoS)")
pos_company_has_template = fields.Boolean(related='pos_config_id.company_has_template')
pos_default_bill_ids = fields.Many2many(related='pos_config_id.default_bill_ids', readonly=False)
pos_default_fiscal_position_id = fields.Many2one('account.fiscal.position', string='Default Fiscal Position', compute='_compute_pos_fiscal_positions', readonly=False, store=True)
pos_fiscal_position_ids = fields.Many2many('account.fiscal.position', string='Fiscal Positions', compute='_compute_pos_fiscal_positions', readonly=False, store=True)
pos_has_active_session = fields.Boolean(related='pos_config_id.has_active_session')
pos_iface_available_categ_ids = fields.Many2many('pos.category', string='Available PoS Product Categories', compute='_compute_pos_iface_available_categ_ids', readonly=False, store=True)
pos_iface_big_scrollbars = fields.Boolean(related='pos_config_id.iface_big_scrollbars', readonly=False)
pos_iface_cashdrawer = fields.Boolean(string='Cashdrawer', compute='_compute_pos_iface_cashdrawer', readonly=False, store=True)
pos_iface_customer_facing_display_local = fields.Boolean(related='pos_config_id.iface_customer_facing_display_local', readonly=False)
pos_iface_customer_facing_display_via_proxy = fields.Boolean(string='Customer Facing Display', compute='_compute_pos_iface_customer_facing_display_via_proxy', readonly=False, store=True)
pos_iface_customer_facing_display_background_image_1920 = fields.Image(related='pos_config_id.iface_customer_facing_display_background_image_1920', readonly=False)
pos_iface_electronic_scale = fields.Boolean(string='Electronic Scale', compute='_compute_pos_iface_electronic_scale', readonly=False, store=True)
pos_iface_print_auto = fields.Boolean(related='pos_config_id.iface_print_auto', readonly=False)
pos_iface_print_skip_screen = fields.Boolean(related='pos_config_id.iface_print_skip_screen', readonly=False)
pos_iface_print_via_proxy = fields.Boolean(string='Print via Proxy', compute='_compute_pos_iface_print_via_proxy', readonly=False, store=True)
pos_iface_scan_via_proxy = fields.Boolean(string='Scan via Proxy', compute='_compute_pos_iface_scan_via_proxy', readonly=False, store=True)
pos_iface_start_categ_id = fields.Many2one('pos.category', string='Initial Category', compute='_compute_pos_iface_start_categ_id', readonly=False, store=True)
pos_iface_tax_included = fields.Selection(related='pos_config_id.iface_tax_included', readonly=False)
pos_iface_tipproduct = fields.Boolean(related='pos_config_id.iface_tipproduct', readonly=False)
pos_invoice_journal_id = fields.Many2one(related='pos_config_id.invoice_journal_id', readonly=False)
pos_is_header_or_footer = fields.Boolean(related='pos_config_id.is_header_or_footer', readonly=False)
pos_is_margins_costs_accessible_to_every_user = fields.Boolean(related='pos_config_id.is_margins_costs_accessible_to_every_user', readonly=False)
pos_is_posbox = fields.Boolean(related='pos_config_id.is_posbox', readonly=False)
pos_journal_id = fields.Many2one(related='pos_config_id.journal_id', readonly=False)
pos_limit_categories = fields.Boolean(related='pos_config_id.limit_categories', readonly=False)
pos_manual_discount = fields.Boolean(related='pos_config_id.manual_discount', readonly=False)
pos_only_round_cash_method = fields.Boolean(related='pos_config_id.only_round_cash_method', readonly=False)
pos_other_devices = fields.Boolean(related='pos_config_id.other_devices', readonly=False)
pos_payment_method_ids = fields.Many2many(related='pos_config_id.payment_method_ids', readonly=False)
pos_picking_policy = fields.Selection(related='pos_config_id.picking_policy', readonly=False)
pos_picking_type_id = fields.Many2one(related='pos_config_id.picking_type_id', readonly=False)
pos_pricelist_id = fields.Many2one('product.pricelist', string='Default Pricelist', compute='_compute_pos_pricelist_id', readonly=False, store=True)
pos_proxy_ip = fields.Char(string='IP Address', compute='_compute_pos_proxy_ip', readonly=False, store=True)
pos_receipt_footer = fields.Text(string='Receipt Footer', compute='_compute_pos_receipt_header_footer', readonly=False, store=True)
pos_receipt_header = fields.Text(string='Receipt Header', compute='_compute_pos_receipt_header_footer', readonly=False, store=True)
pos_restrict_price_control = fields.Boolean(related='pos_config_id.restrict_price_control', readonly=False)
pos_rounding_method = fields.Many2one(related='pos_config_id.rounding_method', readonly=False)
pos_route_id = fields.Many2one(related='pos_config_id.route_id', readonly=False)
pos_selectable_categ_ids = fields.Many2many('pos.category', compute='_compute_pos_selectable_categ_ids')
pos_sequence_id = fields.Many2one(related='pos_config_id.sequence_id')
pos_set_maximum_difference = fields.Boolean(related='pos_config_id.set_maximum_difference', readonly=False)
pos_ship_later = fields.Boolean(related='pos_config_id.ship_later', readonly=False)
pos_start_category = fields.Boolean(related='pos_config_id.start_category', readonly=False)
pos_tax_regime_selection = fields.Boolean(related='pos_config_id.tax_regime_selection', readonly=False)
pos_tip_product_id = fields.Many2one('product.product', string='Tip Product', compute='_compute_pos_tip_product_id', readonly=False, store=True)
pos_use_pricelist = fields.Boolean(related='pos_config_id.use_pricelist', readonly=False)
pos_warehouse_id = fields.Many2one(related='pos_config_id.warehouse_id', readonly=False, string="Warehouse (PoS)")
point_of_sale_use_ticket_qr_code = fields.Boolean(related='company_id.point_of_sale_use_ticket_qr_code', readonly=False)
pos_auto_validate_terminal_payment = fields.Boolean(related='pos_config_id.auto_validate_terminal_payment', readonly=False, string="Automatically validates orders paid with a payment terminal.")
pos_trusted_config_ids = fields.Many2many(related='pos_config_id.trusted_config_ids', readonly=False)
point_of_sale_ticket_unique_code = fields.Boolean(related='company_id.point_of_sale_ticket_unique_code', readonly=False)
@api.model_create_multi
def create(self, vals_list):
# STEP: Remove the 'pos' fields from each vals.
# They will be written atomically to `pos_config_id` after the super call.
pos_config_id_to_fields_vals_map = {}
for vals in vals_list:
pos_config_id = vals.get('pos_config_id')
if pos_config_id:
pos_fields_vals = {}
if vals.get('pos_cash_rounding'):
vals['group_cash_rounding'] = True
if vals.get('pos_use_pricelist'):
vals['group_product_pricelist'] = True
for field in self._fields.values():
if field.name == 'pos_config_id':
continue
val = vals.get(field.name)
# Add only to pos_fields_vals if
# 1. _field is in vals -- meaning, the _field is in view.
# 2. _field starts with 'pos_' -- meaning, the _field is a pos field.
if field.name.startswith('pos_') and val is not None:
pos_config_field_name = field.name[4:]
if not pos_config_field_name in self.env['pos.config']._fields:
_logger.warning("The value of '%s' is not properly saved to the pos_config_id field because the destination"
" field '%s' is not a valid field in the pos.config model.", field.name, pos_config_field_name)
else:
pos_fields_vals[pos_config_field_name] = val
del vals[field.name]
pos_config_id_to_fields_vals_map[pos_config_id] = pos_fields_vals
# STEP: Call super on the modified vals_list.
# NOTE: When creating `res.config.settings` records, it doesn't write on *unmodified* related fields.
result = super().create(vals_list)
# STEP: Finally, we write the value of 'pos' fields to 'pos_config_id'.
for pos_config_id, pos_fields_vals in pos_config_id_to_fields_vals_map.items():
pos_config = self.env['pos.config'].browse(pos_config_id)
pos_config.with_context(from_settings_view=True).write(pos_fields_vals)
return result
def set_values(self):
super(ResConfigSettings, self).set_values()
if not self.group_product_pricelist:
self.env['pos.config'].search([
('use_pricelist', '=', True)
]).use_pricelist = False
if not self.group_cash_rounding:
self.env['pos.config'].search([
('cash_rounding', '=', True)
]).cash_rounding = False
def action_pos_config_create_new(self):
return {
'view_mode': 'form',
'res_model': 'pos.config',
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': False,
'context': {'pos_config_open_modal': True, 'pos_config_create_mode': True},
}
def pos_open_ui(self):
if self._context.get('pos_config_id'):
pos_config_id = self._context['pos_config_id']
pos_config = self.env['pos.config'].browse(pos_config_id)
return pos_config.open_ui()
@api.model
def _is_cashdrawer_displayed(self, res_config):
return res_config.pos_iface_print_via_proxy
@api.depends('pos_module_pos_restaurant', 'pos_config_id')
def _compute_pos_printer(self):
for res_config in self:
res_config.update({
'pos_is_order_printer': res_config.pos_config_id.is_order_printer,
})
@api.depends('pos_limit_categories', 'pos_config_id')
def _compute_pos_iface_available_categ_ids(self):
for res_config in self:
if not res_config.pos_limit_categories:
res_config.pos_iface_available_categ_ids = False
else:
res_config.pos_iface_available_categ_ids = res_config.pos_config_id.iface_available_categ_ids
@api.depends('pos_start_category', 'pos_config_id')
def _compute_pos_iface_start_categ_id(self):
for res_config in self:
if not res_config.pos_start_category:
res_config.pos_iface_start_categ_id = False
else:
res_config.pos_iface_start_categ_id = res_config.pos_config_id.iface_start_categ_id
@api.depends('pos_iface_available_categ_ids')
def _compute_pos_selectable_categ_ids(self):
for res_config in self:
if res_config.pos_iface_available_categ_ids:
res_config.pos_selectable_categ_ids = res_config.pos_iface_available_categ_ids
else:
res_config.pos_selectable_categ_ids = self.env['pos.category'].search([])
@api.depends('pos_iface_print_via_proxy', 'pos_config_id')
def _compute_pos_iface_cashdrawer(self):
for res_config in self:
if self._is_cashdrawer_displayed(res_config):
res_config.pos_iface_cashdrawer = res_config.pos_config_id.iface_cashdrawer
else:
res_config.pos_iface_cashdrawer = False
@api.depends('pos_is_header_or_footer', 'pos_config_id')
def _compute_pos_receipt_header_footer(self):
for res_config in self:
if res_config.pos_is_header_or_footer:
res_config.pos_receipt_header = res_config.pos_config_id.receipt_header
res_config.pos_receipt_footer = res_config.pos_config_id.receipt_footer
else:
res_config.pos_receipt_header = False
res_config.pos_receipt_footer = False
@api.depends('pos_tax_regime_selection', 'pos_config_id')
def _compute_pos_fiscal_positions(self):
for res_config in self:
if res_config.pos_tax_regime_selection:
res_config.pos_default_fiscal_position_id = res_config.pos_config_id.default_fiscal_position_id
res_config.pos_fiscal_position_ids = res_config.pos_config_id.fiscal_position_ids
else:
res_config.pos_default_fiscal_position_id = False
res_config.pos_fiscal_position_ids = [(5, 0, 0)]
@api.depends('pos_iface_tipproduct', 'pos_config_id')
def _compute_pos_tip_product_id(self):
for res_config in self:
if res_config.pos_iface_tipproduct:
res_config.pos_tip_product_id = res_config.pos_config_id.tip_product_id
else:
res_config.pos_tip_product_id = False
@api.depends('pos_use_pricelist', 'pos_config_id', 'pos_journal_id')
def _compute_pos_pricelist_id(self):
for res_config in self:
currency_id = res_config.pos_journal_id.currency_id.id if res_config.pos_journal_id.currency_id else res_config.pos_config_id.company_id.currency_id.id
pricelists_in_current_currency = self.env['product.pricelist'].search([
*self.env['product.pricelist']._check_company_domain(res_config.pos_config_id.company_id),
('currency_id', '=', currency_id),
])
if not res_config.pos_use_pricelist:
res_config.pos_pricelist_id = False
res_config.pos_available_pricelist_ids = res_config.pos_config_id.available_pricelist_ids
else:
if any([p.currency_id.id != currency_id for p in res_config.pos_available_pricelist_ids]):
res_config.pos_available_pricelist_ids = pricelists_in_current_currency
res_config.pos_pricelist_id = pricelists_in_current_currency[:1]
else:
res_config.pos_available_pricelist_ids = res_config.pos_config_id.available_pricelist_ids
res_config.pos_pricelist_id = res_config.pos_config_id.pricelist_id
@api.depends('pos_available_pricelist_ids', 'pos_use_pricelist')
def _compute_pos_allowed_pricelist_ids(self):
for res_config in self:
if res_config.pos_use_pricelist:
res_config.pos_allowed_pricelist_ids = res_config.pos_available_pricelist_ids.ids
else:
res_config.pos_allowed_pricelist_ids = self.env['product.pricelist'].search([]).ids
@api.depends('pos_is_posbox', 'pos_config_id')
def _compute_pos_proxy_ip(self):
for res_config in self:
if not res_config.pos_is_posbox:
res_config.pos_proxy_ip = False
else:
res_config.pos_proxy_ip = res_config.pos_config_id.proxy_ip
@api.depends('pos_is_posbox', 'pos_config_id')
def _compute_pos_iface_print_via_proxy(self):
for res_config in self:
if not res_config.pos_is_posbox:
res_config.pos_iface_print_via_proxy = False
else:
res_config.pos_iface_print_via_proxy = res_config.pos_config_id.iface_print_via_proxy
@api.depends('pos_is_posbox', 'pos_config_id')
def _compute_pos_iface_scan_via_proxy(self):
for res_config in self:
if not res_config.pos_is_posbox:
res_config.pos_iface_scan_via_proxy = False
else:
res_config.pos_iface_scan_via_proxy = res_config.pos_config_id.iface_scan_via_proxy
@api.depends('pos_is_posbox', 'pos_config_id')
def _compute_pos_iface_electronic_scale(self):
for res_config in self:
if not res_config.pos_is_posbox:
res_config.pos_iface_electronic_scale = False
else:
res_config.pos_iface_electronic_scale = res_config.pos_config_id.iface_electronic_scale
@api.depends('pos_is_posbox', 'pos_config_id')
def _compute_pos_iface_customer_facing_display_via_proxy(self):
for res_config in self:
if not res_config.pos_is_posbox:
res_config.pos_iface_customer_facing_display_via_proxy = False
else:
res_config.pos_iface_customer_facing_display_via_proxy = res_config.pos_config_id.iface_customer_facing_display_via_proxy
@api.onchange('pos_trusted_config_ids')
def _onchange_trusted_config_ids(self):
for config in self:
removed_trusted_configs = set(config.pos_config_id.trusted_config_ids.ids) - set(config.pos_trusted_config_ids.ids)
for old in config.pos_config_id.trusted_config_ids:
if config.pos_config_id.id not in old.trusted_config_ids.ids:
old._add_trusted_config_id(config.pos_config_id)
if old.id in removed_trusted_configs:
old._remove_trusted_config_id(config.pos_config_id)