Skip to content

Conversation

@AJamal13
Copy link

@AJamal13 AJamal13 commented Jun 21, 2025

This commit introduces a new module inheriting from product_abc_classification_sale_stock to include additional profile types for classifying products by cost or sale price,.

AJamal13 added 2 commits June 21, 2025 22:10
Adding classification by Cost/Sale Price/Sale Margin
@AJamal13
Copy link
Author

Hello @lmignon , @lmignon , @rousseldenis ,
kindly review this code, thanks.

Copy link
Contributor

@lmignon lmignon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AJamal13 Thank you for your proposal. is all the code in github? There's nothing in the proposed code to fill in the new model that have been added.

@AJamal13
Copy link
Author

AJamal13 commented Jun 23, 2025

@AJamal13 Thank you for your proposal. is all the code in github? There's nothing in the proposed code to fill in the new model that have been added.

Thank you for your response, @lmignon
I'm not sure I'm following. would you please explain further ?
I forked the product attribute repo with the purpose of adding a functionality to the following modules:
product_abc_classification
product_abc_classification_sale_stock
to classify by cost/sale price/margin. if I did something in the wrong way please let me know,
Thank you.

@lmignon
Copy link
Contributor

lmignon commented Jun 23, 2025

@AJamal13 Sorry, the most important file was collapsed by default into the diff. Nevertheless can you add unittest plz to ensure the quality of the new addon.

@AJamal13
Copy link
Author

@AJamal13 Sorry, the most important file was collapsed by default into the diff. Nevertheless can you add unittest plz to ensure the quality of the new addon.

Sure, will do

AJamal13 added 7 commits June 24, 2025 00:01
Introduces demo data for finance-based ABC classification, including new XML records and manifest updates. Adds a comprehensive test suite for finance profile logic, validation, and history record creation. Also includes code cleanup, improved formatting, and minor view XML attribute adjustments for consistency.
Added tests to validate that finance profile levels must total 100%, ensure products with negative margin are excluded from ABC ranking for 'sale_margin' profiles, and verify that _get_finance_data_query returns correct SQL for all profile types.
Added tests to validate that finance profile levels must total 100%, ensure products with negative margin are excluded from ABC ranking for 'sale_margin' profiles, and verify that _get_finance_data_query returns correct SQL for all profile types.

Add demo data and tests for finance ABC classification

Introduces demo data for finance-based ABC classification, including new XML records and manifest updates. Adds a comprehensive test suite for finance profile logic, validation, and history record creation. Also includes code cleanup, improved formatting, and minor view XML attribute adjustments for consistency.

fix checks
@AJamal13
Copy link
Author

AJamal13 commented Jun 23, 2025

@lmignon
Hello, kindly review again,

@AJamal13 AJamal13 requested a review from lmignon June 24, 2025 09:56
@quentinDupont
Copy link

Hello !
Thanks for the work !
@AJamal13 Could you ajust your pull request to OCA guidelines please : https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#git
It permits to quickly understand if it's a new module, an improvment, which module is at stake etc.

Thank you !

@AJamal13 AJamal13 changed the title Adding classification by cost and sale price and sale margin. [IMP] Adding classification by cost and sale price and sale margin. Jun 26, 2025
@AJamal13
Copy link
Author

AJamal13 commented Jun 26, 2025

Hello ! Thanks for the work ! @AJamal13 Could you ajust your pull request to OCA guidelines please : https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#git It permits to quickly understand if it's a new module, an improvment, which module is at stake etc.

Thank you !

@quentinDupont Thank you for your response and for the tip,
Done.

),
(
"sale_margin",
"Based on the Sale Margin of delivered sale order line by product",
Copy link
Contributor

@lmignon lmignon Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AJamal13 AFAIK the sale_margin addon provides you the expected margin if all the products sold into the SO are delivered.... If you need to know the real margin based on the delivered qties for your SOL, you need to use the OCA addon sale_margin_delivered and the margin_delivered field provided by this addon. IMO, you should only keep the 2 profiles 'cost' and 'sale_price' in this addon without extra dependency on sale_margin and create a new addon with the extra dependency required in the case where the classification is based on margins.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lmignon sorry re-requested by mistake

@AJamal13 AJamal13 requested a review from lmignon June 26, 2025 11:06
@quentinDupont
Copy link

Hello ! Thanks for the work ! @AJamal13 Could you ajust your pull request to OCA guidelines please : https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#git It permits to quickly understand if it's a new module, an improvment, which module is at stake etc.
Thank you !

@quentinDupont Thank you for your response and for the tip, Done.

You're welcome
You could add "product_abc_classification_finance" in the title :)

@AJamal13 AJamal13 changed the title [IMP] Adding classification by cost and sale price and sale margin. [ADD] product_abc_classification_finance: Classification by cost and sale price Jun 26, 2025
@AJamal13
Copy link
Author

@lmignon Thank you for your response and advise. I excluded the margin from the module, and might add it later.
Would you kindly review the current module ?
Thanks.

],

"installable": True,
"application": False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AJamal13 you should add yourself as the official maintainers 😏

finance_data = self._finance_init_collected_data_instance()
finance_data.product = ProductProduct.browse(product_id)
finance_data.purchase_price = float(
getattr(finance_data.product.product_tmpl_id, "standard_price", 0.0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use getattr ?

# Always set purchase_price (standard cost) from product.template
tmpl = finance_data.product.product_tmpl_id
finance_data.purchase_price = float(
getattr(tmpl, "standard_price", 0.0) or 0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use getattr ?

Comment on lines 183 to 267
ProductClassification = self.env["abc.classification.product.level"]

for profile in to_compute:
# Get finance data per product (list of FinanceSaleData), plus total for percentage computation
finance_data_list, total_value = profile._finance_get_data()
existing_level_ids_to_remove = profile._get_existing_level_ids()
level_percentage = profile._build_ordered_level_cumulative_percentage()
if not level_percentage:
continue
level, percentage = level_percentage.pop(0)
previous_data = None
total_products = len(finance_data_list)
percentage_products = (100.0 / total_products) if total_products else 0.0
# Pick the correct value field for this profile type
if profile.profile_type == "cost":
value_field = "total_cost"
elif profile.profile_type == "sale_price":
value_field = "total_sales"
else:
raise UserError(
_(f"Unknown finance profile_type: {profile.profile_type}")
)

for i, finance_data in enumerate(finance_data_list):
finance_data.total_products = total_products
finance_data.percentage_products = percentage_products
finance_data.cumulated_percentage_products = (
finance_data.percentage_products
if i == 0
else (
finance_data.percentage_products
+ previous_data.cumulated_percentage_products
)
)
# Compute percentages and cumulative percentages for the products
value = getattr(finance_data, value_field, 0.0) or 0.0
finance_data.percentage = (
(100.0 * value / total_value) if total_value else 0.0
)
finance_data.cumulated_percentage = (
finance_data.percentage
if i == 0
else (finance_data.percentage + previous_data.cumulated_percentage)
)
# Allow for floating point imprecision: round to 2 decimals and allow up to 101
if float_round(finance_data.cumulated_percentage, 2) > 100.01:
raise UserError(
_(
"Cumulative percentage greater than 100 (actual: %.4f)."
% finance_data.cumulated_percentage
)
)
finance_data.sum_cumulated_percentages = (
finance_data.cumulated_percentage
+ finance_data.cumulated_percentage_products
)
# Compute ABC classification for the products based on the
# sum of cumulated percentages
if (
finance_data.sum_cumulated_percentages > percentage
and len(level_percentage) > 0
):
level, percentage = level_percentage.pop(0)
product = finance_data.product
levels = product.abc_classification_product_level_ids
product_abc_classification = levels.filtered(
lambda p, prof=profile: p.profile_id == prof
)
finance_data.computed_level = level
if product_abc_classification:
existing_level_ids_to_remove.remove(product_abc_classification.id)
if product_abc_classification.level_id != level:
vals = profile._finance_data_to_vals(finance_data, create=False)
product_abc_classification.write(vals)
else:
vals = profile._finance_data_to_vals(finance_data, create=True)
product_abc_classification = ProductClassification.create(vals)
finance_data.product_level = product_abc_classification
previous_data = finance_data
if finance_data_list:
profile._finance_log_history(finance_data_list)
profile._purge_obsolete_level_values(existing_level_ids_to_remove)
return res
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this code should be placed into a dedicated method to ease readability and modularity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the advice, but in this code i used the method from product_abc_classification_sale_stock as a reference while just adjusting some details, wouldn't it work just fine as it is ?

@AJamal13 AJamal13 requested a review from lmignon June 26, 2025 13:56
@AJamal13
Copy link
Author

@lmignon thank you for your advices,
updated getattrs, is it possible to keep it working as it is currently with _compute_abc_classification method in it's current form ?

@lmignon
Copy link
Contributor

lmignon commented Jun 27, 2025

Thank for all your work @AJamal13
I'm still convinced it would be cleaner to put the logic to compute the classification for a specific profile into a dedicated method but I will not block for this. I wonder if the best approach in terms of modularity wouldn't be to make one module per profile.

  • product_abc_classisifaction_sale_price
  • product_abc_classification_cost
  • product_abc_classification_sale_margin

But it's also a lot of work and if you don't see the point, I won't block for this too.

Can you fix pre-commit plz?

@AJamal13
Copy link
Author

@lmignon thanks for the advice, Done.
Please check again.

Copy link
Contributor

@lmignon lmignon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all your work. Even if I think we could still improve the quality and modularity you already invested a lot of effort to align your initial proposal with the expected OCA quality. The code LGTM, let's move forward 😏

Copy link
Contributor

@jbaudoux jbaudoux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to drop the commits where you merged the mainstream 16.0 branch in your branch. You should rebase on 16.0 instead.
And squash all your commits in a single commit

@AJamal13
Copy link
Author

Thank you for all your work. Even if I think we could still improve the quality and modularity you already invested a lot of effort to align your initial proposal with the expected OCA quality. The code LGTM, let's move forward 😏

Thank you!
Next contribution will be smoother 😁

@AJamal13
Copy link
Author

You need to drop the commits where you merged the mainstream 16.0 branch in your branch. You should rebase on 16.0 instead. And squash all your commits in a single commit

Ok, will do.

@rousseldenis
Copy link
Contributor

rousseldenis commented Jun 27, 2025

@AJamal13 Thanks for this.

As already said, your commits should be cleaned to follow a little bit conventions (they should look like [ADD] product_abc_classification_finance and then, improvement ones like [IMP] product_abc_classification_finance: (....). See this : https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#71commit-message

And you can read the whole page to help you contributing. Thanks

Like this:

image

@rousseldenis rousseldenis added this to the 16.0 milestone Jun 27, 2025
and not rec.warehouse_id
):
raise ValidationError(
_("You must specify a warehouse for {profile_name}").format(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use translation function arguments instead of format()

value_field = "total_sales"
else:
raise UserError(
_(f"Unknown finance profile_type: {profile.profile_type}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

profile._purge_obsolete_level_values(existing_level_ids_to_remove)
return res

def _finance_log_history(self, finance_data_list):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this ??? And why passing through csv implementation as you bypass the ORM?

@rousseldenis
Copy link
Contributor

@AJamal13

@AJamal13
Copy link
Author

AJamal13 commented Jul 3, 2025

@AJamal13

@rousseldenis thank you for your advices, will apply changes and request your review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants