From a1f73dabe70ebd3790d4bfb5543f9eb3950ce747 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 14 Feb 2026 17:34:18 +0100 Subject: [PATCH 1/4] Arbore/arbore#85 add mail_quoted_reply_icon --- mail_quoted_reply_icon/__init__.py | 0 mail_quoted_reply_icon/__manifest__.py | 19 +++++++++++ .../src/models/message_action_view.esm.js | 33 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 mail_quoted_reply_icon/__init__.py create mode 100644 mail_quoted_reply_icon/__manifest__.py create mode 100644 mail_quoted_reply_icon/static/src/models/message_action_view.esm.js diff --git a/mail_quoted_reply_icon/__init__.py b/mail_quoted_reply_icon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mail_quoted_reply_icon/__manifest__.py b/mail_quoted_reply_icon/__manifest__.py new file mode 100644 index 0000000..2feb759 --- /dev/null +++ b/mail_quoted_reply_icon/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Mail Quoted Reply Icon", + "summary": "Changes the icon for quoted reply from fa-reply to fa-envelope", + "version": "16.0.1.0.0", + "category": "Discuss", + "license": "AGPL-3", + "author": "Criptoamrt", + "website": "https://criptomart.net", + "depends": ["mail_quoted_reply"], + "data": [], + "demo": [], + "installable": True, + "application": False, + "assets": { + "web.assets_backend": [ + "/mail_quoted_reply_icon/static/src/models/*.js", + ], + }, +} diff --git a/mail_quoted_reply_icon/static/src/models/message_action_view.esm.js b/mail_quoted_reply_icon/static/src/models/message_action_view.esm.js new file mode 100644 index 0000000..cf8ee56 --- /dev/null +++ b/mail_quoted_reply_icon/static/src/models/message_action_view.esm.js @@ -0,0 +1,33 @@ +/** @odoo-module **/ + +import {registerPatch} from "@mail/model/model_core"; + +registerPatch({ + name: "MessageActionView", + fields: { + classNames: { + compute() { + let classNames = this._super() || ""; + if ( + this.messageAction.messageActionListOwner === + this.messageAction.replyMessageAction + ) { + // Replace the fa-reply icon added by mail_quoted_reply with fa-envelope + classNames = classNames.replace("fa-reply", "fa-envelope"); + } + return classNames; + }, + }, + title: { + compute() { + if ( + this.messageAction.messageActionListOwner === + this.messageAction.replyMessageAction + ) { + return this.env._t("Reply by Email"); + } + return this._super(); + }, + }, + }, +}); From 25faab83bb015b17e7adb34ee07e51eb45abc98e Mon Sep 17 00:00:00 2001 From: luis Date: Tue, 17 Feb 2026 17:36:13 +0100 Subject: [PATCH 2/4] Criptomart/red-supermercados-coop#37 purchase_order_product_recommendation_supermarket: round qty_to_order when product_uom_categ_unit --- .../wizards/purchase_order_recommendation.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py b/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py index ad14f13..9d4840e 100644 --- a/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py +++ b/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py @@ -174,13 +174,21 @@ class PurchaseOrderRecommendationSupermarketWizard(models.TransientModel): if days_with_stock != 0 else 1 ) - + uom_categ_unit = self.env.ref( + "uom.product_uom_categ_unit", raise_if_not_found=False + ) if self.order_days != 0: - qty_to_order = max( - 0, - (self.order_days * res["units_avg_delivered"]) - - res["units_virtual_available"], - ) + qty_calc = (self.order_days * res["units_avg_delivered"]) - res[ + "units_virtual_available" + ] + qty_to_order = max(0, qty_calc) + # Round only if the UoM category is 'uom.product_uom_categ_unit' + if ( + product_id.uom_po_id + and uom_categ_unit + and product_id.uom_po_id.category_id.id == uom_categ_unit.id + ): + qty_to_order = math.ceil(qty_to_order) # Force a minimum suggested quantity when forecast <= 0 and product configured # We apply this BEFORE packaging adjustment so packages logic can upscale it. @@ -198,7 +206,15 @@ class PurchaseOrderRecommendationSupermarketWizard(models.TransientModel): packaging_qty = product_id.packaging_ids[:1].qty if packaging_qty: qty_to_order = math.ceil(qty_to_order / packaging_qty) * packaging_qty - res["units_included"] = qty_to_order + # Round only if the UoM category is 'uom.product_uom_categ_unit' + if ( + product_id.uom_po_id + and uom_categ_unit + and product_id.uom_po_id.category_id.id == uom_categ_unit.id + ): + res["units_included"] = math.ceil(qty_to_order) + else: + res["units_included"] = qty_to_order # Get quantities scrapped domain = self._get_move_line_domain(product_id, src="internal", dst="inventory") From dab4123379310fc6e340002c09dab18208f9648b Mon Sep 17 00:00:00 2001 From: luis Date: Tue, 17 Feb 2026 17:48:09 +0100 Subject: [PATCH 3/4] purchase_order_product_recommendation_supermarket: fix round problem in _compute_packaging_qty --- .../wizards/purchase_order_recommendation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py b/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py index 9d4840e..b385aed 100644 --- a/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py +++ b/purchase_order_product_recommendation_supermarket/wizards/purchase_order_recommendation.py @@ -433,7 +433,7 @@ class PurchaseOrderRecommendationLine(models.TransientModel): def _compute_packaging_qty(self): for rec in self: if rec.packaging_id and rec.packaging_id.qty: - rec.packaging_qty = int(rec.units_included // rec.packaging_id.qty) + rec.packaging_qty = math.ceil(rec.units_included / rec.packaging_id.qty) else: rec.packaging_qty = 0 From bf02699b612a6eff7ffc5b1ae05bed2fba484728 Mon Sep 17 00:00:00 2001 From: luis Date: Tue, 24 Feb 2026 13:37:26 +0100 Subject: [PATCH 4/4] FoodcoopBCN/foodcoopbcn#167 add pos_product_available_link --- pos_product_available_link/README.rst | 43 +++++++++++++++++++ pos_product_available_link/__init__.py | 1 + pos_product_available_link/__manifest__.py | 13 ++++++ pos_product_available_link/models/__init__.py | 1 + pos_product_available_link/models/product.py | 32 ++++++++++++++ .../tests/test_product_available_link.py | 21 +++++++++ 6 files changed, 111 insertions(+) create mode 100644 pos_product_available_link/README.rst create mode 100644 pos_product_available_link/__init__.py create mode 100644 pos_product_available_link/__manifest__.py create mode 100644 pos_product_available_link/models/__init__.py create mode 100644 pos_product_available_link/models/product.py create mode 100644 pos_product_available_link/tests/test_product_available_link.py diff --git a/pos_product_available_link/README.rst b/pos_product_available_link/README.rst new file mode 100644 index 0000000..8956fc4 --- /dev/null +++ b/pos_product_available_link/README.rst @@ -0,0 +1,43 @@ +POS Product Available Link +========================= + +Automatically disables available_in_pos when sale_ok is unchecked on products. + +Features +======== + +* When you uncheck "Can be Sold" (sale_ok), the product is also removed from POS (available_in_pos). +* Prevents products with sale_ok=False from appearing in POS. + +Installation +============ + +Install this module as usual via Apps or with odoo-bin. + +Configuration +============= + +No configuration required. + +Usage +===== + +1. Go to Products. +2. Uncheck "Can be Sold". +3. The product will no longer be available in POS. + +Known issues / Roadmap +====================== + +* Only disables available_in_pos when sale_ok is unchecked. Does not re-enable. + +Credits +======= + +Authors +------- +* Your Name + +Contributors +------------ +* Odoo Community Association (OCA) diff --git a/pos_product_available_link/__init__.py b/pos_product_available_link/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/pos_product_available_link/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_product_available_link/__manifest__.py b/pos_product_available_link/__manifest__.py new file mode 100644 index 0000000..cfea84e --- /dev/null +++ b/pos_product_available_link/__manifest__.py @@ -0,0 +1,13 @@ +{ + "name": "POS Product Available Link", + "summary": "Automatically enable/disable available_in_pos when sale_ok is checked/unchecked.", + "version": "16.0.1.0.0", + "category": "Point of Sale", + "license": "AGPL-3", + "author": "Criptomart", + "website": "https://criptomart.net", + "depends": ["point_of_sale", "product"], + "data": [], + "installable": True, + "application": False, +} diff --git a/pos_product_available_link/models/__init__.py b/pos_product_available_link/models/__init__.py new file mode 100644 index 0000000..9649db7 --- /dev/null +++ b/pos_product_available_link/models/__init__.py @@ -0,0 +1 @@ +from . import product diff --git a/pos_product_available_link/models/product.py b/pos_product_available_link/models/product.py new file mode 100644 index 0000000..683e0e0 --- /dev/null +++ b/pos_product_available_link/models/product.py @@ -0,0 +1,32 @@ +# Copyright 2026 Your Name +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + @api.onchange("sale_ok") + def _onchange_sale_ok_link_available_in_pos(self): + for product in self: + product.available_in_pos = product.sale_ok + + def write(self, vals): + if "sale_ok" in vals: + vals["available_in_pos"] = vals["sale_ok"] + return super().write(vals) + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.onchange("sale_ok") + def _onchange_sale_ok_link_available_in_pos(self): + for product in self: + product.available_in_pos = product.sale_ok + + def write(self, vals): + if "sale_ok" in vals: + vals["available_in_pos"] = vals["sale_ok"] + return super().write(vals) diff --git a/pos_product_available_link/tests/test_product_available_link.py b/pos_product_available_link/tests/test_product_available_link.py new file mode 100644 index 0000000..95084f1 --- /dev/null +++ b/pos_product_available_link/tests/test_product_available_link.py @@ -0,0 +1,21 @@ +from odoo.tests.common import TransactionCase + + +class TestProductAvailableLink(TransactionCase): + def setUp(self): + super().setUp() + self.product = self.env["product.product"].create( + { + "name": "Test Product", + "sale_ok": True, + "available_in_pos": True, + } + ) + + def test_uncheck_sale_ok_disables_available_in_pos(self): + self.product.sale_ok = False + self.assertFalse(self.product.available_in_pos) + + def test_write_sale_ok_false_disables_available_in_pos(self): + self.product.write({"sale_ok": False}) + self.assertFalse(self.product.available_in_pos)