183 lines
9.1 KiB
Python
183 lines
9.1 KiB
Python
import re
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import ValidationError, RedirectWarning, UserError
|
|
from odoo.addons.l10n_at_pos.models.fiskaly_client import FiskalyClient, fiskaly_iap_rpc
|
|
|
|
|
|
class ResCompany(models.Model):
|
|
_inherit = 'res.company'
|
|
|
|
l10n_at_pos_is_tax_exempted = fields.Boolean(string="Tax exempted?", default=False, copy=False)
|
|
l10n_at_pos_test_mode = fields.Boolean(string="Test Fiskaly", default=False, copy=False)
|
|
# Fiskaly api fields
|
|
l10n_at_fiskaly_api_key = fields.Char(string="Fiskaly API Key (AUT)", copy=False)
|
|
l10n_at_fiskaly_api_secret = fields.Char(string="Fiskaly API Secret (AUT)", copy=False)
|
|
l10n_at_fiskaly_access_token = fields.Char(string="API Access Token", copy=False)
|
|
l10n_at_fiskaly_organization_id = fields.Char(string="Fiskaly organization identifier", copy=False)
|
|
l10n_at_is_odoo_managed_org = fields.Boolean(string="Managed By Odoo", default=True, copy=False)
|
|
# FON fields
|
|
l10n_at_fon_participant_id = fields.Char(string="Participation Identifier", copy=False)
|
|
l10n_at_fon_user_id = fields.Char(string="User Identifier", copy=False)
|
|
l10n_at_fon_user_pin = fields.Char(string="User Pin", copy=False)
|
|
l10n_at_is_fon_authenticated = fields.Boolean(string="FON Authenticated?", copy=False)
|
|
# Company SCU
|
|
l10n_at_pos_company_scuid = fields.Char(string="Fiskaly SCU id (uuid)", readonly=True, copy=False)
|
|
|
|
@api.model
|
|
def _load_pos_data_fields(self, config):
|
|
fields = super()._load_pos_data_fields(config)
|
|
fields += ["l10n_at_is_fon_authenticated"]
|
|
return fields
|
|
|
|
def _l10n_at_create_db_payload(self, db_uuid):
|
|
params = {'db_uuid': db_uuid, 'company_id': self.id}
|
|
if self.l10n_at_fiskaly_organization_id:
|
|
params['organization_id'] = self.l10n_at_fiskaly_organization_id
|
|
return params
|
|
|
|
def _l10n_at_create_organization_payload(self):
|
|
self._l10n_at_check_required_fiskaly_fields()
|
|
db_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
|
|
data = {
|
|
'name': f'{self.name} [#{db_uuid}]', # To make sure the name is always unique
|
|
'vat_id': self.vat,
|
|
'display_name': self.display_name,
|
|
'address_line1': self.street,
|
|
'address_line2': self.street2,
|
|
'town': self.city,
|
|
'state': self.state_id.name,
|
|
'zip': self.zip,
|
|
'email': self.email,
|
|
'country_code': 'AUT',
|
|
}
|
|
return {'data': data, **self._l10n_at_create_db_payload(db_uuid)}
|
|
|
|
def _l10n_at_check_required_fiskaly_fields(self):
|
|
msg = ""
|
|
if len(self.name) < 3:
|
|
msg = _("The name should be at least 3 characters long")
|
|
if not self.street or not self.street.strip():
|
|
msg = _("The street should not be empty")
|
|
if not self.zip or not self.zip.strip():
|
|
msg = _("The zip should not be empty")
|
|
if not self.city or not self.city.strip():
|
|
msg = _("The city should not be empty")
|
|
if not self.vat or not self.vat.strip():
|
|
msg = _("The VAT should not be empty")
|
|
if msg:
|
|
raise UserError(msg)
|
|
|
|
def write(self, vals):
|
|
# ensuring values are correct
|
|
msg = ""
|
|
if vals.get('l10n_at_fon_participant_id') and len(vals.get('l10n_at_fon_participant_id')) < 8:
|
|
msg = _("FON participation id should NOT be shorter than 8 characters, please enter correct participation id")
|
|
if vals.get('l10n_at_fon_user_id') and len(vals.get('l10n_at_fon_user_id')) < 5:
|
|
msg = _("FON user id should NOT be shorter than 5 characters, please enter correct user id")
|
|
if vals.get('l10n_at_fon_user_pin') and len(vals.get('l10n_at_fon_user_pin')) < 5:
|
|
msg = _("FON user id should NOT be shorter than 5 characters, please verify your pin")
|
|
if msg:
|
|
raise UserError(msg)
|
|
|
|
# If necessary fields are removed, need to authenticate again
|
|
if any(key in vals for key in ('l10n_at_fiskaly_api_key', 'l10n_at_fiskaly_api_secret')) and self.l10n_at_fiskaly_access_token:
|
|
vals.update({"l10n_at_fiskaly_access_token": ""})
|
|
# New API key/secret means a new organization, so the old SCU and registers
|
|
# no longer exist for it. Clear them to recreate on re-auth.
|
|
vals.update({"l10n_at_pos_company_scuid": False})
|
|
self.env['pos.config'].search([('company_id', 'in', self.ids)]).l10n_at_cash_regid = False
|
|
|
|
if any(key in vals for key in ('l10n_at_fon_participant_id', 'l10n_at_fon_user_id', 'l10n_at_fon_user_pin')) and self.l10n_at_is_fon_authenticated:
|
|
# If any necessary fields are removed, need authentication again
|
|
vals.update({"l10n_at_is_fon_authenticated": False})
|
|
|
|
companies = super().write(vals)
|
|
for company in self:
|
|
if company.country_code == 'AT' and company.l10n_at_fiskaly_organization_id and company.l10n_at_is_odoo_managed_org:
|
|
on_change_fields = ['name', 'street', 'street2', 'zip', 'city', 'vat', 'state_id', 'email']
|
|
if set(on_change_fields) & set(vals):
|
|
params = company._l10n_at_create_organization_payload()
|
|
fiskaly_iap_rpc(self, '/update', params)
|
|
return companies
|
|
|
|
def action_l10n_at_authenticate_fon_credentials(self):
|
|
self.ensure_one()
|
|
fiskaly_client = FiskalyClient(self, self.l10n_at_fiskaly_api_key, self.l10n_at_fiskaly_api_secret)
|
|
fiskaly_client.fon_auth(
|
|
bearer_token=self.l10n_at_fiskaly_access_token,
|
|
data={
|
|
"fon_participant_id": self.l10n_at_fon_participant_id,
|
|
"fon_user_id": self.l10n_at_fon_user_id,
|
|
"fon_user_pin": self.l10n_at_fon_user_pin,
|
|
}
|
|
)
|
|
self.l10n_at_is_fon_authenticated = True
|
|
|
|
# Check if any existing configs which needs to be registered
|
|
pos_configs = self.env['pos.config'].search([('company_id', '=', self.id), ('l10n_at_cash_regid', '=', False)])
|
|
for pos_config in pos_configs:
|
|
pos_config._configure_register()
|
|
return self._notify("success", _("FON Authenticated Successfully!"))
|
|
|
|
def action_generate_fiskaly_credentials(self):
|
|
self.ensure_one()
|
|
if open_sessions := self.env['pos.session'].search([('company_id', '=', self.id), ('state', '!=', 'closed')]):
|
|
action = self.env.ref('point_of_sale.action_pos_config_kanban')
|
|
msg = _('Please close and validate the following open PoS Sessions before generating Fiskaly Credentials.\n'
|
|
'Open sessions: %s', (' '.join(open_sessions.mapped('name')),))
|
|
raise RedirectWarning(msg, action.id, _('Go to Point of Sale'))
|
|
|
|
params = self._l10n_at_create_organization_payload()
|
|
response = fiskaly_iap_rpc(self, '/register', params)
|
|
self.write({
|
|
'l10n_at_fiskaly_organization_id': response[0]['organization_id'],
|
|
'l10n_at_fiskaly_api_key': response[0]['l10n_at_fiskaly_api_key'],
|
|
'l10n_at_fiskaly_api_secret': response[0]['l10n_at_fiskaly_api_secret'],
|
|
})
|
|
|
|
return self.action_auth_fiskaly_credentials()
|
|
|
|
def action_auth_fiskaly_credentials(self, refresh=False):
|
|
self.auth_fiskaly_credentials(refresh=refresh)
|
|
return self._notify("success", _("Fiskaly Authentication Successful!"))
|
|
|
|
def auth_fiskaly_credentials(self, refresh=False):
|
|
"""Authenticate with Fiskaly and store the access token."""
|
|
self.ensure_one()
|
|
fiskaly_client = FiskalyClient(self, self.l10n_at_fiskaly_api_key, self.l10n_at_fiskaly_api_secret)
|
|
self.l10n_at_fiskaly_access_token = fiskaly_client.auth()
|
|
|
|
def _verify_required_fields(self):
|
|
missing_fields_by_company = {}
|
|
for company in self:
|
|
if company.l10n_at_fiskaly_access_token:
|
|
missing_fields = [label for field, label in [
|
|
(company.vat, _("VAT")),
|
|
(company.street, _("Street")),
|
|
(company.country_id, _("Country"))
|
|
] if not field]
|
|
|
|
if missing_fields:
|
|
missing_fields_by_company[company.name] = ", ".join(missing_fields)
|
|
if missing_fields_by_company:
|
|
message = "\n".join(
|
|
f"Company {company} has these fields missing: {fields}"
|
|
for company, fields in missing_fields_by_company.items()
|
|
)
|
|
raise ValidationError(_("The following companies have missing details required to use Fiskaly:\n%s", message))
|
|
|
|
# Austria vat format check
|
|
if not re.match(r"^ATU\d{8}$", self.vat):
|
|
raise UserError(_("Please enter a valid Austrian VAT number in the format ATU12345678"))
|
|
|
|
def _notify(self, notification_type, message=""):
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'type': notification_type,
|
|
'message': message,
|
|
'next': {'type': 'ir.actions.act_window_close'},
|
|
}
|
|
}
|