1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_de_pos_cert/models/res_company.py
2024-12-10 09:04:09 +07:00

202 lines
10 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, _, api
from odoo.addons.iap import jsonrpc
from odoo.exceptions import ValidationError, UserError
import requests
from requests.exceptions import ConnectTimeout
from urllib.parse import urljoin
DEFAULT_ENDPOINT = 'https://l10n-de-pos.api.odoo.com/api/l10n_de_pos'
class ResCompany(models.Model):
_inherit = 'res.company'
l10n_de_fiskaly_api_key = fields.Char(string="Fiskaly API Key", groups="base.group_erp_manager")
l10n_de_fiskaly_api_secret = fields.Char(string="Fiskaly API Secret", groups="base.group_erp_manager")
l10n_de_fiskaly_organization_id = fields.Char(string="Fiskaly Organization ID")
is_country_germany = fields.Boolean(string="Company located in Germany", compute='_compute_is_country_germany')
l10n_de_fiskaly_kassensichv_token = fields.Char(string="Fiskaly Kassensichv Token", groups="base.group_erp_manager", help="Store the temporary token used for the Kassensichv API")
l10n_de_fiskaly_dsfinvk_token = fields.Char(string="Fiskaly DSFinV-K Token", groups="base.group_erp_manager", help="Store the temporary token used for the DSFinV-K API")
@api.depends('country_id')
def _compute_is_country_germany(self):
for company in self:
company.is_country_germany = company.country_id.code == 'DE'
def write(self, values):
res = super().write(values)
for company in self:
if company.l10n_de_is_germany_and_fiskaly():
on_change_fields = ['name', 'street', 'street2', 'zip', 'city', 'vat', 'l10n_de_stnr',
'l10n_de_widnr']
if set(on_change_fields) & set(values):
params = company._l10n_de_create_organization_payload()
self._l10n_de_fiskaly_iap_rpc('/update', params=params)
return res
def l10n_de_is_germany_and_fiskaly(self):
return self.is_country_germany and self.l10n_de_fiskaly_organization_id
@api.model
def _l10n_de_fiskaly_kassensichv_url(self):
return self.env['ir.config_parameter'].sudo().get_param('l10n_de_fiskaly_kassensichv_url', 'https://kassensichv-middleware.fiskaly.com')
@api.model
def _l10n_de_fiskaly_dsfinvk_api_url(self):
return self.env['ir.config_parameter'].sudo().get_param('l10n_de_fiskaly_dsfinvk_url', 'https://dsfinvk.fiskaly.com')
def _l10n_de_fiskaly_kassensichv_auth(self, version):
"""
Return the url and headers containing the token to use the Kassensichv API.
"""
url = urljoin(self._l10n_de_fiskaly_kassensichv_url(), '/api/v%s' % version)
if not self.sudo().l10n_de_fiskaly_kassensichv_token:
auth_response = requests.post(url + '/auth', json={
'api_secret': self.sudo().l10n_de_fiskaly_api_secret,
'api_key': self.sudo().l10n_de_fiskaly_api_key
}, timeout=300)
if auth_response.status_code == 401:
raise ValidationError(_("The combination of your Fiskaly API key and secret is incorrect. " \
"Please update them in your company settings"))
self.sudo().l10n_de_fiskaly_kassensichv_token = auth_response.json()['access_token']
headers = {'Authorization': 'Bearer ' + self.sudo().l10n_de_fiskaly_kassensichv_token}
return url, headers
def _l10n_de_fiskaly_kassensichv_rpc(self, method, path, json=None, version=2, recursive=False):
try:
timeout = 300
url, headers = self._l10n_de_fiskaly_kassensichv_auth(version)
if method == 'GET':
res = requests.get(url + path, headers=headers, timeout=timeout)
elif method == 'POST':
res = requests.post(url + path, headers=headers, json=json, timeout=timeout)
elif method == 'PUT':
res = requests.put(url + path, headers=headers, json=json, timeout=timeout)
elif method == 'PATCH':
res = requests.patch(url + path, headers=headers, json=json, timeout=timeout)
else:
raise UserError(_('Invalid method'))
if res.status_code == 401 and not recursive:
self.sudo().l10n_de_fiskaly_kassensichv_token = None
res = self._l10n_de_fiskaly_kassensichv_rpc(method, path, json, version, True)
res.raise_for_status()
return res
except ConnectionError:
raise UserError(_("Connection lost between Odoo and Fiskaly."))
except ConnectTimeout:
raise UserError(_("There are some connection issues between us and Fiskaly, try again later."))
def _l10n_de_fiskaly_dsfinvk_auth(self, version):
"""
Return the url and headers containing the token to use the DSFinV-K API.
"""
url = urljoin(self._l10n_de_fiskaly_dsfinvk_api_url(), '/api/v%s' % version)
if not self.sudo().l10n_de_fiskaly_dsfinvk_token:
auth_response = requests.post(url + '/auth', json={
'api_secret': self.sudo().l10n_de_fiskaly_api_secret,
'api_key': self.sudo().l10n_de_fiskaly_api_key
}, timeout=300)
if auth_response.status_code == 401:
raise ValidationError(_("The combination of your Fiskaly API key and secret is incorrect. " \
"Please update them in your company settings"))
self.sudo().l10n_de_fiskaly_dsfinvk_token = auth_response.json()['access_token']
headers = {'Authorization': 'Bearer ' + self.sudo().l10n_de_fiskaly_dsfinvk_token}
return url, headers
def _l10n_de_fiskaly_dsfinvk_rpc(self, method, path, json=None, version=0, recursive=False):
try:
timeout = 300
url, headers = self._l10n_de_fiskaly_dsfinvk_auth(version)
if method == 'GET':
res = requests.get(url + path, headers=headers, timeout=timeout)
elif method == 'PUT':
res = requests.put(url + path, headers=headers, json=json, timeout=timeout)
else:
raise UserError(_('Invalid method'))
if res.status_code == 401 and not recursive:
self.sudo().l10n_de_fiskaly_dsfinvk_token = None
res = self._l10n_de_fiskaly_dsfinvk_rpc(method, path, json, version, True)
return res
except ConnectionError:
raise UserError(_("Connection lost between Odoo and Fiskaly."))
except ConnectTimeout:
raise UserError(_("There are some connection issues between us and Fiskaly, try again later."))
def _l10n_de_fiskaly_iap_rpc(self, path, params, version=1):
endpoint = self.env['ir.config_parameter'].sudo().get_param('l10n_de_fiskaly_iap.endpoint', DEFAULT_ENDPOINT)
base_url = '%s/%s' % (endpoint, version)
response = jsonrpc(base_url + path, params=params)
if 'error' in response:
raise UserError(response['error'])
return response
def _l10n_de_check_required_fiskaly_fields(self):
name = ' '.join(self.name.split())
if name.isspace() or len(name) < 3:
raise ValidationError(_("The name should be at least 3 characters long"))
if not self.street or not self.street.strip():
raise ValidationError(_("The street should not be empty"))
if not self.zip or not self.zip.strip():
raise ValidationError(_("The zip should not be empty"))
if not self.city or not self.city.strip():
raise ValidationError(_("The city should not be empty"))
if not self.vat or not self.vat.strip():
raise ValidationError(_("The VAT should not be empty"))
def _l10n_de_create_db_payload(self):
params = {
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
'company_id': self.id,
}
if self.l10n_de_fiskaly_organization_id:
params['organization_id'] = self.l10n_de_fiskaly_organization_id
return params
def _l10n_de_create_organization_payload(self):
self._l10n_de_check_required_fiskaly_fields()
db_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
data = {
'name': '{0} [#{1}]'.format(self.name, db_uuid), # To make sure the name is always unique
'address_line1': self.street,
'zip': self.zip,
'town': self.city,
'country_code': 'DEU', # It will always be Germany since this module will never be active for others
'display_name': self.name,
'address_line2': self.street2,
'vat_id': self.vat,
'tax_number': self.l10n_de_stnr if self.l10n_de_stnr else '',
'economy_id': self.l10n_de_widnr if self.l10n_de_widnr else '',
}
return {'data': data, **self._l10n_de_create_db_payload()}
def l10n_de_action_fiskaly_register(self):
"""
Send a request to Fiskaly in order to register the company at the financial authority.
"""
self.ensure_one()
if not self.l10n_de_fiskaly_organization_id:
params = self._l10n_de_create_organization_payload()
response = self._l10n_de_fiskaly_iap_rpc('/register', params)
if not response.get('ignored'):
if response.get('api_key'):
self.write({
'l10n_de_fiskaly_organization_id': response['organization_id'],
'l10n_de_fiskaly_api_key': response['api_key'],
'l10n_de_fiskaly_api_secret': response['api_secret'],
})
else: # the request to create credentials failed but the company was still well registered
self.l10n_de_fiskaly_organization_id = response['organization_id']
def l10n_de_action_fiskaly_create_new_keys(self):
self.ensure_one()
params = self._l10n_de_create_db_payload()
response = self._l10n_de_fiskaly_iap_rpc('/new_credentials', params=params)
self.write({
'l10n_de_fiskaly_api_key': response['api_key'],
'l10n_de_fiskaly_api_secret': response['api_secret'],
})