Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found
Sélectionner une révision Git
  • 16.0
  • 16.0-perimeter-upd
  • 16.0-refactor-perimeter
  • 16.0-stephane-full-dyn
4 résultats

Cible

Sélectionner le projet cible
  • lefilament/oacc/oacc_repartition_keys
  • arthur-enercoop/oacc_repartition_keys
2 résultats
Sélectionner une révision Git
  • 16.0
1 résultat
Afficher les modifications
Validations sur la source (23)
Affichage de
avec 1546 ajouts et 31 suppressions
env:
browser: true
es6: true
# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
parserOptions:
ecmaVersion: 2019
overrides:
- files:
- "**/*.esm.js"
parserOptions:
sourceType: module
# Globals available in Odoo that shouldn't produce errorings
globals:
_: readonly
$: readonly
fuzzy: readonly
jQuery: readonly
moment: readonly
odoo: readonly
openerp: readonly
owl: readonly
luxon: readonly
# Styling is handled by Prettier, so we only need to enable AST rules;
# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
rules:
accessor-pairs: warn
array-callback-return: warn
callback-return: warn
capitalized-comments:
- warn
- always
- ignoreConsecutiveComments: true
ignoreInlineComments: true
complexity:
- warn
- 15
constructor-super: warn
dot-notation: warn
eqeqeq: warn
global-require: warn
handle-callback-err: warn
id-blacklist: warn
id-match: warn
init-declarations: error
max-depth: warn
max-nested-callbacks: warn
max-statements-per-line: warn
no-alert: warn
no-array-constructor: warn
no-caller: warn
no-case-declarations: warn
no-class-assign: warn
no-cond-assign: error
no-const-assign: error
no-constant-condition: warn
no-control-regex: warn
no-debugger: error
no-delete-var: warn
no-div-regex: warn
no-dupe-args: error
no-dupe-class-members: error
no-dupe-keys: error
no-duplicate-case: error
no-duplicate-imports: error
no-else-return: warn
no-empty-character-class: warn
no-empty-function: error
no-empty-pattern: error
no-empty: warn
no-eq-null: error
no-eval: error
no-ex-assign: error
no-extend-native: warn
no-extra-bind: warn
no-extra-boolean-cast: warn
no-extra-label: warn
no-fallthrough: warn
no-func-assign: error
no-global-assign: error
no-implicit-coercion:
- warn
- allow: ["~"]
no-implicit-globals: warn
no-implied-eval: warn
no-inline-comments: warn
no-inner-declarations: warn
no-invalid-regexp: warn
no-irregular-whitespace: warn
no-iterator: warn
no-label-var: warn
no-labels: warn
no-lone-blocks: warn
no-lonely-if: error
no-mixed-requires: error
no-multi-str: warn
no-native-reassign: error
no-negated-condition: warn
no-negated-in-lhs: error
no-new-func: warn
no-new-object: warn
no-new-require: warn
no-new-symbol: warn
no-new-wrappers: warn
no-new: warn
no-obj-calls: warn
no-octal-escape: warn
no-octal: warn
no-param-reassign: warn
no-path-concat: warn
no-process-env: warn
no-process-exit: warn
no-proto: warn
no-prototype-builtins: warn
no-redeclare: warn
no-regex-spaces: warn
no-restricted-globals: warn
no-restricted-imports: warn
no-restricted-modules: warn
no-restricted-syntax: warn
no-return-assign: error
no-script-url: warn
no-self-assign: warn
no-self-compare: warn
no-sequences: warn
no-shadow-restricted-names: warn
no-shadow: warn
no-sparse-arrays: warn
no-sync: warn
no-this-before-super: warn
no-throw-literal: warn
no-undef-init: warn
no-undef: error
no-unmodified-loop-condition: warn
no-unneeded-ternary: error
no-unreachable: error
no-unsafe-finally: error
no-unused-expressions: error
no-unused-labels: error
no-unused-vars: error
no-use-before-define: error
no-useless-call: warn
no-useless-computed-key: warn
no-useless-concat: warn
no-useless-constructor: warn
no-useless-escape: warn
no-useless-rename: warn
no-void: warn
no-with: warn
operator-assignment: [error, always]
prefer-const: warn
radix: warn
require-yield: warn
sort-imports: warn
spaced-comment: [error, always]
strict: [error, function]
use-isnan: error
valid-jsdoc:
- warn
- prefer:
arg: param
argument: param
augments: extends
constructor: class
exception: throws
func: function
method: function
prop: property
return: returns
virtual: abstract
yield: yields
preferType:
array: Array
bool: Boolean
boolean: Boolean
number: Number
object: Object
str: String
string: String
requireParamDescription: false
requireReturn: false
requireReturnDescription: false
requireReturnType: false
valid-typeof: warn
yoda: warn
......@@ -3,19 +3,32 @@
"summary": "Module de gestion des clés de répartitions enedis OACC",
"author": "Le Filament",
"website": "https://le-filament.com",
"version": "16.0.1.0.0",
"version": "16.0.2.0.0",
"license": "AGPL-3",
"depends": ["oacc", "api_enedis_acc", "queue_job"],
"data": [
"security/ir.model.access.csv",
"security/security.xml",
# datas
# wizard
"wizard/acc_repartition_keys_wizard_views.xml",
"wizard/acc_repartition_keys_file_wizard_views.xml",
"wizard/acc_repartition_keys_compute_wizard_views.xml",
# views
"views/acc_operation_views.xml",
"views/acc_repartition_keys_views.xml",
"views/acc_priority_group_counter_views.xml",
"views/acc_priority_group_views.xml",
"views/acc_repartition_counter_views.xml",
# views menu
"views/menu_views.xml",
],
"assets": {
"web.assets_backend": [
"/oacc_repartition_keys/static/src/js/kanban_button.js",
"/oacc_repartition_keys/static/src/xml/kanban_button.xml",
"/oacc_repartition_keys/static/src/css/custom.css",
],
},
"installable": True,
"auto_install": False,
}
# Copyright 2023- Le Filament (https://le-filament.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
# Rename models to preserve fields during upgrade
openupgrade.rename_models(
env.cr, [("acc.repartition.keys", "acc.repartition.keys.file")]
)
openupgrade.rename_tables(
env.cr, [("acc_repartition_keys", "acc_repartition_keys_file")]
)
from . import acc_operation
from . import acc_repartition_keys
from . import acc_repartition_keys_file
from . import acc_priority_group
from . import acc_priority_group_counter
from . import acc_repartition_counter
# Copyright 2021- Le Filament (https://le-filament.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import logging
import base64
import csv
import math
from collections import OrderedDict
from datetime import date, datetime
from io import StringIO
from odoo import fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__)
from odoo.addons.api_connector.tools.date_utils import utc_to_local
def float_to_str_auto(number):
s = format(number, ".15f")
s = s.rstrip("0").rstrip(".") if "." in s else s
return s
def get_algo_description(algo):
desc = {
"prorata": "La clé de répartition est calculée automatiquement chaque mois,"
" au prorata de la consommation de chacun des consommateurs.",
"static": "La clé de répartition est calculée automatiquement chaque mois, "
"en fonction des coefficients de répartition communiqués "
"initialement à Enedis.",
"dyn_perso_send": "",
"dyn_perso_compute": "",
}
return desc.get(algo, f"Pas de description associée a {algo}")
class AccOperation(models.Model):
......@@ -13,13 +39,46 @@ class AccOperation(models.Model):
# ------------------------------------------------------
# Fields declaration
# ------------------------------------------------------
keys_repartition_ids = fields.One2many(
keys_file_repartition_ids = fields.One2many(
comodel_name="acc.repartition.keys.file",
inverse_name="operation_id",
string="keys files repartition",
required=False,
groups="oacc.group_operation_admin",
)
repartition_keys_id = fields.One2many(
comodel_name="acc.repartition.keys",
inverse_name="operation_id",
string="keys repartition",
string="Repartition key",
required=False,
groups="oacc.group_operation_superadmin",
groups="oacc.group_operation_admin",
)
acc_priority_group_ids = fields.One2many(
comodel_name="acc.priority.group",
inverse_name="acc_operation_id",
string="Groupe de priorité",
)
# uniquement pour des question d affichage
type_algo = fields.Selection(
[
("prorata", "Dynamique par défaut"),
("static", "Statique"),
("dyn_perso_send", "Dynamique simple - envoi de fichier"),
("dyn_perso_compute", "Dynamique simple - calcul automatisé"),
],
string="Type de clé de répartition",
default="prorata",
required=True,
)
algo_description = fields.Text(
string="Description de l algorithme",
default="",
)
# ------------------------------------------------------
# SQL Constraints
# ------------------------------------------------------
......@@ -27,6 +86,108 @@ class AccOperation(models.Model):
# ------------------------------------------------------
# Default methods
# ------------------------------------------------------
def compute_repartition(self):
"""
compute task wich compute keys and generate csv file
"""
self.generate()
def delete_keys(self):
repartitions = self.env["acc.repartition.keys"].search(
[("operation_id", "=", self.id)]
)
for repartition in repartitions:
keys = self.env["acc.repartition.counter"].search(
[("acc_repartition_id", "=", repartition.id)]
)
keys.unlink()
for repartition in repartitions:
repartition.unlink()
def get_affect_sum(self, data_slot):
affect_sum = 0.0
for counter in data_slot.get("affect"):
affect_sum += data_slot.get("affect").get(counter, 0)
return affect_sum
def generate(self):
"""
generate repartition keys
"""
if not self.acc_priority_group_ids:
raise ValidationError(
_("Aucune priorité n'est définie pour cette opération.")
)
self.delete_keys()
repartition = self.env["acc.repartition.keys"].create({"operation_id": self.id})
data = self.env["acc.enedis.raw.cdc"].get_repartition_data(operation_id=self)
# on sauvegarde prod_totale pour permettre en fin de traitement
# le calcul du pourcentage attribué à chaque compteur
for slot in data:
data[slot]["prod_initiale"] = data.get(slot).get("prod_totale")
for priority in self.acc_priority_group_ids:
data = priority.compute(data)
if not data:
raise ValidationError(
_("Pas de données brute pour le mois précedent présente")
)
# contrôle, l'ensemble des affectations ne
# doit pas dépasser la production à affecter
for slot in data:
item = data.get(slot)
affect = item.get("affect")
if affect:
total_prod = item.get("prod_initiale")
remaining_prod = item.get("prod_totale")
affect_sum = self.get_affect_sum(item)
if abs(affect_sum + remaining_prod - total_prod) > 1e-3:
raise ValidationError(
_(
"Une erreur s'est produite lors de "
"l'affectation de l'auto-consommation"
)
)
# enregistrement en base
for slot in data:
item = data.get(slot)
affect = item.get("affect")
if affect:
total_prod = item.get("prod_initiale")
# calcul du pourcentage attribué à chaque
# compteur par rapport à la production totale
weights = {}
for counter_id in affect:
affecte_counter = affect.get(counter_id)
if total_prod == 0:
weight = 0.0
else:
weight = (
math.floor((affecte_counter * 100 / total_prod) * 1e6) / 1e6
)
weights[counter_id] = weight
slot_line = []
for counter in weights:
slot_line.append(
{
"acc_repartition_id": repartition.id,
"time_slot": slot,
"weight": weights[counter],
"acc_counter_id": counter.id,
}
)
self.env["acc.repartition.counter"].create(slot_line)
# ------------------------------------------------------
# Computed fields / Search Fields
......@@ -36,10 +197,222 @@ class AccOperation(models.Model):
# Onchange / Constraints
# ------------------------------------------------------
@api.onchange("type_algo")
def on_change_algo(self):
self.algo_description = get_algo_description(self.type_algo)
# ------------------------------------------------------
# CRUD methods (ORM overrides)
# ------------------------------------------------------
def write(self, vals):
if vals.get("type_algo"):
vals.update(
{"algo_description": get_algo_description(vals.get("type_algo"))}
)
res = super().write(vals)
return res
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
def action_view_repartition_algo_priority_group(self):
"""
Action pour accéder aux algo de calcul defini
"""
action = self.env["ir.actions.actions"]._for_xml_id(
"oacc_repartition_keys.acc_priority_group_counter_act_window"
)
action["context"] = {
"default_acc_operation_id": self.id,
}
action["domain"] = [("acc_operation_id", "=", self.id)]
action["res_id"] = self.id
return action
def action_send_repartition_keys(self):
"""
Action d envoi des clés calculées a Enedis
"""
repartition = self.env["acc.repartition.keys"].search(
[("operation_id", "=", self.id)]
)
if not repartition:
raise UserError(_("Clés de répartition non générées"))
keys = (
self.env["acc.repartition.counter"]
.search([("acc_repartition_id", "=", repartition.id)])
.mapped("time_slot")
)
horodatages = list(OrderedDict.fromkeys(keys))
counter_period = self.get_counter_by_period_day(date_list=horodatages)
for time_slot in horodatages:
keys = self.env["acc.repartition.counter"].search(
[
("acc_repartition_id", "=", repartition.id),
("time_slot", "=", time_slot),
]
)
body = []
for key in keys:
if key.acc_counter_id.name in counter_period.get(
utc_to_local(time_slot, "Europe/Paris").date()
):
body.append(
{
"id": key.acc_counter_id.name,
"key": float_to_str_auto(key.weight),
}
)
if body:
data = {"timestamp": time_slot.strftime("%Y%m%dT%H%M%SZ"), "body": body}
job_description = (
f"{self.name} - Send repartition key at {data.get('timestamp')}"
)
try:
self.with_delay(description=job_description).send_repartition_key(
key=data
)
except ValidationError as exc:
raise UserError(_(str(exc))) from exc
def get_repartition_data_for_csv(self):
"""
generate data for csv
"""
repartition = self.env["acc.repartition.keys"].search(
[("operation_id", "=", self.id)]
)
keys = self.env["acc.repartition.counter"].search(
[("acc_repartition_id", "=", repartition.id)]
)
if not keys:
raise ValidationError(_("Clés de répartition non générées"))
counters = keys.mapped("acc_counter_id")
keys = keys.mapped("time_slot")
horodatages = list(OrderedDict.fromkeys(keys))
hearders = ["Horodate"] + [c.name for c in counters]
data = [hearders]
for time_slot in horodatages:
line = [utc_to_local(time_slot, "Europe/Paris")]
for counter in counters:
w = (
self.env["acc.repartition.counter"]
.search(
[
("acc_counter_id", "=", counter.id),
("time_slot", "=", time_slot),
]
)
.weight
)
line.append(str(w).replace(".", ","))
data.append(line)
return data
def export_repartition(self):
"""
Genere un fichier csv des repartitions
:param operation_id: operation
:return: file
"""
csv_data = self.get_repartition_data_for_csv()
try:
start_date = csv_data[1][0]
end_date = csv_data[-1][0]
d = csv_data[2][0] - start_date
except IndexError as e:
raise ValidationError(_("Pas de données")) from e
ts = int(d.total_seconds() / 60)
if isinstance(start_date, datetime) and isinstance(end_date, datetime):
filename = (
f"{self.name}_{ts}_"
f"{start_date.strftime('%d%m%Y')}_{end_date.strftime('%d%m%Y')}"
)
else:
filename = f"cle_de_repartition_{self.name}"
return self.create_csv(filename, csv_data)
# ------------------------------------------------------
# Common function
# ------------------------------------------------------
def create_csv(self, filename, lines_to_export):
fp = StringIO()
export_file = csv.writer(fp, delimiter=";", quoting=csv.QUOTE_NONE)
# Add header line
for line in lines_to_export:
# Format date value
line_values = [
value
if not isinstance(value, date)
else value.strftime("%d-%m-%Y %H:%M")
for value in line
]
export_file.writerow(line_values)
fp.seek(0)
data = fp.read()
fp.close()
filename = filename + ".csv"
# sauvegarde du fichier de cles
self.env["acc.repartition.keys.file"].create(
{
"csv_file": base64.b64encode(str.encode(data)),
"filename": filename,
"operation_id": self.id,
"date_send": datetime.now(),
}
)
def get_counter_by_period_day(self, date_list):
"""
return counter in period for simple day
{
day1 : [counter...],
day2 : [counter...],
}
"""
counter_period = {}
res = [
d.date()
for d in list(
OrderedDict.fromkeys(
[utc_to_local(h, "Europe/Paris") for h in date_list]
)
)
]
for date_item in res:
counter_period[date_item] = (
self.env["acc.counter.period"]
._get_periods_from_date(
[
("acc_operation_id", "=", self.id),
("prm_type", "=", "delivery"),
],
date_item,
)
.mapped("acc_counter_id.name")
)
return counter_period
from odoo import api, fields, models
class AccPriorityGroup(models.Model):
_name = "acc.priority.group"
_description = "Groupe de clés de répartition"
_rec_name = "display_name"
_order = "sequence, id"
# ------------------------------------------------------
# Fields declaration
# ------------------------------------------------------
acc_operation_id = fields.Many2one("acc.operation", "Opération", required=True)
sequence = fields.Integer(required=True, default=1)
type_algo = fields.Selection(
[
("prorata", "Répartition au prorata"),
("quotepart", "Répartition par quote-part"),
],
string="Répartition au sein du groupe",
default="prorata",
required=True,
)
display_name = fields.Char(
compute="_compute_display_name", store=True, readonly=True
)
acc_priority_group_counter_ids = fields.One2many(
comodel_name="acc.priority.group.counter",
inverse_name="acc_priority_group_id",
string="Affectation de compteur par groupes de priorité",
required=True,
)
counter_datas = fields.Json(compute="_compute_counter_datas")
total_share = fields.Float(compute="_compute_total_share", store=True)
# ------------------------------------------------------
# SQL Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# Default methods
# ------------------------------------------------------
# ------------------------------------------------------
# Computed fields / Search Fields
# ------------------------------------------------------
@api.depends(
"acc_priority_group_counter_ids",
"acc_priority_group_counter_ids.acc_counter_share",
)
def _compute_total_share(self):
for record in self:
record.total_share = sum(
record.acc_priority_group_counter_ids.mapped("acc_counter_share")
)
@api.depends("type_algo", "sequence")
def _compute_display_name(self):
for prio_group in self:
prio_group.display_name = f"Priorité {str(prio_group.sequence)}"
def _compute_counter_datas(self):
for priority in self:
priority.counter_datas = (
priority.acc_priority_group_counter_ids.acc_counter_id.mapped(
lambda q: {
"name": q.name,
"partner": q.partner_id.name,
"street": q.street,
}
)
)
# ------------------------------------------------------
# Onchange / Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# CRUD methods (ORM overrides)
# ------------------------------------------------------
def write(self, vals):
seq = vals.get("sequence")
if seq is not None:
vals["sequence"] = seq + 1
res = super().write(vals)
return res
@api.model
def unlink(self, _id):
self = self.env["acc.priority.group"].browse(_id)
current_operation_id = self.env.context.get("active_id")
existing_groups = self.env["acc.priority.group"].search(
[("acc_operation_id", "=", current_operation_id), ("id", "!=", _id)]
)
seq = 0
for group in existing_groups:
group.sequence = seq
seq += 1
return super().unlink()
@api.model_create_multi
def create(self, vals_list):
"""
si c est le premier groupe cree on y affecte tout les compteurs
"""
current_operation_id = self.env.context.get("active_id")
existing_groups = self.env["acc.priority.group"].search(
[
("acc_operation_id", "=", current_operation_id),
]
)
res = super().create(vals_list)
if existing_groups:
res.sequence = max(existing_groups.mapped("sequence"))
return res
counters_to_affect = self.env["acc.counter"].search(
[
("acc_operation_id", "=", res.acc_operation_id.id),
("type", "in", ["del", "del_inj"]),
]
)
for counter in counters_to_affect:
res.add_counter(counter_id=counter)
return res
def add_counter(self, counter_id):
self.env["acc.priority.group.counter"].create(
{
"acc_priority_group_id": self.id,
"acc_operation_id": self.acc_operation_id.id,
"acc_counter_id": counter_id.id,
"counter_street": counter_id.street,
"counter_owner": counter_id.partner_id.name,
}
)
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
def compute(self, data=None):
compute_algo = {
"prorata": self._prorata,
"quotepart": self._quotepart,
}
d = compute_algo[self.type_algo](data)
return d
def get_conso_sum(self, data_slot):
conso_sum = 0.0
for counter in self.acc_priority_group_counter_ids.acc_counter_id.mapped(
"name"
):
conso_sum += data_slot.get("conso").get(counter, 0)
return conso_sum
def _prorata(self, data):
for slot in data:
remaining_prod = data.get(slot).get("prod_totale")
total_conso = self.get_conso_sum(data.get(slot))
total_affecte = 0.0
if not data.get(slot).get("affect"):
data[slot]["affect"] = {}
for counter in self.acc_priority_group_counter_ids.acc_counter_id:
# un compteur peut être présent dans deux groupes de priorité
already_affected = data.get(slot).get("affect").get(counter, 0)
conso_k = data.get(slot).get("conso").get(counter.name)
# si le compteur rentre en cours de periode il figure dans le
# groupe de repartition mais pas dans les données brutes conso_k = None
# dans ce cas on affecte 0
if remaining_prod <= 0 or total_conso == 0 or conso_k is None:
newly_affected = 0
sum_affected = already_affected + 0.0
else:
part_a_affecter = (remaining_prod * conso_k) / total_conso
sum_affected = min(conso_k, (already_affected + part_a_affecter))
newly_affected = sum_affected - already_affected
data[slot]["affect"][counter] = sum_affected
total_affecte += newly_affected
# on met à jour la prod restant à affecter
data[slot]["prod_totale"] = remaining_prod - total_affecte
# possiblement on peut avoir affecté plus que la p
# roduction total_affecte=496.00000000000006
return data
def _quotepart(self, data):
total_share = self.total_share
for slot in data:
remaining_prod = data.get(slot).get("prod_totale")
total_affecte = 0.0
if not data.get(slot).get("affect"):
data[slot]["affect"] = {}
for priority_group_counter in self.acc_priority_group_counter_ids:
counter = priority_group_counter.acc_counter_id
already_affected = data.get(slot).get("affect").get(counter, 0)
counter_share = priority_group_counter.acc_counter_share
conso_k = data.get(slot).get("conso").get(counter.name)
if (
remaining_prod <= 0
or total_share == 0
or conso_k is None
or counter_share == 0
):
newly_affected = 0
sum_affected = already_affected + 0.0
else:
part_a_affecter = (remaining_prod * counter_share) / total_share
sum_affected = min(conso_k, (already_affected + part_a_affecter))
newly_affected = sum_affected - already_affected
data[slot]["affect"][counter] = sum_affected
total_affecte += newly_affected
data[slot]["prod_totale"] = remaining_prod - total_affecte
return data
from odoo import api, fields, models
from odoo.osv import expression
class AccPriorityGroupCounter(models.Model):
_name = "acc.priority.group.counter"
_description = "Clé de répartition par groupes de priorité"
_rec_name = "acc_counter_id"
# ------------------------------------------------------
# Fields declaration
# ------------------------------------------------------
acc_priority_group_id = fields.Many2one(
"acc.priority.group",
"Groupe de priorité",
required=True,
ondelete="cascade",
group_expand="_group_expand_acc_priority_group_id",
)
acc_operation_id = fields.Many2one(
related="acc_priority_group_id.acc_operation_id", store=True, readonly=True
)
acc_counter_id = fields.Many2one(
"acc.counter",
"PRM",
required=True,
ondelete="cascade",
domain="[('acc_operation_id', '=', acc_operation_id),"
"('type', 'in', ['del', 'del_inj'])]",
)
counter_street = fields.Char(
related="acc_counter_id.street", store=True, readonly=True
)
counter_owner = fields.Char(
related="acc_counter_id.partner_id.name", store=True, readonly=True
)
acc_counter_id_domain = fields.Binary(
string="Counter domain", compute="_compute_acc_counter_id_domain"
)
acc_counter_share = fields.Float(
string="Quote-part",
help="Part de la production affectée à ce PRM, "
"au prorata de l'ensemble des parts affectées aux autres PRMs",
)
group_type_algo = fields.Selection(
related="acc_priority_group_id.type_algo", readonly=True
)
acc_counter_percentage = fields.Float(
compute="_compute_acc_counter_percentage", store=True
)
# ------------------------------------------------------
# SQL Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# Default methods
# ------------------------------------------------------
# ------------------------------------------------------
# Computed fields / Search Fields
# ------------------------------------------------------
@api.depends("acc_priority_group_id.total_share")
def _compute_acc_counter_percentage(self):
for record in self:
total_share = record.acc_priority_group_id.total_share
record.acc_counter_percentage = (
record.acc_counter_share / total_share if total_share else 0
)
@api.depends("acc_counter_id", "acc_priority_group_id")
def _compute_acc_counter_id_domain(self):
for counter in self:
domain = [
("acc_operation_id", "=", counter.acc_operation_id.id),
("type", "in", ["del", "del_inj"]),
]
extended_domain = expression.AND(
[
domain,
[
(
"id",
"not in",
counter.acc_priority_group_id.acc_priority_group_counter_ids.acc_counter_id.ids,
)
],
]
)
counter.acc_counter_id_domain = extended_domain
# ------------------------------------------------------
# Onchange / Constraints
# ------------------------------------------------------
# @api.constrains("acc_counter_id_domain", "acc_counter_id")
# def _check_only_one_counter_per_group(self):
# """
# Vérification qu'il n'y a pas plusieurs fois le meme compteur dans un groupe
# """
#
# domain = expression.AND([self.acc_counter_id_domain, ])
#
# s = self.env["acc.counter"].search(self.acc_counter_id_domain)
# l = s.ids
# c = self.acc_counter_id.id
#
# if c not in l:
# raise UserError("Compteur déjà affecté a ce groupe")
# ------------------------------------------------------
# CRUD methods (ORM overrides)
# ------------------------------------------------------
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
def _group_expand_acc_priority_group_id(self, groups, domain, order):
operation_id = self.env.context.get("default_acc_operation_id")
if not operation_id:
group_ids = self.env["acc.priority.group"].search(
domain=[], order="sequence, id"
)
else:
group_ids = self.env["acc.priority.group"].search(
domain=[("acc_operation_id", "=", operation_id)], order="sequence, id"
)
return group_ids
def action_open_counter_share_form(self):
return {
"type": "ir.actions.act_window",
"name": "Modifier la quote-part du point de soutirage",
"res_model": "acc.priority.group.counter",
"view_mode": "form",
"target": "new",
"res_id": self.id,
"view_id": self.env.ref(
"oacc_repartition_keys.acc_operation_priority_group_counter_share_form"
).id,
#'context': {'default_field_name': 'Auto-filled Text'}
}
from odoo import api, fields, models
from odoo.tools import index_exists
class AccRepartitionCounter(models.Model):
_name = "acc.repartition.counter"
_description = "Priorité par compteur"
acc_repartition_id = fields.Many2one("acc.repartition.keys", "Clé", required=True)
weight = fields.Float(string="Répartition en pourcentage", required=False)
time_slot = fields.Datetime("Horodatage de la clé", required=True)
acc_counter_id = fields.Many2one("acc.counter", string="Compteur", required=True)
acc_operation_id = fields.Many2one(
comodel_name="acc.operation", compute="_compute_operation_id", store=True
)
def init(self):
ret = super().init()
if not index_exists(self._cr, "acc_repartition_counter_index"):
self._cr.execute(
"""
CREATE INDEX acc_repartition_counter_index
ON
acc_repartition_counter (time_slot, acc_counter_id)
"""
)
return ret
# ------------------------------------------------------
# SQL Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# Default methods
# ------------------------------------------------------
# ------------------------------------------------------
# Computed fields / Search Fields
# ------------------------------------------------------
@api.depends("acc_repartition_id")
def _compute_operation_id(self):
for key in self:
key.acc_operation_id = key.acc_repartition_id.operation_id
# ------------------------------------------------------
# Onchange / Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# CRUD methods (ORM overrides)
# ------------------------------------------------------
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
......@@ -4,14 +4,16 @@ from odoo import fields, models
class AccRepartitionKeys(models.Model):
_name = "acc.repartition.keys"
_description = "clés de repartition"
_order = "date_send DESC, id DESC"
# ------------------------------------------------------
# Fields declaration
csv_file = fields.Binary("Contenu du fichier CSV", required=True)
filename = fields.Char("Nom du fichier", required=True)
date_send = fields.Date("Date de l'envoi des clés", required=True, default=None)
# ------------------------------------------------------
operation_id = fields.Many2one("acc.operation", "Opération", required=True)
acc_repartition_counter_ids = fields.One2many(
comodel_name="acc.repartition.counter",
inverse_name="acc_repartition_id",
string="Compteur",
)
# ------------------------------------------------------
# SQL Constraints
......
from odoo import fields, models
class AccRepartitionKeysFile(models.Model):
_name = "acc.repartition.keys.file"
_description = "Fichier de clés de repartition"
_order = "date_send DESC, id DESC"
# ------------------------------------------------------
# Fields declaration
csv_file = fields.Binary("Contenu du fichier CSV", required=True)
filename = fields.Char("Nom du fichier", required=True)
date_send = fields.Date("Date de l'envoi des clés", default=None)
operation_id = fields.Many2one("acc.operation", "Opération", required=True)
# ------------------------------------------------------
# SQL Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# Default methods
# ------------------------------------------------------
# ------------------------------------------------------
# Computed fields / Search Fields
# ------------------------------------------------------
# ------------------------------------------------------
# Onchange / Constraints
# ------------------------------------------------------
# ------------------------------------------------------
# CRUD methods (ORM overrides)
# ------------------------------------------------------
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
# ------------------------------------------------------
# Business methods
# ------------------------------------------------------
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
<<<<<<< HEAD
"access_acc_repartition_keys_group_operation_superadmin","acc_repartition_keys group_operation_superadmin","model_acc_repartition_keys","oacc.group_operation_superadmin",1,1,1,0
"access_acc_repartition_keys_wizard_group_operation_superadmin","acc_repartition_keys_wizard group_operation_superadmin","model_acc_repartition_keys_wizard","oacc.group_operation_superadmin",1,1,1,1
"access_acc_repartition_keys_file_group_operation_superadmin","acc_repartition_keys_file group_operation_superadmin","model_acc_repartition_keys_file","oacc.group_operation_superadmin",1,1,1,0
"access_acc_repartition_keys_operation_superadmin","acc_repartition_keys group_operation_superadmin","model_acc_repartition_keys","oacc.group_operation_superadmin",1,1,1,1
"access_acc_repartition_keys_file_wizard_group_operation_superadmin","acc_repartition_keys_file_wizard group_operation_superadmin","model_acc_repartition_keys_file_wizard","oacc.group_operation_superadmin",1,1,1,1
"access_acc_priority_group_counter_operation_superadmin","acc_priority_group_counter group_operation_superadmin","model_acc_priority_group_counter","oacc.group_operation_superadmin",1,1,1,1
"access_acc_priority_group_operation_superadmin","acc_priority_group group_operation_superadmin","model_acc_priority_group","oacc.group_operation_superadmin",1,1,1,1
"access_acc_repartition_counter_group_operation_superadmin","acc_repartition_counter group_operation_superadmin","model_acc_repartition_counter","oacc.group_operation_superadmin",1,1,1,1
"access_acc_repartition_keys_compute_wizard_group_operation_superadmin","acc_repartition_keys_compute_wizard group_operation_superadmin","model_acc_repartition_keys_compute_wizard","oacc.group_operation_superadmin",1,1,1,1
=======
"access_acc_repartition_keys_group_operation_admin","acc_repartition_keys group_operation_admin","model_acc_repartition_keys","oacc.group_operation_admin",1,1,1,0
"access_acc_repartition_keys_wizard_group_operation_admin","acc_repartition_keys_wizard group_operation_admin","model_acc_repartition_keys_wizard","oacc.group_operation_admin",1,1,1,1
>>>>>>> f8f50dae1c02fa6e972c2873b9ef1d6bf5f0efdc
"access_acc_priority_group_counter_operation_admin","acc_priority_group_counter group_operation_admin","model_acc_priority_group_counter","oacc.group_operation_admin",1,1,1,1
"access_acc_priority_group_operation_admin","acc_priority_group group_operation_admin","model_acc_priority_group","oacc.group_operation_admin",1,1,1,1
"access_acc_repartition_counter_group_operation_admin","acc_repartition_counter group_operation_admin","model_acc_repartition_counter","oacc.group_operation_admin",1,1,1,1
"access_acc_repartition_keys_file_wizard_group_operation_admin","acc_repartition_keys_file_wizard group_operation_admin","model_acc_repartition_keys_file_wizard","oacc.group_operation_admin",1,1,1,1
"access_acc_repartition_keys_file_group_operation_admin","acc_repartition_keys_file group_operation_admin","model_acc_repartition_keys_file","oacc.group_operation_admin",1,1,1,1
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2021- Le Filament (https://le-filament.com)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="acc_priority_group_admin_rule" model="ir.rule">
<field name="name">Groupes de priorité</field>
<field ref="model_acc_priority_group" name="model_id" />
<field
name="domain_force"
>[('acc_operation_id', 'in', list(user.commercial_partner_id.acc_operation_admin_ids.ids))]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_admin'))]" />
</record>
<record id="acc_priority_group_superadmin_rule" model="ir.rule">
<field name="name">Groupes de priorité</field>
<field ref="model_acc_priority_group" name="model_id" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_superadmin'))]" />
</record>
<record id="acc_priority_group_counter_admin_rule" model="ir.rule">
<field name="name">Compteur pour repartition</field>
<field ref="model_acc_priority_group_counter" name="model_id" />
<field
name="domain_force"
>[('acc_operation_id', 'in', list(user.commercial_partner_id.acc_operation_admin_ids.ids))]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_admin'))]" />
</record>
<record id="acc_priority_group_counter_superadmin_rule" model="ir.rule">
<field name="name">Compteur pour repartition</field>
<field ref="model_acc_priority_group_counter" name="model_id" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_superadmin'))]" />
</record>
<record id="acc_repartition_counter_admin_rule" model="ir.rule">
<field name="name">Clés de repartition par compteur</field>
<field ref="model_acc_repartition_counter" name="model_id" />
<field
name="domain_force"
>[('acc_operation_id', 'in', list(user.commercial_partner_id.acc_operation_admin_ids.ids))]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_admin'))]" />
</record>
<record id="acc_repartition_counter_superadmin_rule" model="ir.rule">
<field name="name">Clés de repartition par compteur</field>
<field ref="model_acc_repartition_counter" name="model_id" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_superadmin'))]" />
</record>
<record id="acc_repartition_keys_admin_rule" model="ir.rule">
<field name="name">Ensemble des clés par operation</field>
<field ref="model_acc_repartition_keys" name="model_id" />
<field
name="domain_force"
>[('operation_id', 'in', list(user.commercial_partner_id.acc_operation_admin_ids.ids))]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_admin'))]" />
</record>
<record id="acc_repartition_keys_superadmin_rule" model="ir.rule">
<field name="name">Ensemble des clés par operation</field>
<field ref="model_acc_repartition_keys" name="model_id" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_superadmin'))]" />
</record>
<record id="acc_repartition_keys_file_admin_rule" model="ir.rule">
<field name="name">Fichier de clés de repartition</field>
<field ref="model_acc_repartition_keys_file" name="model_id" />
<field
name="domain_force"
>[('operation_id', 'in', list(user.commercial_partner_id.acc_operation_admin_ids.ids))]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_admin'))]" />
</record>
<record id="acc_repartition_keys_file_superadmin_rule" model="ir.rule">
<field name="name">Fichier de clés de repartition</field>
<field ref="model_acc_repartition_keys_file" name="model_id" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('oacc.group_operation_superadmin'))]" />
</record>
</odoo>
.oacc_quick_create.o_form_editable .o_cell .o_form_label {
margin-bottom: 0 !important;
}
/** @odoo-module */
import {KanbanController} from "@web/views/kanban/kanban_controller";
import {registry} from "@web/core/registry";
import {kanbanView} from "@web/views/kanban/kanban_view";
export class PriorityGroupKanbanController extends KanbanController {
setup() {
super.setup();
}
async CreatePriorityGroupClick() {
self = this;
this.actionService.doAction(
{
type: "ir.actions.act_window",
res_model: "acc.priority.group",
name: "Creer un groupe de priorité",
view_mode: "form",
view_type: "form",
views: [[false, "form"]],
target: "new",
res_id: false,
context: this.props.context || {},
},
{
onClose: async () => {
await self.model.root.load();
self.render(true);
},
}
);
}
}
registry.category("views").add("button_in_kanban", {
...kanbanView,
Controller: PriorityGroupKanbanController,
buttonTemplate: "button_priority_group.KanbanView.Buttons",
});
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<t
t-name="button_priority_group.KanbanView.Buttons"
t-inherit="web.KanbanView.Buttons"
>
<xpath expr="//*[@class='btn btn-primary o-kanban-button-new']" position="after">
<button
type="button"
class="btn btn-primary"
style="margin-left: 10px;"
t-on-click="CreatePriorityGroupClick"
>
Creer un groupe de priorité
</button>
</xpath>
</t>
</odoo>
......@@ -9,13 +9,42 @@
<field name="arch" type="xml">
<page name="other_infos" position="before">
<page string="Clés de répartition" name="keys">
<div>
<group>
<field name="type_algo" />
</group>
<field name="algo_description" readonly="1" />
</div>
<separator />
<button
string="Importer un fichier"
string="Calcul clés de répartition"
type="action"
name="%(oacc_repartition_keys.acc_repartition_keys_wizard_action)d"
name="%(oacc_repartition_keys.acc_repartition_keys_compute_wizard_action)d"
class="btn-primary"
attrs="{'invisible': [('type_algo', 'in', ['prorata', 'static', 'dyn_perso_send'])]}"
groups="oacc.group_operation_superadmin"
/>
<field name="keys_repartition_ids" mode="tree">
<button
string="Algorithme de clé de répartition"
type="object"
name="action_view_repartition_algo_priority_group"
class="btn-primary ms-2"
context="{'default_acc_operation_id': active_id}"
attrs="{'invisible': [('type_algo', 'in', ['prorata', 'static', 'dyn_perso_send'])]}"
groups="oacc.group_operation_admin"
/>
<separator />
<button
string="Importer un fichier de clés"
type="action"
name="%(oacc_repartition_keys.acc_repartition_keys_file_wizard_action)d"
class="btn-primary mb-3"
attrs="{'invisible': [('type_algo', 'not in', ['dyn_perso_send'])]}"
groups="oacc.group_operation_admin"
/>
<field name="keys_file_repartition_ids" mode="tree">
<tree create="0" editable="bottom" delete="0">
<field name="date_send" readonly="1" />
<field name="filename" invisible="1" />
......@@ -25,6 +54,7 @@
filename="filename"
readonly="1"
string="Fichier chargé"
groups="oacc.group_operation_admin"
/>
</tree>
</field>
......@@ -32,4 +62,5 @@
</page>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="acc_operation_priority_group_counter_share_form" model="ir.ui.view">
<field name="name">acc_operation_priority_group_counter_share_form</field>
<field name="model">acc.priority.group.counter</field>
<field name="arch" type="xml">
<form string="My Model">
<div>
<group>
<field name="acc_counter_share" style="width: 15em;" />
</group>
</div>
</form>
</field>
</record>
<record id="acc_priority_group_counter_act_window" model="ir.actions.act_window">
<field name="name">Clé de répartition par groupes de priorité</field>
<field name="res_model">acc.priority.group.counter</field>
<field name="view_mode">kanban</field>
<field name="domain">[("acc_operation_id", "=", active_id)]</field>
<field name="context">{"default_acc_operation_id": active_id}</field>
</record>
<record id="acc_operation_priority_group_counter_form" model="ir.ui.view">
<field name="name">acc.operation.priority.group.counter.form</field>
<field name="model">acc.priority.group.counter</field>
<field name="arch" type="xml">
<form class="oacc_quick_create">
<field name="acc_priority_group_id" invisible="1" />
<field name="acc_operation_id" invisible="1" />
<field name="acc_counter_id_domain" invisible="1" />
<group>
<field
name="acc_counter_id"
options="{'no_create_edit': True, 'no_create': True}"
domain="acc_counter_id_domain"
/>
</group>
<field name="group_type_algo" invisible="1" />
<group attrs="{'invisible': [('group_type_algo', '!=', 'quotepart')]}">
<field name="acc_counter_share" style="width: 15em;" />
</group>
</form>
</field>
</record>
<record id="acc_operation_priority_group_counter_kanban" model="ir.ui.view">
<field name="name">acc.operation.priority.group.counter.kanban</field>
<field name="model">acc.priority.group.counter</field>
<field name="arch" type="xml">
<kanban
default_group_by="acc_priority_group_id"
class="o_kanban_medium_column"
on_create="quick_create"
quick_create_view="oacc_repartition_keys.acc_operation_priority_group_counter_form"
archivable="false"
sample="1"
js_class="button_in_kanban"
group_create="false"
>
<field name="acc_counter_id" />
<field name="acc_priority_group_id" />
<field name="counter_street" />
<field name="counter_owner" />
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card">
<div class="oe_kanban_content">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<a
type="delete"
style="position: absolute; right: 5px; top: 5px;"
>X</a>
<strong class="o_kanban_record_title">
<field
name="acc_counter_id"
options="{'no_open': True}"
/>
</strong>
</div>
</div>
<div class="o_kanban_record_body">
<div><field name="counter_owner" /></div>
<div><field name="counter_street" /></div>
<field name="group_type_algo" invisible="1" />
<group
attrs="{'invisible': [('group_type_algo', '!=', 'quotepart')]}"
>
<div class="d-flex justify-content-between">
<div>
<div>
Quote-part: <field
name="acc_counter_share"
/>
(<field
name="acc_counter_percentage"
widget="percentage"
/>)
</div>
</div>
<div class="align-self-end"><button
name="action_open_counter_share_form"
type="object"
class="btn btn-sm btn-primary text-end"
>Modifier</button></div>
</div>
</group>
</div>
</div>
<div class="clearfix" />
</div>
</t>
</templates>
</kanban>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="acc_priority_group_act_window" model="ir.actions.act_window">
<field name="name">Groupes de priorités</field>
<field name="res_model">acc.priority.group</field>
<field name="view_mode">kanban,form</field>
</record>
<record id="acc_priority_group_form" model="ir.ui.view">
<field name="name">acc.priority.group.form</field>
<field name="model">acc.priority.group</field>
<field name="arch" type="xml">
<form>
<group>
<field name="acc_operation_id" readonly="1" />
<field name="type_algo" />
</group>
</form>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2021- Le Filament (https://le-filament.com)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="acc_repartition_counter_filter" model="ir.ui.view">
<field name="name">acc.acc_repartition_counter.filter</field>
<field name="model">acc.repartition.counter</field>
<field name="arch" type="xml">
<search string="Recherche">
<field name="acc_repartition_id" string="Opération" />
<field name="acc_counter_id" string="PRM" />
<field name="time_slot" string="Horodatage" />
<separator />
<group expand="0" string="Group By">
<filter
name="group_operation"
string="Opération"
context="{'group_by':'acc_operation_id'}"
/>
<filter
name="group_time_slot"
string="Horodatage"
context="{'group_by':'time_slot'}"
/>
<filter
name="group_counter"
string="PRMs"
context="{'group_by':'acc_counter_id'}"
/>
</group>
</search>
</field>
</record>
<record id="acc_repartition_counter_form_view" model="ir.ui.view">
<field name="name">acc.acc_repartition_counter.form</field>
<field name="model">acc.repartition.counter</field>
<field name="arch" type="xml">
<form string="CDC Enedis">
<sheet>
<div class="oe_title">
<label for="time_slot" />
<h1>
<field name="time_slot" />
</h1>
</div>
<group>
<group>
<field
name="acc_operation_id"
options="{'no_create_edit': True, 'no_create': True}"
/>
<field
name="acc_counter_id"
options="{'no_create_edit': True, 'no_create': True}"
/>
<field
name="time_slot"
options="{'no_create_edit': True, 'no_create': True}"
/>
<field
name="weight"
options="{'no_create_edit': True, 'no_create': True}"
/>
</group>
<group>
<field name="time_slot" />
<field name="acc_counter_id" />
<field name="weight" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="acc_repartition_counter_tree_view" model="ir.ui.view">
<field name="name">acc.acc_repartition_counter.tree</field>
<field name="model">acc.repartition.counter</field>
<field name="arch" type="xml">
<tree>
<field
name="acc_operation_id"
options="{'no_create_edit': True, 'no_create': True}"
/>
<field name="time_slot" />
<field
name="acc_counter_id"
options="{'no_create_edit': True, 'no_create': True}"
/>
<field name="weight" />
</tree>
</field>
</record>
<record id="acc_repartition_counter_pivot_view" model="ir.ui.view">
<field name="name">acc.acc_repartition_counter.pivot</field>
<field name="model">acc.repartition.counter</field>
<field name="arch" type="xml">
<pivot string="Clés de repartition" sample="1">
<field name="acc_operation_id" type="row" />
<field name="time_slot" type="col" />
<field name="weight" type="measure" />
</pivot>
</field>
</record>
<record id="acc_repartition_counter_act_window" model="ir.actions.act_window">
<field name="name">CLES CALCULEES</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">acc.repartition.counter</field>
<field name="view_mode">pivot,tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Créer une entrée de données
</p>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- <data>-->
<record id="acc_keys_repartition_action" model="ir.actions.act_window">
<field name="name">Keys</field>
<field name="res_model">acc.repartition.keys</field>
<data>
<!-- KEYS FILES-->
<record id="acc_keys_repartition_file_action" model="ir.actions.act_window">
<field name="name">Keys file</field>
<field name="res_model">acc.repartition.keys.file</field>
<field name="view_mode">tree,form</field>
</record>
<record id="acc_keys_repartition_tree" model="ir.ui.view">
<field name="name">acc.repartition.keys.tree</field>
<field name="model">acc.repartition.keys</field>
<record id="acc_keys_file_repartition_tree" model="ir.ui.view">
<field name="name">acc.repartition.keys.file.tree</field>
<field name="model">acc.repartition.keys.file</field>
<field name="arch" type="xml">
<tree string="Keys" create="false">
<tree string="Keys file" create="false">
<field name="date_send" />
<field name="csv_file" />
<field name="operation_id" />
</tree>
</field>
</record>
<!-- </data>-->
</data>
</odoo>