From e3a3854a2ee54ee75672ba629f06dbac4e92690c Mon Sep 17 00:00:00 2001 From: VoicuStefan2001 Date: Tue, 7 Jul 2026 17:13:55 +0300 Subject: [PATCH] [PORT] deltatech_picking_transit: port 18.0 -> 19.0 (setup la instalare, guard, fix compute/copy) Portare a modificarilor din 18.0.0.0.15, adaptate la Odoo 19: - API 19: `self.env._()`/`env._()` in loc de `from odoo import _`. - Fix real de portare: `move_ids_without_package` (eliminat in Odoo 19) inlocuit cu `move_ids` -> versiunea 19.0 anterioara era rupta pe caile de creare/copiere. - post_init_hook (doar la instalare): locatie de tranzit per companie + tipuri de operatie 2 pasi (livrare cu transfer automat, receptie) pe fiecare depozit. - Al doilea transfer se poate crea manual doar dupa validarea primului. - Al doilea transfer mosteneste cantitatea efectiv trimisa in tranzit. - Fix compute is_transit_transfer / sub_location_existent pe recordset multiplu, efect de blocare mutat intr-un onchange dedicat; batch la copy_move_lines. - Include si fix-ul sudo la crearea celui de-al doilea transfer (tichet 8970). Pereche cu PR-ul pe 18.0: https://github.com/dhongu/deltatech/pull/2629 Co-Authored-By: Claude Opus 4.8 --- deltatech_picking_transit/README.rst | 24 ++++ deltatech_picking_transit/__init__.py | 1 + deltatech_picking_transit/__manifest__.py | 3 +- deltatech_picking_transit/hooks.py | 79 ++++++++++++ .../models/stock_picking.py | 108 +++++++++++----- deltatech_picking_transit/readme/HISTORY.md | 17 +++ .../static/description/index.html | 121 ++++++++++-------- .../views/stock_picking_views.xml | 2 +- 8 files changed, 266 insertions(+), 89 deletions(-) create mode 100644 deltatech_picking_transit/hooks.py create mode 100644 deltatech_picking_transit/readme/HISTORY.md diff --git a/deltatech_picking_transit/README.rst b/deltatech_picking_transit/README.rst index aefaeae39..8922289f6 100644 --- a/deltatech_picking_transit/README.rst +++ b/deltatech_picking_transit/README.rst @@ -49,6 +49,30 @@ Features: .. contents:: :local: +Changelog +========= + +19.0.0.0.10 +----------- + +- Port from 18.0.0.0.15. +- Setup on install: create a per-company transit stock location and, on + each warehouse, a two-step delivery operation type (with automatic + second transfer) and a two-step reception operation type, wired to the + warehouse main stock location and the transit location. Runs only at + install, so databases already using the module are not affected. +- The second transfer can now be created manually only after the first + transfer is validated, so the goods are actually in the transit + location. +- The second transfer now inherits the quantity actually moved to + transit, so the flow works even when the operator filled only the + "Quantity" field and left the "Demand" at 0. +- Fixed the ``is_transit_transfer`` and ``sub_location_existent`` + computes to work on multi-record sets (no more singleton errors) and + removed the side effect from the compute method. +- Also carries the ``sudo`` fix for creating the second transfer (ticket + 8970). + Bug Tracker =========== diff --git a/deltatech_picking_transit/__init__.py b/deltatech_picking_transit/__init__.py index 9b4296142..700d6ab98 100644 --- a/deltatech_picking_transit/__init__.py +++ b/deltatech_picking_transit/__init__.py @@ -1,2 +1,3 @@ from . import models from . import wizard +from .hooks import post_init_hook diff --git a/deltatech_picking_transit/__manifest__.py b/deltatech_picking_transit/__manifest__.py index a4e002375..f704f17f8 100644 --- a/deltatech_picking_transit/__manifest__.py +++ b/deltatech_picking_transit/__manifest__.py @@ -1,7 +1,7 @@ { "images": ["static/description/main_screenshot.png"], "name": "Stock Auto Transfer", - "version": "19.0.0.0.9", + "version": "19.0.0.0.10", "author": "Terrabit, Voicu Stefan", "website": "https://www.terrabit.ro", "category": "Warehouse", @@ -15,5 +15,6 @@ "views/stock_picking_type_view.xml", ], "development_status": "Beta", + "post_init_hook": "post_init_hook", "maintainers": ["VoicuStefan2001"], } diff --git a/deltatech_picking_transit/hooks.py b/deltatech_picking_transit/hooks.py new file mode 100644 index 000000000..bbe0c3b41 --- /dev/null +++ b/deltatech_picking_transit/hooks.py @@ -0,0 +1,79 @@ +# hooks.py + + +def post_init_hook(env): + """Set up the two-step transit configuration on a fresh install. + + Runs only at install time (never on module update), so databases that + already use this module are left untouched. For every company it creates a + dedicated transit stock location and, on each of the company warehouses, a + two-step delivery operation type (with automatic second transfer) and a + two-step reception operation type, wired to the warehouse main stock + location and the transit location. + """ + for company in env["res.company"].search([]): + _setup_company_two_step_transit(env, company) + + +def _setup_company_two_step_transit(env, company): + Location = env["stock.location"].with_company(company) + PickingType = env["stock.picking.type"].with_company(company) + + parent_location = env.ref("stock.stock_location_locations", raise_if_not_found=False) + transit_location = Location.create( + { + "name": env._("2-Step Transit"), + "usage": "transit", + "location_id": parent_location.id if parent_location else False, + "company_id": company.id, + } + ) + + warehouses = env["stock.warehouse"].search([("company_id", "=", company.id)]) + for warehouse in warehouses: + stock_location = warehouse.lot_stock_id + + # Two-step delivery: warehouse stock -> transit, with automatic + # creation of the second (reception) transfer on validation. + if not _has_two_step_type(PickingType, warehouse, "delivery"): + PickingType.create( + { + "name": env._("2-Step Delivery"), + "code": "internal", + "sequence_code": "2SD", + "warehouse_id": warehouse.id, + "company_id": company.id, + "default_location_src_id": stock_location.id, + "default_location_dest_id": transit_location.id, + "two_step_transfer_use": "delivery", + "auto_second_transfer": True, + } + ) + + # Two-step reception: transit -> warehouse stock. + if not _has_two_step_type(PickingType, warehouse, "reception"): + PickingType.create( + { + "name": env._("2-Step Reception"), + "code": "internal", + "sequence_code": "2SR", + "warehouse_id": warehouse.id, + "company_id": company.id, + "default_location_src_id": transit_location.id, + "default_location_dest_id": stock_location.id, + "two_step_transfer_use": "reception", + } + ) + + +def _has_two_step_type(PickingType, warehouse, use): + return bool( + PickingType.search( + [ + ("warehouse_id", "=", warehouse.id), + ("code", "=", "internal"), + ("two_step_transfer_use", "=", use), + ], + limit=1, + ) + ) diff --git a/deltatech_picking_transit/models/stock_picking.py b/deltatech_picking_transit/models/stock_picking.py index c996c8d22..62e546eab 100644 --- a/deltatech_picking_transit/models/stock_picking.py +++ b/deltatech_picking_transit/models/stock_picking.py @@ -11,6 +11,7 @@ class StockPicking(models.Model): sub_location_existent = fields.Boolean(default=False, compute="_compute_sub_location_existent") second_transfer_created = fields.Boolean(default=False) source_transfer_id = fields.Many2one("stock.picking") + destionation_transfer_id = fields.Many2one("stock.picking") create_second_transfer_automatically = fields.Boolean( string="Create Second Transfer Automatically", related="picking_type_id.auto_second_transfer", @@ -18,8 +19,16 @@ class StockPicking(models.Model): ) def open_transfer_wizard(self): + self.ensure_one() if self.second_transfer_created: raise UserError(self.env._("Second transfer already created.")) + if self.state != "done": + raise UserError( + self.env._( + "Validate this transfer first. The second transfer can only be " + "created after the goods have arrived in the transit location." + ) + ) return { "name": "Create Transfer", "type": "ir.actions.act_window", @@ -30,24 +39,29 @@ def open_transfer_wizard(self): } def create_second_transfer_wizard(self, final_dest_location_id, picking_type_id): + # the operator validating the first transfer may not have access rights + # on the operation type / locations of the receiving warehouse + picking_type_id = picking_type_id.sudo() + final_dest_location_id = final_dest_location_id.sudo() for picking in self: if picking.picking_type_id.code == "internal": new_picking_vals = { "picking_type_id": picking_type_id.id, "location_id": picking.location_dest_id.id, "location_dest_id": final_dest_location_id.id, - "move_ids_without_package": [], + "move_ids": [], } - new_picking = self.env["stock.picking"].create(new_picking_vals) + new_picking = self.env["stock.picking"].sudo().create(new_picking_vals) self.copy_move_lines(picking, new_picking) new_picking.action_confirm() # new_picking.action_assign() # new_picking.do_unreserve() - self.second_transfer_created = True + picking.second_transfer_created = True message = self.env._("This transfer was generated from %s.") % picking.name new_picking.message_post(body=message) new_picking.source_transfer_id = picking.id + picking.destionation_transfer_id = new_picking.id message = self.env._("Transfer %s was generated.") % new_picking.name picking.message_post(body=message) @@ -56,15 +70,22 @@ def create_second_transfer_wizard(self, final_dest_location_id, picking_type_id) return new_picking def copy_move_lines(self, source_picking, target_picking): - for move in source_picking.move_ids_without_package: - move.copy( - { - "picking_id": target_picking.id, - "location_id": source_picking.location_dest_id.id, - "location_dest_id": target_picking.location_dest_id.id, - "state": "draft", - } - ) + moves = source_picking.move_ids + if not moves: + return + default = { + "picking_id": target_picking.id, + "location_id": source_picking.location_dest_id.id, + "location_dest_id": target_picking.location_dest_id.id, + "state": "draft", + } + vals_list = moves.sudo().copy_data(default) + for move, vals in zip(moves, vals_list): + # the second transfer must move what actually arrived in transit: + # use the done quantity so the flow still works when the operator + # filled only the "Quantity" field and left the "Demand" at 0 + vals["product_uom_qty"] = move.quantity or move.product_uom_qty + self.env["stock.move"].sudo().create(vals_list) # @api.model # def create(self, vals): @@ -74,6 +95,7 @@ def copy_move_lines(self, source_picking, target_picking): # # res.immediate_transfer = False # return res + @api.depends("picking_type_id") def _compute_sub_location_existent(self): for record in self: sub_location_usage = ( @@ -81,7 +103,7 @@ def _compute_sub_location_existent(self): .sudo() .get_param(key="deltatech_picking_transit.use_sub_locations", default=False) ) - if sub_location_usage and self.picking_type_id.code == "internal": + if sub_location_usage and record.picking_type_id.code == "internal": record.sub_location_existent = True else: record.sub_location_existent = False @@ -98,18 +120,21 @@ def reassign_location(self): if quants: move_line.location_id = quants[0].location_id - @api.onchange("picking_type_id") + @api.depends("picking_type_id", "second_transfer_created") def _compute_is_transit_transfer(self): for record in self: - if self.second_transfer_created: - record.is_transit_transfer = False - return - if record.picking_type_id.code == "internal" and record.picking_type_id.two_step_transfer_use == "delivery": - record.is_transit_transfer = True - record.action_toggle_is_locked() - # record.immediate_transfer = False - else: - record.is_transit_transfer = False + record.is_transit_transfer = bool( + not record.second_transfer_created + and record.picking_type_id.code == "internal" + and record.picking_type_id.two_step_transfer_use == "delivery" + ) + + @api.onchange("picking_type_id") + def _onchange_picking_type_lock_transit(self): + # lock the transit transfer so the move lines cannot be edited before + # the second transfer is generated + if self.is_transit_transfer: + self.action_toggle_is_locked() def button_validate(self): for picking in self: @@ -127,15 +152,21 @@ def button_validate(self): "You must set a partner before validating the picking when you are using 2 step picking with auto create on the second transfer." ) ) - warehouse = self.env["stock.warehouse"].search([("partner_id", "=", picking.partner_id.id)], limit=1) + warehouse = ( + self.env["stock.warehouse"].sudo().search([("partner_id", "=", picking.partner_id.id)], limit=1) + ) if warehouse: - next_operation = self.env["stock.picking.type"].search( - [ - ("warehouse_id", "=", warehouse.id), - ("code", "=", "internal"), - ("two_step_transfer_use", "=", "reception"), - ], - limit=1, + next_operation = ( + self.env["stock.picking.type"] + .sudo() + .search( + [ + ("warehouse_id", "=", warehouse.id), + ("code", "=", "internal"), + ("two_step_transfer_use", "=", "reception"), + ], + limit=1, + ) ) if next_operation: picking.create_second_transfer_wizard(next_operation.default_location_dest_id, next_operation) @@ -144,10 +175,21 @@ def button_validate(self): else: raise UserError(self.env._("No warehouse found for partner %s") % picking.partner_id.name) if picking.source_transfer_id: - for move in picking.move_ids_without_package: - other_moves = picking.source_transfer_id.move_ids_without_package.filtered( + for move in picking.move_ids: + other_moves = picking.source_transfer_id.move_ids.filtered( lambda x: x.product_id == move.product_id ) + if not other_moves: + possible_picking = self.env["stock.picking"] + picking_now = picking.source_transfer_id + while picking_now.backorder_ids: + picking_now = picking_now.backorder_ids[0] + possible_picking |= picking_now + if possible_picking: + for backorder in possible_picking: + other_moves = backorder.move_ids.filtered(lambda x: x.product_id == move.product_id) + if other_moves: + break if not other_moves: raise UserError( self.env._( diff --git a/deltatech_picking_transit/readme/HISTORY.md b/deltatech_picking_transit/readme/HISTORY.md new file mode 100644 index 000000000..904058865 --- /dev/null +++ b/deltatech_picking_transit/readme/HISTORY.md @@ -0,0 +1,17 @@ +## 19.0.0.0.10 + +- Port from 18.0.0.0.15. +- Setup on install: create a per-company transit stock location and, on each + warehouse, a two-step delivery operation type (with automatic second transfer) + and a two-step reception operation type, wired to the warehouse main stock + location and the transit location. Runs only at install, so databases already + using the module are not affected. +- The second transfer can now be created manually only after the first transfer + is validated, so the goods are actually in the transit location. +- The second transfer now inherits the quantity actually moved to transit, so + the flow works even when the operator filled only the "Quantity" field and + left the "Demand" at 0. +- Fixed the `is_transit_transfer` and `sub_location_existent` computes to work + on multi-record sets (no more singleton errors) and removed the side effect + from the compute method. +- Also carries the `sudo` fix for creating the second transfer (ticket 8970). diff --git a/deltatech_picking_transit/static/description/index.html b/deltatech_picking_transit/static/description/index.html index 58d433a23..29910102d 100644 --- a/deltatech_picking_transit/static/description/index.html +++ b/deltatech_picking_transit/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -359,90 +360,102 @@ -
-
-
Odoo Partner  •  Terrabit
-

Stock Auto Transfer

-

Automate internal transfer from transit location

-
- Odoo 19.0 - Online • Odoo.sh • On-premise - Optional support -
-
+
+

Stock Auto Transfer

-
-

Features:

-
  • On the stock picking type form you can add “next operation”
  • when you make an internal transfer with a picking type that has the +

    Beta License: LGPL-3 dhongu/deltatech

    +

    Features:

    +
      +
    • On the stock picking type form you can add “next operation”
    • +
    • when you make an internal transfer with a picking type that has the “next operation” set, the system gives you the option to make the -transfer a 2 step one with the button “create transfer”
    • the button will create another transfer from the transit location to -the location selected on the wizard with the same move lines
    • after the second transfer is created, you will not be able to modify -the move lines of the initial transfer
    • v17.0.0.0.9: added the option for the second transfer to be created -automatically without the need of the wizard
        -
      • on the stock picking type there will be a check box “Auto Second +transfer a 2 step one with the button “create transfer”
      • +
      • the button will create another transfer from the transit location to +the location selected on the wizard with the same move lines
      • +
      • after the second transfer is created, you will not be able to modify +the move lines of the initial transfer
      • +
      • v17.0.0.0.9: added the option for the second transfer to be created +automatically without the need of the wizard
          +
        • on the stock picking type there will be a check box “Auto Second Transfer” this check box will apper only if the “Two Step Transfer Use” is set to Delivery
        • -
        • if the check box is set, when validating the first transfer, the +
        • if the check box is set, when validating the first transfer, the system will try to find the Reception location based on the partner of the transfer (use the contact associated to the second warehouse)
        • -
        • if the “Source Document” is set on the picking the system will +
        • if the “Source Document” is set on the picking the system will not automatically create the second transfer
        -
      +
    • +
    +

    Table of contents

    + +
    +

    Changelog

    +
    +

    19.0.0.0.10

    +
      +
    • Port from 18.0.0.0.15.
    • +
    • Setup on install: create a per-company transit stock location and, on +each warehouse, a two-step delivery operation type (with automatic +second transfer) and a two-step reception operation type, wired to the +warehouse main stock location and the transit location. Runs only at +install, so databases already using the module are not affected.
    • +
    • The second transfer can now be created manually only after the first +transfer is validated, so the goods are actually in the transit +location.
    • +
    • The second transfer now inherits the quantity actually moved to +transit, so the flow works even when the operator filled only the +“Quantity” field and left the “Demand” at 0.
    • +
    • Fixed the is_transit_transfer and sub_location_existent +computes to work on multi-record sets (no more singleton errors) and +removed the side effect from the compute method.
    • +
    • Also carries the sudo fix for creating the second transfer (ticket +8970).
    • +
    +
    +
    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on Terrabit Issues. In case of trouble, please check there if your issue has already been reported.

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

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • Terrabit
    • Voicu Stefan
    -

    Maintainers

    +

    Maintainers

    Current maintainer:

    VoicuStefan2001

    This module is part of the dhongu/deltatech project on GitHub.

    You are welcome to contribute.

    -
-
-
-

Need help getting started?

-

- We are an Odoo partner building apps for the Romanian market (SAGA & WinMentor - export; Romanian accounting localization in progress). Direct support from the team - that built the module.

- Contact Terrabit → -
-
TERRABIT
-
- © Terrabit Solutions SRL  •  - terrabit.ro -  •  Odoo apps for Romania, Ireland & Moldova -
-
-
-
diff --git a/deltatech_picking_transit/views/stock_picking_views.xml b/deltatech_picking_transit/views/stock_picking_views.xml index b167b4f04..eba4fe970 100644 --- a/deltatech_picking_transit/views/stock_picking_views.xml +++ b/deltatech_picking_transit/views/stock_picking_views.xml @@ -21,7 +21,7 @@ type="object" string="Create Transfer" class="btn-primary" - invisible="is_transit_transfer==False or origin!=False or create_second_transfer_automatically==True" + invisible="is_transit_transfer==False or origin!=False or create_second_transfer_automatically==True or state!='done'" />