diff --git a/mrp_subcontracting_serial_mass_produce/README.rst b/mrp_subcontracting_serial_mass_produce/README.rst new file mode 100644 index 00000000..c963f003 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/README.rst @@ -0,0 +1,118 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +====================================== +MRP Subcontracting Serial Mass Produce +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:174af8f3b1fadbca323b2d7546a18edd5b1efcacad8d90d2d9c77a32f02319b1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/16.0/mrp_subcontracting_serial_mass_produce + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-16-0/manufacture-16-0-mrp_subcontracting_serial_mass_produce + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module automatically records subcontracted components when using +the **Mass Produce** wizard to assign serial numbers to subcontracting +manufacturing orders with serial-tracked finished products. It also adds +a convenient **Mass Produce** button to the subcontracting "Record +Components" form for easier access. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +With Odoo's standard workflow, when using the subcontracting operation +type and assigning serial numbers via **Mass Produce** on a +subcontracting MO with serial-tracked finished products, the system +assigns serials but does not automatically record components. + +This is problematic because when receiving multiple quantities, users +need to assign serials at once to reduce operation time. Without +automatic component recording, users must manually open and record +components for each individual MO, which is time-consuming. + +This module solves this by: + +- Automatically recording components for all MOs when Mass Produce is + used +- Adding a **Mass Produce** button to the "Record Components" form for + easier access + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Quartile + +Contributors +------------ + +- ``Quartile ``\ \_: + + - Toshikimi Shigenobu + - Aung Ko Ko Lin + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-aungkokolin1997| image:: https://github.com/aungkokolin1997.png?size=40px + :target: https://github.com/aungkokolin1997 + :alt: aungkokolin1997 +.. |maintainer-nobuQuartile| image:: https://github.com/nobuQuartile.png?size=40px + :target: https://github.com/nobuQuartile + :alt: nobuQuartile + +Current `maintainers `__: + +|maintainer-aungkokolin1997| |maintainer-nobuQuartile| + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_subcontracting_serial_mass_produce/__init__.py b/mrp_subcontracting_serial_mass_produce/__init__.py new file mode 100644 index 00000000..40272379 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/mrp_subcontracting_serial_mass_produce/__manifest__.py b/mrp_subcontracting_serial_mass_produce/__manifest__.py new file mode 100644 index 00000000..0cadf2da --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "MRP Subcontracting Serial Mass Produce", + "summary": "Auto-record components via Mass Produce for serial subcontracting", + "version": "16.0.1.0.0", + "author": "Quartile, Odoo Community Association (OCA)", + "category": "Manufacturing", + "website": "https://github.com/OCA/manufacture", + "depends": ["mrp_subcontracting"], + "data": ["views/mrp_production_views.xml"], + "license": "AGPL-3", + "maintainers": ["aungkokolin1997", "nobuQuartile"], + "installable": True, +} diff --git a/mrp_subcontracting_serial_mass_produce/readme/CONTEXT.md b/mrp_subcontracting_serial_mass_produce/readme/CONTEXT.md new file mode 100644 index 00000000..dea3bfe3 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/readme/CONTEXT.md @@ -0,0 +1,14 @@ +With Odoo's standard workflow, when using the subcontracting operation +type and assigning serial numbers via **Mass Produce** on a subcontracting +MO with serial-tracked finished products, the system assigns serials but +does not automatically record components. + +This is problematic because when receiving multiple quantities, users +need to assign serials at once to reduce operation time. Without +automatic component recording, users must manually open and record +components for each individual MO, which is time-consuming. + +This module solves this by: + +* Automatically recording components for all MOs when Mass Produce is used +* Adding a **Mass Produce** button to the "Record Components" form for easier access diff --git a/mrp_subcontracting_serial_mass_produce/readme/CONTRIBUTORS.md b/mrp_subcontracting_serial_mass_produce/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..a75d5b9b --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +* `Quartile `_: + + * Toshikimi Shigenobu + * Aung Ko Ko Lin diff --git a/mrp_subcontracting_serial_mass_produce/readme/DESCRIPTION.md b/mrp_subcontracting_serial_mass_produce/readme/DESCRIPTION.md new file mode 100644 index 00000000..9231033b --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +This module automatically records subcontracted components when using +the **Mass Produce** wizard to assign serial numbers to subcontracting +manufacturing orders with serial-tracked finished products. It also adds +a convenient **Mass Produce** button to the subcontracting "Record Components" +form for easier access. diff --git a/mrp_subcontracting_serial_mass_produce/static/description/index.html b/mrp_subcontracting_serial_mass_produce/static/description/index.html new file mode 100644 index 00000000..3475489c --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/static/description/index.html @@ -0,0 +1,458 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

MRP Subcontracting Serial Mass Produce

+ +

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runboat

+

This module automatically records subcontracted components when using +the Mass Produce wizard to assign serial numbers to subcontracting +manufacturing orders with serial-tracked finished products. It also adds +a convenient Mass Produce button to the subcontracting “Record +Components” form for easier access.

+

Table of contents

+ +
+

Use Cases / Context

+

With Odoo’s standard workflow, when using the subcontracting operation +type and assigning serial numbers via Mass Produce on a +subcontracting MO with serial-tracked finished products, the system +assigns serials but does not automatically record components.

+

This is problematic because when receiving multiple quantities, users +need to assign serials at once to reduce operation time. Without +automatic component recording, users must manually open and record +components for each individual MO, which is time-consuming.

+

This module solves this by:

+
    +
  • Automatically recording components for all MOs when Mass Produce is +used
  • +
  • Adding a Mass Produce button to the “Record Components” form for +easier access
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Quartile
  • +
+
+
+

Contributors

+
    +
  • Quartile <https://www.quartile.co>_:
      +
    • Toshikimi Shigenobu
    • +
    • Aung Ko Ko Lin
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainers:

+

aungkokolin1997 nobuQuartile

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/mrp_subcontracting_serial_mass_produce/tests/__init__.py b/mrp_subcontracting_serial_mass_produce/tests/__init__.py new file mode 100644 index 00000000..bc997316 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_subcontracting_serial_mass_produce diff --git a/mrp_subcontracting_serial_mass_produce/tests/test_mrp_subcontracting_serial_mass_produce.py b/mrp_subcontracting_serial_mass_produce/tests/test_mrp_subcontracting_serial_mass_produce.py new file mode 100644 index 00000000..9627e471 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/tests/test_mrp_subcontracting_serial_mass_produce.py @@ -0,0 +1,113 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.fields import Command +from odoo.tests import TransactionCase +from odoo.tests.common import Form + + +class TestMrpSubcontractingSerialMassProduce(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.subcontractor = cls.env["res.partner"].create( + {"name": "Test Subcontractor"} + ) + cls.subcontract_location = cls.subcontractor.property_stock_subcontractor + cls.comp1 = cls.env["product.product"].create( + {"name": "Component 1", "type": "product"} + ) + cls.comp2 = cls.env["product.product"].create( + {"name": "Component 2", "type": "product"} + ) + cls.finished = cls.env["product.product"].create( + {"name": "Finished Product", "type": "product", "tracking": "serial"} + ) + cls.bom = cls.env["mrp.bom"].create( + { + "product_tmpl_id": cls.finished.product_tmpl_id.id, + "type": "subcontract", + "subcontractor_ids": [Command.link(cls.subcontractor.id)], + "bom_line_ids": [ + Command.create({"product_id": cls.comp1.id, "product_qty": 1}), + Command.create({"product_id": cls.comp2.id, "product_qty": 1}), + ], + } + ) + cls.env["stock.quant"]._update_available_quantity( + cls.comp1, cls.subcontract_location, 10 + ) + cls.env["stock.quant"]._update_available_quantity( + cls.comp2, cls.subcontract_location, 10 + ) + picking_form = Form(cls.env["stock.picking"]) + picking_form.picking_type_id = cls.env.ref("stock.picking_type_in") + picking_form.partner_id = cls.subcontractor + with picking_form.move_ids_without_package.new() as move: + move.product_id = cls.finished + move.product_uom_qty = 2 + cls.receipt = picking_form.save() + cls.receipt.action_confirm() + + def _run_mass_produce(self, count=2, starting_serial="SN0001"): + production = self.receipt.move_ids.move_orig_ids.production_id + production.action_assign() + action = production.action_serial_mass_produce_wizard() + wizard = Form(self.env["stock.assign.serial"].with_context(**action["context"])) + wizard.next_serial_number = starting_serial + wizard.next_serial_count = count + action = wizard.save().generate_serial_numbers_production() + return Form(self.env["stock.assign.serial"].browse(action["res_id"])) + + def test_mass_produce_auto_records_components(self): + wizard = self._run_mass_produce() + wizard.save().apply() + productions = self.receipt.move_ids.move_orig_ids.production_id.sorted("id") + self.assertEqual(len(productions), 2) + self.assertEqual( + productions.mapped("subcontracting_has_been_recorded"), + [True, True], + "Mass Produce should auto-record components for both MOs", + ) + self.assertEqual( + productions.mapped("lot_producing_id.name"), + ["SN0001", "SN0002"], + "Serial numbers should be assigned to split productions", + ) + self.assertEqual( + sorted(self.receipt.move_line_ids.mapped("lot_id.name")), + ["SN0001", "SN0002"], + "Serial numbers should be synced to receipt move lines", + ) + + def test_mass_produce_skips_strict_consumption_warning(self): + # Mass Produce records each split MO with skip_consumption=True, since the + # consumed qty is set by _split_productions rather than the BoM. Without it, + # a strict-consumption discrepancy makes subcontracting_record_component + # return a consumption-wizard action that this automated flow discards, + # silently leaving the MO unrecorded. + # Here we fabricate that discrepancy by adding a BoM line after MO creation. + comp3 = self.env["product.product"].create( + {"name": "Component 3", "type": "product"} + ) + self.env["stock.quant"]._update_available_quantity( + comp3, self.subcontract_location, 10 + ) + self.bom.write( + { + "consumption": "strict", + "bom_line_ids": [ + Command.create({"product_id": comp3.id, "product_qty": 1}) + ], + } + ) + wizard = self._run_mass_produce() + wizard.save().apply() + productions = self.receipt.move_ids.move_orig_ids.production_id + self.assertEqual(len(productions), 2) + self.assertEqual( + productions.mapped("subcontracting_has_been_recorded"), + [True, True], + "skip_consumption=True should bypass strict BoM consumption " + "checks so every split MO is recorded", + ) diff --git a/mrp_subcontracting_serial_mass_produce/views/mrp_production_views.xml b/mrp_subcontracting_serial_mass_produce/views/mrp_production_views.xml new file mode 100644 index 00000000..e0764089 --- /dev/null +++ b/mrp_subcontracting_serial_mass_produce/views/mrp_production_views.xml @@ -0,0 +1,37 @@ + + + + mrp.production.subcontracting.form.view.mass.produce + mrp.production + + + + + + + {'invisible': [('product_tracking', 'in', ('none', False))], 'required': [('product_tracking', 'not in', ('none', False)), ('show_serial_mass_produce', '=', False)]} + + +