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
Commits on Source (115)
Showing
with 606 additions and 5 deletions
{
"name": "Energy Community",
"version": "14.0.2.1.0",
"version": "14.0.3.1.0",
"depends": [
"account",
"cooperator_account_banking_mandate",
......
......@@ -18,6 +18,7 @@
"data": [
"data/energy_project.reseller.csv",
"data/energy_project.supplier.csv",
"data/uom_data.xml",
"security/res_groups.xml",
"security/ir.model.access.csv",
"security/ir_rule_data.xml",
......
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="energy_uom_category" model="uom.category">
<field name="name">Energy</field>
</record>
<record id="kw_uom" model="uom.uom">
<field name="name">kW</field>
<field name="uom_type">reference</field>
<field name="category_id" ref="energy_uom_category" />
<field name="active">1</field>
<field name="rounding">0.01000</field>
</record>
</data>
</odoo>
from . import models
from . import wizards
from . import controllers
from . import tests
......@@ -13,9 +13,13 @@
"depends": [
"base",
"mail",
"contract",
"contract_variable_quantity",
"contract_queue_job",
"energy_project",
"partner_firstname",
"web_m2x_options",
"l10n_es",
],
"data": [
"security/ir.model.access.csv",
......@@ -24,6 +28,7 @@
"data/ir_sequence_data.xml",
"data/ir_attactment_data.xml",
"data/custom_paper_format_views.xml",
"data/contract_line_qty_formula_data.xml",
"views/selfconsumption_views.xml",
"views/supply_point_views.xml",
"views/res_partner_views.xml",
......@@ -31,6 +36,8 @@
"views/supply_point_assignation_views.xml",
"wizards/selfconsumption_import_wizard_views.xml",
"wizards/distribution_table_import_wizard_views.xml",
"wizards/contract_generation_wizard_views.xml",
"wizards/define_invoicing_mode_wizard_view.xml",
"reports/selfconsumption_reports.xml",
],
}
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="power_acquired_formula" model="contract.line.qty.formula">
<field name="name">Power Acquired Formula</field>
<field name="code">
days_timedelta = contract.next_period_date_end - contract.next_period_date_start
if days_timedelta:
# Add one so it counts the same day too (month = 29 + 1)
days_between = days_timedelta.days + 1
else:
days_between = 0
result = contract.supply_point_assignation_id.distribution_table_id.selfconsumption_project_id.power * contract.supply_point_assignation_id.coefficient * days_between
</field>
</record>
<record id="energy_delivered_formula" model="contract.line.qty.formula">
<field name="name">Energy Delivered Formula</field>
<field name="code">
days_timedelta = contract.next_period_date_end - contract.next_period_date_start
energy_delivered = 0
if days_timedelta:
# Add one so it counts the same day too (month = 29 + 1)
days_between = days_timedelta.days + 1
else:
days_between = 0
if 'energy_delivered' in context:
energy_delivered = context['energy_delivered']
result = energy_delivered * contract.supply_point_assignation_id.coefficient * days_between
</field>
</record>
<record
id="energy_delivered_variable_formula"
model="contract.line.qty.formula"
>
<field name="name">Energy Delivered Variable Formula</field>
<field name="code">
result = 0
</field>
</record>
</data>
</odoo>
......@@ -4,3 +4,5 @@ from . import partner
from . import distribution_table
from . import supply_point_assignation
from . import project
from . import contract
from . import product
from odoo import fields, models
class Contract(models.Model):
_inherit = "contract.contract"
supply_point_assignation_id = fields.Many2one(
"energy_selfconsumption.supply_point_assignation",
string="Selfconsumption project",
)
project_id = fields.Many2one(
"energy_project.project",
ondelete="restrict",
string="Energy Project",
related="supply_point_assignation_id.distribution_table_id.selfconsumption_project_id.project_id",
)
from odoo import fields, models
class Product(models.Model):
_inherit = "product.product"
contract_template_id = fields.Many2one("contract.template")
......@@ -3,6 +3,15 @@ from datetime import datetime
from odoo import _, fields, models
from odoo.exceptions import ValidationError
INVOICING_VALUES = [
("power_acquired", _("Power Acquired")),
("energy_delivered", _("Energy Delivered")),
(
"energy_delivered_variable",
_("Energy Delivered Variable Hourly Coefficient"),
),
]
class Selfconsumption(models.Model):
_name = "energy_selfconsumption.selfconsumption"
......@@ -20,6 +29,13 @@ class Selfconsumption(models.Model):
for record in self:
record.inscription_count = len(record.inscription_ids)
def _compute_contract_count(self):
for record in self:
related_contracts = self.env["contract.contract"].search_count(
[("project_id", "=", record.id)]
)
record.contracts_count = related_contracts
def _compute_report_distribution_table(self):
"""
This compute field gets the distribution table needed to generate the reports.
......@@ -52,7 +68,7 @@ class Selfconsumption(models.Model):
required=True,
default=lambda self: self.env.company.partner_id,
)
power = fields.Float(string="Generation Power (kW)")
power = fields.Float(string="Rated Power (kWn)")
distribution_table_ids = fields.One2many(
"energy_selfconsumption.distribution_table",
"selfconsumption_project_id",
......@@ -68,6 +84,19 @@ class Selfconsumption(models.Model):
"energy_project.inscription", "project_id", readonly=True
)
inscription_count = fields.Integer(compute=_compute_inscription_count)
contracts_count = fields.Integer(compute=_compute_contract_count)
invoicing_mode = fields.Selection(INVOICING_VALUES, string="Invoicing Mode")
product_id = fields.Many2one("product.product", string="Product")
contract_template_id = fields.Many2one(
"contract.template",
string="Contract Template",
related="product_id.contract_template_id",
)
reseller_id = fields.Many2one(
"energy_project.reseller",
string="Energy Reseller",
help="Select the associated Energy Reseller",
)
def get_distribution_tables(self):
self.ensure_one()
......@@ -91,6 +120,17 @@ class Selfconsumption(models.Model):
"context": {"create": True, "default_project_id": self.id},
}
def get_contracts(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": "Contracts",
"view_mode": "tree,form",
"res_model": "contract.contract",
"domain": [("project_id", "=", self.id)],
"context": {"create": True, "default_project_id": self.id},
}
def distribution_table_state(self, actual_state, new_state):
distribution_table_to_activate = self.distribution_table_ids.filtered(
lambda table: table.state == actual_state
......@@ -107,15 +147,55 @@ class Selfconsumption(models.Model):
self.distribution_table_state("validated", "process")
def activate(self):
"""
Activates the energy self-consumption project, performing various validations.
This method checks for the presence of a valid code, CIL, and rated power
for the project. If all validations pass, it instances a wizard and generating
contracts for the project.
Note:
The change of state for the 'self-consumption' and 'distribution_table'
models is performed in the wizard that gets opened. These state changes
are executed only after the contracts have been successfully generated.
Returns:
dict: A dictionary containing the action details for opening the wizard.
Raises:
ValidationError: If the project does not have a valid Code, CIL, or Rated Power.
"""
for record in self:
if not record.code:
raise ValidationError(_("Project must have a valid Code."))
if not record.cil:
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."))
record.write({"state": "active"})
self.distribution_table_state("process", "active")
raise ValidationError(_("Project must have a valid Rated Power."))
if not record.invoicing_mode:
raise ValidationError(
_("Project must have defined a invoicing mode before activation.")
)
# Create ContractGenerationWizard
contract_wizard = self.env[
"energy_selfconsumption.contract_generation.wizard"
].create({"selfconsumption_id": self.id})
# Generate Contracts
contract_wizard.generate_contracts_button()
def set_invoicing_mode(self):
return {
"name": _("Define Invoicing Mode"),
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "energy_selfconsumption.define_invoicing_mode.wizard",
"views": [(False, "form")],
"view_id": False,
"target": "new",
"context": {"default_selfconsumption_id": self.id},
}
def action_selfconsumption_import_wizard(self):
self.ensure_one()
......
......@@ -9,4 +9,6 @@ access_energy_selfconsumption_distribution_table_admin,energy_selfconsumption.di
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
access_energy_selfconsumption_contract_generation_wizard_admin,energy_selfconsumption.contract_generation.wizard.admin,model_energy_selfconsumption_contract_generation_wizard,energy_project.group_admin,1,1,1,1
accces_energy_selfconsumption_coefficient_report,energy_selfconsumption.coefficient_report.admin,model_energy_selfconsumption_coefficient_report,energy_project.group_admin,1,1,1,1
access_energy_elfconsumptionn_define_invoicing_mode_admin,energy_selfconsumption.define_invoicing_mode.wizard.admin,model_energy_selfconsumption_define_invoicing_mode_wizard,energy_project.group_admin,1,1,1,1
from . import test_contract_generation_wizard
from datetime import datetime
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestContractGenerationWizard(TransactionCase):
def setUp(self):
super().setUp()
self.partner = self.env["res.partner"].create({"name": "test partner"})
self.selfconsumption = self.env[
"energy_selfconsumption.selfconsumption"
].create(
{
"name": "test Selfconsumption Project",
"type": self.env.ref(
"energy_selfconsumption.selfconsumption_project_type"
).id,
"code": "ES0397277816188340VL",
"cil": "001ES0397277816188340VL",
"state": "activation",
"power": 100,
"street": "Carrer de Sants, 79",
"zip": "08014",
"city": "Barcelona",
"state_id": self.env.ref("base.state_es_b").id,
"country_id": self.env.ref("base.es").id,
}
)
self.inscription = self.env["energy_project.inscription"].create(
{
"project_id": self.selfconsumption.project_id.id,
"partner_id": self.partner.id,
"effective_date": datetime.today(),
}
)
self.supply_point = self.env["energy_selfconsumption.supply_point"].create(
{
"code": "ES0029542181297829TM",
"street": "C. de Sta. Catalina",
"street2": "55º B",
"zip": "08014",
"city": "Barcelona",
"state_id": self.env.ref("base.state_es_b").id,
"country_id": self.env.ref("base.es").id,
"owner_id": self.partner.id,
"partner_id": self.partner.id,
}
)
self.distribution_table = self.env[
"energy_selfconsumption.distribution_table"
].create(
{
"name": "DT001",
"selfconsumption_project_id": self.selfconsumption.id,
"type": "fixed",
"state": "process",
}
)
self.supply_point_assignation = self.env[
"energy_selfconsumption.supply_point_assignation"
].create(
{
"distribution_table_id": self.distribution_table.id,
"supply_point_id": self.supply_point.id,
"coefficient": 1,
}
)
self.define_invoicing_mode_wizard = self.env[
"energy_selfconsumption.define_invoicing_mode.wizard"
].create(
{
"selfconsumption_id": self.selfconsumption.id,
"price": 0.1,
"recurring_interval": 1,
"recurring_rule_type": "monthly",
"invoicing_mode": "power_acquired",
}
)
self.contract_generation_wizard = self.env[
"energy_selfconsumption.contract_generation.wizard"
].create(
{
"selfconsumption_id": self.selfconsumption.id,
}
)
def test_generation_contracts(self):
res = self.define_invoicing_mode_wizard.save_data_to_selfconsumption()
self.assertEqual(
res,
{
"type": "ir.actions.act_window_close",
},
)
res = self.contract_generation_wizard.generate_contracts_button()
self.assertEqual(res, True)
related_contract = self.env["contract.contract"].search(
[("project_id", "=", self.selfconsumption.project_id.id)]
)
contract_line = related_contract[0].contract_line_ids[0]
days_timedelta = (
contract_line.next_period_date_end - contract_line.next_period_date_start
)
expected_quantity = 100 * 1 * (days_timedelta.days + 1)
related_contract[0].recurring_create_invoice()
invoice = related_contract._get_related_invoices()
self.assertEqual(invoice.invoice_line_ids[0].quantity, expected_quantity)
self.assertEqual(invoice.invoice_line_ids[0].price_unit, 0.1)
......@@ -69,6 +69,12 @@
string="Download Partition Coefficient"
attrs="{'invisible':[('state','not in',['activation', 'active'])]}"
/>
<button
type="object"
string="Define Invoicing Mode"
name="set_invoicing_mode"
attrs="{'invisible':[('invoicing_mode', '!=', False)]}"
/>
<field
name="state"
......@@ -106,6 +112,21 @@
widget="statinfo"
/>
</button>
<button
class="oe_stat_button"
type="object"
name="get_contracts"
icon="fa-files-o"
attrs="{'invisible': [('state', '!=', 'active')]}"
>
<field
string="Contracts"
name="contracts_count"
widget="statinfo"
/>
</button>
</div>
<widget
name="web_ribbon"
......@@ -144,6 +165,11 @@
<field
name="owner_id"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
/>
<field
name="reseller_id"
attrs="{'readonly': [('state', 'not in', ['draft', 'activation'])]}"
options="{'no_create': True, 'no_edit':True, 'no_open': True}"
/>
</group>
<group>
......@@ -196,6 +222,25 @@
</div>
</group>
</group>
<notebook colspan="4">
<page
string="Invoicing Mode"
name="invoicing_mode"
autofocus="autofocus"
attrs="{'invisible':[('invoicing_mode', '==', False)]}"
>
<group>
<field name="invoicing_mode" readonly="True" />
<field name="product_id" readonly="True" />
<field
name="contract_template_id"
readonly="True"
/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
......
from . import selfconsumption_import_wizard
from . import distribution_table_import_wizard
from . import contract_generation_wizard
from . import define_invoicing_mode_wizard
from odoo import _, fields, models
class ContractGenerationWizard(models.TransientModel):
_name = "energy_selfconsumption.contract_generation.wizard"
selfconsumption_id = fields.Many2one(
"energy_selfconsumption.selfconsumption", readonly=True
)
def generate_contracts_button(self):
"""
This method generates contracts based on supply point assignations.
It first creates a product and a contract formula. It then
aggregates supply point assignations by partner and owner
to generate the contracts.
In the other hand, if the process was successful, the state of self-consumption
and the distribution_table changes to 'active'.
Returns:
bool: Always True, indicating successful execution.
Raises:
UserWarning: When no accounting journal is found.
SomeException: When no distribution table in process of activation is found.
"""
# Get distribution table
distribution_id = (
self.selfconsumption_id.distribution_table_ids.filtered_domain(
[("state", "=", "process")]
)
)
if not distribution_id:
raise _("There is no distribution table in proces of activation.")
# Create contracts
for supply_point_assignation in distribution_id.supply_point_assignation_ids:
contract = self.env["contract.contract"].create(
{
"name": _("Contract - %s - %s")
% (
self.selfconsumption_id.name,
supply_point_assignation.supply_point_id.partner_id.name,
),
"partner_id": supply_point_assignation.supply_point_id.partner_id.id,
"supply_point_assignation_id": supply_point_assignation.id,
"company_id": self.env.company.id,
"date_start": fields.date.today(),
"contract_template_id": self.selfconsumption_id.product_id.contract_template_id.id,
}
)
# We use the next method from the contract model to update the contract fields with contract template
contract._onchange_contract_template_id()
for contract_line_id in contract.contract_line_ids:
contract_line_id.write(
{
"name": contract_line_id.name.format(
code=supply_point_assignation.supply_point_id.code,
owner_id=supply_point_assignation.supply_point_id.owner_id.display_name,
),
}
)
# Update selfconsumption and distribution_table state
self.selfconsumption_id.write({"state": "active"})
self.selfconsumption_id.distribution_table_state("process", "active")
return True
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="contract_generation_wizard_form_view" model="ir.ui.view">
<field
name="name"
>energy_selfconsumption.contract_generation_wizard.form</field>
<field
name="model"
>energy_selfconsumption.contract_generation.wizard</field>
<field name="arch" type="xml">
<form string="Contract Generation">
<footer>
<button
type="object"
name="generate_contracts_button"
>Generate</button>
</footer>
</form>
</field>
</record>
</data>
</odoo>
from odoo import _, fields, models
from odoo.exceptions import UserError
from ..models.selfconsumption import INVOICING_VALUES
class ContractGenerationWizard(models.TransientModel):
_name = "energy_selfconsumption.define_invoicing_mode.wizard"
_inherit = [
"contract.recurrency.mixin",
]
invoicing_mode = fields.Selection(
INVOICING_VALUES,
string="Invoicing Mode",
default="power_acquired",
required=True,
)
price = fields.Float(required=True)
recurring_interval = fields.Integer(
required=True,
)
recurring_rule_type = fields.Selection(
default="monthlylastday",
required=True,
)
selfconsumption_id = fields.Many2one(
"energy_selfconsumption.selfconsumption", readonly=True
)
def _prepare_product_values(self):
account_income_xml_id = "l10n_es.%i_account_common_7050" % self.env.company.id
account_income_id = self.env.ref(account_income_xml_id)
account_tax_xml_id = (
"l10n_es.%i_account_tax_template_s_iva21s" % self.env.company.id
)
account_tax_id = self.env.ref(account_tax_xml_id)
uom_kw_id = self.env.ref("energy_project.kw_uom")
return {
"name": self.selfconsumption_id.name,
"type": "service",
"lst_price": self.price,
"company_id": self.env.company.id,
"property_account_income_id": account_income_id.id,
"taxes_id": [account_tax_id.id],
"sale_ok": True,
"purchase_ok": False,
"uom_id": uom_kw_id.id,
"uom_po_id": uom_kw_id.id,
}
def _prepare_contract_template_values(self, journal_id, contract_line):
return {
"name": self.selfconsumption_id.name,
"journal_id": journal_id.id,
"company_id": self.env.company.id,
"contract_line_ids": contract_line,
"recurring_interval": self.recurring_interval,
"recurring_rule_type": self.recurring_rule_type,
"recurring_invoicing_type": "post-paid",
}
def _prepare_contract_line_template_values(self, product_id, formula_contract_id):
return {
"product_id": product_id.id,
"automatic_price": True,
"company_id": self.env.company.id,
"qty_type": "variable",
"qty_formula_id": formula_contract_id.id,
"uom_id": product_id.uom_id.id,
# Values are formatted in contract_generation_wizard
"name": """CUPS: {code}\nOwner: {owner_id}\nInvoicing period: #START# - #END#""",
}
def save_data_to_selfconsumption(self):
if self.invoicing_mode == "energy_delivered_variable":
raise UserError(_("This invoicing mode is not yet implemented"))
# Create product
product_id = self.env["product.product"].create(self._prepare_product_values())
# TODO:Update formula energy_delivered_variable.
formula_contract_id = None
if self.invoicing_mode == "power_acquired":
formula_contract_id = self.env.ref(
"energy_selfconsumption.power_acquired_formula"
)
elif self.invoicing_mode == "energy_delivered":
formula_contract_id = self.env.ref(
"energy_selfconsumption.energy_delivered_formula"
)
elif self.invoicing_mode == "energy_delivered_variable":
formula_contract_id = self.env.ref(
"energy_selfconsumption.energy_delivered_variable_formula"
)
# Search accounting journal
journal_id = self.env["account.journal"].search(
[("company_id", "=", self.env.company.id), ("type", "=", "sale")], limit=1
)
if not journal_id:
raise UserWarning(_("Accounting Journal not found."))
# Create Contract Template
contract_line = [
(
0,
0,
self._prepare_contract_line_template_values(
product_id, formula_contract_id
),
)
]
contract_template_id = self.env["contract.template"].create(
self._prepare_contract_template_values(journal_id, contract_line)
)
product_id.write({"contract_template_id": contract_template_id.id})
self.selfconsumption_id.write(
{
"invoicing_mode": self.invoicing_mode,
"product_id": product_id,
}
)
return {
"type": "ir.actions.act_window_close",
}
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="define_invoicing_mode_wizard_form_view">
<field
name="name"
>energy_selfconsumption.define_invoicing_mode.wizard.form</field>
<field
name="model"
>energy_selfconsumption.define_invoicing_mode.wizard</field>
<field name="arch" type="xml">
<form string="Define Invoicing Mode Wizard">
<sheet>
<group>
<group col="1">
<field name="invoicing_mode" />
<field
name="price"
string="kWh Price (€/kWh)"
attrs="{'invisible': [('invoicing_mode', 'not in', ['energy_delivered', 'energy_delivered_variable'])]}"
/>
<field
name="price"
string="kWn Price per day (€/kWn/day)"
attrs="{'invisible': [('invoicing_mode', '!=', 'power_acquired')]}"
/>
</group>
<group col="2">
<field name="recurring_interval" />
<field name="recurring_rule_type" />
</group>
</group>
</sheet>
<footer>
<button
name="save_data_to_selfconsumption"
string="Save Data"
type="object"
class="btn-primary"
/>
</footer>
</form>
</field>
</record>
</data>
</odoo>
......@@ -6,6 +6,7 @@ setuptools.setup(
"depends_override": {
"energy_project": "odoo14-addon-energy-project==14.0.2.0.0",
"web_m2x_options": "odoo14-addon-web-m2x-options==14.0.1.1.1",
"contract_queue_job": "odoo14-addon-contract-queue-job==14.0.1.0.1.dev3",
}
}
)