Skip to content

Commit

Permalink
[ADD] sustainability*: product's supplier emission factor preceding o…
Browse files Browse the repository at this point in the history
…rder (#223)
  • Loading branch information
BonnetAdam committed Jan 31, 2025
1 parent 4cd683d commit 6917373
Show file tree
Hide file tree
Showing 24 changed files with 973 additions and 149 deletions.
1 change: 1 addition & 0 deletions sustainability/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"views/carbon_factor_contributor.xml",
"views/carbon_factor_type.xml",
"views/product_category.xml",
"views/product_supplierinfo.xml",
"views/res_country.xml",
"views/res_config_settings.xml",
"views/sustainability_scenario.xml",
Expand Down
2 changes: 2 additions & 0 deletions sustainability/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from . import res_company
from . import res_config_settings
from . import res_partner

from . import account_account
from . import account_analytic_line
Expand All @@ -18,6 +19,7 @@
from . import product_category
from . import product_product
from . import product_template
from . import product_supplierinfo
from . import res_country
from . import carbon_factor_database
from . import carbon_factor_contributor
Expand Down
49 changes: 48 additions & 1 deletion sustainability/models/account_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ class AccountMoveLine(models.Model):
compute="_compute_is_carbon_positive", store=False, readonly=True
)

carbon_supplier_id = fields.Many2one(
comodel_name="product.supplierinfo",
string="Supplier Info",
compute="_compute_carbon_supplier_id",
)

def _compute_carbon_supplier_id(self):
"""
Compute the carbon_supplier_id field.
Note that since the same seller can be added multiple times to the same product,
we need to filter the sellers by the partner_id of the order.
If there are multiple matches,
we'll take the most recent one.
"""

for line in self:
seller = line.product_id.seller_ids.filtered(
lambda s: s.partner_id.id == line.move_id.partner_id.id # noqa: B023
)
line.carbon_supplier_id = seller[-1] if len(seller) > 1 else seller

def _prepare_analytic_distribution_line(
self, distribution, account_id, distribution_on_each_plan
) -> dict:
Expand Down Expand Up @@ -98,6 +119,14 @@ def _compute_carbon_is_date_locked(self):
# --------------------------------------------

@api.depends(
# Seller
"product_id.seller_ids",
"product_id.seller_ids.carbon_in_factor_id",
"move_id.partner_id",
# Partner
"partner_id",
"partner_id.carbon_in_factor_id",
# Other
"account_id.carbon_in_factor_id",
"product_id.carbon_in_factor_id",
"product_id.carbon_out_factor_id",
Expand Down Expand Up @@ -130,7 +159,25 @@ def _get_state_field_name(self) -> str:

@api.model
def _get_carbon_compute_possible_fields(self) -> list[str]:
return ["product_id", "account_id"]
return ["carbon_supplier_id", "product_id", "partner_id", "account_id"]

# --- Partner ---
def can_use_partner_id_carbon_value(self) -> bool:
self.ensure_one()
return self.move_id.is_outbound(include_receipts=True) and (
self.partner_id and self.partner_id.can_compute_carbon_value("in")
)

# --- Supplier ---
def can_use_carbon_supplier_id_carbon_value(self) -> bool:
self.ensure_one()
return bool(
self.carbon_supplier_id
) and self.carbon_supplier_id.can_compute_carbon_value("in")

def get_carbon_supplier_id_carbon_compute_values(self) -> dict:
self.ensure_one()
return self.get_product_id_carbon_compute_values()

def _get_carbon_compute_kwargs(self) -> dict:
res = super()._get_carbon_compute_kwargs()
Expand Down
28 changes: 27 additions & 1 deletion sustainability/models/carbon_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class CarbonFactor(models.Model):
product_qty = fields.Integer(compute="_compute_product_qty")
product_categ_qty = fields.Integer(compute="_compute_product_categ_qty")
account_move_qty = fields.Integer(compute="_compute_account_move_qty")
contact_qty = fields.Integer(compute="_compute_contact_qty")
supplierinfo_qty = fields.Integer(compute="_compute_supplierinfo_qty")

# --------------------------------------------

Expand Down Expand Up @@ -185,6 +187,16 @@ def _compute_product_categ_qty(self):
for factor in self:
factor.product_categ_qty = count_data.get(factor.id, 0)

def _compute_contact_qty(self):
count_data = self._get_count_by_model(model="res.partner")
for factor in self:
factor.contact_qty = count_data.get(factor.id, 0)

def _compute_supplierinfo_qty(self):
count_data = self._get_count_by_model(model="product.supplierinfo")
for factor in self:
factor.supplierinfo_qty = count_data.get(factor.id, 0)

def _compute_carbon_currency_id(self):
for factor in self:
factor.carbon_currency_id = (
Expand Down Expand Up @@ -567,11 +579,25 @@ def action_see_product_categ_ids(self):
return self._generate_action(
title=_("Product Category for"),
model="product.category",
ids=self._get_distribution_lines_res_ids("product.template"),
ids=self._get_distribution_lines_res_ids("product.category"),
)

def action_see_account_move_ids(self):
origins = self.env["carbon.line.origin"].search([("factor_id", "in", self.ids)])
return self._generate_action(
title=_("Journal Entries"), model="account.move", ids=origins.move_id.ids
)

def action_see_contact_ids(self):
return self._generate_action(
title="Contact",
model="res.partner",
ids=self._get_distribution_lines_res_ids("res.partner"),
)

def action_see_supplierinfo_ids(self):
return self._generate_action(
title="Supplier Info",
model="product.supplierinfo",
ids=self._get_distribution_lines_res_ids("product.supplierinfo"),
)
2 changes: 2 additions & 0 deletions sustainability/models/carbon_line_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,14 @@ def get_carbon_sign(self) -> int:
"""
return 1

@api.model
def _get_computation_levels_mapping(self) -> dict:
return {
"account.move.line": _("Carbon on invoice"),
"product.product": _("Product"),
"product.category": _("Product category"),
"product.template": _("Product template"),
"product.supplierinfo": _("Supplier info"),
"res.partner": _("Partner"),
"account.account": _("Account"),
"res.company": _("Company fallback"),
Expand Down
30 changes: 20 additions & 10 deletions sustainability/models/carbon_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@


# Todo: make this extendable from sub modules
CARBON_MODELS = [
"carbon.factor",
"product.category",
"product.product",
"product.template",
"res.partner",
"res.company",
"res.country",
]


class CarbonMixin(models.AbstractModel):
Expand All @@ -55,6 +46,20 @@ class CarbonMixin(models.AbstractModel):
_carbon_types = ["in", "out"]
_fallback_records = []

# TODO: Thinks about compute this from env['carbon.line.mixin']._get_computation_levels_mapping()
@api.model
def _CARBON_MODELS(cls):
return [
"carbon.factor",
"product.category",
"product.product",
"product.supplierinfo",
"product.template",
"res.partner",
"res.company",
"res.country",
]

@api.constrains("carbon_in_use_distribution", "carbon_in_distribution_line_ids")
def _check_carbon_in_distribution(self):
for record in self.filtered("carbon_in_use_distribution"):
Expand All @@ -75,7 +80,9 @@ def _get_available_carbon_compute_methods(self) -> list[tuple[str, str]]:
@api.model
def _selection_fallback_model(self):
return [
(x, _(self.env[x]._description)) for x in CARBON_MODELS if x in self.env
(x, _(self.env[x]._description))
for x in self._CARBON_MODELS()
if x in self.env
]

def get_allowed_factors(self):
Expand Down Expand Up @@ -270,6 +277,9 @@ def _search_fallback_record(self, carbon_type: str):
self.ensure_one()
fallback_path = []
for rec in self._build_fallback_records_list(carbon_type):
# skip unsaved records with temporary IDs
if not rec.id or isinstance(rec.id, str) and rec.id.startswith("NewId"):
continue
fallback_path.append(rec)
if rec.has_valid_carbon_value(carbon_type):
return fallback_path
Expand Down
28 changes: 28 additions & 0 deletions sustainability/models/product_supplierinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from odoo import api, models


class ProductSupplierInfo(models.Model):
_name = "product.supplierinfo"
_inherit = ["product.supplierinfo", "carbon.mixin"]

def _update_carbon_in_fields(self, vals):
"""
Helper method to update carbon_in_is_manual and carbon_in_mode based on carbon_in_factor_id.
"""
if vals.get("carbon_in_factor_id"):
vals["carbon_in_is_manual"] = True
vals["carbon_in_mode"] = "manual"
else:
vals["carbon_in_is_manual"] = False
vals["carbon_in_mode"] = "auto"
return vals

@api.model
def create(self, vals):
vals = self._update_carbon_in_fields(vals)
return super().create(vals)

def write(self, vals):
if "carbon_in_factor_id" in vals:
vals = self._update_carbon_in_fields(vals)
return super().write(vals)
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,14 @@ def _cron_initial_carbon_compute_res_partner(self):
"sustainability_purchase.cron_initial_carbon_compute_res_partner"
)
_logger.warning(
"Please deactivate cron '%s' as it is not needed anymore."
% cron_id.name
f"Please deactivate cron '{cron_id.name}' as it is not needed anymore."
)
return

clock = time.perf_counter()
total = 0
_logger.info(
"Running _cron_initial_carbon_compute_res_partner on %s records"
% len(partners)
f"Running _cron_initial_carbon_compute_res_partner on {len(partners)} records"
)

for partner in partners:
Expand All @@ -73,8 +71,7 @@ def _cron_initial_carbon_compute_res_partner(self):
# Catch here any exceptions if you need to.
except Exception as e:
_logger.error(
"Error on cron _cron_initial_carbon_compute_res_partner : Exception: %s"
% e
f"Error on cron _cron_initial_carbon_compute_res_partner : Exception: {e}"
)

_logger.info(
Expand Down
2 changes: 2 additions & 0 deletions sustainability/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import test_conversion
from . import test_vendors_bill
from . import test_action
98 changes: 98 additions & 0 deletions sustainability/tests/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime

from odoo import Command
from odoo.tests import TransactionCase


Expand Down Expand Up @@ -116,3 +117,100 @@ def setUpClass(cls):
"groups_id": [(6, 0, [cls.env.ref("base.group_user").id])],
}
)
cls.setUpClassVendor()

@classmethod
def setUpClassVendor(cls):
# Vendor Bills Related
# Vendor Carbon Factors
cls.vendor_carbon_factor_vendor_1 = cls.env["carbon.factor"].create(
dict(
name="Vendor Carbon Factor 1 (VENDOR)",
carbon_compute_method="monetary",
value_ids=[
Command.create(
dict(
date=datetime.today().strftime("%Y-%m-%d %H:%M"),
carbon_monetary_currency_id=cls.currency_usd.id,
carbon_value=5,
)
)
],
)
)
cls.vendor_carbon_factor_product_1 = cls.env["carbon.factor"].create(
dict(
name="Vendor Carbon Factor 1 (PRODUCT)",
carbon_compute_method="monetary",
value_ids=[
Command.create(
dict(
date=datetime.today().strftime("%Y-%m-%d %H:%M"),
carbon_monetary_currency_id=cls.currency_usd.id,
carbon_value=10,
)
)
],
)
)
# Vendor Partner
cls.vendor_partner_1 = cls.env["res.partner"].create(
dict(
name="Vendor Partner 1",
email="[email protected]",
phone="+123456789",
street="123 Vendor Street",
city="Vendor City",
country_id=cls.env.ref("base.us").id,
carbon_in_factor_id=cls.vendor_carbon_factor_vendor_1.id,
)
)

# Vendor Product Category
cls.vendor_product_category_1 = cls.env["product.category"].create(
dict(
name="Vendor Product Category 1",
)
)
# Vendor Product Template
cls.vendor_product_template_1 = cls.env["product.template"].create(
dict(
name="Vendor Product 1",
categ_id=cls.vendor_product_category_1.id,
list_price=100.0,
carbon_in_factor_id=cls.vendor_carbon_factor_product_1.id,
seller_ids=[
Command.create(
dict(
currency_id=cls.currency_usd.id,
delay=0,
min_qty=1,
partner_id=cls.vendor_partner_1.id,
price=100,
)
)
],
)
)
# Vendor Product Product
cls.vendor_product_product_1 = cls.env["product.product"].search(
[("product_tmpl_id", "=", cls.vendor_product_template_1.id)], limit=1
)
# Vendor Account Move
cls.vendor_account_move = cls.env["account.move"]
cls.vendor_account_move_1 = cls.env["account.move"].create(
dict(
move_type="in_invoice",
partner_id=cls.vendor_partner_1.id,
invoice_date=datetime.today().strftime("%Y-%m-%d"),
invoice_line_ids=[
Command.create(
dict(
product_id=cls.vendor_product_product_1.id,
quantity=10.0,
)
)
],
)
)
cls.vendor_account_move |= cls.vendor_account_move_1
Loading

0 comments on commit 6917373

Please sign in to comment.