forked from Mapan/odoo17e
202 lines
10 KiB
Python
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'],
|
|
})
|