Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
from . import product_packaging
from . import product_packaging_level
from . import product_product
from . import product_template
from . import sale_order_line

View File

@@ -0,0 +1,18 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import fields, models
class ProductPackaging(models.Model):
_inherit = "product.packaging"
force_sale_qty = fields.Boolean(
string="Force sale quantity",
help="Determine if during the creation of a sale order line, the "
"quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You sell a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the "
"quantity to the superior unit (5 for this example).",
)

View File

@@ -0,0 +1,27 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, exceptions, models
class ProductPackagingLevel(models.Model):
_inherit = "product.packaging.level"
@api.constrains("can_be_sold")
def _check_sell_only_by_packaging_can_be_sold_packaging_ids(self):
for record in self:
if record.can_be_sold:
continue
products = record.packaging_ids.product_id
templates = products.product_tmpl_id
try:
templates._check_sell_only_by_packaging_can_be_sold_packaging_ids()
except exceptions.ValidationError as e:
raise exceptions.ValidationError(
self.env._(
'Packaging level %s must stay with "Can be sold",'
' at least one product configured as "sell only'
' by packaging" is using it.',
record.display_name,
),
) from e

View File

@@ -0,0 +1,60 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo.tools import float_compare, float_round
class ProductProduct(models.Model):
_inherit = "product.product"
min_sellable_qty = fields.Float(
compute="_compute_variant_min_sellable_qty",
help=(
"Minimum sellable quantity, according to the available packagings, "
"if Only Sell by Packaging is set."
),
)
@api.depends(
"sell_only_by_packaging",
"packaging_ids.qty",
"packaging_ids.sales",
)
def _compute_variant_min_sellable_qty(self):
for record in self:
record.min_sellable_qty = 0.0
if record.sell_only_by_packaging and record.packaging_ids:
sellable_pkgs = record.packaging_ids.filtered(lambda p: p.sales)
record.min_sellable_qty = fields.first(
sellable_pkgs.sorted(lambda p: p.qty)
).qty
def _convert_packaging_qty(self, qty, uom, packaging):
"""
Convert the given qty with given UoM to the packaging uom.
To do that, first transform the qty to the reference UoM and then
transform using the packaging UoM.
The given qty is not updated if the product has sell_only_by_packaging
set to False or if the packaging is not set.
Inspired from sale_stock/models.sale_order.py _check_package(...)
:param qty: float
:return: float
"""
if not self or not packaging:
return qty
self.ensure_one()
if self.sell_only_by_packaging and packaging.force_sale_qty:
q = self.uom_id._compute_quantity(packaging.qty, uom)
if (
qty
and q
and float_compare(
qty / q,
float_round(qty / q, precision_rounding=1.0),
precision_rounding=0.001,
)
!= 0
):
qty = qty - (qty % q) + q
return qty

View File

@@ -0,0 +1,81 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
_inherit = "product.template"
sell_only_by_packaging = fields.Boolean(
string="Only sell by packaging",
company_dependent=True,
default=False,
help="Restrict the usage of this product on sale order lines without "
"packaging defined",
copy=False,
)
min_sellable_qty = fields.Float(
compute="_compute_template_min_sellable_qty",
help=(
"Minimum sellable quantity, according to the available packagings, "
"if Only Sell by Packaging is set."
),
)
@api.depends(
"sell_only_by_packaging",
"uom_id.factor",
"product_variant_ids.min_sellable_qty",
)
def _compute_template_min_sellable_qty(self):
for record in self:
record.min_sellable_qty = 0.0
if len(record.product_variant_ids) == 1:
# Pick the value from the variant if there's only 1
record.min_sellable_qty = record.product_variant_ids.min_sellable_qty
@api.constrains("sell_only_by_packaging", "sale_ok")
def _check_sell_only_by_packaging_sale_ok(self):
for product in self:
if product.sell_only_by_packaging and not product.sale_ok:
raise ValidationError(
self.env._(
"Product %s cannot be defined to be sold only by "
"packaging if it cannot be sold.",
product.name,
),
)
@api.constrains("sell_only_by_packaging", "packaging_ids")
def _check_sell_only_by_packaging_can_be_sold_packaging_ids(self):
for product in self:
if product.sell_only_by_packaging:
if (
# Product template only condition
len(product.product_variant_ids) == 1
and not any(pack.sales for pack in product.packaging_ids)
# Product variants condition
or len(product.product_variant_ids) > 1
and not any(
pack.sales
for pack in product.product_variant_ids.mapped("packaging_ids")
)
):
raise ValidationError(
self.env._(
"Product %s cannot be defined to be sold only by "
"packaging if it does not have any packaging that "
"can be sold defined.",
product.name,
),
)
@api.depends("sale_ok")
def _compute_expense_policy(self):
self.filtered(
lambda t: not t.sale_ok and t.sell_only_by_packaging
).sell_only_by_packaging = False
return super()._compute_expense_policy()

View File

@@ -0,0 +1,47 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import api, models
from odoo.exceptions import ValidationError
from odoo.tools import float_compare
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@api.constrains(
"product_id", "product_packaging_id", "product_packaging_qty", "product_uom_qty"
)
def _check_product_packaging_sell_only_by_packaging(self):
for line in self:
if not line.product_id.sell_only_by_packaging or not line.product_uom_qty:
continue
if (
not line.product_packaging_id
or float_compare(
line.product_packaging_qty,
int(line.product_packaging_qty),
precision_digits=2,
)
!= 0
):
raise ValidationError(
self.env._(
"Product %s can only be sold with a packaging and a "
"packaging quantity.",
line.product_id.name,
),
)
def _force_qty_with_package(self):
self.ensure_one()
qty = self.product_id._convert_packaging_qty(
self.product_uom_qty, self.product_uom, packaging=self.product_packaging_id
)
self.product_uom_qty = qty
return True
@api.onchange("product_uom_qty")
def _onchange_product_uom_qty(self):
self._force_qty_with_package()