166 lines
6.0 KiB
Python
166 lines
6.0 KiB
Python
import uuid
|
|
import requests
|
|
|
|
from odoo import _, api, fields, models
|
|
from requests.exceptions import Timeout, ConnectionError, HTTPError
|
|
|
|
DEMO_EFAKTURA_URL = 'https://demoefaktura.mfin.gov.rs/api/publicApi/sales-invoice/ubl'
|
|
EFAKTURA_URL = 'https://efakturadev.mfin.gov.rs/api/publicApi/sales-invoice/ubl'
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = 'account.move'
|
|
|
|
l10n_rs_edi_uuid = fields.Char(
|
|
string="RS Invoice UUID",
|
|
compute="_compute_l10n_rs_edi_uuid",
|
|
copy=False,
|
|
store=True,
|
|
help="Unique Identifier for an invoice used as request id",
|
|
)
|
|
|
|
l10n_rs_edi_is_eligible = fields.Boolean(
|
|
compute="_compute_l10n_rs_edi_is_eligible",
|
|
store=True,
|
|
help="Technical field to determine if this invoice is eligible to be e-invoiced.",
|
|
)
|
|
|
|
l10n_rs_edi_attachment_file = fields.Binary(
|
|
string="Serbian E-Invoice XML File",
|
|
copy=False,
|
|
attachment=True,
|
|
help="Serbia: technical field holding the e-invoice XML data.",
|
|
)
|
|
|
|
l10n_rs_edi_attachment_id = fields.Many2one(
|
|
comodel_name='ir.attachment',
|
|
string="eFaktura XML Attachment",
|
|
compute=lambda self: self._compute_linked_attachment_id('l10n_rs_edi_attachment_id', 'l10n_rs_edi_attachment_file'),
|
|
depends=['l10n_rs_edi_attachment_file'],
|
|
)
|
|
|
|
l10n_rs_edi_state = fields.Selection(
|
|
string="Serbia E-Invoice state",
|
|
selection=[
|
|
('sent', 'Sent'),
|
|
('sending_failed', 'Error'),
|
|
],
|
|
tracking=True,
|
|
readonly=True,
|
|
copy=False,
|
|
)
|
|
|
|
l10n_rs_edi_error = fields.Text(
|
|
string="Serbia E-Invoice error",
|
|
copy=False,
|
|
readonly=True,
|
|
)
|
|
|
|
l10n_rs_tax_date_obligations_code = fields.Selection(
|
|
string="Tax Date Obligations",
|
|
selection=[
|
|
('35', 'By Delivery Date'),
|
|
('3', 'By Issuance Date'),
|
|
('432', 'By Billing System'),
|
|
],
|
|
store=True,
|
|
readonly=False,
|
|
compute='_compute_l10n_rs_tax_date_obligations_code',
|
|
)
|
|
l10n_rs_edi_invoice = fields.Char(string="Invoice Id", copy=False)
|
|
l10n_rs_edi_sales_invoice = fields.Char(string="Sales Invoice Id", copy=False)
|
|
l10n_rs_edi_purchase_invoice = fields.Char(string="Purchase Invoice Id", copy=False)
|
|
|
|
@api.depends('country_code', 'move_type')
|
|
def _compute_show_delivery_date(self):
|
|
# EXTENDS 'account'
|
|
super()._compute_show_delivery_date()
|
|
for move in self:
|
|
if move.l10n_rs_edi_is_eligible:
|
|
move.show_delivery_date = True
|
|
|
|
@api.depends("country_code", "move_type")
|
|
def _compute_l10n_rs_edi_is_eligible(self):
|
|
for move in self:
|
|
move.l10n_rs_edi_is_eligible = move.country_code == 'RS' and move.is_sale_document() and move.l10n_rs_edi_state in (False, 'sending_failed')
|
|
|
|
@api.depends('country_code')
|
|
def _compute_l10n_rs_tax_date_obligations_code(self):
|
|
for move in self:
|
|
if move.country_code == 'RS':
|
|
move.l10n_rs_tax_date_obligations_code = '3'
|
|
|
|
@api.depends('l10n_rs_edi_state')
|
|
def _compute_show_reset_to_draft_button(self):
|
|
# EXTENDS 'account'
|
|
super()._compute_show_reset_to_draft_button()
|
|
for move in self:
|
|
if move.show_reset_to_draft_button and move.l10n_rs_edi_state == 'sent':
|
|
move.show_reset_to_draft_button = False
|
|
|
|
@api.depends('l10n_rs_edi_is_eligible')
|
|
def _compute_l10n_rs_edi_uuid(self):
|
|
for move in self:
|
|
if move.l10n_rs_edi_is_eligible and not move.l10n_rs_edi_uuid:
|
|
move.l10n_rs_edi_uuid = uuid.uuid4()
|
|
|
|
def button_draft(self):
|
|
# EXTENDS 'account'
|
|
self.write({
|
|
"l10n_rs_edi_error": False,
|
|
"l10n_rs_edi_state": False,
|
|
})
|
|
return super().button_draft()
|
|
|
|
def _l10n_rs_edi_send(self, send_to_cir):
|
|
self.ensure_one()
|
|
self.env['res.company']._with_locked_records(self)
|
|
xml, errors = self.env['account.edi.xml.ubl.rs']._export_invoice(self)
|
|
if errors:
|
|
return xml, errors
|
|
params = {
|
|
'requestId': self.l10n_rs_edi_uuid,
|
|
'sendToCir': 'Yes' if send_to_cir else 'No'
|
|
}
|
|
url = DEMO_EFAKTURA_URL if self.company_id.l10n_rs_edi_demo_env else EFAKTURA_URL
|
|
headers = {
|
|
'Content-Type': 'application/xml',
|
|
'ApiKey': self.company_id.l10n_rs_edi_api_key,
|
|
}
|
|
error_message = False
|
|
try:
|
|
response = requests.post(url=url, params=params, headers=headers, data=xml, timeout=30)
|
|
response.raise_for_status()
|
|
except (Timeout, ConnectionError, HTTPError) as exception:
|
|
error_message = _("There was a problem with the connection with eFaktura: %s", exception)
|
|
self.message_post(body=error_message)
|
|
return xml, error_message
|
|
dict_response = {}
|
|
try:
|
|
dict_response = response.json()
|
|
except requests.exceptions.JSONDecodeError as e:
|
|
error_message = _("Invalid response from eFaktura: %s", str(e))
|
|
self.l10n_rs_edi_state = 'sending_failed' if error_message else 'sent'
|
|
self.l10n_rs_edi_error = error_message
|
|
self.l10n_rs_edi_invoice = dict_response.get('InvoiceId')
|
|
self.l10n_rs_edi_purchase_invoice = dict_response.get('PurchaseInvoiceId')
|
|
self.l10n_rs_edi_sales_invoice = dict_response.get('SalesInvoiceId')
|
|
return xml, error_message
|
|
|
|
def _l10n_rs_edi_get_attachment_values(self, xml):
|
|
self.ensure_one()
|
|
return {
|
|
'name': self._l10n_rs_edi_get_xml_attachment_name(),
|
|
'mimetype': 'application/xml',
|
|
'description': _('RS E-Invoice: %s', self.move_type),
|
|
'company_id': self.company_id.id,
|
|
'res_id': self.id,
|
|
'res_model': self._name,
|
|
'res_field': 'l10n_rs_edi_attachment_file',
|
|
'raw': xml,
|
|
'type': 'binary',
|
|
}
|
|
|
|
def _l10n_rs_edi_get_xml_attachment_name(self):
|
|
return f"{self.name.replace('/', '_')}_edi.xml"
|