Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • coopdevs/comunitats-energetiques/odoo-ce
1 result
Show changes
Showing
with 662 additions and 101 deletions
......@@ -502,7 +502,7 @@ msgstr "Estableix en activació"
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.selfconsumption_form_view
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.supply_point_form_view
msgid "State"
msgstr "Estat"
msgstr "Província"
#. module: energy_selfconsumption
#: model:ir.model.fields,help:energy_selfconsumption.field_energy_selfconsumption_selfconsumption__activity_state
......
......@@ -502,7 +502,7 @@ msgstr "Establecer en activación"
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.selfconsumption_form_view
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.supply_point_form_view
msgid "State"
msgstr "Estado"
msgstr "Provincia"
#. module: energy_selfconsumption
#: model:ir.model.fields,help:energy_selfconsumption.field_energy_selfconsumption_selfconsumption__activity_state
......
......@@ -502,7 +502,7 @@ msgstr "Ezarri aktibazioan"
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.selfconsumption_form_view
#: model_terms:ir.ui.view,arch_db:energy_selfconsumption.supply_point_form_view
msgid "State"
msgstr "Estatu"
msgstr "Probintzia"
#. module: energy_selfconsumption
#: model:ir.model.fields,help:energy_selfconsumption.field_energy_selfconsumption_selfconsumption__activity_state
......
......@@ -4,6 +4,8 @@ from odoo.exceptions import ValidationError
STATE_VALUES = [
("draft", _("Draft")),
("validated", _("Validated")),
("process", _("In process")),
("active", _("Active")),
]
TYPE_VALUES = [
......@@ -24,6 +26,7 @@ class DistributionTable(models.Model):
name = fields.Char(readonly=True)
selfconsumption_project_id = fields.Many2one('energy_selfconsumption.selfconsumption', required=True)
selfconsumption_project_state = fields.Selection(related='selfconsumption_project_id.state')
type = fields.Selection(TYPE_VALUES, default="fixed", required=True, string="Modality")
state = fields.Selection(STATE_VALUES, default="draft", required=True)
supply_point_assignation_ids = fields.One2many('energy_selfconsumption.supply_point_assignation',
......@@ -38,7 +41,7 @@ class DistributionTable(models.Model):
def create(self, vals):
vals['name'] = self.env.ref('energy_selfconsumption.distribution_table_sequence', False).next_by_id()
return super(DistributionTable, self).create(vals)
@api.onchange('selfconsumption_project_id')
def _onchange_selfconsumption_project_id(self):
self.supply_point_assignation_ids = False
......@@ -47,8 +50,24 @@ class DistributionTable(models.Model):
for record in self:
if not record.coefficient_is_valid:
raise ValidationError(_("Coefficient distribution must sum to 1."))
if not record.selfconsumption_project_id.state == 'activation':
raise ValidationError(_("Self-consumption project is not in activation"))
if record.selfconsumption_project_id.distribution_table_ids.filtered_domain([('state', '=', 'validated')]):
raise ValidationError(_("Self-consumption project already has a validated table"))
if record.selfconsumption_project_id.distribution_table_ids.filtered_domain([('state', '=', 'process')]):
raise ValidationError(_("Self-consumption project already has a table in process"))
record.write({"state": "validated"})
def button_draft(self):
for record in self:
record.write({"state": "draft"})
def action_distribution_table_import_wizard(self):
self.ensure_one()
return {
'name': _('Import Distribution Table'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'energy_selfconsumption.distribution_table_import.wizard',
'views': [(False, 'form')],
'view_id': False,
'target': 'new',
}
......@@ -25,8 +25,11 @@ class Selfconsumption(models.Model):
cil = fields.Char(string="CIL", help="Production facility code for liquidation purposes")
owner_id = fields.Many2one("res.partner", string="Owner", required=True, default=lambda self: self.env.company.partner_id)
power = fields.Float(string="Generation Power (kW)")
distribution_table_ids = fields.One2many('energy_selfconsumption.distribution_table', 'selfconsumption_project_id',
readonly=True)
distribution_table_ids = fields.One2many(
"energy_selfconsumption.distribution_table",
"selfconsumption_project_id",
readonly=True,
)
distribution_table_count = fields.Integer(compute=_compute_distribution_table_count)
inscription_ids = fields.One2many('energy_project.inscription', 'project_id', readonly=True)
inscription_count = fields.Integer(compute=_compute_inscription_count)
......@@ -53,9 +56,16 @@ class Selfconsumption(models.Model):
'context': {'create': True, 'default_project_id': self.id},
}
def set_activation(self):
def distribution_table_state(self, actual_state, new_state):
distribution_table_to_activate = self.distribution_table_ids.filtered(lambda table: table.state == actual_state)
distribution_table_to_activate.write({"state": new_state})
def set_in_activation_state(self):
for record in self:
record.write({"state": "activation"})
if not record.distribution_table_ids.filtered_domain([('state', '=', 'validated')]):
raise ValidationError(_("Must have a valid Distribution Table."))
record.write({"state": "activation"})
self.distribution_table_state("validated", "process")
def activate(self):
for record in self:
......@@ -65,6 +75,27 @@ class Selfconsumption(models.Model):
raise ValidationError(_("Project must have a valid CIL."))
if not record.power or record.power <= 0:
raise ValidationError(_("Project must have a valid Generation Power."))
if not record.distribution_table_ids.filtered_domain([('state', '=', 'validated')]):
raise ValidationError(_("Must have a valid Distribution Table."))
record.write({"state": "active"})
self.distribution_table_state("process", "active")
def action_selfconsumption_import_wizard(self):
self.ensure_one()
return {
'name': _('Import Inscriptions and Supply Points'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'energy_selfconsumption.selfconsumption_import.wizard',
'views': [(False, 'form')],
'view_id': False,
'target': 'new',
}
def set_inscription(self, selfconsumption_state):
for record in self:
record.write({"state": "inscription"})
if selfconsumption_state == 'activation':
self.distribution_table_state("process", "validated")
def set_draft(self):
for record in self:
record.write({"state": "draft"})
from odoo import fields, models
from odoo import fields, models, _
class SupplyPoint(models.Model):
......@@ -6,9 +6,20 @@ class SupplyPoint(models.Model):
_description = "Energy Supply Point"
_inherit = ["mail.thread", "mail.activity.mixin"]
_sql_constraints = {
(
"unique_code_company_id",
"unique (code, company_id)",
_("A supply point with this code already exists."),
)
}
name = fields.Char(required=True)
code = fields.Char(string="CUPS", required=True)
owner_id = fields.Many2one("res.partner", string="Owner", required=True)
owner_id = fields.Many2one("res.partner", string="Owner", required=True,
help="Partner with the legal obligation of the supply point")
partner_id = fields.Many2one("res.partner", string="Partner", required=True,
help="Partner subscribed to the self-consumption project")
company_id = fields.Many2one(
"res.company", default=lambda self: self.env.company, readonly=True
)
......
......@@ -20,16 +20,26 @@ class SupplyPointAssignation(models.Model):
.filtered_domain([('id', 'not in', record.distribution_table_id.supply_point_assignation_ids.mapped(
'supply_point_id.id'))])
distribution_table_id = fields.Many2one('energy_selfconsumption.distribution_table', required=True)
distribution_table_id = fields.Many2one(
"energy_selfconsumption.distribution_table", required=True
)
selfconsumption_project_id = fields.Many2one(related='distribution_table_id.selfconsumption_project_id')
distribution_table_state = fields.Selection(related='distribution_table_id.state')
distribution_table_create_date = fields.Datetime(related='distribution_table_id.create_date')
supply_point_id = fields.Many2one('energy_selfconsumption.supply_point', required=True)
coefficient = fields.Float(string='Distribution coefficient', digits=(1, 5), required=True,
help="The sum of all the coefficients must result in 1")
owner_id = fields.Many2one("res.partner", related='supply_point_id.owner_id')
code = fields.Char(related='supply_point_id.code')
table_coefficient_is_valid = fields.Boolean(related='distribution_table_id.coefficient_is_valid')
supply_point_id = fields.Many2one(
"energy_selfconsumption.supply_point", required=True
)
coefficient = fields.Float(
string="Distribution coefficient",
digits=(1, 5),
required=True,
help="The sum of all the coefficients must result in 1",
)
owner_id = fields.Many2one("res.partner", related="supply_point_id.owner_id")
code = fields.Char(related="supply_point_id.code")
table_coefficient_is_valid = fields.Boolean(
related="distribution_table_id.coefficient_is_valid"
)
supply_point_filtered_ids = fields.One2many('energy_selfconsumption.supply_point',
compute=_compute_supply_point_filtered_ids, readonly=True)
......
......@@ -7,3 +7,5 @@ access_energy_selfconsumption_selfconsumption_admin,energy_selfconsumption.selfc
access_energy_selfconsumption_supply_point_admin,energy_selfconsumption.supply_point.admin,model_energy_selfconsumption_supply_point,energy_project.group_admin,1,1,1,1
access_energy_selfconsumption_distribution_table_admin,energy_selfconsumption.distribution_table.admin,model_energy_selfconsumption_distribution_table,energy_project.group_admin,1,1,1,1
access_energy_selfconsumption_supply_point_assignation_admin,energy_selfconsumption.supply_point_assignation.admin,model_energy_selfconsumption_supply_point_assignation,energy_project.group_admin,1,1,1,1
access_energy_selfconsumption_selfconsumption_import_wizard_admin,energy_selfconsumption.selfconsumption_import.wizard.admin,model_energy_selfconsumption_selfconsumption_import_wizard,energy_project.group_admin,1,1,1,1
access_energy_selfconsumption_distribution_table_import_wizard_admin,energy_selfconsumption.distribution_table_import.wizard.admin,model_energy_selfconsumption_distribution_table_import_wizard,energy_project.group_admin,1,1,1,1
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="distribution_table_form_view" model="ir.ui.view">
<field name="name">energy_selfconsumption.distribution_table_form_view.form</field>
<field name="model">energy_selfconsumption.distribution_table</field>
......@@ -13,13 +12,27 @@
string="Validate"
name="button_validate"
states="draft"
class="btn btn-primary"
/>
<button
type="object"
string="Reset to draft"
name="button_draft"
attrs="{'invisible':['|',('state','not in',['validated']),('selfconsumption_project_state','not in',['inscription'])]}"
/>
<button
type="object"
name="action_distribution_table_import_wizard"
string="Import table"
states="draft"
/>
<field
name="state"
widget="statusbar"
readonly="True"
statusbar_visible="draft,validated"
statusbar_visible="draft,validated,process,active"
/>
<field name="selfconsumption_project_state" invisible="1"/>
</header>
<sheet>
<widget
......@@ -106,4 +119,4 @@
</field>
</record>
</data>
</odoo>
\ No newline at end of file
</odoo>
......@@ -28,7 +28,8 @@
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" type="object" name="get_supply_points" icon="fa-lightbulb-o">
<button class="oe_stat_button" type="object" name="get_supply_points" icon="fa-lightbulb-o"
context="{'default_partner_id': id, 'default_owner_id': id}">
<field string="Supply Points" name="supply_point_count" widget="statinfo"/>
</button>
</div>
......
......@@ -10,21 +10,52 @@
<header>
<button
type="object"
string="Set in activation"
name="set_activation"
string="Set in inscription"
name="set_inscription"
args="['draft']"
attrs="{'invisible':[('state','not in',['draft'])]}"
class="btn btn-primary"
/>
<button
type="object"
string="Set in activation"
name="set_in_activation_state"
attrs="{'invisible':[('state','not in',['inscription'])]}"
class="btn btn-primary"
/>
<button
type="object"
string="Activate"
name="activate"
attrs="{'invisible':[('state','not in',['activation'])]}"
class="btn btn-primary"
/>
<button
type="object"
string="Back to In Inscription status"
name="set_inscription"
args="['activation']"
attrs="{'invisible':[('state','not in',['activation'])]}"
confirm="Are you sure you want to change the project status to the previous step?
If so, the distribution table will return to the 'validated' state."
/>
<button
type="object"
string="Set to Draft"
name="set_draft"
attrs="{'invisible':[('state','not in',['inscription'])]}"
/>
<button
type="object"
name="action_selfconsumption_import_wizard"
string="Import inscriptions and supply points"
attrs="{'invisible':[('state','not in',['draft'])]}"
/>
<field
name="state"
widget="statusbar"
readonly="True"
statusbar_visible="draft,activation,active"
statusbar_visible="draft,inscription,activation,active"
/>
</header>
<sheet>
......@@ -34,45 +65,53 @@
<field string="Inscriptions" name="inscription_count" widget="statinfo"/>
</button>
<button class="oe_stat_button" type="object" name="get_distribution_tables"
icon="fa-table" context="{'default_owner_id': id}"
attrs="{'invisible': [('state', '==', 'draft')]}">
<field string="Distribution Tables" name="distribution_table_count" widget="statinfo"/>
<button
class="oe_stat_button"
type="object"
name="get_distribution_tables"
icon="fa-table"
attrs="{'invisible': [('state', '==', 'draft')]}"
>
<field
string="Distribution Tables"
name="distribution_table_count"
widget="statinfo"
/>
</button>
</div>
<widget
name="web_ribbon"
text="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
name="web_ribbon"
text="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<field name="active" invisible="True"/>
<field name="active" invisible="True" />
<div class="oe_title">
<label for="name"/>
<label for="name" />
<h1>
<field
name="name"
placeholder="Title"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
name="name"
placeholder="Title"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
</h1>
</div>
<group>
<group>
<field name="company_id" invisible="True"/>
<field name="id" invisible="True"/>
<field name="type" invisible="True"/>
<field name="company_id" invisible="True" />
<field name="id" invisible="True" />
<field name="type" invisible="True" />
<field
name="code"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
name="code"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="cil"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
name="cil"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="power"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
name="power"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="owner_id"
......@@ -80,33 +119,60 @@
/>
</group>
<group>
<span class="o_form_label o_td_label" name="address_name">
<span
class="o_form_label o_td_label"
name="address_name"
>
<b>Address</b>
</span>
<div class="o_address_format">
<field name="street" placeholder="Street..." class="o_address_street"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field name="street2" placeholder="Street 2..." class="o_address_street"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field name="city" placeholder="City" class="o_address_city"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field name="state_id" class="o_address_state" placeholder="State"
options="{'no_open': True, 'no_quick_create': True}"
context="{'country_id': country_id, 'default_country_id': country_id, 'zip': zip}"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field name="zip" placeholder="ZIP" class="o_address_zip"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field name="country_id" placeholder="Country" class="o_address_country"
options="{&quot;no_open&quot;: True, &quot;no_create&quot;: True}"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"/>
<field
name="street"
placeholder="Street..."
class="o_address_street"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="street2"
placeholder="Street 2..."
class="o_address_street"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="city"
placeholder="City"
class="o_address_city"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="state_id"
class="o_address_state"
placeholder="State"
options="{'no_open': True, 'no_quick_create': True}"
context="{'country_id': country_id, 'default_country_id': country_id, 'zip': zip}"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="zip"
placeholder="ZIP"
class="o_address_zip"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="country_id"
placeholder="Country"
class="o_address_country"
options="{&quot;no_open&quot;: True, &quot;no_create&quot;: True}"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
</div>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
......@@ -119,10 +185,8 @@
<tree string="Self-Consumption Projects">
<field name="name"/>
<field name="street"/>
<field name="street2"/>
<field name="state_id"/>
<field name="zip"/>
<field name="country_id"/>
<field name="city"/>
<field name="inscription_count"/>
<field name="power"/>
<field name="state"/>
</tree>
......@@ -135,8 +199,8 @@
<field name="res_model">energy_selfconsumption.selfconsumption</field>
<field name="view_mode">tree,form</field>
<field
name="context"
eval="{'default_type': ref('energy_selfconsumption.selfconsumption_project_type')}"
name="context"
eval="{'default_type': ref('energy_selfconsumption.selfconsumption_project_type')}"
/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
......@@ -146,16 +210,16 @@
</record>
<menuitem
name="Self-consumption Management"
id="root"
web_icon="energy_selfconsumption,static/description/icon.png"
name="Self-consumption Management"
id="root"
web_icon="energy_selfconsumption,static/description/icon.png"
/>
<menuitem name="Management" id="management_category_menu" parent="root"/>
<menuitem name="Management" id="management_category_menu" parent="root" />
<menuitem
name="Project"
id="project_menu"
parent="management_category_menu"
action="selfconsumption_form_view_act_window"
name="Project"
id="project_menu"
parent="management_category_menu"
action="selfconsumption_form_view_act_window"
/>
</data>
</odoo>
......@@ -17,6 +17,7 @@
<group>
<group>
<field name="code"/>
<field name="partner_id" options="{'no_create': True}"/>
<field name="owner_id"/>
</group>
<group>
......@@ -65,6 +66,7 @@
<tree string="Supply Points">
<field name="name"/>
<field name="code"/>
<field name="partner_id"/>
<field name="owner_id"/>
</tree>
</field>
......
from . import selfconsumption_import_wizard
from . import distribution_table_import_wizard
\ No newline at end of file
from odoo import fields, models, _, api
from odoo.exceptions import UserError, ValidationError
import base64
import logging
from io import StringIO
import chardet
from csv import reader
import werkzeug
from odoo.http import request
logger = logging.getLogger(__name__)
class DistributionTableImportWizard(models.TransientModel):
_name = 'energy_selfconsumption.distribution_table_import.wizard'
import_file = fields.Binary(string="Import File (*.csv)")
fname = fields.Char(string="File Name")
delimiter = fields.Char(default=',', required=True, string='File Delimiter', help='Delimiter in import CSV file.')
quotechar = fields.Char(default='"', required=True, string='File Quotechar', help='Quotechar in import CSV file.')
encoding = fields.Char(default='utf-8', required=True, string='File Encoding',
help='Enconding format in import CSV file.')
@api.constrains('import_file')
def _constrains_import_file(self):
if self.fname:
format = str(self.fname.split(".")[1])
if format != 'csv':
raise ValidationError(_("Only csv format files are accepted."))
def import_file_button(self):
file_data = base64.b64decode(self.import_file)
parsing_data = self.with_context(active_id=self.ids[0])._parse_file(file_data)
active_id = self.env.context.get('active_id')
distribution_table = self.env['energy_selfconsumption.distribution_table'].browse(active_id)
self.import_all_lines(parsing_data, distribution_table)
return True
def download_template_button(self):
distribution_table_example_attachment = self.env.ref(
'energy_selfconsumption.distribution_table_example_attachment')
download_url = '/web/content/{}/?download=true'.format(str(distribution_table_example_attachment.id))
return {
"type": "ir.actions.act_url",
"url": download_url,
"target": "new",
}
def _parse_file(self, data_file):
self.ensure_one()
try:
csv_options = {}
csv_options["delimiter"] = self.delimiter
csv_options["quotechar"] = self.quotechar
try:
decoded_file = data_file.decode(self.encoding)
except UnicodeDecodeError:
detected_encoding = chardet.detect(data_file).get("encoding", False)
if not detected_encoding:
raise UserError(
_("No valid encoding was found for the attached file")
)
decoded_file = data_file.decode(detected_encoding)
csv = reader(StringIO(decoded_file), **csv_options)
return list(csv)
except BaseException:
logger.warning("Parser error", exc_info=True)
raise UserError(_("Error parsing the file"))
def import_all_lines(self, data, distribution_table):
supply_point_assignation_values_list = []
for index, line in enumerate(data[1:]):
value = self.get_supply_point_assignation_values(line)
supply_point_assignation_values_list.append((0, 0, value))
distribution_table.write(
{
'supply_point_assignation_ids': supply_point_assignation_values_list
}
)
def get_supply_point_assignation_values(self, line):
return {
'supply_point_id': self.get_supply_point_id(code=line[0]),
'coefficient': self.get_coefficient(coefficient=line[1])
}
def get_supply_point_id(self, code):
supply_point_id = self.env['energy_selfconsumption.supply_point'].search_read([('code', '=', code)], ['id'])
if not supply_point_id:
raise ValidationError(_('There isn\'t any supply point with this code: {code}').format(code=code))
return supply_point_id[0]['id']
def get_coefficient(self, coefficient):
try:
return fields.Float.convert_to_write(self=fields.Float(), value=coefficient, record=self.env[
'energy_selfconsumption.supply_point_assignation'].coefficient)
except ValueError:
return 0
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="distribution_table_import_wizard_form_view" model="ir.ui.view">
<field name="name">energy_selfconsumption.distribution_table_import.wizard.form</field>
<field name="model">energy_selfconsumption.distribution_table_import.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<p>You can download an example template for the import of distribution tables. The template must
have the following format and be a CSV file.</p>
<button type="object" class="btn btn-primary" name="download_template_button">Download
Template</button>
<separator/>
<group>
<field name="import_file" filename="fname" widget="binary"></field>
<field name="fname" invisible="1"></field>
</group>
<group>
<field name="delimiter"></field>
<field name="quotechar"></field>
<field name="encoding"></field>
</group>
</sheet>
<footer>
<button type="object" name="import_file_button">Import</button>
</footer>
</form>
</field>
</record>
<record id="distribution_table_import_wizard_action" model="ir.actions.act_window">
<field name="name">Import Distribution Table</field>
<field name="res_model">energy_selfconsumption.distribution_table_import.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>
import base64
import logging
from datetime import datetime
from csv import reader
from io import StringIO
import chardet
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
logger = logging.getLogger(__name__)
class SelfconsumptionImportWizard(models.TransientModel):
_name = "energy_selfconsumption.selfconsumption_import.wizard"
import_file = fields.Binary(string="Import File (*.csv)")
fname = fields.Char(string="File Name")
delimiter = fields.Char(
default=",",
required=True,
string="File Delimiter",
help="Delimiter in import CSV file.",
)
quotechar = fields.Char(
default='"',
required=True,
string="File Quotechar",
help="Quotechar in import CSV file.",
)
encoding = fields.Char(
default="utf-8",
required=True,
string="File Encoding",
help="Enconding format in import CSV file.",
)
date_format = fields.Char(
default="%d/%m/%Y",
required=True,
string="Date Format",
help="Date format for effective date.",
)
@api.constrains("import_file")
def _constrains_import_file(self):
if self.fname:
format = str(self.fname.split(".")[1])
if format != 'csv':
raise ValidationError(_("Only csv format files are accepted."))
def import_file_button(self):
self.flush()
error_string_list = ""
file_data = base64.b64decode(self.import_file)
parsing_data = self.with_context(active_id=self.ids[0])._parse_file(file_data)
active_id = self.env.context.get("active_id")
project = self.env["energy_selfconsumption.selfconsumption"].browse(active_id)
for index, line in enumerate(parsing_data[1:], start=2):
import_dict = self.get_line_dict(line)
result = self.import_line(import_dict, project)
if not result[0]:
error_string_list = "".join(
[
error_string_list,
_("<li>Line {line}: {error}</li>\n").format(line=index, error=result[1]),
]
)
if error_string_list:
project.message_post(
subject=_("Import Errors"),
body=_("Import errors found: <ul>{list}</ul>").format(
list=error_string_list
)
)
return True
def download_template_button(self):
distribution_table_example_attachment = self.env.ref('energy_selfconsumption.selfconsumption_table_example_attachment')
download_url = '/web/content/{}/?download=true'.format(str(distribution_table_example_attachment.id))
return {
"type": "ir.actions.act_url",
"url": download_url,
"target": "new",
}
def download_list_button(self):
list_state_attachment = self.env.ref('energy_selfconsumption.list_state_attachment')
download_url = '/web/content/{}/?download=true'.format(str(list_state_attachment.id))
return {
"type": "ir.actions.act_url",
"url": download_url,
"target": "new",
}
def get_line_dict(self, line):
return {
"partner_vat": line[0] or False,
"effective_date": line[1] or False,
"code": line[2] or False,
"street": line[3] or False,
"street2": line[4] or False,
"city": line[5] or False,
"state": line[6] or False,
"postal_code": line[7] or False,
"country": line[8] or False,
"owner_vat": line[9] or False,
"owner_firstname": line[10] or False,
"owner_lastname": line[11] or False,
}
def _parse_file(self, data_file):
self.ensure_one()
try:
csv_options = {}
csv_options["delimiter"] = self.delimiter
csv_options["quotechar"] = self.quotechar
try:
decoded_file = data_file.decode(self.encoding)
except UnicodeDecodeError:
detected_encoding = chardet.detect(data_file).get("encoding", False)
if not detected_encoding:
raise UserError(
_("No valid encoding was found for the attached file")
)
decoded_file = data_file.decode(detected_encoding)
csv = reader(StringIO(decoded_file), **csv_options)
return list(csv)
except BaseException:
logger.warning("Parser error", exc_info=True)
raise UserError(_("Error parsing the file"))
def import_line(self, line_dict, project):
partner = self.env["res.partner"].search(
[
"|",
("vat", "=", line_dict["partner_vat"]),
("vat", "=ilike", line_dict["partner_vat"]),
],
limit=1,
)
if not partner:
return False, _("Partner with VAT:<b>{vat}</b> was not found.").format(
vat=line_dict["partner_vat"]
)
if not project.inscription_ids.filtered_domain(
[("partner_id", "=", partner.id)]
):
try:
if line_dict["effective_date"]:
effective_date = datetime.strptime(line_dict["effective_date"], self.date_format).date()
else:
effective_date = fields.date.today()
self.env["energy_project.inscription"].create(
{
"project_id": project.id,
"partner_id": partner.id,
"effective_date": effective_date,
}
)
except Exception as e:
return False, _("Could not create inscription for {vat}. {error}").format(
vat=line_dict["partner_vat"], error=e
)
supply_point = self.env["energy_selfconsumption.supply_point"].search(
[("code", "=", line_dict["code"])]
)
if supply_point and supply_point.partner_id != partner:
return False, _(
"The supply point partner {supply_partner} and the partner {vat} in the inscription are different."
).format(supply_partner=supply_point.partner_id.vat, vat=partner.vat)
if not supply_point:
result = self.create_supply_point(line_dict, partner)
if not result[0]:
return result
return True, False
def create_supply_point(self, line_dict, partner):
if line_dict["owner_vat"]:
owner = self.env["res.partner"].search(
[
"|",
("vat", "=", line_dict["owner_vat"]),
("vat", "=ilike", line_dict["owner_vat"]),
],
limit=1,
)
if not owner:
try:
owner = self.env["res.partner"].create(
{
"vat": line_dict["owner_vat"],
"firstname": line_dict["owner_firstname"],
"lastname": line_dict["owner_lastname"],
"company_type": "person",
}
)
except Exception as e:
return False, _("Owner could not be created: {error}").format(error=e)
else:
owner = partner
country = self.env["res.country"].search([("code", "=", line_dict["country"])])
if not country:
return False, _("Country code was not found: {country}").format(
country=line_dict["country"]
)
state = self.env["res.country.state"].search(
[("code", "=", line_dict["state"]), ("country_id", "=", country.id)]
)
if not state:
return False, _("State code was not found: {state}").format(
state=line_dict["state"]
)
return self.env["energy_selfconsumption.supply_point"].create(
{
"code": line_dict["code"],
"name": line_dict["code"],
"street": line_dict["street"],
"street2": line_dict["street2"],
"city": line_dict["city"],
"zip": line_dict["postal_code"],
"state_id": state.id,
"country_id": country.id,
"owner_id": owner.id,
"partner_id": partner.id,
}
)
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="selfconsumption_import_wizard_form_view" model="ir.ui.view">
<field name="name">energy_selfconsumption.selfconsumption_import.wizard.form</field>
<field name="model">energy_selfconsumption.selfconsumption_import.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<p>You can download an example template for the importation of inscriptions and supply points.
The template must have the following format and be a CSV file.</p>
<p>In the state list file, you can check which code is assigned for your state.</p>
<button type="object" class="btn btn-primary mr-1" name="download_template_button">Download
Template</button>
<button type="object" name="download_list_button">Download State List</button>
<separator/>
<group>
<field name="import_file" filename="fname" widget="binary"></field>
<field name="fname" invisible="1"></field>
</group>
<group>
<field name="date_format"></field>
<field name="delimiter"></field>
<field name="quotechar"></field>
<field name="encoding"></field>
</group>
</sheet>
<footer>
<button type="object" name="import_file_button">Import</button>
</footer>
</form>
</field>
</record>
<record id="selfconsumption_import_wizard_action" model="ir.actions.act_window">
<field name="name">Import data</field>
<field name="res_model">energy_selfconsumption.selfconsumption_import.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>
......@@ -9,7 +9,7 @@
"author": "Coopdevs Treball SCCL & Som Energia SCCL",
"website": "https://somcomunitats.coop/",
"category": "Customizations",
"version": "14.0.1.1.2",
"version": "14.0.1.1.3",
"depends": [
"energy_selfconsumption",
"cooperator",
......
......@@ -4,13 +4,12 @@ from odoo import fields, models, api
class SupplyPoint(models.Model):
_inherit = "energy_selfconsumption.supply_point"
cooperator_id = fields.Many2one(
"res.partner",
partner_id = fields.Many2one(
string="Cooperator",
required=True,
domain=[("member", "=", True)],
help="Cooperator subscribed to the self-consumption project"
)
@api.onchange('cooperator_id')
@api.onchange('partner_id')
def _onchange_cooperator_id(self):
self.owner_id = self.cooperator_id
self.owner_id = self.partner_id
......@@ -20,17 +20,5 @@
parent="energy_selfconsumption.contacts_category_menu"
action="action_partner_cooperator_form"
/>
<record id="view_partner_form_inherit" model="ir.ui.view">
<field name="name">res.partner.form.button.context</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="energy_selfconsumption.view_partner_form_inherit" />
<field name="arch" type="xml">
<xpath expr="//button[@name='get_supply_points']" position="attributes">
<attribute name="context">{'default_cooperator_id': id}</attribute>
</xpath>
</field>
</record>
</data>
</odoo>