Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf02699b61 | |||
| dab4123379 | |||
| 25faab83bb | |||
| a1f73dabe7 |
10 changed files with 187 additions and 8 deletions
0
mail_quoted_reply_icon/__init__.py
Normal file
0
mail_quoted_reply_icon/__init__.py
Normal file
19
mail_quoted_reply_icon/__manifest__.py
Normal file
19
mail_quoted_reply_icon/__manifest__.py
Normal file
|
|
@ -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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
43
pos_product_available_link/README.rst
Normal file
43
pos_product_available_link/README.rst
Normal file
|
|
@ -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)
|
||||||
1
pos_product_available_link/__init__.py
Normal file
1
pos_product_available_link/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
13
pos_product_available_link/__manifest__.py
Normal file
13
pos_product_available_link/__manifest__.py
Normal file
|
|
@ -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,
|
||||||
|
}
|
||||||
1
pos_product_available_link/models/__init__.py
Normal file
1
pos_product_available_link/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import product
|
||||||
32
pos_product_available_link/models/product.py
Normal file
32
pos_product_available_link/models/product.py
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -174,13 +174,21 @@ class PurchaseOrderRecommendationSupermarketWizard(models.TransientModel):
|
||||||
if days_with_stock != 0
|
if days_with_stock != 0
|
||||||
else 1
|
else 1
|
||||||
)
|
)
|
||||||
|
uom_categ_unit = self.env.ref(
|
||||||
if self.order_days != 0:
|
"uom.product_uom_categ_unit", raise_if_not_found=False
|
||||||
qty_to_order = max(
|
|
||||||
0,
|
|
||||||
(self.order_days * res["units_avg_delivered"])
|
|
||||||
- res["units_virtual_available"],
|
|
||||||
)
|
)
|
||||||
|
if self.order_days != 0:
|
||||||
|
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
|
# Force a minimum suggested quantity when forecast <= 0 and product configured
|
||||||
# We apply this BEFORE packaging adjustment so packages logic can upscale it.
|
# We apply this BEFORE packaging adjustment so packages logic can upscale it.
|
||||||
|
|
@ -198,6 +206,14 @@ class PurchaseOrderRecommendationSupermarketWizard(models.TransientModel):
|
||||||
packaging_qty = product_id.packaging_ids[:1].qty
|
packaging_qty = product_id.packaging_ids[:1].qty
|
||||||
if packaging_qty:
|
if packaging_qty:
|
||||||
qty_to_order = math.ceil(qty_to_order / packaging_qty) * packaging_qty
|
qty_to_order = math.ceil(qty_to_order / packaging_qty) * packaging_qty
|
||||||
|
# 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
|
res["units_included"] = qty_to_order
|
||||||
|
|
||||||
# Get quantities scrapped
|
# Get quantities scrapped
|
||||||
|
|
@ -417,7 +433,7 @@ class PurchaseOrderRecommendationLine(models.TransientModel):
|
||||||
def _compute_packaging_qty(self):
|
def _compute_packaging_qty(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.packaging_id and rec.packaging_id.qty:
|
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:
|
else:
|
||||||
rec.packaging_qty = 0
|
rec.packaging_qty = 0
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue