Initial commit: Ventor Odoo packages (4 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:49:21 +02:00
commit 1f20ad87e6
190 changed files with 10375 additions and 0 deletions

54
README.md Normal file
View file

@ -0,0 +1,54 @@
# Ventor Odoo Packages
This repository contains **4** Odoo packages from Ventor vendor.
## About Ventor
Ventor is a recognized vendor in the Odoo ecosystem, providing specialized addons and customizations.
## Packages Included (4 packages)
- **odoo-bringout-ventor-custom_import_wizard** - Custom Import Wizard
- **odoo-bringout-ventor-outgoing_routing** - Outgoing Routing
- **odoo-bringout-ventor-product_multiple_barcodes** - Product Multiple Barcodes
- **odoo-bringout-ventor-ventor_base** - Ventor Base
## Installation
Install any package from this collection:
```bash
# Install from local directory
pip install packages/ventor/PACKAGE_NAME/
# Install in development mode
pip install -e packages/ventor/PACKAGE_NAME/
# Using uv (recommended for speed)
uv add packages/ventor/PACKAGE_NAME/
```
## Repository Structure
Each package in this repository follows the standard Odoo addon structure:
```
ventor/
├── odoo-bringout-ventor-ADDON/
│ ├── ADDON_NAME/ # Complete addon code
│ │ ├── __init__.py
│ │ ├── __manifest__.py
│ │ └── ... (models, views, etc.)
│ ├── pyproject.toml # Python package configuration
│ └── README.md # Package documentation
└── ...
```
## License
Each package maintains its original license as specified by Ventor.
## Support
For support with these packages, please refer to the original Ventor documentation or community resources.

View file

@ -0,0 +1,46 @@
# Custom Import Wizard
Custom Odoo addon: custom_import_wizard
## Installation
```bash
pip install odoo-bringout-ventor-custom_import_wizard
```
## Dependencies
This addon depends on:
## Manifest Information
- **Name**: Custom Import Wizard
- **Version**: 16.0.1.0.0
- **Category**: Tools
- **License**: LGPL-3
- **Installable**: True
## Source
Custom addon from bringout-ventor vendor, addon `custom_import_wizard`.
## License
This package maintains the original LGPL-3 license from the addon.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,2 @@
Custom Import Wizard
====================

View file

@ -0,0 +1,6 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
# Localfolder:
from . import models
from . import wizard

View file

@ -0,0 +1,22 @@
# pylint: disable=missing-docstring
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
{
"name": "Custom Import Wizard",
"category": "Tools",
"version": "16.0.1.0.0",
"website": "https://ventor.tech",
"author": "VentorTech OU",
"license": "LGPL-3",
"depends": [],
"data": [
"security/security.xml",
"security/ir.model.access.csv",
"views/custom_import_history.xml",
"wizard/custom_import_wizard.xml",
"wizard/info_result_import_wizard.xml",
],
"external_dependencies": {"python": ["xlrd"]},
"installable": True,
}

View file

@ -0,0 +1,2 @@
Custom Import Wizard
====================

View file

@ -0,0 +1,230 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * custom_import_wizard
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 13:42+0000\n"
"PO-Revision-Date: 2022-08-18 13:42+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: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Are you sure?"
msgstr "Are you sure?"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Cancel"
msgstr "Otkaži"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__create_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__create_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__create_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__create_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: custom_import_wizard
#: model:res.groups,name:custom_import_wizard.custom_import_group_user
msgid "Custom Import"
msgstr "Prilagođeni uvoz"
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_custom_import_history
msgid "Custom Import History"
msgstr "Prilagođeni uvoz History"
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_custom_import_wizard
msgid "Custom Import Wizard"
msgstr "Prilagođeni uvoz Čarobnjak"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__display_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__display_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__error_details
msgid "Error Details"
msgstr "Greška Detalji"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__extra_info
msgid "Extra Info"
msgstr "Dodatne informacije"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Final Import"
msgstr "Final Uvoz"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__formatted_file
msgid "Formatted File"
msgstr "Formaatted File"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__formatted_file_name
msgid "Formatted File Name"
msgstr "Formaatted File Naziv"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__id
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__id
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__id
msgid "ID"
msgstr "ID"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Import"
msgstr "Uvoz"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__name
msgid "Import Date"
msgstr "Uvoz Datum"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_history_view_form
msgid "Import History"
msgstr "Uvoz History"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_history_view_form
msgid "Imported by"
msgstr "Uvozed by"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.view_info_result_import_wizard
msgid "Info"
msgstr "Informacija"
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_info_result_import_wizard
msgid "Info Result Import Wizard"
msgstr "Info Result Uvoz Čarobnjak"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__message
msgid "Info: "
msgstr "Info: "
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history____last_update
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard____last_update
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__write_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__write_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__write_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__write_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__model
msgid "Model"
msgstr "Model"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.view_info_result_import_wizard
msgid "OK"
msgstr "U redu"
#. module: custom_import_wizard
#: model:res.groups,comment:custom_import_wizard.custom_import_group_user
msgid "Only responsible can access import functionallity"
msgstr "Samo odgovorna osoba može pristupiti funkciji uvoza"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__original_file
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__original_file
msgid "Original File"
msgstr "Original File"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__original_file_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__original_file_name
msgid "Original File Name"
msgstr "Original File Naziv"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__result_file
msgid "Result File"
msgstr "Result File"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__result_file_name
msgid "Result File Name"
msgstr "Result File Naziv"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__sample_file
msgid "Sample File"
msgstr "Sample File"
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Sample Format"
msgstr "Sample Formaat"
#. module: custom_import_wizard
#: code:addons/custom_import_wizard/wizard/custom_import_wizard.py:0
#, python-format
msgid ""
"Successfully imported: {} | Duplicates not imported: {} | Error importing "
"(bad info): {}"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Test Import"
msgstr "Test Uvoz"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_duplicated
msgid "Total Duplicated"
msgstr "Ukupno Duplicated"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_errors
msgid "Total Errors"
msgstr "Ukupno Greškas"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_imported
msgid "Total Imported"
msgstr "Ukupno Uvozed"
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__user_id
msgid "User"
msgstr "Korisnik"

View file

@ -0,0 +1,230 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * custom_import_wizard
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 13:42+0000\n"
"PO-Revision-Date: 2022-08-18 13:42+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: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Are you sure?"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Cancel"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__create_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__create_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__create_uid
msgid "Created by"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__create_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__create_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__create_date
msgid "Created on"
msgstr ""
#. module: custom_import_wizard
#: model:res.groups,name:custom_import_wizard.custom_import_group_user
msgid "Custom Import"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_custom_import_history
msgid "Custom Import History"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_custom_import_wizard
msgid "Custom Import Wizard"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__display_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__display_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__display_name
msgid "Display Name"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__error_details
msgid "Error Details"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__extra_info
msgid "Extra Info"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Final Import"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__formatted_file
msgid "Formatted File"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__formatted_file_name
msgid "Formatted File Name"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__id
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__id
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__id
msgid "ID"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Import"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__name
msgid "Import Date"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_history_view_form
msgid "Import History"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_history_view_form
msgid "Imported by"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.view_info_result_import_wizard
msgid "Info"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model,name:custom_import_wizard.model_info_result_import_wizard
msgid "Info Result Import Wizard"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__message
msgid "Info: "
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history____last_update
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard____last_update
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard____last_update
msgid "Last Modified on"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__write_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__write_uid
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__write_uid
msgid "Last Updated by"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__write_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__write_date
#: model:ir.model.fields,field_description:custom_import_wizard.field_info_result_import_wizard__write_date
msgid "Last Updated on"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__model
msgid "Model"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.view_info_result_import_wizard
msgid "OK"
msgstr ""
#. module: custom_import_wizard
#: model:res.groups,comment:custom_import_wizard.custom_import_group_user
msgid "Only responsible can access import functionallity"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__original_file
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__original_file
msgid "Original File"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__original_file_name
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__original_file_name
msgid "Original File Name"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__result_file
msgid "Result File"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__result_file_name
msgid "Result File Name"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_wizard__sample_file
msgid "Sample File"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Sample Format"
msgstr ""
#. module: custom_import_wizard
#: code:addons/custom_import_wizard/wizard/custom_import_wizard.py:0
#, python-format
msgid ""
"Successfully imported: {} | Duplicates not imported: {} | Error importing "
"(bad info): {}"
msgstr ""
#. module: custom_import_wizard
#: model_terms:ir.ui.view,arch_db:custom_import_wizard.custom_import_wizard_view_form
msgid "Test Import"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_duplicated
msgid "Total Duplicated"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_errors
msgid "Total Errors"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__total_imported
msgid "Total Imported"
msgstr ""
#. module: custom_import_wizard
#: model:ir.model.fields,field_description:custom_import_wizard.field_custom_import_history__user_id
msgid "User"
msgstr ""

View file

@ -0,0 +1,5 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
# Localfolder:
from . import custom_import_history

View file

@ -0,0 +1,31 @@
"""
Copyright 2020 VentorTech OU
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
"""
# Stdlib:
from datetime import datetime
# Odoo:
from odoo import fields, models
# pylint: disable=too-few-public-methods
class CustomImportHistory(models.Model):
""" History for import """
_name = "custom.import.history"
_description = "Custom Import History"
name = fields.Datetime(default=lambda self: datetime.now(), string="Import Date")
original_file = fields.Binary()
formatted_file = fields.Binary()
original_file_name = fields.Char()
formatted_file_name = fields.Char()
total_imported = fields.Integer()
total_duplicated = fields.Integer()
total_errors = fields.Integer()
user_id = fields.Many2one("res.users", default=lambda self: self.env.user)
model = fields.Char()
extra_info = fields.Char()
error_details = fields.Char()

View file

@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
import_history,import_history,model_custom_import_history,custom_import_wizard.custom_import_group_user,1,0,1,0
import_custom_wizard,import_custom_wizard,model_custom_import_wizard,custom_import_wizard.custom_import_group_user,1,0,1,0
info_result_import_wizard,info_result_import_wizard,model_info_result_import_wizard,custom_import_wizard.custom_import_group_user,1,0,1,0
import_history_admin,import_history,model_custom_import_history,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 import_history import_history model_custom_import_history custom_import_wizard.custom_import_group_user 1 0 1 0
3 import_custom_wizard import_custom_wizard model_custom_import_wizard custom_import_wizard.custom_import_group_user 1 0 1 0
4 info_result_import_wizard info_result_import_wizard model_info_result_import_wizard custom_import_wizard.custom_import_group_user 1 0 1 0
5 import_history_admin import_history model_custom_import_history base.group_system 1 1 1 1

View file

@ -0,0 +1,6 @@
<odoo>
<record id="custom_import_group_user" model="res.groups">
<field name="name">Custom Import</field>
<field name="comment">Only responsible can access import functionallity</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,29 @@
<section class="oe_container">
<div class="oe_row">
<div class="oe_span12">
<h2 class="oe_slogan">Custom Import Wizard</h2>
</div>
<div class="oe_span12" style="text-align: center; margin-bottom: 5px">
<p style="padding: 5px; font-size: 16px; font-weight: bold; background-color: yellow; display: inline">
TO AVOID ANY ISSUES, PLEASE, USE ALWAYS LATEST VERSION FROM OUR GITHUB REPOSITORY -
<a href="https://github.com/ventor-tech/merp/tree/16.0">
https://github.com/ventor-tech/merp/tree/16.0
</a>
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Notes</h2>
<p class="oe_mt32">
<a target="_blank" href="https://ventor.tech/">VentorTech (https://ventor.tech/)</a> is company specialized on building Personalized Inventory and Product Management System.
</p>
<p class="oe_mt32">
For all questions contact <a target="_blank" href="mailto:hello@ventor.tech">hello@ventor.tech</a>.
</p>
</div>
</div>
</section>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- custom.import.history form view -->
<record id="custom_import_history_view_form" model="ir.ui.view">
<field name="name">custom.import.history.view.form</field>
<field name="model">custom.import.history</field>
<field name="arch" type="xml">
<form string="Import History" create="false" duplicate="false">
<sheet>
<group>
<field name="user_id" string="Imported by"/>
<field name="model"/>
<field name="original_file_name" invisible="1"/>
<field name="formatted_file_name" invisible="1"/>
<field name="original_file" widget="binary" filename="original_file_name" />
<field name="formatted_file" widget="binary" filename="formatted_file_name"/>
<field name="total_imported"/>
<field name="total_duplicated"/>
<field name="total_errors"/>
<field name="extra_info" widget="text"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- crm.lead.import.history tree view -->
<record id="custom_import_history_view_tree" model="ir.ui.view">
<field name="name">custom.import.history.view.tree</field>
<field name="model">custom.import.history</field>
<field name="arch" type="xml">
<tree import ="false" create="false">
<field name="model"/>
<field name="name"/>
<field name="user_id"/>
</tree>
</field>
</record>
</odoo>

View file

@ -0,0 +1,6 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
# Localfolder:
from . import custom_import_wizard
from . import info_result_import_wizard

View file

@ -0,0 +1,257 @@
"""
Copyright 2020 VentorTech OU
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
"""
# Stdlib:
import binascii
import io
import logging
from collections import defaultdict
# Odoo:
from odoo import _, fields, models
from odoo.exceptions import ValidationError
# Thirdparty:
import xlrd
try:
from odoo.tools.misc import xlsxwriter
except ImportError:
import xlsxwriter
_logger = logging.getLogger(__name__)
IMPORT_STATUSES = ["success", "error", "duplicate"]
class CustomImportWizard(models.TransientModel):
""" Allows you to import records without duplicates and errors """
_name = "custom.import.wizard"
_description = "Custom Import Wizard"
original_file = fields.Binary()
original_file_name = fields.Char()
result_file = fields.Binary()
result_file_name = fields.Char()
sample_file = fields.Binary()
def read_original_file(self, no_convert_indexes=None):
""" Get data from imported file """
if no_convert_indexes is None:
no_convert_indexes = []
if not self.original_file:
raise ValidationError('No file!')
book = xlrd.open_workbook(
file_contents=binascii.a2b_base64(self.original_file) or b""
)
sheet = book.sheet_by_index(0)
lines = []
for row_no in range(1, sheet.nrows):
row = sheet.row(row_no)
if all([not str(cell.value).strip() for cell in row]):
continue
lines.append(self.read_original_line(row))
return lines
# pylint: disable=no-self-use
def read_original_line(self, row):
""" This method is for formatting raw data from a cell """
line = {"values": [], "errors": [], "status": "success"}
for cell in row:
line["values"].append(str(cell.value))
return line
# pylint: disable=no-self-use
def _get_formats(self):
return {
"header": {"align": "center", "bold": True, "font_size": "10px"},
"success": {"font_size": "8px"},
"error": {"font_size": "8px", "bg_color": "#fccfac"},
"duplicate": {"font_size": "8px", "bg_color": "#f29d9d"},
}
def write_result_file(self, headers, lines):
"""
Writes result XLSX file
Arguments:
headers {list} -- List of column headers ['Company Name', 'Email']
]
lines {list} -- List of dictionaries [
{
'values': ['My Company', 'test@example.com'],
'errors': [],
'status': 'success'
},
{
'values': ['Mega LLC', 'mega@example.com'],
'errors': ['Error 1', 'Error 2'],
'status': 'error'
},
]
"""
with io.BytesIO() as output:
with xlsxwriter.Workbook(output, {"in_memory": True}) as workbook:
sheet = workbook.add_worksheet()
sheet.set_column(0, 12, 30)
col = row = 0
# Write headers
for header in headers:
sheet.write(row, col, header)
col += 1
def _write_lines_by_status(lines_by_status, row):
for line in lines_by_status:
row += 1
col = 0
# Write line values
for val in line["values"]:
sheet.write(row, col, val)
col += 1
# Write Status
sheet.write(row, col, line["status"])
# Write Errors
col += 1
sheet.write(row, col, "; ".join(line["errors"]))
return row
for status in IMPORT_STATUSES:
row = _write_lines_by_status(
filter(lambda l, st=status: l["status"] == st, lines), row
)
output.seek(0)
self.result_file = binascii.b2a_base64(output.read())
self.result_file_name = self.original_file_name.replace(
".xlsx", "_formatted.xlsx"
)
def download_result_file(self):
""" Downloading file from odoo field"""
return {
"name": "Result File",
"type": "ir.actions.act_url",
"url": (
"/web/content/?model={model}&id={id}&field=result_file&"
"download=true&filename={fname}"
).format(model=self._name, id=self.id, fname=self.result_file_name),
"target": "new",
}
# pylint: disable=no-self-use
def _get_model_name(self):
return ""
# pylint: disable=no-self-use
def _get_extra_info(self):
return ""
def create_import_history(self, totals):
"""
Creating history for created lines
Arguments:
totals {dict}
Returns:
record("custom.import.history") -- history of created records
"""
return self.env["custom.import.history"].create(
{
"original_file": self.original_file,
"formatted_file": self.result_file,
"original_file_name": self.original_file_name,
"formatted_file_name": self.result_file_name,
"extra_info": self._get_extra_info(),
"total_imported": totals.get("success", 0),
"total_duplicated": totals.get("duplicate", 0),
"total_errors": totals.get("error", 0),
"model": self._get_model_name(),
}
)
# pylint: disable=no-self-use,unused-argument
def handle_import(self, test=True):
"""
Inherit and override this method in child model
Arguments:
totals {boolean} -- indicates test import
Returns:
{list} -- list of lines
"""
return []
def test_import(self):
""" Download formatted file without importing correct records to db """
self.handle_import()
return self.download_result_file()
# pylint: disable=no-self-use
def get_totals(self, lines):
"""
Count totals of imported lines splited by import statuses
"""
res = defaultdict(int)
for line in lines:
res[line["status"]] += 1
return res
def final_import(self):
""" Importing correct record """
lines = self.handle_import(test=False)
import_history_record = self.create_import_history(self.get_totals(lines))
context = {
"default_message": _(
"Successfully imported: {} | "
"Duplicates not imported: {} | Error importing (bad info): {}"
).format(
import_history_record.total_imported,
import_history_record.total_duplicated,
import_history_record.total_errors,
)
}
return {
"name": "Import Leads Info",
"type": "ir.actions.act_window",
"res_model": "info.result.import.wizard",
"view_mode": "form",
"view_type": "form",
"target": "new",
"context": context,
}
# pylint: disable=no-self-use
def _get_sample_file_path(self):
"""
Inherit and override this method in child models
Return a path the sample xlsx file that will be returned to the user
"""
return ""
def print_sample_xlsx(self):
""" Print sample file how to should be created leads """
xlsx_file_path = self._get_sample_file_path()
file_content = open(xlsx_file_path, "rb").read()
self.sample_file = binascii.b2a_base64(file_content)
return {
"name": "Sample File",
"type": "ir.actions.act_url",
"url": (
"/web/content/?model={model}&id={id}&field=sample_file&"
"download=true&filename={fname}"
).format(model=self._name, id=self.id, fname="Sample Import.xlsx"),
"target": "new",
}

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="custom_import_wizard_view_form" model="ir.ui.view">
<field name="name">custom.import.wizard.form</field>
<field name="model">custom.import.wizard</field>
<field name="arch" type="xml">
<form string="Import">
<group col="2">
<field name="original_file_name" invisible="1"/>
<field name="original_file" widget="binary" filename="original_file_name"/>
</group>
<footer>
<button
name="test_import"
string="Test Import"
class="oe_highlight"
type="object"
default_focus="1"
/>
<button
name="final_import"
string="Final Import"
type="object"
class="oe_highlight"
confirm="Are you sure?"
/>
<button
name="print_sample_xlsx"
string="Sample Format"
class="btn btn-default"
type="object"
/>
<button string="Cancel" class="btn btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,11 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
# Odoo:
from odoo import fields, models
class InfoResultImportWizard(models.TransientModel):
_name = "info.result.import.wizard"
_description = "Info Result Import Wizard"
message = fields.Char(string="Info: ", readonly=True, store=True)

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_info_result_import_wizard" model="ir.ui.view">
<field name="name">info.result.import.wizard.form</field>
<field name="model">info.result.import.wizard</field>
<field name="arch" type="xml">
<form string="Info">
<group>
<field name="message" widget="text"/>
</group>
<footer>
<button string="OK" special="cancel" class="oe_highlight"/>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Custom_import_wizard Module - custom_import_wizard
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for custom_import_wizard. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,3 @@
# Dependencies
No explicit module dependencies declared.

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon custom_import_wizard or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-ventor-custom_import_wizard"
# or
uv pip install odoo-bringout-ventor-custom_import_wizard"
```

View file

@ -0,0 +1,12 @@
# Models
Detected core models and extensions in custom_import_wizard.
```mermaid
classDiagram
class custom_import_history
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: custom_import_wizard. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon custom_import_wizard
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,42 @@
# Security
Access control and security definitions in custom_import_wizard.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../custom_import_wizard/security/ir.model.access.csv)**
- 4 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[security.xml](../custom_import_wizard/security/security.xml)**
- 1 security groups defined
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../custom_import_wizard/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[security.xml](../custom_import_wizard/security/security.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon custom_import_wizard
```

View file

@ -0,0 +1,9 @@
# Wizards
Transient models exposed as UI wizards in custom_import_wizard.
```mermaid
classDiagram
class CustomImportWizard
class InfoResultImportWizard
```

View file

@ -0,0 +1,41 @@
[project]
name = "odoo-bringout-ventor-custom_import_wizard"
version = "16.0.0"
description = "Custom Import Wizard - Custom Odoo addon"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["custom_import_wizard"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,48 @@
# Picking and Reservation Strategy
Custom Odoo addon: outgoing_routing
## Installation
```bash
pip install odoo-bringout-ventor-outgoing_routing
```
## Dependencies
This addon depends on:
- sale_management
- stock_picking_batch
- ventor_base
## Manifest Information
- **Name**: Picking and Reservation Strategy
- **Version**: 16.0.1.1.0
- **Category**: N/A
- **License**: LGPL-3
- **Installable**: True
## Source
Custom addon from bringout-ventor vendor, addon `outgoing_routing`.
## License
This package maintains the original LGPL-3 license from the addon.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Outgoing_routing Module - outgoing_routing
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for outgoing_routing. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,7 @@
# Dependencies
This addon depends on:
- [sale_management](../../odoo-bringout-oca-ocb-sale_management)
- [stock_picking_batch](../../odoo-bringout-oca-ocb-stock_picking_batch)
- [ventor_base](../../odoo-bringout-ventor-ventor_base)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon outgoing_routing or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-ventor-outgoing_routing"
# or
uv pip install odoo-bringout-ventor-outgoing_routing"
```

View file

@ -0,0 +1,19 @@
# Models
Detected core models and extensions in outgoing_routing.
```mermaid
classDiagram
class stock_picking
class stock_picking_batch
class stock_picking_mixin
class res_company
class res_config_settings
class stock_location
class stock_move_line
class stock_quant
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: outgoing_routing. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon outgoing_routing
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon outgoing_routing
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,10 @@
================================
Picking and Reservation Strategy
================================
* Allows to automatically build optimal picking routes and apply custom reservation options.
16.0.1.1.0 (2023-11-04)
***********************
* Fixed tests

View file

@ -0,0 +1,5 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from . import models
from . import tests

View file

@ -0,0 +1,26 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
{
'name': 'Picking and Reservation Strategy',
"version": "16.0.1.1.0",
'author': 'VentorTech',
'website': 'https://ventor.tech/',
'license': 'LGPL-3',
'installable': True,
'images': ['static/description/images/image1.JPG'],
'summary': 'Allows to automatically build optimal picking routes and apply custom reservation options',
'depends': [
'sale_management',
'stock_picking_batch',
'ventor_base',
],
'data': [
'data/product_removal.xml',
'views/res_config.xml',
'views/stock.xml',
'views/picking.xml',
'views/report_stockpicking.xml',
'views/stock_picking_wave.xml',
],
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="removal_location_priority" model="product.removal">
<field name="name">By Location Priority</field>
<field name="method">location_priority</field>
</record>
</odoo>

View file

@ -0,0 +1,42 @@
Please read our detailed guide about optimal picking routes in your warehouse here - https://ventor.tech/warehouse-management/how-to-build-picking-routes-in-your-warehouse-for-walking-minimization/
|
Read also Picking and Packing optimization guide - https://ventor.tech/mobile/pick-more-walk-less-full-picking-and-packing-optimization-in-odoo/
|
==========================
Quick configuration guide
==========================
|
1. Install the Ventor Outgoing routing module on your Odoo server (and all dependencies)
2. Assign removal priority (sequence) for each location. You can do it manually or `import <https://ventor.tech/warehouse-management/how-to-build-picking-routes-in-your-warehouse-for-walking-minimization/#upload-route>`_ a .csv file
|
.. image:: images/image7.png
:width: 800px
|
3. Go to Settings > Ventor/mERP. Set up needed Picking strategy and Reservation strategy
|
.. image:: images/image8.png
:width: 800px
|
\*Reservation strategy available for Odoo 12 and higher
Change Log
##########
|
* 16.0.1.1.0 (2023-11-04)
- Fixed tests

View file

@ -0,0 +1,272 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * outgoing_routing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 13:42+0000\n"
"PO-Revision-Date: 2022-08-18 13:42+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: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "- quants are reserved according to Picking startegy (see above)"
msgstr "- quants are reserved according to Picking startegy (see above)"
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location name 'A' and beyond)"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location name 'Z' to 'A')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location with removal priority "
"'0' to '∞')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location with removal priority "
"'∞' to '0')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "- quants are reserved in Odoo standard way (FIFO/LIFO)"
msgstr "- quants are reserved in Odoo standard way (FIFO/LIFO)"
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from <b>location</b> '<b>A</b>' to '<b>Z</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from <b>location</b> '<b>Z</b>' to '<b>A</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location contains <b>product</b> '<b>A</b>' "
"to '<b>Z</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location contains <b>product</b> '<b>Z</b>' "
"to '<b>A</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location with <b>removal priority</b> "
"'<b>0</b>' to '<b>∞</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location with <b>removal priority</b> "
"'<b>∞</b>' to '<b>0</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "<br/>Your current settings:"
msgstr "<br/>Your current Postavke:"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_order__0
msgid "Ascending (A-Z)"
msgstr "Ascending (A-Z)"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking_batch
msgid "Batch Transfer"
msgstr "Skupni prijenos"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__base
msgid "By Picking Strategy"
msgstr "By Picking Strategy"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__quantity
msgid "By Quantity"
msgstr "Po količini"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_res_company
msgid "Companies"
msgstr "Kompanije"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__company_id
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__company_id
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_mixin__company_id
msgid "Company"
msgstr "Preduzeće"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_res_config_settings
msgid "Config Settings"
msgstr "Postavke"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__none
msgid "Default"
msgstr "Zadano"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_order__1
msgid "Descending (Z-A)"
msgstr "Descending (Z-A)"
#. module: outgoing_routing
#: code:addons/outgoing_routing/models/stock_picking.py:0
#, python-format
msgid "Hint: operations are sorted by {} in {} order."
msgstr "Hint: Operacije are sorted by {} in {} Narudžba."
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_location
msgid "Inventory Locations"
msgstr "Lokacije inventure"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__location_id_name
msgid "Location name"
msgstr "Lokacija name"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__location_id_removal_prio
msgid "Location removal priority"
msgstr "Location removal priority"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__related_pack_operations
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_wave_form
msgid "Operations"
msgstr "Operacije"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__operations_to_pick
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__operations_to_pick
msgid "Operations to Pick"
msgstr "Operacije to Pick"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__outgoing_routing_order
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__outgoing_routing_order
msgid "Picking Order"
msgstr "Picking Narudžba"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__outgoing_routing_strategy
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__outgoing_routing_strategy
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Picking Strategy"
msgstr "Picking Strategy"
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Please, select parameters to calculate route through warehouse(s):"
msgstr "Please, select parameters to calculate route through Skladište(s):"
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Please, select parameters to reorder quants during reservation:"
msgstr "Please, select parameters to reorder quants during reservation:"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_move_line
msgid "Product Moves (Stock Move Line)"
msgstr "Skladišna kretanja proizvoda(stavke)"
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__product_id_name
msgid "Product name"
msgstr "Proizvod Naziv"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_quant
msgid "Quants"
msgstr "Količine"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_location__removal_prio
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_quant__removal_prio
msgid "Removal Priority"
msgstr "Prioritet uklanjanja"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__stock_reservation_strategy
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__stock_reservation_strategy
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Reservation Strategy"
msgstr "Reservation Strategy"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_mixin__routing_module_version
msgid "Routing Module Version"
msgstr "Routing Module Version"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_location__strategy_sequence
msgid "Sequence"
msgstr "Sekvenca"
#. module: outgoing_routing
#: model:ir.model.fields,help:outgoing_routing.field_stock_location__strategy_sequence
msgid "Sequence based on warehouse location outgoing strategy/order"
msgstr "Sequence based on Skladište location outgoing strategy/Narudžba"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking_mixin
msgid "Stock Picking Mixin"
msgstr "Zalihe Picking Mješavina"
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__strategy_order_r
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__strategy_order_r
msgid "Strategy Order"
msgstr "Strategy Narudžba"
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_form
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_wave_form
msgid "To Process"
msgstr "Za obradu"
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking
msgid "Transfer"
msgstr "Prijenos"

View file

@ -0,0 +1,272 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * outgoing_routing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 13:42+0000\n"
"PO-Revision-Date: 2022-08-18 13:42+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: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "- quants are reserved according to Picking startegy (see above)"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location name 'A' and beyond)"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location name 'Z' to 'A')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location with removal priority "
"'0' to '∞')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- quants are reserved first in locations that contain a sufficient amount of"
" product and have higher priority (i.e. from location with removal priority "
"'∞' to '0')"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "- quants are reserved in Odoo standard way (FIFO/LIFO)"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from <b>location</b> '<b>A</b>' to '<b>Z</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from <b>location</b> '<b>Z</b>' to '<b>A</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location contains <b>product</b> '<b>A</b>' "
"to '<b>Z</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location contains <b>product</b> '<b>Z</b>' "
"to '<b>A</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location with <b>removal priority</b> "
"'<b>0</b>' to '<b>∞</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid ""
"- the route is calculated from location with <b>removal priority</b> "
"'<b>∞</b>' to '<b>0</b>'"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "<br/>Your current settings:"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_order__0
msgid "Ascending (A-Z)"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking_batch
msgid "Batch Transfer"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__base
msgid "By Picking Strategy"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__quantity
msgid "By Quantity"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_res_company
msgid "Companies"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__company_id
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__company_id
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_mixin__company_id
msgid "Company"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__stock_reservation_strategy__none
msgid "Default"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_order__1
msgid "Descending (Z-A)"
msgstr ""
#. module: outgoing_routing
#: code:addons/outgoing_routing/models/stock_picking.py:0
#, python-format
msgid "Hint: operations are sorted by {} in {} order."
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_location
msgid "Inventory Locations"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__location_id_name
msgid "Location name"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__location_id_removal_prio
msgid "Location removal priority"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__related_pack_operations
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_wave_form
msgid "Operations"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__operations_to_pick
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__operations_to_pick
msgid "Operations to Pick"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__outgoing_routing_order
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__outgoing_routing_order
msgid "Picking Order"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__outgoing_routing_strategy
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__outgoing_routing_strategy
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Picking Strategy"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Please, select parameters to calculate route through warehouse(s):"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Please, select parameters to reorder quants during reservation:"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_move_line
msgid "Product Moves (Stock Move Line)"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields.selection,name:outgoing_routing.selection__res_company__outgoing_routing_strategy__product_id_name
msgid "Product name"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_quant
msgid "Quants"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_location__removal_prio
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_quant__removal_prio
msgid "Removal Priority"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__stock_reservation_strategy
#: model:ir.model.fields,field_description:outgoing_routing.field_res_config_settings__stock_reservation_strategy
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_stock_config_settings
msgid "Reservation Strategy"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_res_company__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__routing_module_version
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_mixin__routing_module_version
msgid "Routing Module Version"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_location__strategy_sequence
msgid "Sequence"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,help:outgoing_routing.field_stock_location__strategy_sequence
msgid "Sequence based on warehouse location outgoing strategy/order"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking_mixin
msgid "Stock Picking Mixin"
msgstr ""
#. module: outgoing_routing
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking__strategy_order_r
#: model:ir.model.fields,field_description:outgoing_routing.field_stock_picking_batch__strategy_order_r
msgid "Strategy Order"
msgstr ""
#. module: outgoing_routing
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_form
#: model_terms:ir.ui.view,arch_db:outgoing_routing.view_picking_wave_form
msgid "To Process"
msgstr ""
#. module: outgoing_routing
#: model:ir.model,name:outgoing_routing.model_stock_picking
msgid "Transfer"
msgstr ""

View file

@ -0,0 +1,53 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
import logging
logger = logging.getLogger(__name__)
def pre_init_hook(cr):
"""
The objective of this hook is to speed up the installation
of the module on an existing Odoo instance.
Without this script, big databases can take a long time to install this
module.
"""
set_stock_location_priority_default(cr)
set_stock_quant_location_priority_default(cr)
def set_stock_location_priority_default(cr):
cr.execute(
"""SELECT column_name
FROM information_schema.columns
WHERE table_name='stock_location' AND
column_name='removal_prio'"""
)
if not cr.fetchone():
logger.info("Creating field removal_prio on stock_location")
cr.execute(
"""
ALTER TABLE stock_location
ADD COLUMN removal_prio integer
DEFAULT 0;
"""
)
def set_stock_quant_location_priority_default(cr):
cr.execute(
"""SELECT column_name
FROM information_schema.columns
WHERE table_name='stock_quant' AND
column_name='removal_prio'"""
)
if not cr.fetchone():
logger.info("Creating field removal_prio on stock_quant")
cr.execute(
"""
ALTER TABLE stock_quant
ADD COLUMN removal_prio integer
DEFAULT 0;
"""
)

View file

@ -0,0 +1,12 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from . import res_company
from . import res_config
from . import stock_location
from . import stock_picking_mixin
from . import stock_picking
from . import stock_pack_operation
from . import stock_picking_wave
from . import stock_quant

View file

@ -0,0 +1,50 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, fields
class Company(models.Model):
_inherit = 'res.company'
outgoing_routing_strategy = fields.Selection(
[
# path should be valid for both stock pickings and quants
('location_id.removal_prio', 'Location removal priority'),
('location_id.name', 'Location name'),
('product_id.name', 'Product name'),
],
string='Picking Strategy', default='location_id.name')
outgoing_routing_order = fields.Selection(
[
('0', 'Ascending (A-Z)'),
('1', 'Descending (Z-A)'),
],
string='Picking Order', default='0')
stock_reservation_strategy = fields.Selection(
[
('base', 'By Picking Strategy'),
('quantity', 'By Quantity'),
('none', 'Default'),
],
string='Reservation Strategy', default='base')
routing_module_version = fields.Char(
string='Routing Module Version',
compute='_compute_routing_module_version',
compute_sudo=True,
)
def _compute_routing_module_version(self):
self.env.cr.execute(
"SELECT latest_version FROM ir_module_module WHERE name='outgoing_routing'"
)
result = self.env.cr.fetchone()
full_version = result and result[0]
split_value = full_version and full_version.split('.')
module_version = split_value and '.'.join(split_value[-3:])
for rec in self:
rec.routing_module_version = module_version

View file

@ -0,0 +1,23 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, fields
class StockConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
outgoing_routing_strategy = fields.Selection(
string='Picking Strategy',
related='company_id.outgoing_routing_strategy',
readonly=False)
outgoing_routing_order = fields.Selection(
string='Picking Order',
related='company_id.outgoing_routing_order',
readonly=False)
stock_reservation_strategy = fields.Selection(
string='Reservation Strategy',
related='company_id.stock_reservation_strategy',
readonly=False)

View file

@ -0,0 +1,53 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, fields, api
class StockLocation(models.Model):
_inherit = "stock.location"
removal_prio = fields.Integer(
string='Removal Priority',
default=0,
)
strategy_sequence = fields.Integer(
string='Sequence',
help='Sequence based on warehouse location outgoing strategy/order',
compute='_compute_outgoing_strategy_sequence',
store=False,
)
def _compute_outgoing_strategy_sequence(self):
"""
"""
strategy = self.env.user.company_id.outgoing_routing_strategy
strategy_order = self.env.user.company_id.outgoing_routing_order
if strategy and len(strategy.split('.')) > 1:
base, field = strategy.split('.', 1)
if base not in ('location_id') and field not in self:
return
else:
return
res = self.sudo().search([], order='{} {}'.format(
field, ['asc', 'desc'][int(strategy_order)]))
processed = self.env['stock.location']
for sequence, location in enumerate(res):
if location not in self:
continue
location.strategy_sequence = sequence
processed |= location
remaining_locations = self - processed
max_seq = len(res)
for remaining in remaining_locations:
remaining.strategy_sequence = max_seq
@api.onchange('location_id')
def _onchange_parent_location(self):
""" Set location's parent removal priority by default
"""
if self.location_id:
self.removal_prio = self.location_id.removal_prio

View file

@ -0,0 +1,29 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, api
class StockPackOperation(models.Model):
_inherit = 'stock.move.line'
@api.model
def _compute_operation_valid(self):
res = True
if hasattr(super(StockPackOperation, self), '_compute_operation_valid'):
res &= super(StockPackOperation, self)._compute_operation_valid()
res &= self.qty_done != self.reserved_qty
return res
def _get_operation_attr(self, attr, flag):
if not flag:
return getattr(self, attr)
return getattr((self.package_level_id or self), attr)
def _get_operation_tuple(self):
self.ensure_one()
show_pack = self.picking_id.picking_type_id.show_entire_packs
return (
('id', self._get_operation_attr('id', show_pack)),
('_type', self._get_operation_attr('_name', show_pack)),
)

View file

@ -0,0 +1,81 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import http
from odoo import models, fields, api, _
import functools
class StockPicking(models.Model):
_name = 'stock.picking'
_inherit = ['stock.picking', 'stock.picking.mixin']
operations_to_pick = fields.Many2many(
'stock.move.line', relation='picking_operations_to_pick',
string='Operations to Pick',
compute='_compute_operations_to_pick', store=False)
strategy_order_r = fields.Char(
string='Strategy Order',
compute='_compute_operations_to_pick',
store=False,
)
@api.depends(
'move_line_ids',
'move_line_ids.location_id',
'move_line_ids.qty_done',
)
def _compute_operations_to_pick(self):
"""
"""
strategy = self.env.user.company_id.outgoing_routing_strategy
strategy_order = self.env.user.company_id.outgoing_routing_order
for rec in self:
all_operations = self.env['stock.move.line'].search([
('picking_id', '=', rec.id),
])
rec.strategy_order_r = rec.get_strategy_string(strategy, strategy_order)
rec.operations_to_pick = rec.sort_operations(all_operations, strategy, strategy_order)
def sort_printer_picking_list(self, move_line_ids):
""" sort list of pack operations by configured field
"""
strategy = self.env.user.company_id.outgoing_routing_strategy
strategy_order = self.env.user.company_id.outgoing_routing_order
return self.sort_operations(move_line_ids, strategy, strategy_order)
def get_strategy_string(self, strategy, strategy_order):
"""
"""
settings = self.env['res.company'].fields_get([
'outgoing_routing_strategy',
'outgoing_routing_order',
])
strategies = settings['outgoing_routing_strategy']['selection']
orders = settings['outgoing_routing_order']['selection']
result = _('Hint: operations are sorted by {} in {} order.').format(
dict(strategies)[strategy].lower(),
dict(orders)[strategy_order].lower()
)
return result
def sort_operations(self, all_operations, strategy, strategy_order):
"""
"""
def _r_getattr(obj, attr, *args):
return functools.reduce(getattr, [obj] + attr.split('.'))
validated_operations = all_operations.filtered(lambda op: op._compute_operation_valid())
result = validated_operations.sorted(
key=lambda op: _r_getattr(op, strategy, 'None'),
reverse=int(strategy_order)
)
return result

View file

@ -0,0 +1,60 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, fields
import logging
_logger = logging.getLogger(__file__)
class StockPickingMixin(models.AbstractModel):
_name = 'stock.picking.mixin'
_description = 'Stock Picking Mixin'
company_id = fields.Many2one(
comodel_name='res.company',
)
routing_module_version = fields.Char(
related='company_id.routing_module_version',
)
@staticmethod
def _recheck_record_list(record_list):
rechecked_list = []
for rec in record_list:
if rec.get('_type') == 'stock.package_level' and rec.get('is_done'):
continue
rechecked_list.append(rec)
return rechecked_list
def _read_record(self, record_tuple):
"""
record_tuple = (
('id', 100),
('_type', 'stock.move.line'),
)
id:: number (int)
_type:: 'stock.move.line' or 'stock.package_level' (str)
"""
record_dict = dict(record_tuple)
record = self.env[record_dict['_type']].browse(record_dict['id'])
record_dict.update(record.read()[0])
return record_dict
def serialize_record_ventor(self, rec_id):
"""Record serialization for the Ventor app."""
filtered_list = []
try:
stock_object = self.search([
('id', '=', int(rec_id)),
])
except Exception as ex:
_logger.error(ex)
return filtered_list
full_list = [rec._get_operation_tuple() for rec in stock_object.operations_to_pick]
[filtered_list.append(rec) for rec in full_list if rec not in filtered_list]
record_list = [self._read_record(rec) for rec in filtered_list]
return self._recheck_record_list(record_list)

View file

@ -0,0 +1,58 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import models, fields, api
class PickingWave(models.Model):
_name = 'stock.picking.batch'
_inherit = ['stock.picking.batch', 'stock.picking.mixin']
related_pack_operations = fields.Many2many(
'stock.move.line', relation='wave_pack_operations',
string='Operations',
compute='_compute_related_pack_operations', store=True)
operations_to_pick = fields.Many2many(
'stock.move.line', relation='wave_operations_to_pick',
string='Operations to Pick',
compute='_compute_operations_to_pick', store=False)
strategy_order_r = fields.Char(
string='Strategy Order',
compute='_compute_operations_to_pick',
store=False,
)
@api.depends(
'picking_ids',
'picking_ids.move_line_ids',
)
def _compute_related_pack_operations(self):
for rec in self:
res = self.env['stock.move.line']
for picking in rec.picking_ids:
for operation in picking.move_line_ids:
res += operation
rec.related_pack_operations = res
@api.depends(
'picking_ids',
'picking_ids.move_line_ids',
'picking_ids.move_line_ids.location_id',
'picking_ids.move_line_ids.qty_done',
)
def _compute_operations_to_pick(self):
strategy = self.env.user.company_id.outgoing_routing_strategy
strategy_order = self.env.user.company_id.outgoing_routing_order
for rec in self:
picking_ids = rec.picking_ids.ids
all_operations = self.env['stock.move.line'].search([
('picking_id', 'in', picking_ids),
])
rec.strategy_order_r = self.env['stock.picking'].get_strategy_string(strategy,
strategy_order)
rec.operations_to_pick = self.env['stock.picking'].sort_operations(all_operations,
strategy,
strategy_order)

View file

@ -0,0 +1,108 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo import api, models, fields
from odoo.tools.float_utils import float_compare
import functools
class StockQuant(models.Model):
_inherit = 'stock.quant'
removal_prio = fields.Integer(
related="location_id.removal_prio",
store=True,
)
@api.model
def _get_removal_strategy_order(self, removal_strategy):
# THIS IS A OVERRIDE STANDARD METHOD
strategy_order = self.env.user.company_id.outgoing_routing_order
if removal_strategy == 'location_priority':
return 'removal_prio %s, id' % (['ASC', 'DESC'][int(strategy_order)])
return super(StockQuant, self)._get_removal_strategy_order(removal_strategy)
@api.model
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
""" Updates reserved quantity in quants
"""
self = self.with_context(reservation_strategy=self.env.user.company_id.stock_reservation_strategy, reservation_quantity=quantity)
return super(StockQuant, self)._update_reserved_quantity(product_id, location_id, quantity, lot_id, package_id, owner_id, strict)
def _gather(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
""" Gather (and reorder, if required) quants
"""
context = dict(self.env.context)
quants = super(StockQuant, self)._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
if self._context.get('skip_ventor_reordering') or (product_id.categ_id and product_id.categ_id.removal_strategy_id)\
or location_id.removal_strategy_id:
return quants
strategy = self.env.user.company_id.stock_reservation_strategy
func_reorder = getattr(self, '_reorder_{}'.format(strategy))
if func_reorder:
quants = func_reorder(quants, product_id)
# mom taught us to clean after ourselves
if context.get('reservation_strategy'):
del context['reservation_strategy']
if context.get('reservation_quantity'):
del context['reservation_quantity']
self = self.with_context(context)
return quants
def _reorder_none(self, quants, product_id):
""" No reorder, i.e. use out of the box strategy
"""
return quants
def _reorder_base(self, quants, product_id):
""" Reorders quants by location removal priority
"""
def _r_getattr(obj, attr, *args):
return functools.reduce(getattr, [obj] + attr.split('.'))
route = self.env.user.company_id.outgoing_routing_strategy
order = self.env.user.company_id.outgoing_routing_order
return quants.sorted(
key=lambda op: _r_getattr(op, route, 'None'),
reverse=int(order)
)
def _reorder_quantity(self, quants, product_id):
""" Reorders quants by product quantity in locations and location priority
"""
default_route = 'name' # i.e. location_id.name
route = self.env.user.company_id.outgoing_routing_strategy
order = self.env.user.company_id.outgoing_routing_order
base, field = route.split('.', 1)
startegy = field if base in ('location_id') else default_route
required = self.env.context.get('reservation_quantity', 0)
rounding = product_id.uom_id.rounding
locations = {}
queues = [self.env['stock.quant'], self.env['stock.quant']] # (lprio, hprio)
for quant in quants:
locations.setdefault(quant.location_id, []).append(quant)
for location in sorted(locations,
key=lambda location: getattr(location, startegy, 'None'),
reverse=int(order)
):
location_quants = locations.get(location)
quantity = sum([qt.quantity - qt.reserved_quantity for qt in location_quants])
priority = float_compare(quantity, required, precision_rounding=rounding) >= 0
for location_quant in location_quants:
queues[priority] |= location_quant
return queues[True] + queues[False]

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View file

@ -0,0 +1,182 @@
<section class="oe_container">
<div class="row">
<div class="col-sm-12">
<h2 class="oe_slogan">Picking and Reservation Strategy</h2>
</div>
<div class="col-sm-12" style="text-align: center; margin-bottom: 5px">
<p style="padding: 5px; font-size: 16px; font-weight: bold; background-color: yellow; display: inline">
TO AVOID ANY ISSUES, PLEASE, USE ALWAYS LATEST VERSION FROM OUR GITHUB REPOSITORY -
<a href="https://github.com/ventor-tech/merp/tree/16.0">
https://github.com/ventor-tech/merp/tree/16.0
</a>
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="row">
<div class="col-sm-12" style="margin-bottom: 16px">
<h4 style="font-size: 24px; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans; text-align: center;">
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
<b>Description</b>
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
</h4>
<div style="padding:0.2em 0.5em; font-size: 120%;">
<p>The app allows creating optimal picking routes in your warehouse and process picking orders and batches in an optimal way. Additionally, you can change reservation priority and make reservations in the way you want, without following FIFO/LIFO methods. Pickers get the shortest pathway, save picking time, and reduce extra walking around the warehouse.</p>
</div>
</div>
<div class="oe_row_img oe_centered">
<div class="oe_demo" style="border: none">
<a href="images/image2.gif"><img class="img img-responsive" style="display: block; width: 90%; margin: 32px auto" src="images/image2.gif"></a>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="row">
<div class="col-sm-12" style="margin-bottom: 16px">
<h4 style="font-size: 24px; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans; text-align: center;">
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
<b>Key points</b>
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
</h4>
<div style="margin: 32px 0.5em 16px 0.5em; font-size: 120%;">
<ul style="margin-bottom: 0">
<li style="margin-bottom: 15px">All products inside outgoing shipments are reserved from locations according to the removal priority field. That means you define locations from where Odoo will reserve products for delivery orders (*for Odoo 12 and higher)</li>
<li style="margin-bottom: 15px">All products inside outgoing shipments are sorted according to the removal priority field. That means all product lines inside delivery orders are sorted by the optimal route to pick products from the start location to the destination</li>
<li style="margin-bottom: 15px">Works with both Odoo Community and Enterprise (self-hosted or odoo.sh)</li>
<li>Fully supportable by the Ventor app to guide pickers through an optimal route</li>
</ul>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="row">
<div class="col-sm-12" style="margin-bottom: 16px">
<h4 style="font-size: 24px; font-family: 'Lato', 'Open Sans', 'Helvetica', Sans; text-align: center;">
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
<b>Manage</b>
<span class="d-none d-sm-inline-block" style="width: 10%; margin: 32px 20px; border-top: solid 1px; vertical-align: middle; opacity: 0.3"></span>
</h4>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<div class="oe_row_img oe_centered">
<a href="images/image3.png">
<img class="oe_picture oe_screenshot" src="images/image3.png">
</a>
</div>
</div>
<div class="col-sm-4 pt-sm-4 pb-sm-4 pl-sm-1 pr-sm-5 pl-5 pr-5">
<p>
Sort pack operations in picking depending on selected strategy (see list below) and order (ascending or descending):
</p>
<ul>
<li>sort by source locations name</li>
<li>sort by location removal strategy priority</li>
<li>sort by product name</li>
</ul>
<p>
where removal strategy priority is an integer value that should be used to define order (priority) of warehouse locations.
</p>
<p>
Sorted list of pack operations could now be found on a new tab 'To Process' on picking form.
</p>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<div class="oe_row_img oe_centered">
<a href="images/image4.png">
<img class="oe_picture oe_screenshot" src="images/image4.png">
</a>
</div>
</div>
<div class="col-sm-4 pt-sm-4 pb-sm-4 pl-sm-1 pr-sm-5 pl-5 pr-5">
<p>
Sort quants to be reserved depending on selected rule (see list below) in addition to standard FIFO/LIFO/FEFO rules:
</p>
<ul>
<li>outgoing routing (see above): by location name and removal strategy priority</li>
<li>by quantity: first reverse quants in location that contain a sufficient amount of product (and higher priority according to outgoing routing settings)</li>
<li>or default (out-of-the-box) strategy</li>
</ul>
<p>
Thus, the module allows users to calculate the most effective route (based on your settings) to collect all ordered products from the entire warehouse to save your time.
</p>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<div class="oe_row_img oe_centered">
<a href="images/image5.png">
<img class="oe_picture oe_screenshot" src="images/image5.png">
</a>
</div>
</div>
<div class="col-sm-4 pt-sm-4 pb-sm-4 pl-sm-1 pr-sm-5 pl-5 pr-5">
<p>
Sorts list of pack operations in picking report in accordance with available strategies.
</p>
</div>
</div>
<div class="row">
<div class="col-sm-12" style="text-align: center; margin-top: 32px">
<p>
DOWNLOAD VENTOR APP - https://ventor.app
</p>
<a class="btn"
role="button"
href="https://www.youtube.com/channel/UCifgh537ZIoK-4VL_JKk2mA"
style="display: block-inline;
margin: 32px 10px 16px;
border-radius: 10px;
font-size: 19px;
font-family: 'Montserrat';
font-weight: 700;
color: #fff;
padding: 16px 45px;
background-color: RGB(213, 0, 6)">
VISIT OUR YOUTUBE CHANNEL
</a>
</div>
</div>
</section>
<section class="oe_container">
<div class="row">
<div class="col-sm-12">
<h2 class="oe_slogan">Notes</h2>
<div style="padding:0.2em 0.5em; font-size: 120%;">
<p>The <b>routing feature was designed to work only with the "Ventor" application</b> that can be downloaded from:</p>
<ol>
<li>
<a target="_blank" href="https://ventor.app/">https://ventor.app/</a> website - version obtained from here is suitable for all types of organizations
</li>
<li>
<a target="_blank" href="https://play.google.com/store/apps/details?id=com.xpansa.merp.warehouse">Google Play</a> - has limitations, not possible to customize app by request - https://play.google.com/store/apps/details?id=com.xpansa.merp.warehouse
</li>
</ol>
<p><b>Custom reservation works independently from the "Ventor" mobile app.</b> (*the feature available for Odoo 12 and higher)</p>
<p>"Ventor" works both on regular mobile phones with Android 4.1+ and with professional barcode scanners.
</p>
<p class="oe_mt32">
<a target="_blank" href="https://ventor.tech/">VentorTech (https://ventor.tech/)</a> is company specialized on building Personalized Inventory and Product Management System.
</p>
<p class="oe_mt32">
For all questions contact <a target="_blank" href="mailto:hello@ventor.tech">hello@ventor.tech</a>.
</p>
</div>
<div class="oe_picture">
<a href="https://ventor.tech/">
<img class="img img-responsive" style="display: block; width: 50%; margin: auto" src="images/image6.png">
</a>
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,9 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from . import test_merp_outgoing_routing
from . import test_merp_picking_wave_base
from . import test_merp_quants_location_routing
from . import test_stock_reservation_by_name
from . import test_stock_reservation_by_priority
from . import test_stock_reservation_by_quantity

View file

@ -0,0 +1,150 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo.tests.common import TransactionCase
from datetime import datetime
class TestMerpOutgoingRouting(TransactionCase):
def setUp(self):
super(TestMerpOutgoingRouting, self).setUp()
self.location_1 = self.env['stock.location'].create({
'name': 'test_location_1',
'removal_prio': 2
})
self.location_2 = self.env['stock.location'].create({
'name': 'test_location_2',
'removal_prio': 3
})
self.location_3 = self.env['stock.location'].create({
'name': 'test_location_3',
'removal_prio': 1
})
self.location_4 = self.env['stock.location'].create({
'name': 'test_location_4',
'removal_prio': 4
})
company = self.env.user.company_id
picking_type = self.env['stock.picking.type'].search([], limit=1)
self.stock_picking = self.env['stock.picking'].create({
'name': 'test_stock_picking',
'location_id': self.location_1.id,
'location_dest_id': self.location_2.id,
'move_type': 'one',
'company_id': company.id,
'picking_type_id': picking_type.id
})
products = self.env['product.product']
for suf in range(1, 5):
products += self.create_product(suf)
self.move_line_1 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 1.0,
'location_id': self.location_1.id,
'date': datetime.now(),
'location_dest_id': self.location_2.id,
'reserved_uom_qty': 20.0,
'product_uom_id': products[1].uom_id.id,
'product_id': products[1].id
})
self.move_line_2 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 2.0,
'location_id': self.location_2.id,
'date': datetime.now(),
'location_dest_id': self.location_3.id,
'reserved_uom_qty': 25.0,
'product_uom_id': products[0].uom_id.id,
'product_id': products[0].id
})
self.move_line_3 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 3.0,
'location_id': self.location_3.id,
'date': datetime.now(),
'location_dest_id': self.location_2.id,
'reserved_uom_qty': 15.0,
'product_uom_id': products[2].uom_id.id,
'product_id': products[2].id
})
self.move_line_4 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 10.0,
'location_id': self.location_4.id,
'date': datetime.now(),
'location_dest_id': self.location_2.id,
'reserved_uom_qty': 10.0,
'product_uom_id': products[3].uom_id.id,
'product_id': products[3].id
})
def create_product(self, suf):
name = 'test_product_{}'.format(suf)
product_data = {
'name': name,
}
return self.env['product.product'].create(product_data)
def test_sort_alphabet_a_z(self):
self.set_way_outgoing_routing('location_id.name', '0')
locations_name = self.stock_picking.mapped('operations_to_pick.location_id.name')
self.assertEqual(locations_name[0], 'test_location_1')
self.assertEqual(locations_name[1], 'test_location_2')
self.assertEqual(locations_name[2], 'test_location_3')
def test_sort_alphabet_z_a(self):
self.set_way_outgoing_routing('location_id.name', '1')
locations_name = self.stock_picking.mapped('operations_to_pick.location_id.name')
self.assertEqual(locations_name[0], 'test_location_3')
self.assertEqual(locations_name[1], 'test_location_2')
self.assertEqual(locations_name[2], 'test_location_1')
def test_sort_removal_priority_a_z(self):
self.set_way_outgoing_routing('location_id.removal_prio', '0')
locations_removal_prio = self.stock_picking\
.mapped('operations_to_pick.location_id.removal_prio')
self.assertEqual(locations_removal_prio[0], 1)
self.assertEqual(locations_removal_prio[1], 2)
self.assertEqual(locations_removal_prio[2], 3)
def test_sort_removal_priority_z_a(self):
self.set_way_outgoing_routing('location_id.removal_prio', '1')
locations_removal_prio = self.stock_picking\
.mapped('operations_to_pick.location_id.removal_prio')
self.assertEqual(locations_removal_prio[0], 3)
self.assertEqual(locations_removal_prio[1], 2)
self.assertEqual(locations_removal_prio[2], 1)
def test_sort_by_products_name_a_z(self):
self.set_way_outgoing_routing('product_id.name', '0')
products_name = self.stock_picking \
.mapped('operations_to_pick.product_id.name')
self.assertEqual(products_name[0], 'test_product_1')
self.assertEqual(products_name[1], 'test_product_2')
self.assertEqual(products_name[2], 'test_product_3')
def test_sort_by_products_name_z_a(self):
self.set_way_outgoing_routing('product_id.name', '1')
products_name = self.stock_picking \
.mapped('operations_to_pick.product_id.name')
self.assertEqual(products_name[0], 'test_product_3')
self.assertEqual(products_name[1], 'test_product_2')
self.assertEqual(products_name[2], 'test_product_1')
def set_way_outgoing_routing(self, outgoing_routing_strategy, outgoing_routing_order):
config = self.env['res.config.settings'].create({
'outgoing_routing_strategy': outgoing_routing_strategy,
'outgoing_routing_order': outgoing_routing_order
})
config.execute()

View file

@ -0,0 +1,170 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from odoo.tests.common import TransactionCase
from datetime import datetime
class TestMerpPickingWaveBase(TransactionCase):
def setUp(self):
super(TestMerpPickingWaveBase, self).setUp()
self.location_1 = self.env['stock.location'].create({
'name': 'test_location_1',
'removal_prio': 2
})
self.location_2 = self.env['stock.location'].create({
'name': 'test_location_2',
'removal_prio': 3
})
self.location_3 = self.env['stock.location'].create({
'name': 'test_location_3',
'removal_prio': 1
})
self.location_4 = self.env['stock.location'].create({
'name': 'test_location_4',
'removal_prio': 4
})
company = self.env.user.company_id
picking_type = self.env['stock.picking.type'].search([], limit=1)
self.stock_picking = self.env['stock.picking'].create({
'name': 'test_stock_picking',
'location_id': self.location_1.id,
'location_dest_id': self.location_2.id,
'move_type': 'one',
'company_id': company.id,
'picking_type_id': picking_type.id
})
self.picking_batch = self.env['stock.picking.batch'].create({
'name': 'test_stock_picking_batch',
'picking_ids': [(4, self.stock_picking.id, 0)]
})
products = self.env['product.product']
for suf in range(1, 5):
products += self.create_product(suf)
self.move_line_1 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 1.0,
'location_id': self.location_1.id,
'date': datetime.now(),
'location_dest_id': self.location_2.id,
'reserved_uom_qty': 20.0,
'product_uom_id': products[1].uom_id.id,
'product_id': products[1].id
})
self.move_line_2 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 2.0,
'location_id': self.location_2.id,
'date': datetime.now(),
'location_dest_id': self.location_3.id,
'reserved_uom_qty': 25.0,
'product_uom_id': products[1].uom_id.id,
'product_id': products[0].id
})
self.move_line_3 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 3.0,
'location_id': self.location_3.id,
'date': datetime.now(),
'location_dest_id': self.location_1.id,
'reserved_uom_qty': 15.0,
'product_uom_id': products[2].uom_id.id,
'product_id': products[2].id
})
self.move_line_4 = self.env['stock.move.line'].create({
'picking_id': self.stock_picking.id,
'qty_done': 10.0,
'location_id': self.location_4.id,
'date': datetime.now(),
'location_dest_id': self.location_2.id,
'reserved_uom_qty': 10.0,
'product_uom_id': products[3].uom_id.id,
'product_id': products[3].id
})
def create_product(self, suf):
name = 'test_product_{}'.format(suf)
product_data = {
'name': name,
}
return self.env['product.product'].create(product_data)
def test_related_pack_operations(self):
self.assertEqual(len(self.picking_batch.related_pack_operations), 4)
def test_operations_to_pick(self):
self.assertEqual(len(self.picking_batch.operations_to_pick), 3)
def test_sort_alphabet_a_z(self):
outgoing_routing_strategy = 'location_id.name'
outgoing_routing_order = '0'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
locations_name = self.picking_batch.mapped('operations_to_pick.location_id.name')
self.assertEqual(locations_name[0], 'test_location_1')
self.assertEqual(locations_name[1], 'test_location_2')
self.assertEqual(locations_name[2], 'test_location_3')
def test_sort_alphabet_z_a(self):
outgoing_routing_strategy = 'location_id.name'
outgoing_routing_order = '1'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
locations_name = self.picking_batch.mapped('operations_to_pick.location_id.name')
self.assertEqual(locations_name[0], 'test_location_3')
self.assertEqual(locations_name[1], 'test_location_2')
self.assertEqual(locations_name[2], 'test_location_1')
def test_sort_removal_priority_a_z(self):
outgoing_routing_strategy = 'location_id.removal_prio'
outgoing_routing_order = '0'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
locations_removal_prio = self.picking_batch \
.mapped('operations_to_pick.location_id.removal_prio')
self.assertEqual(locations_removal_prio[0], 1)
self.assertEqual(locations_removal_prio[1], 2)
self.assertEqual(locations_removal_prio[2], 3)
def test_sort_removal_priority_z_a(self):
outgoing_routing_strategy = 'location_id.removal_prio'
outgoing_routing_order = '1'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
locations_removal_prio = self.picking_batch \
.mapped('operations_to_pick.location_id.removal_prio')
self.assertEqual(locations_removal_prio[0], 3)
self.assertEqual(locations_removal_prio[1], 2)
self.assertEqual(locations_removal_prio[2], 1)
def test_sort_by_products_name_a_z(self):
outgoing_routing_strategy = 'product_id.name'
outgoing_routing_order = '0'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
products_name = self.picking_batch \
.mapped('operations_to_pick.product_id.name')
self.assertEqual(products_name[0], 'test_product_1')
self.assertEqual(products_name[1], 'test_product_2')
self.assertEqual(products_name[2], 'test_product_3')
def test_sort_by_products_name_z_a(self):
outgoing_routing_strategy = 'product_id.name'
outgoing_routing_order = '1'
self.set_way_outgoing_routing(outgoing_routing_strategy, outgoing_routing_order)
products_name = self.picking_batch \
.mapped('operations_to_pick.product_id.name')
self.assertEqual(products_name[0], 'test_product_3')
self.assertEqual(products_name[1], 'test_product_2')
self.assertEqual(products_name[2], 'test_product_1')
def set_way_outgoing_routing(self, outgoing_routing_strategy, outgoing_routing_order):
config = self.env['res.config.settings'].create({
'outgoing_routing_strategy': outgoing_routing_strategy,
'outgoing_routing_order': outgoing_routing_order
})
config.execute()

View file

@ -0,0 +1,162 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from datetime import date
from odoo.tests.common import TransactionCase
class TestMerpQuantsLocationRouting(TransactionCase):
def setUp(self):
super(TestMerpQuantsLocationRouting, self).setUp()
self.res_users_model = self.env['res.users']
self.stock_location_model = self.env['stock.location']
self.stock_warehouse_model = self.env['stock.warehouse']
self.stock_picking_model = self.env['stock.picking']
self.stock_change_model = self.env['stock.change.product.qty']
self.product_model = self.env['product.product']
self.quant_model = self.env['stock.quant']
self.picking_internal = self.env.ref('stock.picking_type_internal')
self.picking_out = self.env.ref('stock.picking_type_out')
self.location_supplier = self.env.ref('stock.stock_location_suppliers')
self.company = self.env.ref('base.main_company')
self.wh1 = self.stock_warehouse_model.create({
'name': 'WH2',
'code': 'WH2',
})
# Removal strategies:
self.fifo = self.env.ref('stock.removal_fifo')
self.lifo = self.env.ref('stock.removal_lifo')
self.removal_location_priority = self.env.ref(
'outgoing_routing.removal_location_priority'
)
# Create locations:
self.stock = self.stock_location_model.create({
'name': 'Default Base',
'usage': 'internal',
})
self.location_A = self.stock_location_model.create({
'name': 'location_A',
'usage': 'internal',
'location_id': self.stock.id,
'removal_prio': 1,
})
self.location_B = self.stock_location_model.create({
'name': 'Location_B',
'usage': 'internal',
'location_id': self.stock.id,
'removal_prio': 0,
})
self.stock_2 = self.stock_location_model.create({
'name': 'Another Location',
'usage': 'internal',
})
# Create a product
self.product_1 = self.product_model.create({
'name': 'Product 1',
'type': 'product',
})
# Create quants
today = date.today()
quant_1 = self.quant_model.create({
'product_id': self.product_1.id,
'location_id': self.location_A.id,
'quantity': 10.0,
'in_date': today,
})
quant_2 = self.quant_model.create({
'product_id': self.product_1.id,
'location_id': self.location_B.id,
'quantity': 5.0,
'in_date': today,
})
self.quants = quant_1 + quant_2
def _create_picking(self, picking_type, location, location_dest, qty):
move_line_values = {
'name': 'Default Test Move',
'product_id': self.product_1.id,
'product_uom': self.product_1.uom_id.id,
'product_uom_qty': qty,
'location_id': location.id,
'location_dest_id': location_dest.id,
'price_unit': 2,
}
picking = self.stock_picking_model.create({
'picking_type_id': picking_type.id,
'location_id': location.id,
'location_dest_id': location_dest.id,
'move_ids': [(0, 0, move_line_values)],
})
return picking
def test_stock_removal_location_by_removal_location_priority(self):
"""Tests removal priority with Location Priority strategy."""
self.stock.removal_strategy_id = self.removal_location_priority
# Quants must start unreserved
for quant in self.quants:
self.assertEqual(
quant.reserved_quantity,
0.0,
'Quant must not have reserved qty right now.'
)
if quant.location_id == self.location_A:
self.assertEqual(
quant.removal_prio,
1,
'Removal Priority Location must be 1'
)
if quant.location_id == self.location_B:
self.assertEqual(
quant.removal_prio,
0,
'Removal Priority Location must be 0'
)
self.assertEqual(
self.quants[0].in_date,
self.quants[1].in_date,
'Dates must be Equal'
)
picking_1 = self._create_picking(
self.picking_internal,
self.stock,
self.stock_2,
5,
)
# picking_1.flush()
picking_1.action_confirm()
picking_1.action_assign()
# Quants must be reserved in Location B (lower removal_priority value).
for quant in self.quants:
if quant.location_id == self.location_A:
self.assertEqual(
quant.reserved_quantity,
0.0,
'This quant must not have reserved qty.'
)
if quant.location_id == self.location_B:
self.assertEqual(
quant.reserved_quantity,
5.0,
'This quant must have 5 reserved qty.'
)

View file

@ -0,0 +1,110 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from datetime import date
from odoo.tests.common import TransactionCase
class TestStockRouting(TransactionCase):
def setUp(self):
super(TestStockRouting, self).setUp()
self.base_company = self.env.ref('base.main_company')
self.product_product_model = self.env['product.product']
self.res_users_model = self.env['res.users']
self.stock_location_model = self.env['stock.location']
self.stock_move_model = self.env['stock.move']
self.stock_picking_model = self.env['stock.picking']
self.stock_quant_model = self.env['stock.quant']
self.picking_internal = self.env.ref('stock.picking_type_internal')
today = date.today()
self.env['res.config.settings'].create({
'outgoing_routing_strategy': 'location_id.name',
'outgoing_routing_order': '0',
'stock_reservation_strategy': 'none',
}).execute()
self.stock_A = self.stock_location_model.create({
'name': 'A',
'usage': 'internal',
})
self.stock_A1 = self.stock_location_model.create({
'name': 'A-1',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 2,
})
self.stock_A2 = self.stock_location_model.create({
'name': 'A-2',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 3,
})
self.stock_A3 = self.stock_location_model.create({
'name': 'A-3',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 1,
})
self.stock_B = self.stock_location_model.create({
'name': 'B',
'usage': 'internal',
})
self.product_Z = self.product_product_model.create({
'name': 'Product',
'type': 'product',
})
quant_1 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A1.id, # prio:2
'quantity': 15.0,
'in_date': today,
})
quant_2 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A2.id, # prio:3
'quantity': 5.0,
'in_date': today,
})
quant_3 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A3.id, # prio:1
'quantity': 10.0,
'in_date': today,
})
self.quants = quant_1 + quant_2 + quant_3
def test_stock_reservation_by_name_case1(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 10)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-1')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-3')
def test_stock_reservation_by_name_case2(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 12)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 12.0, '12 products should be reserved in A-1')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-3')
def test_stock_reservation_by_name_case3(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 22)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 15.0, '15 products should be reserved in A-1')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 5.0, '5 products should be reserved in A-2')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 2.0, '2 products should be reserved in A-3')

View file

@ -0,0 +1,110 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from datetime import date
from odoo.tests.common import TransactionCase
class TestStockRouting(TransactionCase):
def setUp(self):
super(TestStockRouting, self).setUp()
self.base_company = self.env.ref('base.main_company')
self.product_product_model = self.env['product.product']
self.res_users_model = self.env['res.users']
self.stock_location_model = self.env['stock.location']
self.stock_move_model = self.env['stock.move']
self.stock_picking_model = self.env['stock.picking']
self.stock_quant_model = self.env['stock.quant']
self.picking_internal = self.env.ref('stock.picking_type_internal')
today = date.today()
self.env['res.config.settings'].create({
'outgoing_routing_strategy': 'location_id.removal_prio',
'outgoing_routing_order': '0',
'stock_reservation_strategy': 'base',
}).execute()
self.stock_A = self.stock_location_model.create({
'name': 'A',
'usage': 'internal',
})
self.stock_A1 = self.stock_location_model.create({
'name': 'A-1',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 2,
})
self.stock_A2 = self.stock_location_model.create({
'name': 'A-2',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 3,
})
self.stock_A3 = self.stock_location_model.create({
'name': 'A-3',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 1,
})
self.stock_B = self.stock_location_model.create({
'name': 'B',
'usage': 'internal',
})
self.product_Z = self.product_product_model.create({
'name': 'Product',
'type': 'product',
})
quant_1 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A1.id, # prio:2
'quantity': 15.0,
'in_date': today,
})
quant_2 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A2.id, # prio:3
'quantity': 5.0,
'in_date': today,
})
quant_3 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A3.id, # prio:1
'quantity': 10.0,
'in_date': today,
})
self.quants = quant_1 + quant_2 + quant_3
def test_stock_reservation_by_priority_case1(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 10)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-3 (prio:1)')
def test_stock_reservation_by_priority_case2(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 12)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 2.0, '2 products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-3 (prio:1)')
def test_stock_reservation_by_priority_case3(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 22)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 12.0, '12 products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-3 (prio:1)')

View file

@ -0,0 +1,110 @@
# Copyright 2020 VentorTech OU
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from datetime import date
from odoo.tests.common import TransactionCase
class TestStockRouting(TransactionCase):
def setUp(self):
super(TestStockRouting, self).setUp()
self.base_company = self.env.ref('base.main_company')
self.product_product_model = self.env['product.product']
self.res_users_model = self.env['res.users']
self.stock_location_model = self.env['stock.location']
self.stock_move_model = self.env['stock.move']
self.stock_picking_model = self.env['stock.picking']
self.stock_quant_model = self.env['stock.quant']
self.picking_internal = self.env.ref('stock.picking_type_internal')
today = date.today()
self.env['res.config.settings'].create({
'outgoing_routing_strategy': 'location_id.removal_prio',
'outgoing_routing_order': '0',
'stock_reservation_strategy': 'quantity',
}).execute()
self.stock_A = self.stock_location_model.create({
'name': 'A',
'usage': 'internal',
})
self.stock_A1 = self.stock_location_model.create({
'name': 'A-1',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 2,
})
self.stock_A2 = self.stock_location_model.create({
'name': 'A-2',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 3,
})
self.stock_A3 = self.stock_location_model.create({
'name': 'A-3',
'usage': 'internal',
'location_id': self.stock_A.id,
'removal_prio': 1,
})
self.stock_B = self.stock_location_model.create({
'name': 'B',
'usage': 'internal',
})
self.product_Z = self.product_product_model.create({
'name': 'Product',
'type': 'product',
})
quant_1 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A1.id, # prio:2
'quantity': 15.0,
'in_date': today,
})
quant_2 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A2.id, # prio:3
'quantity': 5.0,
'in_date': today,
})
quant_3 = self.stock_quant_model.create({
'product_id': self.product_Z.id,
'location_id': self.stock_A3.id, # prio:1
'quantity': 10.0,
'in_date': today,
})
self.quants = quant_1 + quant_2 + quant_3
def test_stock_reservation_by_quantity_case1(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 10)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-3 (prio:1)')
def test_stock_reservation_by_quantity_case2(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 12)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 12.0, 'No products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, '12 products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-3 (prio:1)')
def test_stock_reservation_by_quantity_case3(self):
quants = self.stock_quant_model._update_reserved_quantity(self.product_Z, self.stock_A, 22)
for quant, quantity in quants:
if quant.location_id == self.stock_A1: self.assertEqual(quant.reserved_quantity, 12.0, '12 products should be reserved in A-1 (prio:2)')
if quant.location_id == self.stock_A2: self.assertEqual(quant.reserved_quantity, 0.0, 'No products should be reserved in A-2 (prio:3)')
if quant.location_id == self.stock_A3: self.assertEqual(quant.reserved_quantity, 10.0, '10 products should be reserved in A-3 (prio:1)')

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="name">stock.picking.form.inherit</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='move_line_ids_without_package']/.." position="after">
<page string="To Process" name="operations_to_pick" groups="ventor_base.merp_debug">
<group>
<field name="routing_module_version" readonly="1"/>
</group>
<field name="strategy_order_r" readonly="1"/>
<field name="operations_to_pick" readonly="1">
<tree>
<field name="picking_id"/>
<field name="product_id"/>
<field name="product_uom_id" groups="uom.group_uom"/>
<field name="owner_id" groups="stock.group_tracking_owner"/>
<field name="location_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="reserved_qty"/>
<field name="qty_done"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_picking_sort_move_line_ids" inherit_id="stock.report_picking">
<xpath expr="//t[contains(@t-as, 'ml')]" position="attributes">
<attribute name="t-foreach">o.sort_printer_picking_list(o.move_ids_without_package.mapped('move_line_ids'))</attribute>
</xpath>
<xpath expr="//tr[contains(@t-as, 'package')]" position="attributes">
<attribute name="t-foreach">o.sort_printer_picking_list(o.package_level_ids)</attribute>
</xpath>
</template>
</odoo>

View file

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_stock_config_settings" model="ir.ui.view">
<field name="name">Ventor/mERP Picking Wave - Stock Settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@data-key='ventor_base']" position="inside" >
<h2>Picking Strategy</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-4 o_setting_box">
<div class="" attrs="{'invisible': [('module_outgoing_routing','=',False)]}">
Please, select parameters to calculate route through warehouse(s):
</div>
<div class="text-muted" attrs="{'invisible': [('module_outgoing_routing','=',False)]}">
<br/>Your current settings:
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'location_id.name'), ('outgoing_routing_order', '=', '0')]}">
- the route is calculated from <b>location</b> '<b>A</b>' to '<b>Z</b>'
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'location_id.name'), ('outgoing_routing_order', '=', '1')]}">
- the route is calculated from <b>location</b> '<b>Z</b>' to '<b>A</b>'
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'location_id.removal_prio'), ('outgoing_routing_order', '=', '0')]}">
- the route is calculated from location with <b>removal priority</b> '<b>0</b>' to '<b></b>'
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'location_id.removal_prio'), ('outgoing_routing_order', '=', '1')]}">
- the route is calculated from location with <b>removal priority</b> '<b></b>' to '<b>0</b>'
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'product_id.name'), ('outgoing_routing_order', '=', '0')]}">
- the route is calculated from location contains <b>product</b> '<b>A</b>' to '<b>Z</b>'
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', '&amp;', ('outgoing_routing_strategy', '=', 'product_id.name'), ('outgoing_routing_order', '=', '1')]}">
- the route is calculated from location contains <b>product</b> '<b>Z</b>' to '<b>A</b>'
</div>
</div>
<div class="col-xs-12 col-md-4 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="outgoing_routing_strategy"/>
<div class="text-muted"></div>
<field name="outgoing_routing_strategy"
class="o_light_label"
widget="radio"
attrs="{'invisible': [('module_outgoing_routing','=',False)]}"/>
</div>
</div>
<div class="col-xs-12 col-md-4 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="outgoing_routing_order"/>
<div class="text-muted"></div>
<field name="outgoing_routing_order"
class="o_light_label"
widget="radio"
attrs="{'invisible': [('module_outgoing_routing','=',False)]}"/>
</div>
</div>
</div>
<div class="row mt16 o_settings_container">
</div>
<h2>Reservation Strategy</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-4 o_setting_box">
<div class="" attrs="{'invisible': [('module_outgoing_routing','=',False)]}">
Please, select parameters to reorder quants during reservation:
</div>
<div class="text-muted" attrs="{'invisible': [('module_outgoing_routing','=',False)]}">
<br/>Your current settings:
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', ('stock_reservation_strategy', '=', 'none')]}">
- quants are reserved in Odoo standard way (FIFO/LIFO)
</div>
<div class="text-muted" attrs="{'invisible': ['|', ('module_outgoing_routing','=',False),
'!', ('stock_reservation_strategy', '=', 'base')]}">
- quants are reserved according to Picking strategy (see above)
</div>
<div class="text-muted" attrs="{'invisible': ['|', '|', ('module_outgoing_routing','=',False), ('outgoing_routing_strategy', '=', 'location_id.removal_prio'),
'!', '&amp;', ('stock_reservation_strategy', '=', 'quantity'), ('outgoing_routing_order', '=', '0')]}">
- quants are reserved first in locations that contain a sufficient amount of product and have higher priority (i.e. from location name 'A' and beyond)
</div>
<div class="text-muted" attrs="{'invisible': ['|', '|', ('module_outgoing_routing','=',False), ('outgoing_routing_strategy', '=', 'location_id.removal_prio'),
'!', '&amp;', ('stock_reservation_strategy', '=', 'quantity'), ('outgoing_routing_order', '=', '1')]}">
- quants are reserved first in locations that contain a sufficient amount of product and have higher priority (i.e. from location name 'Z' to 'A')
</div>
<div class="text-muted" attrs="{'invisible': ['|', '|', ('module_outgoing_routing','=',False), ('outgoing_routing_strategy', '!=', 'location_id.removal_prio'),
'!', '&amp;', ('stock_reservation_strategy', '=', 'quantity'), ('outgoing_routing_order', '=', '0')]}">
- quants are reserved first in locations that contain a sufficient amount of product and have higher priority (i.e. from location with removal priority '0' to '∞')
</div>
<div class="text-muted" attrs="{'invisible': ['|', '|', ('module_outgoing_routing','=',False), ('outgoing_routing_strategy', '!=', 'location_id.removal_prio'),
'!', '&amp;', ('stock_reservation_strategy', '=', 'quantity'), ('outgoing_routing_order', '=', '1')]}">
- quants are reserved first in locations that contain a sufficient amount of product and have higher priority (i.e. from location with removal priority '∞' to '0')
</div>
</div>
<div class="col-xs-12 col-md-8 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="stock_reservation_strategy"/>
<div class="text-muted"></div>
<field name="stock_reservation_strategy"
class="o_light_label"
widget="radio"
attrs="{'invisible': [('module_outgoing_routing','=',False)]}"/>
</div>
</div>
<div class="col-xs-12 col-md-4 o_setting_box">
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_location_tree2" model="ir.ui.view">
<field name="name">stock.location.tree.removal.strategy</field>
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_tree2" />
<field name="arch" type="xml">
<xpath expr="//field[@name='company_id']" position="after">
<field name="removal_prio" />
</xpath>
</field>
</record>
<record id="view_location_form" model="ir.ui.view">
<field name="name">stock.location.form.removal.strategy</field>
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_form" />
<field name="arch" type="xml">
<xpath expr="//group" position="inside">
<group name="merp">
<field name="removal_prio" />
</group>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_picking_wave_form" model="ir.ui.view">
<field name="name">stock.picking.wave.form.inherit</field>
<field name="model">stock.picking.batch</field>
<field name="inherit_id" ref="stock_picking_batch.stock_picking_batch_form"/>
<field name="arch" type="xml">
<xpath expr="/form/sheet/notebook/page[last()]" position="after">
<page string="Operations" name="operations">
<field name="related_pack_operations" readonly="1">
<tree>
<field name="picking_id"/>
<field name="product_id"/>
<field name="product_uom_id" groups="uom.group_uom"/>
<field name="owner_id" groups="stock.group_tracking_owner"/>
<field name="location_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="reserved_qty"/>
<field name="qty_done"/>
</tree>
</field>
</page>
<page string="To Process" name="operations_to_pick" groups="ventor_base.merp_debug">
<field name="strategy_order_r" readonly="1" />
<field name="operations_to_pick" readonly="1">
<tree>
<field name="picking_id"/>
<field name="product_id"/>
<field name="product_uom_id" groups="uom.group_uom"/>
<field name="owner_id" groups="stock.group_tracking_owner"/>
<field name="location_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="reserved_qty"/>
<field name="qty_done"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,44 @@
[project]
name = "odoo-bringout-ventor-outgoing_routing"
version = "16.0.0"
description = "Picking and Reservation Strategy - Allows to automatically build optimal picking routes and apply custom reservation options"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-ventor-sale_management>=16.0.0",
"odoo-bringout-ventor-stock_picking_batch>=16.0.0",
"odoo-bringout-ventor-ventor_base>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["outgoing_routing"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,49 @@
# Product Multiple Barcodes
Custom Odoo addon: product_multiple_barcodes
## Installation
```bash
pip install odoo-bringout-ventor-product_multiple_barcodes
```
## Dependencies
This addon depends on:
- product
- sale
- purchase
- stock
## Manifest Information
- **Name**: Product Multiple Barcodes
- **Version**: 16.0.1.0.0
- **Category**: N/A
- **License**: LGPL-3
- **Installable**: True
## Source
Custom addon from bringout-ventor vendor, addon `product_multiple_barcodes`.
## License
This package maintains the original LGPL-3 license from the addon.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Product_multiple_barcodes Module - product_multiple_barcodes
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for product_multiple_barcodes. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,8 @@
# Dependencies
This addon depends on:
- [product](../../odoo-bringout-oca-ocb-product)
- [sale](../../odoo-bringout-oca-ocb-sale)
- [purchase](../../odoo-bringout-oca-ocb-purchase)
- [stock](../../odoo-bringout-oca-ocb-stock)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon product_multiple_barcodes or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-ventor-product_multiple_barcodes"
# or
uv pip install odoo-bringout-ventor-product_multiple_barcodes"
```

View file

@ -0,0 +1,14 @@
# Models
Detected core models and extensions in product_multiple_barcodes.
```mermaid
classDiagram
class product_barcode_multi
class product_product
class product_template
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: product_multiple_barcodes. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon product_multiple_barcodes
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,34 @@
# Security
Access control and security definitions in product_multiple_barcodes.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../product_multiple_barcodes/security/ir.model.access.csv)**
- 5 model access rules
## Record Rules
Row-level security rules defined in:
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../product_multiple_barcodes/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

Some files were not shown because too many files have changed in this diff Show more