[IMP] nfu_sale_order_batch_packaging: add Open Products tab

This commit is contained in:
Niels Göttsch 2026-06-01 12:26:19 +02:00
parent f513c0919f
commit a4c9a57ef5
No known key found for this signature in database
GPG key ID: D36A5892D98A60AD
10 changed files with 107 additions and 6 deletions

View file

@ -25,6 +25,8 @@ NFU Sale Order Batch Packaging
This module adds a minium and a maximum order quantity field to Sale Orders This module adds a minium and a maximum order quantity field to Sale Orders
and adds a packaging default to sale order batch. and adds a packaging default to sale order batch.
It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.
**Table of contents** **Table of contents**
.. contents:: .. contents::

View file

@ -93,6 +93,11 @@ msgstr "Anzeigename"
msgid "Fill Packages" msgid "Fill Packages"
msgstr "Pakete befüllen" msgstr "Pakete befüllen"
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
msgid "Open Products"
msgstr "Offene Produkte"
#. module: nfu_sale_order_batch_packaging #. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__full #: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__full
msgid "Full" msgid "Full"

View file

@ -1,5 +1,7 @@
from odoo import api, fields, models from odoo import api, fields, models
from odoo.addons.sale_order_batch.models.sale_order_batch import READONLY_FIELD_STATES
class SaleOrderBatch(models.Model): class SaleOrderBatch(models.Model):
_inherit = "sale.order.batch" _inherit = "sale.order.batch"
@ -10,6 +12,12 @@ class SaleOrderBatch(models.Model):
has_fillable_packages = fields.Boolean( has_fillable_packages = fields.Boolean(
compute="_compute_has_fillable_packages", compute="_compute_has_fillable_packages",
) )
open_product_ids = fields.Many2many(
comodel_name="sale.order.batch.product",
compute="_compute_open_product_ids",
inverse="_inverse_open_product_ids",
states=READONLY_FIELD_STATES,
)
@api.depends( @api.depends(
"sale_order_line_ids.product_uom_qty", "sale_order_line_ids.product_uom_qty",
@ -29,6 +37,16 @@ class SaleOrderBatch(models.Model):
p.open_packaging_qty > 0 for p in batch.product_ids p.open_packaging_qty > 0 for p in batch.product_ids
) )
@api.depends("product_ids.open_packaging_qty")
def _compute_open_product_ids(self):
for batch in self:
batch.open_product_ids = batch.product_ids.filtered(
lambda p: p.open_packaging_qty > 0
)
def _inverse_open_product_ids(self):
pass
def action_restore_ordered_qty(self): def action_restore_ordered_qty(self):
self.ensure_one() self.ensure_one()
lines = self.sale_order_line_ids.filtered( lines = self.sale_order_line_ids.filtered(

View file

@ -96,6 +96,32 @@ class SaleOrderBatchProduct(models.Model):
for line in product.sale_order_line_ids for line in product.sale_order_line_ids
) )
def action_fill_packages(self):
self.ensure_one()
if self.open_packaging_qty > 0 and self.open_packaging_max_qty == 0.0:
line_ids = [(0, 0, {"batch_product_id": self.id})]
zero_line_ids = []
elif self.open_packaging_qty > 0 and self.open_packaging_max_qty != 0.0:
line_ids = []
zero_line_ids = [(0, 0, {"batch_product_id": self.id})]
else:
return
wizard = self.env["sale.order.batch.packaging.fill"].create(
{
"batch_id": self.batch_id.id,
"line_ids": line_ids,
"zero_line_ids": zero_line_ids,
}
)
return {
"type": "ir.actions.act_window",
"name": "Fill Open Packages",
"res_model": "sale.order.batch.packaging.fill",
"res_id": wizard.id,
"view_mode": "form",
"target": "new",
}
def action_restore_ordered_qty(self): def action_restore_ordered_qty(self):
self.ensure_one() self.ensure_one()
lines = self.sale_order_line_ids.filtered( lines = self.sale_order_line_ids.filtered(
@ -128,7 +154,11 @@ class SaleOrderBatchProduct(models.Model):
fair_share = float_round(remaining / n, precision_rounding=rounding) fair_share = float_round(remaining / n, precision_rounding=rounding)
addition = min(room, fair_share) addition = min(room, fair_share)
if addition > 0: if addition > 0:
line.write({"product_uom_qty": line.product_uom_qty + addition}) new_qty = float_round(
line.product_uom_qty + addition, precision_rounding=rounding
)
new_qty = min(new_qty, line.product_uom_max_qty)
line.write({"product_uom_qty": new_qty})
distributed += addition distributed += addition
last_line = line last_line = line
n -= 1 n -= 1
@ -137,7 +167,11 @@ class SaleOrderBatchProduct(models.Model):
# Attach any sub-rounding remainder directly to the last eligible line # Attach any sub-rounding remainder directly to the last eligible line
remainder = target - distributed remainder = target - distributed
if remainder > 1e-9 and last_line: if remainder > 1e-9 and last_line:
last_line.write({"product_uom_qty": last_line.product_uom_qty + remainder}) new_qty = float_round(
last_line.product_uom_qty + remainder, precision_rounding=rounding
)
new_qty = min(new_qty, last_line.product_uom_max_qty)
last_line.write({"product_uom_qty": new_qty})
def _zero_packaging(self): def _zero_packaging(self):
self.ensure_one() self.ensure_one()

View file

@ -1,5 +1,6 @@
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import float_round
class SaleOrderLine(models.Model): class SaleOrderLine(models.Model):
@ -21,7 +22,13 @@ class SaleOrderLine(models.Model):
@api.constrains("product_uom_qty", "product_uom_max_qty") @api.constrains("product_uom_qty", "product_uom_max_qty")
def _check_product_uom_qty(self): def _check_product_uom_qty(self):
for order_line in self: for order_line in self:
if order_line.product_uom_qty > order_line.product_uom_max_qty: if (
float_round(
order_line.product_uom_max_qty - order_line.product_uom_qty,
precision_rounding=order_line.product_uom.rounding,
)
< 0
):
raise UserError( raise UserError(
_( _(
f"{order_line.order_id.name}, {order_line.product_id.name}: " f"{order_line.order_id.name}, {order_line.product_id.name}: "

View file

@ -1,2 +1,4 @@
This module adds a minium and a maximum order quantity field to Sale Orders This module adds a minium and a maximum order quantity field to Sale Orders
and adds a packaging default to sale order batch. and adds a packaging default to sale order batch.
It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.

View file

@ -372,6 +372,7 @@ ul.auto-toc {
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/bakeupboys/nfu/tree/16.0/nfu_sale_order_batch_packaging"><img alt="bakeupboys/nfu" src="https://img.shields.io/badge/github-bakeupboys%2Fnfu-lightgray.png?logo=github" /></a></p> <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/bakeupboys/nfu/tree/16.0/nfu_sale_order_batch_packaging"><img alt="bakeupboys/nfu" src="https://img.shields.io/badge/github-bakeupboys%2Fnfu-lightgray.png?logo=github" /></a></p>
<p>This module adds a minium and a maximum order quantity field to Sale Orders <p>This module adds a minium and a maximum order quantity field to Sale Orders
and adds a packaging default to sale order batch.</p> and adds a packaging default to sale order batch.</p>
<p>It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.</p>
<p><strong>Table of contents</strong></p> <p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents"> <div class="contents local topic" id="contents">
<ul class="simple"> <ul class="simple">

View file

@ -17,6 +17,14 @@
</xpath> </xpath>
<xpath expr="//header" position="inside"> <xpath expr="//header" position="inside">
<field name="has_qty_adjusted" invisible="1"/> <field name="has_qty_adjusted" invisible="1"/>
<field name="open_packaging_qty" invisible="1"/>
<button
name="action_fill_packages"
type="object"
string="Fill Packages"
class="btn-primary"
attrs="{'invisible': [('open_packaging_qty', '=', 0.0)]}"
/>
<button <button
name="action_restore_ordered_qty" name="action_restore_ordered_qty"
type="object" type="object"

View file

@ -57,6 +57,30 @@
<xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes"> <xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes">
<attribute name="decoration-warning">(open_packaging_qty != 0)</attribute> <attribute name="decoration-warning">(open_packaging_qty != 0)</attribute>
</xpath> </xpath>
<!-- Open Products tab -->
<xpath expr="//page[@name='products']" position="after">
<page string="Open Products" name="open_products">
<field name="open_product_ids">
<tree create="0" delete="0">
<field name="product_template_id" optional="hide"/>
<field name="product_id"/>
<field name="product_uom_qty"/>
<field name="product_uom_max_qty"/>
<field
name="open_packaging_qty"
decoration-danger="open_packaging_qty != 0"
/>
<field name="open_packaging_max_qty" optional="hide"/>
<field
name="open_packaging_state"
widget="badge"
decoration-warning="open_packaging_state == 'open'"
decoration-danger="open_packaging_state == 'last_open'"
/>
</tree>
</field>
</page>
</xpath>
</field> </field>
</record> </record>
</odoo> </odoo>

View file

@ -14,7 +14,7 @@
> >
<field name="has_fill_lines" invisible="1"/> <field name="has_fill_lines" invisible="1"/>
<field name="line_ids"> <field name="line_ids">
<tree editable="bottom"> <tree editable="bottom" delete="0">
<field name="product_id" readonly="1"/> <field name="product_id" readonly="1"/>
<field name="open_packaging_qty" readonly="1"/> <field name="open_packaging_qty" readonly="1"/>
<field name="batch_fill_precision"/> <field name="batch_fill_precision"/>
@ -24,7 +24,7 @@
<page string="Zero Out" name="zero_out" attrs="{'invisible': [('has_zero_lines', '=', False)]}"> <page string="Zero Out" name="zero_out" attrs="{'invisible': [('has_zero_lines', '=', False)]}">
<field name="has_zero_lines" invisible="1"/> <field name="has_zero_lines" invisible="1"/>
<field name="zero_line_ids"> <field name="zero_line_ids">
<tree editable="bottom"> <tree editable="bottom" delete="0">
<field name="product_id" readonly="1"/> <field name="product_id" readonly="1"/>
<field name="open_packaging_qty" readonly="1"/> <field name="open_packaging_qty" readonly="1"/>
<field name="keep_qty"/> <field name="keep_qty"/>