Files
enterprise-addons/pos_blackbox_be/models/pos_config.py
T

342 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api
from odoo.exceptions import UserError
from odoo.exceptions import ValidationError
from odoo.tools.translate import _
from odoo.service.common import exp_version
from uuid import uuid4
class PosConfig(models.Model):
_inherit = "pos.config"
iface_fiscal_data_module = fields.Many2one(
"iot.device",
domain=lambda self: ['&', ('type', '=', 'fiscal_data_module'), '|', ('company_id', '=', False), ('company_id', '=', self.env.company.id)],
)
certified_blackbox_identifier = fields.Char(
"Blackbox Identifier",
store=True,
compute="_compute_certified_pos",
readonly=True,
)
pos_version = fields.Char('Odoo Version', compute='_compute_odoo_version')
def _compute_odoo_version(self):
self.pos_version = exp_version()['server_serie']
def _is_blackbox_v1_required(self):
return bool(self.certified_blackbox_identifier)
def _uses_blackbox_v1(self):
return self.certified_blackbox_identifier and self.iface_fiscal_data_module
@api.model
def _load_pos_data_read(self, records, config):
read_records = super()._load_pos_data_read(records, config)
if read_records and config._uses_blackbox_v1():
record = read_records[0]
record["_product_product_work_in"] = self.env.ref("pos_blackbox_be.product_product_work_in").id
record["_product_product_work_out"] = self.env.ref("pos_blackbox_be.product_product_work_out").id
return read_records
@api.depends("iface_fiscal_data_module")
def _compute_certified_pos(self):
for config in self:
if config.iface_fiscal_data_module:
config.certified_blackbox_identifier = config.iface_fiscal_data_module.name[
-14:
]
self.env["pos_blackbox_be.log"].sudo().create([{
"action": "create",
"model_name": config._name,
"record_name": config.name,
"description": "Session started with: %s" % config.certified_blackbox_identifier,
}])
if not self.env['ir.sequence'].search([('code', '=', f'pos_blackbox_be.NS_blackbox_{config.certified_blackbox_identifier}')]):
self.env['ir.sequence'].sudo().create({
'name': _("NS Order by blackbox"),
'padding': 4,
'code': f'pos_blackbox_be.NS_blackbox_{config.certified_blackbox_identifier}',
'company_id': False,
})
self.env['ir.sequence'].sudo().create({
'name': _("PS Order by blackbox"),
'padding': 4,
'code': f'pos_blackbox_be.PS_blackbox_{config.certified_blackbox_identifier}',
'company_id': False,
})
def write(self, vals):
if (vals.get('iface_fiscal_data_module') or self._is_blackbox_v1_required()):
cash_rounding = self._create_default_cashrounding()
if cash_rounding:
vals['cash_rounding'] = True
vals['rounding_method'] = cash_rounding.id
vals['only_round_cash_method'] = True
vals['iface_print_auto'] = True
vals['iface_print_skip_screen'] = True
return super().write(vals)
def _is_fdm_required(self):
return self.certified_blackbox_identifier or self.env['pos.config'].search_count(
domain=[
*self.env['account.journal']._check_company_domain(self.company_id),
('certified_blackbox_identifier', '!=', False),
],
limit=1
)
def _check_is_certified_pos(self):
action = self.env['pos.config'].action_pos_config_modal_edit()
action['res_id'] = self.id
fdm_required = self._is_fdm_required()
fdm_ids = self.env['iot.device'].search([
*self.env['iot.device']._check_company_domain(self.company_id),
('type', '=', 'fiscal_data_module'),
], limit=2)
if not self.iface_fiscal_data_module and fdm_required:
self.is_posbox = True
if len(fdm_ids) != 1:
action['context']['fdm_required'] = True
return action
self.iface_fiscal_data_module = fdm_ids[0] # If only one FDM is available, set it automatically
return False
@api.depends("iface_fiscal_data_module")
def _compute_iot_device_ids(self):
super()._compute_iot_device_ids()
for config in self:
if config.is_posbox:
config.iot_device_ids += config.iface_fiscal_data_module
def _action_to_open_ui(self):
res = super()._action_to_open_ui()
if self.current_session_id.state == "opened":
self.env['pos.blackbox.log.ip']._log_ip(self, None)
return res
def _check_before_creating_new_session(self):
res = self._check_is_certified_pos()
if res:
return res
if self.iface_fiscal_data_module:
self._check_loyalty()
res = self._check_insz_user() or self._check_company_address() or self._check_printer_connected()
if res:
return res
self._check_work_product_taxes_and_categories()
self._check_employee_insz_or_bis_number()
self._check_cash_rounding()
return super()._check_before_creating_new_session()
def _check_loyalty(self):
for config in self:
if self.env['ir.module.module'].search([('name', '=', 'pos_loyalty'), ('state', '=', 'installed')]) and len(config._get_program_ids().filtered(lambda p: p.company_id == config.company_id)) > 0:
raise UserError(
_(
"Loyalty programs and gift card cannot be used on a PoS associated with a blackbox."
)
)
def _check_work_product_taxes_and_categories(self):
work_products = self._get_work_products()
for work_product in work_products:
if not work_product.available_in_pos:
raise ValidationError(
_("The WORK IN/OUT products must be available in the POS.")
)
if (
not work_product.taxes_id
or any(t.amount != 0 for t in work_product.taxes_id)
):
raise ValidationError(
_("The WORK IN/OUT products must have a taxes with 0%.")
)
if (len(work_product.pos_categ_ids) != 1):
raise ValidationError(
_("The WORK IN/OUT products must have one and only one POS category. We advise you to use the POS category named 'Fiscal category'.")
)
if (len(work_products.pos_categ_ids) != 1):
raise ValidationError(
_("The WORK IN/OUT products must have the same POS category. We advise you to use the POS category named 'Fiscal category'.")
)
def _check_insz_user(self):
if not self.env.user.insz_or_bis_number:
action = self.env['ir.actions.actions']._for_xml_id('base.action_res_users')
action['res_id'] = self.env.user.id
action['views'] = [[self.env.ref('base.view_users_form').id, 'form']]
action['target'] = 'new'
action['context'] = {
'insz_required': True,
}
return action
return False
def _check_company_address(self):
if not self.company_id.street or not self.company_id.company_registry:
action = self.env['ir.actions.actions']._for_xml_id('base.action_res_company_form')
action['res_id'] = self.company_id.id
action['views'] = [[self.env.ref('base.view_company_form').id, 'form']]
action['target'] = 'new'
action['context'] = {
'company_address_required': not self.company_id.street,
'company_vat_required': not self.company_id.company_registry,
}
return action
return False
@api.constrains("iface_fiscal_data_module", "fiscal_position_ids")
def _check_posbox_fp_tax_code(self):
invalid_tax_lines = [
(fp.name, tax.name)
for config in self
for fp in config.fiscal_position_ids
for tax in fp.tax_ids
if (
not tax.tax_group_id.pos_receipt_label
and any(tax.original_tax_ids.tax_group_id.mapped('pos_receipt_label'))
)
]
if invalid_tax_lines:
raise ValidationError(_("Fiscal Position %(fp_name)s (tax %(tax_dest_name)s) has an invalid tax amount. Only 21%%, 12%%, 6%% and 0%% are allowed.", fp_name=invalid_tax_lines[0][0], tax_dest_name=invalid_tax_lines[0][1]))
def _check_employee_insz_or_bis_number(self):
for config in self:
if config.module_pos_hr:
all_employee_ids = self.env['hr.employee'].search(self._employee_domain(self.env.uid))
comp_user = self.env.user.with_company(self.company_id)
emp_names = [emp.name for emp in all_employee_ids if not emp.sudo().insz_or_bis_number] + ([comp_user.name] if not comp_user.employee_id.sudo().insz_or_bis_number else [])
if len(emp_names) > 0:
raise ValidationError(
_("%s must have an National Register Number.", ", ".join(emp_names))
)
def _check_cash_rounding(self):
if self.payment_method_ids.filtered(lambda p: p.is_cash_count):
if not self.cash_rounding:
raise ValidationError(_("Cash rounding must be enabled"))
if (
self.rounding_method.rounding != 0.05
or self.rounding_method.rounding_method != "HALF-UP"
):
raise ValidationError(
_('The rounding method must be set to 0.05 and "Nearest"')
)
def _check_printer_connected(self):
epson_printer = False
if hasattr(self, "epson_printer_ip"):
epson_printer = self.epson_printer_ip
if not self.iface_printer_id and not epson_printer:
action = self.env['pos.config'].action_pos_config_modal_edit()
action['res_id'] = self.id
action['context'].update({'printer_required': True})
return action
if not self.iface_print_auto:
raise ValidationError(_("Automatic Receipt Printing must be activated"))
if not self.iface_print_skip_screen:
raise ValidationError(_("Skip Preview Screen must be activated"))
return False
def _get_work_products(self):
empty_product = self.env['product.product']
work_in_product = self.env.ref('pos_blackbox_be.product_product_work_in', raise_if_not_found=False) or empty_product
work_out_product = self.env.ref('pos_blackbox_be.product_product_work_out', raise_if_not_found=False) or empty_product
return work_in_product | work_out_product
def _get_special_products(self):
return super()._get_special_products() | self._get_work_products()
def get_NS_sequence_next(self):
return self.env['ir.sequence'].next_by_code(f'pos_blackbox_be.NS_blackbox_{self.certified_blackbox_identifier}')
def get_PS_sequence_next(self):
return self.env['ir.sequence'].next_by_code(f'pos_blackbox_be.PS_blackbox_{self.certified_blackbox_identifier}')
@api.model
def _create_default_cashrounding(self):
cash_rounding = self.env.ref('pos_blackbox_be.default_l10n_be_cash_rounding', raise_if_not_found=False)
if cash_rounding:
return cash_rounding
if self.env.company.chart_template == "be_comp":
profit_account = self.env.ref(f'account.{self.env.company.id}_a743', raise_if_not_found=False)
loss_account = self.env.ref(f'account.{self.env.company.id}_a643', raise_if_not_found=False)
if profit_account and loss_account:
cash_rounding = self.env['account.cash.rounding'].create({
'name': _('Belgian Cash Rounding'),
'rounding_method': 'HALF-UP',
'rounding': 0.05,
'profit_account_id': profit_account.id,
'loss_account_id': loss_account.id,
})
self.env['ir.model.data']._update_xmlids([
{
'xml_id': 'pos_blackbox_be.default_l10n_be_cash_rounding',
'record': cash_rounding,
'noupdate': True,
}
])
return cash_rounding
return False
@api.model
def _send_order_to_blackbox(self, order, clock=False, clock_in=True):
iface_fiscal_data_module = order.config_id.iface_fiscal_data_module
blackbox_data = order._create_order_for_blackbox(clock, clock_in)
message = {
"iot_identifier": iface_fiscal_data_module.iot_id.identifier,
"iot_identifiers": [iface_fiscal_data_module.iot_id.identifier],
"device_identifier": iface_fiscal_data_module.identifier,
"device_identifiers": [iface_fiscal_data_module.identifier],
"action": "registerReceiptWeb",
"high_level_message": blackbox_data,
"id": order.id,
}
self.env['iot.channel'].send_message(message)
return True
def _clock_kiosk_user(self, clock_in):
pos_reference, _ = self._get_next_order_refs()
clock_order = self.env['pos.order'].create({
'session_id': self.current_session_id.id,
'company_id': self.company_id.id,
'config_id': self.id,
'user_id': self.current_user_id.id,
'access_token': str(uuid4()),
'pos_reference': pos_reference,
'amount_tax': 0,
'amount_total': 0,
'amount_paid': 0,
'amount_return': 0,
'lines': [(0, 0, {
'name': 'Work in' if clock_in else 'Work out',
'product_id': self.env.ref('pos_blackbox_be.product_product_work_in').id if clock_in else self.env.ref('pos_blackbox_be.product_product_work_out').id,
'price_subtotal': 0,
'price_subtotal_incl': 0,
'tax_ids': [(6, 0, self.env.ref('pos_blackbox_be.product_product_work_in').taxes_id.ids)],
'qty': 1,
})]
})
clock_order.action_pos_order_paid()
self._send_order_to_blackbox(clock_order, True, clock_in)
def action_close_kiosk_session(self):
if self._uses_blackbox_v1():
self._clock_kiosk_user(False)
return super().action_close_kiosk_session()
@api.model
def _load_pos_self_data_fields(self, pos_config_id):
fields = super()._load_pos_self_data_fields(pos_config_id)
return fields + ['iface_fiscal_data_module']