Compare commits

..

No commits in common. "b02994d178b467f460ffa0de22f4414de990c80d" and "f913eac3121855b772a597eeda1a1f02236fb150" have entirely different histories.

23 changed files with 46 additions and 1157 deletions

View file

@ -25,8 +25,6 @@ NFU Sale Order Batch Packaging
This module adds a minium and a maximum order quantity field to Sale Orders
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**
.. contents::
@ -35,24 +33,23 @@ It adds an autosplitting methode that respects the maximum ordered qty for every
Changelog
=========
- 16.0.0.0.1: Initial module.
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
- 16.0.1.16.0.1.0: Live
- 16.0.1.2.0: Add regeneration action for batch products
- 16.0.1.2.1: refactoring
- 16.0.1.3.0: add packaging states
- 16.0.1.3.1: add open_max_qty
- 16.0.1.3.2: fix color indicator
- 16.0.1.3.3: Enable packagings on installation
- 16.0.1.3.4:
- 0.0.1: Initial module.
- 1.0.0: rename to nfu_sale_order_batch_packaging
- 1.1.0: Live
- 1.2.0: Add regeneration action for batch products
- 1.2.1: refactoring
- 1.3.0: add packaging states
- 1.3.1: add open_max_qty
- 1.3.2: fix color indicator
- 1.3.3: Enable packagings on installation
- 1.3.4:
- on creation set max_qty to product_uom_qty if not set
- remove zero exception for max_qty
- store open_packaging_qty on batch product module to make it sortable
- always adjust max_qty to be at least uom_qty except you only set max qty
- 16.0.1.3.4: store all quantities and round open values
- 16.0.1.3.6: make sure packages are readable by the user
- 16.0.1.3.7: really make sure packages are readable by the user
- 16.0.1.4.0: Add automatic batch fillup
- 1.3.4: store all quantities and round open values
- 1.3.6: make sure packages are readable by the user
- 1.3.7: really make sure packages are readable by the user
Bug Tracker
===========

View file

@ -1,18 +1 @@
from . import models
from . import wizard
from odoo import api, SUPERUSER_ID
def post_init_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
stueck = env.ref(
"nfu_sale_order_batch_packaging.product_uom_stueck",
raise_if_not_found=False,
)
if not stueck:
return
imd = env["ir.model.data"].search(
[("module", "=", "_import_"), ("name", "=", "uom.product_uom_piece")]
)
if imd:
imd.write({"res_id": stueck.id})

View file

@ -4,18 +4,13 @@
"author": "BAKEUP",
"website": "https://www.bakeup.org",
"category": "Sale",
"version": "16.0.1.4.0",
"version": "16.0.1.3.7",
"depends": ["sale", "sale_order_batch"],
"data": [
"security/ir.model.access.csv",
"data/ir_config_parameter.xml",
"data/uom_data.xml",
"wizard/sale_order_batch_packaging_fill_views.xml",
"views/uom_uom_views.xml",
"views/sale_order_views.xml",
"views/sale_order_batch_views.xml",
"views/sale_order_batch_product_views.xml",
],
"license": "LGPL-3",
"post_init_hook": "post_init_hook",
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="uom.product_uom_unit" model="uom.uom">
<field name="batch_fill_precision">1.0</field>
</record>
<record id="product_uom_stueck" model="uom.uom">
<field name="name">Stück</field>
<field name="category_id" ref="uom.product_uom_categ_unit"/>
<field name="uom_type">smaller</field>
<field name="rounding">0.01</field>
<field name="ratio">1.0</field>
<field name="batch_fill_precision">1.0</field>
</record>
</odoo>

View file

@ -1,274 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * nfu_sale_order_batch_packaging
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-29 21:37+0000\n"
"PO-Revision-Date: 2026-05-29 21:37+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__batch_id
msgid "Batch"
msgstr "Charge"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_uom_uom__batch_fill_precision
msgid "Batch Fill Precision"
msgstr "Füllgenauigkeit"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill
msgid "Batch Packaging Auto-Fill Wizard"
msgstr "Assistent zur automatischen Befüllung"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill_line
msgid "Batch Packaging Auto-Fill Wizard Line"
msgstr ""
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill_zero_line
msgid "Batch Packaging Auto-Fill Zero-Out Line"
msgstr ""
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__batch_product_id
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__batch_product_id
msgid "Batch Product"
msgstr "Sammelprodukt"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__batch_uom_max_qty
msgid "Batch UOM Max Qty"
msgstr ""
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__batch_uom_qty
msgid "Batch UOM Qty"
msgstr ""
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
msgid "Cancel"
msgstr "Abbrechen"
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
msgid "Confirm"
msgstr "Bestätigen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__create_uid
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__create_uid
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__create_uid
msgid "Created by"
msgstr "Erstellt von"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__create_date
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__create_date
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__create_date
msgid "Created on"
msgstr "Erstellt am"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__display_name
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__display_name
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__display_name
msgid "Display Name"
msgstr "Anzeigename"
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
msgid "Fill Packages"
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
#: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__full
msgid "Full"
msgstr "Voll"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__has_fill_lines
msgid "Has Fill Lines"
msgstr "Hat Füllzeilen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch__has_fillable_packages
msgid "Has Fillable Packages"
msgstr "Hat befüllbare Pakete"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch__has_qty_adjusted
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__has_qty_adjusted
msgid "Has Qty Adjusted"
msgstr "Menge angepasst"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__has_zero_lines
msgid "Has Zero Lines"
msgstr "Hat Nullzeilen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__id
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__id
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__id
msgid "ID"
msgstr "ID"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__keep_qty
msgid "Keep"
msgstr "Behalten"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill____last_update
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line____last_update
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line____last_update
msgid "Last Modified on"
msgstr "Zuletzt geändert am"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__write_uid
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__write_uid
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__write_uid
msgid "Last Updated by"
msgstr "Zuletzt aktualisiert von"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__write_date
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__write_date
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__write_date
msgid "Last Updated on"
msgstr "Zuletzt aktualisiert am"
#. 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__last_open
msgid "Last package open"
msgstr "Letztes Paket offen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__line_ids
msgid "Line"
msgstr "Zeile"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__product_uom_max_qty
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__product_uom_max_qty
msgid "Max Qty"
msgstr "Max. Menge"
#. 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__open
msgid "Open"
msgstr "Offen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_max_qty
msgid "Open Packaging Max Qty"
msgstr "Max. Menge der offenen Verpackung"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__open_packaging_qty
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__open_packaging_qty
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_qty
msgid "Open Packaging Qty"
msgstr "Menge der offenen Verpackung"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_state
msgid "Open Packaging State"
msgstr "Status der offenen Verpackung"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__product_uom_ordered_qty
msgid "Ordered Qty"
msgstr "Bestellte Menge"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__batch_fill_precision
msgid "Precision"
msgstr "Genauigkeit"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_product_template
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__product_id
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__product_id
msgid "Product"
msgstr "Produkt"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_uom_uom
msgid "Product Unit of Measure"
msgstr "Maßeinheit des Produkts"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_product_product
msgid "Product Variant"
msgstr "Produktvariante"
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_product_form
msgid "Restore Ordered Qty"
msgstr "Bestellte Menge wiederherstellen"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,help:nfu_sale_order_batch_packaging.field_uom_uom__batch_fill_precision
msgid ""
"Rounding step used by the batch fill wizard for this UOM (e.g. 1.0 for whole"
" units, 0.1 for one decimal)."
msgstr ""
"Rundungsschritt des Füll-Assistenten für diese Maßeinheit (z.B. 1.0"
" für ganze Einheiten, 0.1 für eine Dezimalstelle)."
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_product
msgid "Sale Order Batch Product"
msgstr ""
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch
msgid "Sales Order Batch"
msgstr "Sammelverkaufsauftrag"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_line
msgid "Sales Order Line"
msgstr "Verkaufsauftragszeile"
#. module: nfu_sale_order_batch_packaging
#: model:uom.uom,name:nfu_sale_order_batch_packaging.product_uom_stueck
msgid "Stück"
msgstr "Stück"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__wizard_id
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__wizard_id
msgid "Wizard"
msgstr "Assistent"
#. module: nfu_sale_order_batch_packaging
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__zero_line_ids
msgid "Zero Line"
msgstr "Nullzeile"
#. module: nfu_sale_order_batch_packaging
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
msgid "Zero Out"
msgstr "Auf Null setzen"

View file

@ -1,6 +1,4 @@
from . import product_product
from . import product_template
from . import sale_order_batch
from . import sale_order_batch_product
from . import sale_order_line
from . import uom_uom

View file

@ -1,81 +0,0 @@
from odoo import api, fields, models
from odoo.addons.sale_order_batch.models.sale_order_batch import READONLY_FIELD_STATES
class SaleOrderBatch(models.Model):
_inherit = "sale.order.batch"
has_qty_adjusted = fields.Boolean(
compute="_compute_has_qty_adjusted",
)
has_fillable_packages = fields.Boolean(
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(
"sale_order_line_ids.product_uom_qty",
"sale_order_line_ids.product_uom_ordered_qty",
)
def _compute_has_qty_adjusted(self):
for batch in self:
batch.has_qty_adjusted = any(
line.product_uom_qty != line.product_uom_ordered_qty
for line in batch.sale_order_line_ids
)
@api.depends("product_ids.open_packaging_qty")
def _compute_has_fillable_packages(self):
for batch in self:
batch.has_fillable_packages = any(
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):
self.ensure_one()
lines = self.sale_order_line_ids.filtered(
lambda l: l.product_uom_qty != l.product_uom_ordered_qty
)
for line in lines:
line.write({"product_uom_qty": line.product_uom_ordered_qty})
def action_fill_packages(self):
self.ensure_one()
eligible = self.product_ids.filtered(
lambda p: p.open_packaging_qty > 0 and p.open_packaging_max_qty == 0.0
)
ineligible = self.product_ids.filtered(
lambda p: p.open_packaging_qty > 0 and p.open_packaging_max_qty != 0.0
)
wizard = self.env["sale.order.batch.packaging.fill"].create(
{
"batch_id": self.id,
"line_ids": [(0, 0, {"batch_product_id": p.id}) for p in eligible],
"zero_line_ids": [
(0, 0, {"batch_product_id": p.id}) for p in ineligible
],
}
)
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",
}

View file

@ -1,5 +1,4 @@
from odoo import api, fields, models
from odoo.tools import float_is_zero, float_round
PACKAGING_STATES = [
@ -81,102 +80,6 @@ class SaleOrderBatchProduct(models.Model):
else:
product.open_packaging_state = "last_open"
has_qty_adjusted = fields.Boolean(
compute="_compute_has_qty_adjusted",
)
@api.depends(
"sale_order_line_ids.product_uom_qty",
"sale_order_line_ids.product_uom_ordered_qty",
)
def _compute_has_qty_adjusted(self):
for product in self:
product.has_qty_adjusted = any(
line.product_uom_qty != line.product_uom_ordered_qty
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):
self.ensure_one()
lines = self.sale_order_line_ids.filtered(
lambda l: l.product_uom_ordered_qty != 0
and l.product_uom_qty != l.product_uom_ordered_qty
)
for line in lines:
line.write({"product_uom_qty": line.product_uom_ordered_qty})
def _fill_packaging(self, rounding):
self.ensure_one()
for line in self.sale_order_line_ids.filtered(
lambda l: l.product_uom_ordered_qty == 0
):
line.write({"product_uom_ordered_qty": line.product_uom_qty})
target = self.open_packaging_qty
eligible = sorted(
[
(line, line.product_uom_max_qty - line.product_uom_qty)
for line in self.sale_order_line_ids
if line.product_uom_max_qty > line.product_uom_qty
],
key=lambda x: x[1],
)
n = len(eligible)
distributed = 0.0
last_line = None
for line, room in eligible:
remaining = target - distributed
fair_share = float_round(remaining / n, precision_rounding=rounding)
addition = min(room, fair_share)
if addition > 0:
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
last_line = line
n -= 1
if float_is_zero(target - distributed, precision_rounding=rounding):
break
# Attach any sub-rounding remainder directly to the last eligible line
remainder = target - distributed
if remainder > 1e-9 and last_line:
new_qty = min(
last_line.product_uom_qty + remainder, last_line.product_uom_max_qty
)
last_line.write({"product_uom_qty": new_qty})
def _zero_packaging(self):
self.ensure_one()
for line in self.sale_order_line_ids:
line.write({"product_uom_qty": 0.0})
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:

View file

@ -1,13 +1,12 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import float_round
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
product_uom_ordered_qty = fields.Float(
string="Ordered Qty", digits="Product Unit of Measure", default=0.0
string="Ordered Qty", digits="Product Unit of Measure", default=1.0
)
product_uom_max_qty = fields.Float(
string="Max Qty", digits="Product Unit of Measure"
@ -22,13 +21,7 @@ class SaleOrderLine(models.Model):
@api.constrains("product_uom_qty", "product_uom_max_qty")
def _check_product_uom_qty(self):
for order_line in self:
if (
float_round(
order_line.product_uom_max_qty - order_line.product_uom_qty,
precision_rounding=order_line.product_uom.rounding,
)
< 0
):
if order_line.product_uom_qty > order_line.product_uom_max_qty:
raise UserError(
_(
f"{order_line.order_id.name}, {order_line.product_id.name}: "
@ -39,18 +32,16 @@ class SaleOrderLine(models.Model):
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
product_qty = vals.get("product_uom_qty", 0.0)
if (
not vals.get("product_uom_max_qty")
or vals.get("product_uom_max_qty") == 0
):
product_qty = vals.get("product_uom_qty")
vals["product_uom_max_qty"] = product_qty
if not vals.get("product_uom_ordered_qty"):
vals["product_uom_ordered_qty"] = product_qty
if vals.get("product_id") and not vals.get("product_packaging_id"):
product_id = vals.get("product_id")
# Make sure we're not sudo
# e.x. when coming from webshop
# e.x. when comming from webshop
packaging = self.env["product.packaging"].search(
[
("product_id", "=", product_id),

View file

@ -1,17 +0,0 @@
from odoo import api, fields, models
class UomUom(models.Model):
_inherit = "uom.uom"
batch_fill_precision = fields.Float(
compute="_compute_batch_fill_precision",
store=True,
readonly=False,
help="Rounding step used by the batch fill wizard for this UOM (e.g. 1.0 for whole units, 0.1 for one decimal).",
)
@api.depends("rounding")
def _compute_batch_fill_precision(self):
for uom in self:
uom.batch_fill_precision = uom.rounding

View file

@ -1,4 +1,2 @@
This module adds a minium and a maximum order quantity field to Sale Orders
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.
and adds a packaging default to sale order batch.

View file

@ -1,18 +1,17 @@
- 16.0.0.0.1: Initial module.
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
- 16.0.1.1.0: Live
- 16.0.1.2.0: Add regeneration action for batch products
- 16.0.1.2.1: refactoring
- 16.0.1.3.0: add packaging states
- 16.0.1.3.1: add open_max_qty
- 16.0.1.3.2: fix color indicator
- 16.0.1.3.3: Enable packagings on installation
- 16.0.1.3.4:
- 0.0.1: Initial module.
- 1.0.0: rename to nfu_sale_order_batch_packaging
- 1.1.0: Live
- 1.2.0: Add regeneration action for batch products
- 1.2.1: refactoring
- 1.3.0: add packaging states
- 1.3.1: add open_max_qty
- 1.3.2: fix color indicator
- 1.3.3: Enable packagings on installation
- 1.3.4:
- on creation set max_qty to product_uom_qty if not set
- remove zero exception for max_qty
- store open_packaging_qty on batch product module to make it sortable
- always adjust max_qty to be at least uom_qty except you only set max qty
- 16.0.1.3.4: store all quantities and round open values
- 16.0.1.3.6: make sure packages are readable by the user
- 16.0.1.3.7: really make sure packages are readable by the user
- 16.0.1.4.0: Add automatic batch fillup
- 1.3.4: store all quantities and round open values
- 1.3.6: make sure packages are readable by the user
- 1.3.7: really make sure packages are readable by the user

View file

@ -1,4 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_batch_packaging_fill,sale.order.batch.packaging.fill,model_sale_order_batch_packaging_fill,base.group_user,1,1,1,1
access_batch_packaging_fill_line,sale.order.batch.packaging.fill.line,model_sale_order_batch_packaging_fill_line,base.group_user,1,1,1,1
access_batch_packaging_fill_zero_line,sale.order.batch.packaging.fill.zero.line,model_sale_order_batch_packaging_fill_zero_line,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_batch_packaging_fill sale.order.batch.packaging.fill model_sale_order_batch_packaging_fill base.group_user 1 1 1 1
3 access_batch_packaging_fill_line sale.order.batch.packaging.fill.line model_sale_order_batch_packaging_fill_line base.group_user 1 1 1 1
4 access_batch_packaging_fill_zero_line sale.order.batch.packaging.fill.zero.line model_sale_order_batch_packaging_fill_zero_line base.group_user 1 1 1 1

View file

@ -372,7 +372,6 @@ 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>This module adds a minium and a maximum order quantity field to Sale Orders
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>
<div class="contents local topic" id="contents">
<ul class="simple">
@ -389,17 +388,17 @@ and adds a packaging default to sale order batch.</p>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#toc-entry-1">Changelog</a></h1>
<ul class="simple">
<li>16.0.0.0.1: Initial module.</li>
<li>16.0.1.0.0: rename to nfu_sale_order_batch_packaging</li>
<li>16.0.1.16.0.1.0: Live</li>
<li>16.0.1.2.0: Add regeneration action for batch products</li>
<li>16.0.1.2.1: refactoring</li>
<li>16.0.1.3.0: add packaging states</li>
<li>16.0.1.3.1: add open_max_qty</li>
<li>16.0.1.3.2: fix color indicator</li>
<li>16.0.1.3.3: Enable packagings on installation</li>
<li>0.0.1: Initial module.</li>
<li>1.0.0: rename to nfu_sale_order_batch_packaging</li>
<li>1.1.0: Live</li>
<li>1.2.0: Add regeneration action for batch products</li>
<li>1.2.1: refactoring</li>
<li>1.3.0: add packaging states</li>
<li>1.3.1: add open_max_qty</li>
<li>1.3.2: fix color indicator</li>
<li>1.3.3: Enable packagings on installation</li>
<li><dl class="first docutils">
<dt>16.0.1.3.4:</dt>
<dt>1.3.4:</dt>
<dd><ul class="first last">
<li>on creation set max_qty to product_uom_qty if not set</li>
<li>remove zero exception for max_qty</li>
@ -409,10 +408,9 @@ and adds a packaging default to sale order batch.</p>
</dd>
</dl>
</li>
<li>16.0.1.3.4: store all quantities and round open values</li>
<li>16.0.1.3.6: make sure packages are readable by the user</li>
<li>16.0.1.3.7: really make sure packages are readable by the user</li>
<li>16.0.1.4.0: Add automatic batch fillup</li>
<li>1.3.4: store all quantities and round open values</li>
<li>1.3.6: make sure packages are readable by the user</li>
<li>1.3.7: really make sure packages are readable by the user</li>
</ul>
</div>
<div class="section" id="bug-tracker">

View file

@ -1 +0,0 @@
from . import test_batch_packaging_fill

View file

@ -1,366 +0,0 @@
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
class TestBatchPackagingFill(TransactionCase):
def setUp(self):
super().setUp()
self.uom_kg = self.env.ref("uom.product_uom_kgm")
self.uom_unit = self.env.ref("uom.product_uom_unit")
self.partner = self.env["res.partner"].create({"name": "Test Partner"})
self.batch = self.env["sale.order.batch"].create(
{"name": "Test Batch", "state": "in_progress"}
)
def _make_product(self, uom, packaging_qty):
product = self.env["product.product"].create(
{
"name": f"Product {uom.name} {packaging_qty}",
"uom_id": uom.id,
"uom_po_id": uom.id,
"type": "consu",
}
)
self.env["product.packaging"].create(
{
"name": "Pack",
"product_id": product.id,
"qty": packaging_qty,
"sales": True,
}
)
return product
def _add_lines(self, product, lines_data):
"""Create one sale order in self.batch with one line per (qty, max_qty) pair.
Returns the sale.order.batch.product that aggregates all lines."""
order = self.env["sale.order"].create(
{"partner_id": self.partner.id, "batch_id": self.batch.id}
)
for qty, max_qty in lines_data:
self.env["sale.order.line"].create(
{
"order_id": order.id,
"product_id": product.id,
"product_uom_qty": qty,
"product_uom_max_qty": max_qty,
"price_unit": 10.0,
}
)
return self.batch.product_ids.filtered(lambda p: p.product_id == product)
# ------------------------------------------------------------------
# _fill_packaging
# ------------------------------------------------------------------
def test_even_split(self):
"""All lines with identical room each receive an equal share."""
product = self._make_product(self.uom_kg, 10.0)
# total = 24, open = 6; each line has exactly 3 kg of room
bp = self._add_lines(product, [(7.0, 10.0), (8.0, 11.0), (9.0, 12.0)])
self.assertAlmostEqual(bp.open_packaging_qty, 6.0, places=2)
bp._fill_packaging(0.001)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
# each line must have gained exactly 2 kg
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(
line.product_uom_qty,
line.product_uom_max_qty - 1.0,
places=3,
)
def test_constrained_line_capped_at_max(self):
"""A line that cannot take its fair share is capped; remainder is spread evenly."""
product = self._make_product(self.uom_kg, 10.0)
# total = 23, open = 7
# rooms: 1 (capped), 10, 7 → sorted: 1, 7, 10
bp = self._add_lines(product, [(8.0, 9.0), (5.0, 15.0), (10.0, 17.0)])
self.assertAlmostEqual(bp.open_packaging_qty, 7.0, places=2)
bp._fill_packaging(0.001)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
capped = bp.sale_order_line_ids.filtered(
lambda line: line.product_uom_max_qty == 9.0
)
# capped line must be at its max
self.assertAlmostEqual(capped.product_uom_qty, 9.0, places=3)
# every line must stay within its max
for line in bp.sale_order_line_ids:
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
def test_whole_units_rounding_produces_integers(self):
"""precision=1.0 yields only whole-number additions."""
product = self._make_product(self.uom_unit, 6.0)
# total = 7, open = 5; rooms: 3 and 5
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
self.assertAlmostEqual(bp.open_packaging_qty, 5.0, places=2)
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
bp._fill_packaging(1.0)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
for line in bp.sale_order_line_ids:
added = line.product_uom_qty - orig[line.id]
self.assertAlmostEqual(added, round(added), places=6)
def test_decimal_rounding_uses_tenth_steps(self):
"""precision=0.1 distributes in 0.1-unit steps."""
product = self._make_product(self.uom_kg, 10.0)
# total = 7, open = 3; rooms: 7 and 6 → sorted: line2(6), line1(7)
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
self.assertAlmostEqual(bp.open_packaging_qty, 3.0, places=2)
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
bp._fill_packaging(0.1)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
for line in bp.sale_order_line_ids:
added = round(line.product_uom_qty - orig[line.id], 6)
# must be a multiple of 0.1 (within floating-point tolerance)
self.assertAlmostEqual(round(added / 0.1) * 0.1, added, places=5)
def test_sub_rounding_remainder_is_attached(self):
"""A remainder smaller than the rounding step is added directly to the last line."""
product = self._make_product(self.uom_kg, 3.0)
# total = 2.7, open = 0.3; with precision=1.0 every fair_share rounds to 0
bp = self._add_lines(product, [(0.9, 1.5), (0.9, 1.5), (0.9, 1.5)])
self.assertAlmostEqual(bp.open_packaging_qty, 0.3, places=2)
bp._fill_packaging(1.0)
# The whole 0.3 remainder must have been placed on one line
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
# Every line must still respect its max qty
for line in bp.sale_order_line_ids:
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
def test_no_line_exceeds_max_qty(self):
"""After filling no sale order line may exceed its max qty."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(6.0, 8.0), (7.0, 12.0), (4.0, 9.0)])
bp._fill_packaging(0.001)
for line in bp.sale_order_line_ids:
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
# ------------------------------------------------------------------
# Wizard
# ------------------------------------------------------------------
def test_wizard_includes_eligible_products_only(self):
"""Eligible products land in line_ids; ineligible ones land in zero_line_ids."""
eligible = self._make_product(self.uom_kg, 10.0)
ineligible = self._make_product(self.uom_kg, 10.0)
# eligible: total max (30) covers needed (23+7=30)
self._add_lines(eligible, [(23.0, 30.0)])
# ineligible: total max (24) < 23+7=30 → open_packaging_max_qty != 0
self._add_lines(ineligible, [(23.0, 24.0)])
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertEqual(len(wizard.line_ids), 1)
self.assertEqual(wizard.line_ids.product_id, eligible)
self.assertEqual(len(wizard.zero_line_ids), 1)
self.assertEqual(wizard.zero_line_ids.product_id, ineligible)
def test_wizard_default_precision_for_unit_uom(self):
"""UOMs with batch_fill_precision=1.0 default to precision 1.0."""
product = self._make_product(self.uom_unit, 6.0)
self._add_lines(product, [(3.0, 9.0)])
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
def test_wizard_default_precision_for_stueck_uom(self):
"""Stück UOM (batch_fill_precision=1.0) defaults to precision 1.0."""
uom_stueck = self.env.ref("nfu_sale_order_batch_packaging.product_uom_stueck")
product = self._make_product(uom_stueck, 6.0)
self._add_lines(product, [(3.0, 9.0)])
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
def test_wizard_default_precision_for_kg_uom(self):
"""UOMs with default batch_fill_precision use 0.1."""
product = self._make_product(self.uom_kg, 10.0)
self._add_lines(product, [(23.0, 30.0)])
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 0.1)
def test_wizard_fill_closes_open_packaging(self):
"""End-to-end: confirming the wizard brings open_packaging_qty to zero."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(23.0, 30.0)])
self.assertGreater(bp.open_packaging_qty, 0.0)
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
wizard.action_fill()
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
# ------------------------------------------------------------------
# Restore
# ------------------------------------------------------------------
def test_restore_batch_resets_all_lines(self):
"""action_restore_ordered_qty on the batch sets every line back to ordered qty."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(23.0, 30.0)])
bp._fill_packaging(0.1)
# fill must have increased some qty
self.assertTrue(
any(
line.product_uom_qty != line.product_uom_ordered_qty
for line in bp.sale_order_line_ids
)
)
self.batch.action_restore_ordered_qty()
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(
line.product_uom_qty, line.product_uom_ordered_qty, places=3
)
def test_restore_batch_product_resets_its_lines_only(self):
"""action_restore_ordered_qty on a batch product only touches its own lines."""
product_a = self._make_product(self.uom_kg, 10.0)
product_b = self._make_product(self.uom_kg, 10.0)
bp_a = self._add_lines(product_a, [(23.0, 30.0)])
bp_b = self._add_lines(product_b, [(23.0, 30.0)])
bp_a._fill_packaging(0.1)
bp_b._fill_packaging(0.1)
bp_a.action_restore_ordered_qty()
for line in bp_a.sale_order_line_ids:
self.assertAlmostEqual(
line.product_uom_qty, line.product_uom_ordered_qty, places=3
)
# product_b lines must be unchanged
self.assertTrue(
any(
line.product_uom_qty != line.product_uom_ordered_qty
for line in bp_b.sale_order_line_ids
)
)
def test_has_qty_adjusted_false_before_fill(self):
"""has_qty_adjusted is False when no line has been modified."""
product = self._make_product(self.uom_kg, 10.0)
self._add_lines(product, [(10.0, 15.0)])
self.assertFalse(self.batch.has_qty_adjusted)
def test_has_qty_adjusted_true_after_fill(self):
"""has_qty_adjusted becomes True once a line quantity is increased."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(23.0, 30.0)])
bp._fill_packaging(0.1)
self.assertTrue(self.batch.has_qty_adjusted)
def test_has_qty_adjusted_false_after_restore(self):
"""has_qty_adjusted returns to False after restoring ordered quantities."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(23.0, 30.0)])
bp._fill_packaging(0.1)
self.batch.action_restore_ordered_qty()
self.assertFalse(self.batch.has_qty_adjusted)
def test_has_fillable_packages_false_when_no_open_qty(self):
"""has_fillable_packages is False when all products have complete packages."""
product = self._make_product(self.uom_kg, 10.0)
self._add_lines(product, [(10.0, 15.0)]) # qty is exact multiple of packaging
self.assertFalse(self.batch.has_fillable_packages)
def test_has_fillable_packages_true_for_eligible(self):
"""has_fillable_packages is True when an eligible product has open packaging qty."""
product = self._make_product(self.uom_kg, 10.0)
self._add_lines(product, [(23.0, 30.0)]) # open_packaging_max_qty == 0
self.assertTrue(self.batch.has_fillable_packages)
def test_has_fillable_packages_true_for_ineligible(self):
"""has_fillable_packages is True even for ineligible (zero-out) products."""
product = self._make_product(self.uom_kg, 10.0)
self._add_lines(
product, [(23.0, 24.0)]
) # max too tight → open_packaging_max_qty != 0
self.assertGreater(
self.batch.product_ids.filtered(
lambda p: p.product_id == product
).open_packaging_max_qty,
0.0,
)
self.assertTrue(self.batch.has_fillable_packages)
# ------------------------------------------------------------------
# Zero-out
# ------------------------------------------------------------------
def test_zero_packaging_sets_lines_to_zero(self):
"""_zero_packaging sets all sale order line quantities to 0."""
product = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(product, [(23.0, 24.0)]) # ineligible (max too tight)
self.assertGreater(bp.open_packaging_qty, 0.0)
bp._zero_packaging()
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
def test_wizard_zeros_ineligible_by_default(self):
"""Confirming the wizard zeros ineligible lines (keep_qty=False default)."""
ineligible = self._make_product(self.uom_kg, 10.0)
self._add_lines(ineligible, [(23.0, 24.0)])
bp = self.batch.product_ids.filtered(lambda p: p.product_id == ineligible)
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertFalse(wizard.zero_line_ids.keep_qty)
wizard.action_fill()
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
def test_wizard_keeps_ineligible_when_keep_qty_true(self):
"""Lines with keep_qty=True are not zeroed out."""
ineligible = self._make_product(self.uom_kg, 10.0)
bp = self._add_lines(ineligible, [(23.0, 24.0)])
orig_qtys = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
wizard.zero_line_ids.write({"keep_qty": True})
wizard.action_fill()
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(line.product_uom_qty, orig_qtys[line.id], places=3)
def test_wizard_respects_precision_override(self):
"""Overriding batch_fill_precision on a wizard line changes the applied rounding."""
product = self._make_product(self.uom_kg, 10.0)
# total = 7, open = 3; use 2 lines (rooms: 7 and 6)
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
action = self.batch.action_fill_packages()
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
# Force whole-units even though UOM is kg
wizard.line_ids.write({"batch_fill_precision": 1.0})
wizard.action_fill()
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
for line in bp.sale_order_line_ids:
# additions must be whole numbers when precision=1.0
self.assertAlmostEqual(
line.product_uom_qty, round(line.product_uom_qty), places=6
)

View file

@ -15,23 +15,6 @@
<field name="open_packaging_max_qty" decoration-danger="(open_packaging_max_qty != 0.0)"/>
<field name="open_packaging_state"/>
</xpath>
<xpath expr="//header" position="inside">
<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
name="action_restore_ordered_qty"
type="object"
string="Restore Ordered Qty"
attrs="{'invisible': [('has_qty_adjusted', '=', False)]}"
/>
</xpath>
<xpath
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
position="before"

View file

@ -5,37 +5,6 @@
<field name="model">sale.order.batch</field>
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
<field name="arch" type="xml">
<!-- Header buttons -->
<xpath expr="//button[@id='action_confirm_primary']" position="before">
<field name="has_qty_adjusted" invisible="1"/>
<field name="has_fillable_packages" invisible="1"/>
<button
name="action_fill_packages"
type="object"
class="btn-primary"
string="Fill Packages"
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', False)]}"
/>
</xpath>
<xpath expr="//button[@id='action_confirm_primary']" position="attributes">
<attribute
name="attrs"
>{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', True)]}</attribute>
</xpath>
<xpath expr="//button[@id='action_confirm_secondary']" position="attributes">
<attribute
name="attrs"
>{"invisible": ["|", ["state", "not in", ["draft", "in_progress"]], ["has_fillable_packages", "=", False]]}</attribute>
</xpath>
<xpath expr="//button[@id='create_invoices']" position="before">
<button
name="action_restore_ordered_qty"
type="object"
string="Restore Ordered Qty"
attrs="{'invisible': ['|', ('state', '!=', 'in_progress'),('has_qty_adjusted', '=', False)]}"
/>
</xpath>
<!-- Fields -->
<xpath
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
position="before"
@ -57,30 +26,6 @@
<xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes">
<attribute name="decoration-warning">(open_packaging_qty != 0)</attribute>
</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>
</record>
</odoo>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_uom_form_view_batch_fill" model="ir.ui.view">
<field name="name">uom.uom.form.batch.fill</field>
<field name="model">uom.uom</field>
<field name="inherit_id" ref="uom.product_uom_form_view"/>
<field name="arch" type="xml">
<field name="rounding" position="after">
<field name="batch_fill_precision"/>
</field>
</field>
</record>
</odoo>

View file

@ -1 +0,0 @@
from . import sale_order_batch_packaging_fill

View file

@ -1,78 +0,0 @@
from odoo import api, fields, models
class SaleOrderBatchPackagingFill(models.TransientModel):
_name = "sale.order.batch.packaging.fill"
_description = "Batch Packaging Auto-Fill Wizard"
batch_id = fields.Many2one("sale.order.batch", readonly=True)
line_ids = fields.One2many("sale.order.batch.packaging.fill.line", "wizard_id")
zero_line_ids = fields.One2many(
"sale.order.batch.packaging.fill.zero.line", "wizard_id"
)
has_fill_lines = fields.Boolean(compute="_compute_has_fill_lines")
has_zero_lines = fields.Boolean(compute="_compute_has_zero_lines")
@api.depends("line_ids")
def _compute_has_fill_lines(self):
for wizard in self:
wizard.has_fill_lines = bool(wizard.line_ids)
@api.depends("zero_line_ids")
def _compute_has_zero_lines(self):
for wizard in self:
wizard.has_zero_lines = bool(wizard.zero_line_ids)
def action_fill(self):
for line in self.line_ids:
line.batch_product_id._fill_packaging(line.batch_fill_precision)
for zero_line in self.zero_line_ids.filtered(lambda l: not l.keep_qty):
zero_line.batch_product_id._zero_packaging()
return {"type": "ir.actions.act_window_close"}
class SaleOrderBatchPackagingFillLine(models.TransientModel):
_name = "sale.order.batch.packaging.fill.line"
_description = "Batch Packaging Auto-Fill Wizard Line"
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
product_id = fields.Many2one(
related="batch_product_id.product_id", string="Product"
)
open_packaging_qty = fields.Float(
related="batch_product_id.open_packaging_qty",
string="Open Packaging Qty",
digits=[12, 3],
)
batch_fill_precision = fields.Float(
string="Precision",
compute="_compute_batch_fill_precision",
precompute=True,
store=True,
readonly=False,
)
@api.depends("batch_product_id.product_id.uom_id.batch_fill_precision")
def _compute_batch_fill_precision(self):
for line in self:
line.batch_fill_precision = (
line.batch_product_id.product_id.uom_id.batch_fill_precision
)
class SaleOrderBatchPackagingFillZeroLine(models.TransientModel):
_name = "sale.order.batch.packaging.fill.zero.line"
_description = "Batch Packaging Auto-Fill Zero-Out Line"
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
product_id = fields.Many2one(
related="batch_product_id.product_id", string="Product"
)
open_packaging_qty = fields.Float(
related="batch_product_id.open_packaging_qty",
string="Open Packaging Qty",
digits=[12, 3],
)
keep_qty = fields.Boolean(string="Keep", default=False)

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_sale_order_batch_packaging_fill_form" model="ir.ui.view">
<field name="name">sale.order.batch.packaging.fill.form</field>
<field name="model">sale.order.batch.packaging.fill</field>
<field name="arch" type="xml">
<form>
<sheet>
<notebook>
<page
string="Fill Packages"
name="fill_packages"
attrs="{'invisible': [('has_fill_lines', '=', False)]}"
>
<field name="has_fill_lines" invisible="1"/>
<field name="line_ids">
<tree editable="bottom" delete="0">
<field name="product_id" readonly="1"/>
<field name="open_packaging_qty" readonly="1"/>
<field name="batch_fill_precision"/>
</tree>
</field>
</page>
<page string="Zero Out" name="zero_out" attrs="{'invisible': [('has_zero_lines', '=', False)]}">
<field name="has_zero_lines" invisible="1"/>
<field name="zero_line_ids">
<tree editable="bottom" delete="0">
<field name="product_id" readonly="1"/>
<field name="open_packaging_qty" readonly="1"/>
<field name="keep_qty"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<footer>
<button name="action_fill" type="object" string="Confirm" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -6,20 +6,12 @@
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<field name="has_fillable_packages" invisible="1"/>
<button
name="export_wholesaler_csv"
type="object"
string="Export Wholesaler CSV"
class="oe_highlight"
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress', 'closed']), ('has_fillable_packages', '=', True)]}"
/>
<button
name="export_wholesaler_csv"
type="object"
string="Export Wholesaler CSV"
class="btn-secondary"
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress', 'closed']), ('has_fillable_packages', '=', False)]}"
attrs="{'invisible': [('state', 'not in', ['in_progress','closed'])]}"
/>
</xpath>
</field>