Merge branch '17.0-mig-account_ebics_batch' into '17.0'

[MIG] account_ebics_batch: Migration to 17.0

See merge request Noviat/Noviat_Generic/accounting-ebics!32
This commit is contained in:
Luc De Meyer 2024-02-02 07:35:00 +00:00
commit d8895a0e90
12 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,50 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
============================================
Module to enable batch import of EBICS files
============================================
This module adds a cron job for the automated import of EBICS files.
|
A Log is created during the import in order to document import errors.
If errors have been detected, the Batch Import Log state is set to 'error'.
When all EBICS Files have been imported correctly, the Batch Import Log state is set to 'done'.
|
The user can reprocess the imported EBICS files in status 'draft' via the Log object 'REPROCESS' button until all errors have been cleared.
As an alternative, the user can force the Batch Import Log state to 'done'
(e.g. when the errors have been circumvented via manual encoding or the reprocessing of a single EBICS file).
|
Configuration
=============
Adapt the 'EBICS Batch Import' ir.cron job created during the module installation.
The cron job calls the following python method:
|
.. code-block:: python
_batch_import()
The EBICS download will be performed on all confirmed EBICS connections.
You can limit the automated operation to a subset of your EBICS connections via the ebics_config_ids parameter, e.g.
|
.. code-block:: python
_batch_import(ebics_config_ids=[1,3])

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,21 @@
# Copyright 2009-2024 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "EBICS Files batch import",
"version": "17.0.1.0.0",
"license": "AGPL-3",
"author": "Noviat",
"website": "https://www.noviat.com",
"category": "Accounting & Finance",
"summary": "EBICS Files automated import and processing",
"depends": ["account_ebics"],
"data": [
"security/ir.model.access.csv",
"data/ir_cron_data.xml",
"views/ebics_batch_log_views.xml",
"views/menu.xml",
],
"installable": True,
"images": ["static/description/cover.png"],
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<record id="ir_cron_ebics_batch_import" model="ir.cron">
<field name="name">EBICS Batch Import</field>
<field name="model_id" ref="model_ebics_batch_log" />
<field name="state">code</field>
<field name="code">model._batch_import()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active" eval="False" />
<field name="doall" eval="False" />
</record>
</odoo>

View File

@ -0,0 +1 @@
from . import ebics_batch_log

View File

@ -0,0 +1,204 @@
# Copyright 2009-2024 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from sys import exc_info
from traceback import format_exception
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class EbicsBatchLog(models.Model):
_name = "ebics.batch.log"
_description = "Object to store EBICS Batch Import Logs"
_order = "create_date desc"
date_from = fields.Date()
date_to = fields.Date()
ebics_config_ids = fields.Many2many(
comodel_name="ebics.config", string="EBICS Configurations"
)
log_ids = fields.One2many(
comodel_name="ebics.batch.log.item",
inverse_name="log_id",
string="Batch Import Log Items",
readonly=True,
)
file_ids = fields.Many2many(
comodel_name="ebics.file",
string="Batch Import EBICS Files",
readonly=True,
)
file_count = fields.Integer(
string="EBICS Files Count", compute="_compute_ebics_files_fields", readonly=True
)
has_draft_files = fields.Boolean(compute="_compute_ebics_files_fields")
state = fields.Selection(
selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")],
required=True,
readonly=True,
default="draft",
)
@api.depends("file_ids")
def _compute_ebics_files_fields(self):
for rec in self:
rec.has_draft_files = "draft" in rec.file_ids.mapped("state")
rec.file_count = len(rec.file_ids)
def unlink(self):
for log in self:
if log.state != "draft":
raise UserError(_("Only log objects in state 'draft' can be deleted !"))
return super().unlink()
def button_draft(self):
self.state = "draft"
def button_done(self):
self.state = "done"
def reprocess(self):
import_dict = {"errors": []}
self._ebics_process(import_dict)
self._finalise_processing(import_dict)
def view_ebics_files(self):
action = self.env["ir.actions.actions"]._for_xml_id(
"account_ebics.ebics_file_action_download"
)
action["domain"] = [("id", "in", self.file_ids.ids)]
return action
def _batch_import(self, ebics_config_ids=None, date_from=None, date_to=None):
"""
Call this method from a cron job to automate the EBICS import.
"""
log_model = self.env["ebics.batch.log"]
import_dict = {"errors": []}
configs = self.env["ebics.config"].browse(ebics_config_ids) or self.env[
"ebics.config"
].search(
[
("company_ids", "in", self.env.user.company_ids.ids),
("state", "=", "confirm"),
]
)
log = log_model.create(
{
"ebics_config_ids": [(6, 0, configs.ids)],
"date_from": date_from,
"date_to": date_to,
}
)
ebics_file_ids = []
for config in configs:
err_msg = (
_("Error while processing EBICS connection '%s' :\n") % config.name
)
if config.state == "draft":
import_dict["errors"].append(
err_msg
+ _(
"Please set state to 'Confirm' and "
"Reprocess this EBICS Import Log."
)
)
continue
if not any(config.mapped("ebics_userid_ids.ebics_passphrase_store")):
import_dict["errors"].append(
err_msg
+ _(
"No EBICS UserID with stored passphrase found.\n"
"You should configure such a UserID for automated downloads."
)
)
continue
try:
with self.env.cr.savepoint():
ebics_file_ids += self._ebics_import(
config, date_from, date_to, import_dict
)
except UserError as e:
import_dict["errors"].append(err_msg + " ".join(e.args))
except Exception:
tb = "".join(format_exception(*exc_info()))
import_dict["errors"].append(err_msg + tb)
log.file_ids = [(6, 0, ebics_file_ids)]
try:
log._ebics_process(import_dict)
except UserError as e:
import_dict["errors"].append(err_msg + " ".join(e.args))
except Exception:
tb = "".join(format_exception(*exc_info()))
import_dict["errors"].append(err_msg + tb)
log._finalise_processing(import_dict)
def _finalise_processing(self, import_dict):
log_item_model = self.env["ebics.batch.log.item"]
state = self.has_draft_files and "draft" or "done"
note = ""
error_count = 0
if import_dict["errors"]:
state = "error"
note = "\n\n".join(import_dict["errors"])
error_count = len(import_dict["errors"])
log_item_model.create(
{
"log_id": self.id,
"state": state,
"note": note,
"error_count": error_count,
}
)
self.state = state
def _ebics_import(self, config, date_from, date_to, import_dict):
ebics_userids = config.ebics_userid_ids.filtered(
lambda r: r.ebics_passphrase_store
)
t_userids = ebics_userids.filtered(lambda r: r.signature_class == "T")
ebics_userid = t_userids and t_userids[0] or ebics_userids[0]
xfer_wiz = (
self.env["ebics.xfer"]
.with_context(ebics_download=True)
.create(
{
"ebics_config_id": config.id,
"date_from": date_from,
"date_to": date_to,
}
)
)
xfer_wiz._onchange_ebics_config_id()
xfer_wiz.ebics_userid_id = ebics_userid
res = xfer_wiz.ebics_download()
file_ids = res["context"].get("ebics_file_ids", [])
if res["context"]["err_cnt"]:
import_dict["errors"].append(xfer_wiz.note)
return file_ids
def _ebics_process(self, import_dict):
to_process = self.file_ids.filtered(lambda r: r.state == "draft")
for ebics_file in to_process:
ebics_file.process()
class EbicsBatchLogItem(models.Model):
_name = "ebics.batch.log.item"
_description = "Object to store EBICS Batch Import Log Items"
_order = "create_date desc"
log_id = fields.Many2one(
comodel_name="ebics.batch.log",
string="Batch Object",
ondelete="cascade",
readonly=True,
)
state = fields.Selection(
selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")],
required=True,
readonly=True,
)
note = fields.Text(string="Batch Import Log", readonly=True)
error_count = fields.Integer(string="Number of Errors", required=True, default=0)

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ebics_batch_log,ebics.batch.log,model_ebics_batch_log,account.group_account_invoice,1,1,1,1
access_ebics_batch_log_item,ebics.batch.log.item,model_ebics_batch_log_item,account.group_account_invoice,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ebics_batch_log ebics.batch.log model_ebics_batch_log account.group_account_invoice 1 1 1 1
3 access_ebics_batch_log_item ebics.batch.log.item model_ebics_batch_log_item account.group_account_invoice 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,118 @@
<?xml version="1.0" ?>
<odoo>
<record id="ebics_batch_log_view_search" model="ir.ui.view">
<field name="name">ebics.batch.log.search</field>
<field name="model">ebics.batch.log</field>
<field name="arch" type="xml">
<search string="Search EBICS Batch Import Log Files">
<group col="10" colspan="4">
<field name="create_date" />
<field name="state" />
</group>
<newline />
<group expand="0" string="Group By">
<filter name="group_by_state" string="State" context="{'group_by':'state'}" />
</group>
</search>
</field>
</record>
<record id="ebics_batch_log_view_tree" model="ir.ui.view">
<field name="name">ebics.batch.log.tree</field>
<field name="model">ebics.batch.log</field>
<field name="arch" type="xml">
<tree create="false">
<field name="create_date" />
<field name="file_count" />
<field name="state" />
</tree>
</field>
</record>
<record id="ebics_batch_log_view_form" model="ir.ui.view">
<field name="name">ebics.batch.log.form</field>
<field name="model">ebics.batch.log</field>
<field name="arch" type="xml">
<form string="EBICS Batch Import Log" create="false">
<header>
<button
name="button_draft"
invisible="state not in ('done', 'error')"
string="Set to Draft"
type="object"
/>
<button
name="reprocess"
string="Reprocess"
help="Reprocess 'draft' EBICS Files"
type="object"
class="oe_highlight"
invisible="state, '=', 'done' or not has_draft_files"
/>
<button
name="button_done"
invisible="state not in ('done', 'error')"
string="Mark Done"
type="object"
/>
<field
name="state"
widget="statusbar"
statusbar_visible="draft,done"
statusbar_colors="{'error':'red'}"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button
name="view_ebics_files"
type="object"
class="oe_stat_button"
icon="fa-pencil-square-o"
invisible="file_count == 0"
>
<field name="file_count" widget="statinfo" string="EBICS Files" />
</button>
</div>
<group colspan="4" col="4">
<field name="create_date" />
<field name="ebics_config_ids" widget="many2many_tags" />
<field name="has_draft_files" invisible="1" />
</group>
<notebook colspan="4">
<page string="Batch Import Logs">
<field name="log_ids" nolabel="1">
<tree>
<field name="create_date" />
<field name="state" />
<field name="error_count" />
</tree>
<form string="Batch Import Log">
<group colspan="4" col="6">
<field name="create_date" />
<field name="error_count" />
</group>
<group invisible="not note">
<separator colspan="4" />
<field name="note" nolabel="1" colspan="4" height="360" />
</group>
</form>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="ebics_batch_log_action" model="ir.actions.act_window">
<field name="name">EBICS Batch Import Logs</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.batch.log</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="ebics_batch_log_view_tree" />
<field name="search_view_id" ref="ebics_batch_log_view_search" />
</record>
</odoo>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" ?>
<odoo>
<menuitem
id="ebics_batch_log_menu"
name="EBICS Batch Import Logs"
parent="account_ebics.ebics_processing_menu"
action="ebics_batch_log_action"
sequence="100"
/>
</odoo>