Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import wizard_dms_classification

View file

@ -0,0 +1,208 @@
# Copyright 2024 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import base64
import re
import zipfile
from io import BytesIO
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class WizardDmsClassification(models.TransientModel):
_name = "wizard.dms.classification"
_description = "Wizard Dms Classification"
state = fields.Selection(
selection=[
("draft", "Draft"),
("analyze", "Analyze"),
],
default="draft",
)
template_id = fields.Many2one(
comodel_name="dms.classification.template",
string="Template",
required=True,
)
data_file = fields.Binary(
string="File",
required=True,
)
data_filename = fields.Char()
detail_ids = fields.One2many(
comodel_name="wizard.dms.classification.detail",
inverse_name="parent_id",
string="Details",
)
def _is_zipfile(self):
try:
zipfile.ZipFile(BytesIO(base64.b64decode(self.data_file)))
return True
except (zipfile.BadZipFile, ValueError):
return False
@api.onchange("data_file")
def _onchange_data_file(self):
for item in self.filtered("data_file"):
if not item._is_zipfile():
raise UserError(_("Only .zip files are allowed"))
def _return_item(self):
return {
"context": self.env.context,
"view_type": "form",
"view_mode": "form",
"res_model": self._name,
"res_id": self.id,
"view_id": False,
"type": "ir.actions.act_window",
"target": "new",
}
def _get_directory_from_pattern(self, pattern, directories):
directory = False
for d in directories:
if re.search(pattern, d.complete_name):
directory = d
break
return directory
def action_analyze(self):
"""Process the zip file and generate details."""
details = self._prepare_details_vals()
self.state = "analyze"
self.detail_ids = [(0, 0, vals) for vals in details]
return self._return_item()
def _prepare_details_vals(self):
"""Method that gets the files from .zip and if it apply the filename pattern
it will set it as detail with the corresponding values."""
details = []
zip_file = zipfile.ZipFile(BytesIO(base64.b64decode(self.data_file)))
filename_pattern = self.template_id.filename_pattern
for zip_info in zip_file.infolist():
if zip_info.is_dir():
continue
filename = zip_info.filename
if re.search(filename_pattern, filename):
file_content = zip_file.read(filename)
data_file = base64.b64encode(file_content)
details.append(self._prepare_detail_vals(filename, data_file))
return details
def _prepare_detail_vals(self, full_path, data_file):
"""Method to set the values of each detail. May be extended by other modules.
Clean full_path (remove / from folders)."""
return {
"full_path": full_path,
"data_file": data_file,
}
def _action_classify(self):
"""Create the files (dms.file) in the corresponding directory.
Details that do not have a directory or already have a linked
file are skipped."""
for detail in self.detail_ids.filtered(
lambda x: x.state == "to_classify" and x.directory_id
):
detail._create_dms_file()
def action_classify(self):
self._action_classify()
action = self.env["ir.actions.act_window"]._for_xml_id("dms.action_dms_file")
action["view_mode"] = "tree"
action["views"] = [(False, "tree")]
action["domain"] = [("id", "in", self.mapped("detail_ids.file_id").ids)]
return action
class WizardDmsClassificationDetail(models.TransientModel):
_name = "wizard.dms.classification.detail"
_description = "Wizard Dms Classification Detail"
parent_id = fields.Many2one(
comodel_name="wizard.dms.classification",
string="Parent",
)
full_path = fields.Char(
string="Full path",
required=True,
readonly="True",
)
file_name = fields.Char(
compute="_compute_file_name",
store=True,
string="File name",
)
data_file = fields.Binary(
string="File content",
required=True,
)
directory_id = fields.Many2one(
comodel_name="dms.directory",
string="Directory",
compute="_compute_directory_id",
store=True,
readonly=False,
)
file_id = fields.Many2one(
comodel_name="dms.file",
string="File",
compute="_compute_file_id",
store=True,
readonly=True,
)
state = fields.Selection(
selection=[
("to_classify", "To classify"),
("classified", "Classified"),
],
compute="_compute_state",
store=True,
readonly=True,
)
@api.depends("full_path")
def _compute_file_name(self):
"""File_name field is used to set file_id."""
for item in self:
name = item.full_path
if "/" in name:
name = name.split("/")[-1]
item.file_name = name
@api.depends("file_name")
def _compute_directory_id(self):
directories = self.env["dms.directory"].sudo().search([])
for item in self:
item.directory_id = self.parent_id._get_directory_from_pattern(
self.parent_id.template_id.directory_pattern, directories
)
@api.depends("file_name", "directory_id", "parent_id.state")
def _compute_file_id(self):
for item in self.filtered(lambda x: x.file_name and x.directory_id):
files = item.directory_id.file_ids.filtered(
lambda x: x.name == item.file_name
)
item.file_id = fields.first(files)
@api.depends("file_id")
def _compute_state(self):
items_with_file = self.filtered("file_id")
items_with_file.state = "classified"
(self - items_with_file).state = "to_classify"
def _create_dms_file(self):
self.ensure_one()
if not self.directory_id or self.file_id:
return
self.file_id = self.env["dms.file"].create(
{
"name": self.file_name,
"directory_id": self.directory_id.id,
"content": self.data_file,
}
)

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 Tecnativa - Víctor Martínez
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_wizard_dms_classification_form" model="ir.ui.view">
<field name="model">wizard.dms.classification</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar" readonly="1" />
</header>
<group>
<field
name="template_id"
attrs="{'readonly' : [('state', '!=', 'draft')]}"
/>
<field
name="data_file"
attrs="{'readonly' : [('state', '!=', 'draft')]}"
filename="data_filename"
/>
<field name="data_filename" invisible="1" />
</group>
<notebook>
<page
name="detail_ids"
string="Details"
attrs="{'invisible' : [('state', '=', 'draft')]}"
>
<field name="detail_ids">
<tree editable="bottom" delete="false" create="false">
<field name="full_path" string="File name" />
<field name="file_name" invisible="1" />
<field name="directory_id" />
<field name="file_id" />
<field name="state" />
</tree>
</field>
</page>
</notebook>
<footer>
<button
name="action_analyze"
string="Analyze"
type="object"
class="btn-primary"
attrs="{'invisible' : [('state', '!=', 'draft')]}"
/>
<button
name="action_classify"
string="Classify"
type="object"
class="btn-primary"
attrs="{'invisible' : [('state', '!=', 'analyze')]}"
/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_wizard_dms_classification" model="ir.actions.act_window">
<field name="name">Auto Classification</field>
<field name="res_model">wizard.dms.classification</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="groups_id" eval="[(4, ref('dms.group_dms_user'))]" />
</record>
<menuitem
id="menu_wizard_dms_classification"
name="Auto Classification"
parent="dms.main_menu_dms"
sequence="40"
action="action_wizard_dms_classification"
groups="dms.group_dms_user"
/>
</odoo>