Initial commit: Ventor Odoo packages (4 packages)
54
README.md
Normal 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.
|
||||
46
odoo-bringout-ventor-custom_import_wizard/README.md
Normal 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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Custom Import Wizard
|
||||
====================
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Custom Import Wizard
|
||||
====================
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 ""
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for custom_import_wizard. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Dependencies
|
||||
|
||||
No explicit module dependencies declared.
|
||||
4
odoo-bringout-ventor-custom_import_wizard/doc/FAQ.md
Normal 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.
|
||||
7
odoo-bringout-ventor-custom_import_wizard/doc/INSTALL.md
Normal 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"
|
||||
```
|
||||
12
odoo-bringout-ventor-custom_import_wizard/doc/MODELS.md
Normal 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.
|
||||
|
|
@ -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
|
||||
3
odoo-bringout-ventor-custom_import_wizard/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
42
odoo-bringout-ventor-custom_import_wizard/doc/SECURITY.md
Normal 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
|
||||
|
|
@ -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.
|
||||
7
odoo-bringout-ventor-custom_import_wizard/doc/USAGE.md
Normal 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
|
||||
```
|
||||
9
odoo-bringout-ventor-custom_import_wizard/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Wizards
|
||||
|
||||
Transient models exposed as UI wizards in custom_import_wizard.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class CustomImportWizard
|
||||
class InfoResultImportWizard
|
||||
```
|
||||
41
odoo-bringout-ventor-custom_import_wizard/pyproject.toml
Normal 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",
|
||||
]
|
||||
48
odoo-bringout-ventor-outgoing_routing/README.md
Normal 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
|
||||
32
odoo-bringout-ventor-outgoing_routing/doc/ARCHITECTURE.md
Normal 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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for outgoing_routing. Configure related models, access rights, and options as needed.
|
||||
3
odoo-bringout-ventor-outgoing_routing/doc/CONTROLLERS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -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)
|
||||
4
odoo-bringout-ventor-outgoing_routing/doc/FAQ.md
Normal 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.
|
||||
7
odoo-bringout-ventor-outgoing_routing/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-ventor-outgoing_routing"
|
||||
# or
|
||||
uv pip install odoo-bringout-ventor-outgoing_routing"
|
||||
```
|
||||
19
odoo-bringout-ventor-outgoing_routing/doc/MODELS.md
Normal 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.
|
||||
6
odoo-bringout-ventor-outgoing_routing/doc/OVERVIEW.md
Normal 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
|
||||
3
odoo-bringout-ventor-outgoing_routing/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
8
odoo-bringout-ventor-outgoing_routing/doc/SECURITY.md
Normal 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
|
||||
|
|
@ -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.
|
||||
7
odoo-bringout-ventor-outgoing_routing/doc/USAGE.md
Normal 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
|
||||
```
|
||||
3
odoo-bringout-ventor-outgoing_routing/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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',
|
||||
],
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 ""
|
||||
|
|
@ -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;
|
||||
"""
|
||||
)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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)),
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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]
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 536 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.'
|
||||
)
|
||||
|
|
@ -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')
|
||||
|
|
@ -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)')
|
||||
|
|
@ -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)')
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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),
|
||||
'!', '&', ('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),
|
||||
'!', '&', ('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),
|
||||
'!', '&', ('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),
|
||||
'!', '&', ('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),
|
||||
'!', '&', ('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),
|
||||
'!', '&', ('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'),
|
||||
'!', '&', ('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'),
|
||||
'!', '&', ('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'),
|
||||
'!', '&', ('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'),
|
||||
'!', '&', ('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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
44
odoo-bringout-ventor-outgoing_routing/pyproject.toml
Normal 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",
|
||||
]
|
||||
49
odoo-bringout-ventor-product_multiple_barcodes/README.md
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for product_multiple_barcodes. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-ventor-product_multiple_barcodes"
|
||||
# or
|
||||
uv pip install odoo-bringout-ventor-product_multiple_barcodes"
|
||||
```
|
||||
14
odoo-bringout-ventor-product_multiple_barcodes/doc/MODELS.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||