244 lines
10 KiB
Python
244 lines
10 KiB
Python
from odoo import models, fields, api, _
|
|
from odoo.exceptions import ValidationError, UserError
|
|
from odoo.service.common import exp_version
|
|
import requests
|
|
from requests.exceptions import RequestException
|
|
|
|
|
|
FALLBACK_POS_ID = "CDEM0000000001"
|
|
|
|
|
|
class PosConfig(models.Model):
|
|
_inherit = "pos.config"
|
|
|
|
l10n_be_report_counter = fields.Integer(help="Count the number of reports generated for this POS configuration.", default=1)
|
|
l10n_be_blackbox_be_id = fields.Many2one(
|
|
comodel_name="pos.blackbox.be",
|
|
string="POS Blackbox BE",
|
|
help="The blackbox that is linked to this POS configuration.",
|
|
)
|
|
l10n_be_pos_id = fields.Char(
|
|
string="POS ID",
|
|
store=True,
|
|
compute="_compute_pos_id",
|
|
readonly=True,
|
|
help="The ID of the POS configuration. This ID is used to identify the POS configuration in the blackbox.",
|
|
)
|
|
l10n_be_training_mode = fields.Boolean(
|
|
string="Training Mode",
|
|
help="If checked, the POS will be in training mode.",
|
|
default=False,
|
|
)
|
|
pos_version = fields.Char('Odoo Version', compute='_compute_odoo_version')
|
|
establishment_number = fields.Char(string="Establishment Number", help="The establishment number of the company using this POS configuration.")
|
|
|
|
@api.depends('l10n_be_blackbox_be_id')
|
|
def _compute_pos_id(self):
|
|
for config in self:
|
|
if config.l10n_be_blackbox_be_id and (not config.l10n_be_pos_id or config.l10n_be_pos_id == FALLBACK_POS_ID):
|
|
try:
|
|
config.l10n_be_pos_id = self._get_pos_id_from_fdm()
|
|
except UserError:
|
|
config.l10n_be_pos_id = FALLBACK_POS_ID
|
|
|
|
def _get_pos_id_from_fdm(self):
|
|
"""Retrieve POS ID from FDM service"""
|
|
db_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
|
|
if not db_uuid:
|
|
raise UserError(_("Database UUID not found."))
|
|
endpoint = "https://fdm.odoo.com/_fdm/pos/sequence/"
|
|
|
|
url = f"{endpoint}{db_uuid}"
|
|
try:
|
|
response = requests.get(url, timeout=60)
|
|
response.raise_for_status()
|
|
return response.text
|
|
except RequestException as e:
|
|
raise UserError(
|
|
_("Failed to retrieve POS ID from FDM service:\n%s", e)
|
|
)
|
|
|
|
def action_request_pos_id(self):
|
|
self.ensure_one()
|
|
if self.l10n_be_pos_id and self.l10n_be_pos_id != FALLBACK_POS_ID:
|
|
raise UserError(_("A valid POS ID is already set for this configuration."))
|
|
try:
|
|
self.l10n_be_pos_id = self._get_pos_id_from_fdm()
|
|
except UserError:
|
|
self.l10n_be_pos_id = FALLBACK_POS_ID
|
|
self.env.cr.commit()
|
|
raise
|
|
|
|
def write(self, vals):
|
|
if 'l10n_be_blackbox_be_id' in vals:
|
|
for config in self:
|
|
if config.has_active_session and config.l10n_be_blackbox_be_id.id != vals['l10n_be_blackbox_be_id']:
|
|
raise UserError(_(
|
|
"You cannot change the blackbox of the POS configuration %s while it has an open session. "
|
|
"Please close the session first.",
|
|
config.name,
|
|
))
|
|
if 'l10n_be_pos_id' in vals:
|
|
for config in self:
|
|
if config.has_active_session and config.l10n_be_pos_id != vals['l10n_be_pos_id']:
|
|
raise UserError(_(
|
|
"You cannot change the POS ID of the POS configuration %s while it has an open session. "
|
|
"Please close the session first.",
|
|
config.name,
|
|
))
|
|
if (vals.get('l10n_be_blackbox_be_id') or self.filtered("l10n_be_pos_id")):
|
|
# TODO-FW-19.1: adapt based on this PR: https://github.com/odoo/odoo/pull/229561
|
|
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 _check_l10n_be_before_opening(self):
|
|
for config in self.filtered('l10n_be_pos_id'):
|
|
config._check_blackbox_required()
|
|
for config in self.filtered('l10n_be_blackbox_be_id'):
|
|
config._check_cashier_l10n_be_insz_or_bis_number()
|
|
config._check_cash_rounding()
|
|
config._check_iface_printers()
|
|
config._check_config_company_data()
|
|
config._check_blackbox()
|
|
|
|
def open_ui(self):
|
|
self._check_l10n_be_before_opening()
|
|
return super().open_ui()
|
|
|
|
def _check_blackbox_required(self):
|
|
for config in self:
|
|
if config._is_blackbox_v2_required() and not config.l10n_be_blackbox_be_id:
|
|
raise ValidationError(_(
|
|
"This POS has a registered POS ID and requires a blackbox to be configured."
|
|
))
|
|
|
|
def _is_blackbox_v2_required(self):
|
|
return bool(self.l10n_be_pos_id)
|
|
|
|
def _check_iface_printers(self):
|
|
for config in self:
|
|
if not config.iface_print_auto:
|
|
raise ValidationError(_("Automatic Receipt Printing must be activated"))
|
|
if not config.iface_print_skip_screen:
|
|
raise ValidationError(_("Skip Preview Screen must be activated"))
|
|
|
|
def _check_config_company_data(self):
|
|
for config in self:
|
|
if not config.establishment_number:
|
|
raise ValidationError(_("An establishment number must be set for this POS configuration."))
|
|
if not config.company_id.vat:
|
|
raise ValidationError(_("A VAT number must be set for the company using this POS configuration."))
|
|
if not config.l10n_be_pos_id:
|
|
raise ValidationError(_("A POS ID must be set for this POS configuration."))
|
|
|
|
def _check_cashier_l10n_be_insz_or_bis_number(self):
|
|
for config in self:
|
|
if not self.env.user.l10n_be_insz_or_bis_number and not config.module_pos_hr:
|
|
raise ValidationError(
|
|
_(
|
|
"%(user)s must have an INSZ or BIS number.",
|
|
user=self.env.user.name,
|
|
)
|
|
)
|
|
|
|
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"')
|
|
)
|
|
|
|
@api.constrains('l10n_be_training_mode')
|
|
def _check_l10n_be_training_mode(self):
|
|
for record in self:
|
|
signed_order = self.env['pos.order'].search_count([
|
|
('session_id.config_id', '=', record.id),
|
|
('l10n_be_short_signature', '!=', False),
|
|
], limit=1)
|
|
|
|
if signed_order and record.l10n_be_training_mode:
|
|
raise ValidationError(_(
|
|
"You cannot enable training mode on a POS configuration that has already processed orders."
|
|
))
|
|
|
|
def _check_blackbox(self):
|
|
for config in self:
|
|
if not config.l10n_be_blackbox_be_id.local_ip:
|
|
raise ValidationError(_(
|
|
"The blackbox must have a local IP address configured."
|
|
))
|
|
|
|
@api.model
|
|
def _create_default_cashrounding(self):
|
|
cash_rounding = self.env.ref('l10n_be_pos_blackbox.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': 'l10n_be_pos_blackbox.default_l10n_be_cash_rounding',
|
|
'record': cash_rounding,
|
|
'noupdate': True,
|
|
}
|
|
])
|
|
return cash_rounding
|
|
return False
|
|
|
|
def _compute_odoo_version(self):
|
|
self.pos_version = exp_version()['server_serie']
|
|
|
|
@api.constrains('establishment_number')
|
|
def _check_pos_establishment_number(self):
|
|
for config in self:
|
|
if config.establishment_number and not config.is_valid_est_number(config.establishment_number):
|
|
raise ValidationError(
|
|
_(
|
|
"Invalid establishment number.\n\n"
|
|
"An establishment number must meet the following rules:\n"
|
|
"- It must contain exactly 10 digits\n"
|
|
"- The first digit must be between 2 and 8\n"
|
|
)
|
|
)
|
|
|
|
@api.model
|
|
def is_valid_est_number(self, value):
|
|
""" Validate an establishment unit number (estNo).
|
|
Rules:
|
|
- Length = 10
|
|
- Digits only
|
|
- Starts with a digit from '2' to '8'
|
|
- Must pass the modulo 97 check
|
|
- Example: 8789456149
|
|
"""
|
|
if not value:
|
|
return False
|
|
|
|
if not value.isdigit() or len(value) != 10:
|
|
return False
|
|
|
|
base = int(value[:8])
|
|
key = int(value[8:])
|
|
expected_key = 97 - (base % 97)
|
|
return key == expected_key
|