diff --git a/mrp_sale_grouped/README.rst b/mrp_sale_grouped/README.rst new file mode 100644 index 00000000..c12f358e --- /dev/null +++ b/mrp_sale_grouped/README.rst @@ -0,0 +1,81 @@ +================================ +MRP Grouped Sales and Production +================================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f582984ea7e25a995cea5546f7f67bbcf2c872727bd2815e023791cc2cb5e411 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-grap%2Fgrap--odoo--custom-lightgray.png?logo=github + :target: https://github.com/grap/grap-odoo-custom/tree/12.0/mrp_sale_grouped + :alt: grap/grap-odoo-custom + +|badge1| |badge2| |badge3| + +Quickly manage what you need to produce thanks to grouped sales + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-custom/12.0/mrp_sale_grouped/static/description/mrp_sale_grouped.png + +1 : Create and name your sale grouped sales +2 : Associate sales +3 : Retrieve sales and production order created thanks to this sales +4 : Adjust quantities with Matrix2D view +5 and 6 : Confirme sale orders one by one or all at once +7 : Print PDF that sum up sales by product and sale +8 : Handle your production with wizard assistant, you can handle what components +you have to buy, intermediate and finished products you have to produce. + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-custom/12.0/mrp_sale_grouped/static/description/report_sales_sum_up.png + +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-custom/12.0/mrp_sale_grouped/static/description/production_wizard.png +.. figure:: https://raw.githubusercontent.com/grap/grap-odoo-custom/12.0/mrp_sale_grouped/static/description/production_wizard_report.png + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* To use wizard production assistant, you need to create BoMs for your products + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Quentin Dupont (quentin.dupont@grap.coop) + +Maintainers +~~~~~~~~~~~ + +This module is part of the `grap/grap-odoo-custom `_ project on GitHub. + +You are welcome to contribute. diff --git a/mrp_sale_grouped/__init__.py b/mrp_sale_grouped/__init__.py new file mode 100644 index 00000000..7660e7bf --- /dev/null +++ b/mrp_sale_grouped/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import report +from . import wizard diff --git a/mrp_sale_grouped/__manifest__.py b/mrp_sale_grouped/__manifest__.py new file mode 100644 index 00000000..9d069e1a --- /dev/null +++ b/mrp_sale_grouped/__manifest__.py @@ -0,0 +1,44 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin Dupont (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "MRP Grouped Sales and Production", + "summary": "Quickly manage what you need to produce thanks to grouped sales", + "version": "16.0.1.0.0", + "category": "GRAP - Custom", + "author": "GRAP", + "website": "https://github.com/grap/grap-odoo-custom", + "license": "AGPL-3", + "depends": [ + "sale_mrp", + # OCA + "web_widget_x2many_2d_matrix", + "mrp_bom_simple_report", + "mrp_bom_wizard_production", + # GRAP + ], + "demo": [ + "demo/product.xml", + "demo/bom.xml", + "demo/sale_order.xml", + "demo/sale_order_line.xml", + "demo/mrp_sale_grouped.xml", + ], + "data": [ + "data/report_paperformat.xml", + "data/stock_route.xml", + "security/ir_rule.xml", + "security/ir.model.access.csv", + "report/report_sale_grouped.xml", + "report/ir_actions_report.xml", + "views/action.xml", + "views/menu.xml", + "views/view_mrp_sale_grouped.xml", + "views/view_sale_order.xml", + "wizard/view_sale_grouped_wizard.xml", + "wizard/x2m_matrix_grouped_sales.xml", + "wizard/view_production_assistant_wizard.xml", + ], + "installable": True, +} diff --git a/mrp_sale_grouped/data/report_paperformat.xml b/mrp_sale_grouped/data/report_paperformat.xml new file mode 100644 index 00000000..84b35217 --- /dev/null +++ b/mrp_sale_grouped/data/report_paperformat.xml @@ -0,0 +1,22 @@ + + + + + + MRP Sale Grouped Report + A4 + 0 + 0 + Landscape + 10 + 10 + 5 + 5 + 90 + + + diff --git a/mrp_sale_grouped/data/stock_route.xml b/mrp_sale_grouped/data/stock_route.xml new file mode 100644 index 00000000..4e9cfed1 --- /dev/null +++ b/mrp_sale_grouped/data/stock_route.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/mrp_sale_grouped/demo/bom.xml b/mrp_sale_grouped/demo/bom.xml new file mode 100644 index 00000000..96f074a3 --- /dev/null +++ b/mrp_sale_grouped/demo/bom.xml @@ -0,0 +1,41 @@ + + + + + + + + TOMATO_PIE + + + + + + + 1 + + + + + + 0.5 + + + + + + 0.3 + + + + + + 0.1 + + + + diff --git a/mrp_sale_grouped/demo/mrp_sale_grouped.xml b/mrp_sale_grouped/demo/mrp_sale_grouped.xml new file mode 100644 index 00000000..2c9a5d75 --- /dev/null +++ b/mrp_sale_grouped/demo/mrp_sale_grouped.xml @@ -0,0 +1,14 @@ + + + + + + Production week 63 + + + + diff --git a/mrp_sale_grouped/demo/product.xml b/mrp_sale_grouped/demo/product.xml new file mode 100644 index 00000000..2f17d9fd --- /dev/null +++ b/mrp_sale_grouped/demo/product.xml @@ -0,0 +1,49 @@ + + + + + + + Spinach + + + + 4 + + + + Tomatoes + + + + 3 + + + + Mustard + + + + 10 + + + + Pie + + 3 + + + + Tomato pie + product + + + + + + + diff --git a/mrp_sale_grouped/demo/sale_order.xml b/mrp_sale_grouped/demo/sale_order.xml new file mode 100644 index 00000000..45afe797 --- /dev/null +++ b/mrp_sale_grouped/demo/sale_order.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mrp_sale_grouped/demo/sale_order_line.xml b/mrp_sale_grouped/demo/sale_order_line.xml new file mode 100644 index 00000000..f83a58b7 --- /dev/null +++ b/mrp_sale_grouped/demo/sale_order_line.xml @@ -0,0 +1,40 @@ + + + + + + + + + 4 + 3 + + + + + + 1 + 4 + + + + + + + 10 + 4 + + + + + + + 2 + 7 + + + diff --git a/mrp_sale_grouped/i18n/fr.po b/mrp_sale_grouped/i18n/fr.po new file mode 100644 index 00000000..26feef9b --- /dev/null +++ b/mrp_sale_grouped/i18n/fr.po @@ -0,0 +1,646 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_sale_grouped +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 14:42+0000\n" +"PO-Revision-Date: 2024-12-19 14: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: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Manufacturing Orders" +msgstr "Ordre de fabrication" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "No BoM" +msgstr "Pas de FT" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Sale Orders" +msgstr "Ventes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_needaction +msgid "Action Needed" +msgstr "Nécessite une action" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_ids +msgid "Activities" +msgstr "Activités" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Activité exception décoration" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_state +msgid "Activity State" +msgstr "Status de l'Activité" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icône de type d'activité" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_attachment_count +msgid "Attachment Count" +msgstr "Nombre de pièces jointes" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_sale_grouped_wizard_form +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.x2many_2d_matrix_grouped_sales +msgid "Cancel" +msgstr "Annuler" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__company_id +msgid "Company" +msgstr "Société" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Confirm" +msgstr "Confirmer" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Confirme Quotation" +msgstr "Confirmer le devis" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Confirme all quotations" +msgstr "Confirmer tous les devis" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__create_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__create_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__create_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__create_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__create_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__create_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__display_name +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__display_name +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__display_name +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_follower_ids +msgid "Followers" +msgstr "Abonnés" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_partner_ids +msgid "Followers (Partners)" +msgstr "Abonnés (Partenaires)" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__date +msgid "For example, it will be used for production wizard PDF." +msgstr "" +"Cette date sera utilisée par exemple dans le PDF issu de l'assistant de " +"production." + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.action_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_order__mrp_sale_grouped_id +#: model:ir.ui.menu,name:mrp_sale_grouped.menu_sale_grouped +msgid "Grouped Sale Production" +msgstr "Groupe de ventes à produire" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__has_message +msgid "Has Message" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__id +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__id +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__id +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__id +msgid "ID" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icône pour indiquer une activité d'exception." + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Si coché, de nouveaux messages demandent votre attention." + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Si actif, certains messages ont une erreur de livraison." + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_is_follower +msgid "Is Follower" +msgstr "Est un abonné" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_sale_order_search +msgid "Last 31 days" +msgstr "31 derniers jours" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped____last_update +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard____last_update +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line____last_update +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__write_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__write_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__write_uid +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__write_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__write_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__write_date +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_x2m_matrix_grouped_sales_wizard__line_ids +msgid "Line" +msgstr "Ligne" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard__line_ids +msgid "Lines" +msgstr "Lignes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_main_attachment_id +msgid "Main Attachment" +msgstr "Pièce jointe principale" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Mark as Done" +msgstr "Marquer comme fait" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_has_error +msgid "Message Delivery error" +msgstr "Erreur d'envoi du message" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_ids +msgid "Messages" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_bom_print_purchase_list_wizard__missing_boms_text +msgid "Missing Boms Text" +msgstr "Texte des Fiches Techniques manquantes" + +#. module: mrp_sale_grouped +#: model:product.template,name:mrp_sale_grouped.demo_product_mustard_product_template +msgid "Mustard" +msgstr "Moutarde" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Échéance de mon activité" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__name +msgid "Name" +msgstr "Nom" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Next Activity" +msgstr "Activité suivante" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Date limite de l'Activité à Venir" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_summary +msgid "Next Activity Summary" +msgstr "Résumé d'activité suivant" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_type_id +msgid "Next Activity Type" +msgstr "Type d'Activités à Venir" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__productions_state__no_production +msgid "No Production" +msgstr "Pas de production" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__sales_state__no_sale +msgid "No Sale" +msgstr "Pas de vente" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__notes +msgid "Notes" +msgstr "" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Number" +msgstr "Nombre" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_needaction_counter +msgid "Number of Actions" +msgstr "Nombre d'actions" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__orders_qty +msgid "Number of Sales associated in the grouped Sale Production" +msgstr "Nombre de ventes liées" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__message_has_error_counter +msgid "Number of errors" +msgstr "Nombre d'erreurs" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Nombre de messages exigeant une action" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Nombre de messages avec des erreurs d'envoi" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Open" +msgstr "Ouvrir" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Order Date" +msgstr "Date de commande" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__sale_partner_id +msgid "Partner" +msgstr "Partenaire" + +#. module: mrp_sale_grouped +#: model:product.template,name:mrp_sale_grouped.demo_product_pie_product_template +msgid "Pie" +msgstr "Tarte" + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.mrp_sale_grouped_report_wizard +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_sale_grouped_wizard_form +msgid "Print sales sum up" +msgstr "Imprimer le résumé des ventes" + +#. module: mrp_sale_grouped +#: model:ir.actions.report,name:mrp_sale_grouped.sale_grouped +msgid "Print sales sum up PROUT" +msgstr "Imprimer le résumé des ventes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__product_wo_bom_ids +msgid "Product Wo Bom" +msgstr "Produit sans FT" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__product_wo_bom_qty +msgid "Product Wo Bom Qty" +msgstr "Nb de produits sans FT" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__production_ids +msgid "Production" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_mrp_production +msgid "Production Order" +msgstr "Ordre de production" + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.action_grouped_sale_prod_2_production_order +msgid "Production Orders" +msgstr "Ordres de production" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__production_qty +msgid "Production Qty" +msgstr "Qté de production" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Production assistant" +msgstr "Assistant de production" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__date +msgid "Production date" +msgstr "Date de production" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__productions_state__all_production_done +msgid "Production done" +msgstr "Production terminée" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__productions_state__prod_in_progress +msgid "Production in progress" +msgstr "Production en cours" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__productions_state +msgid "Productions State" +msgstr "État de la production" + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.action_grouped_sale_prod_2_product_product +msgid "Products" +msgstr "Articles" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Quantity" +msgstr "Quantité" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_report_mrp_sale_grouped_report_sale_grouped +msgid "Report for Grouped Sale and Production" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__activity_user_id +msgid "Responsible User" +msgstr "Responsable" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_order_line__sale_grouped_display_name +msgid "Sale Grouped Display Name" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__orders_qty +msgid "Sale Order Quantity" +msgstr "Qté de ventes" + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.action_grouped_sale_prod_2_sale_order +msgid "Sale Orders" +msgstr "Ventes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__sale_id +msgid "Sale order" +msgstr "Vente" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Sales" +msgstr "Ventes" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_sale_order +msgid "Sales Order" +msgstr "Bon de commande" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_sale_order_line +msgid "Sales Order Line" +msgstr "Ligne de commande" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__order_ids +msgid "Sales Orders" +msgstr "Bons de commandes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__sales_state +msgid "Sales State" +msgstr "État des ventes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__sales_state__all_sales_confirmed +msgid "Sales confirmed" +msgstr "Ventes confirmées" + +#. module: mrp_sale_grouped +#: model:ir.model.fields.selection,name:mrp_sale_grouped.selection__mrp_sale_grouped__sales_state__sales_in_progress +msgid "Sales in progress" +msgstr "Ventes en cours" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.x2many_2d_matrix_grouped_sales +msgid "Save new values and Close" +msgstr "Enregistrer les nouvelles valeurs et fermer" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "See and update values" +msgstr "Voir et mettre à jour les quantités" + +#. module: mrp_sale_grouped +#: model:product.template,name:mrp_sale_grouped.demo_product_spinach_product_template +msgid "Spinach" +msgstr "Épinard" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Statut basé sur les activités\n" +"En retard : la date d'échéance est déjà dépassée\n" +"Aujourd'hui : la date d'activité est aujourd'hui\n" +"Planifiée : activités futures" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Tax Total" +msgstr "Total des taxes" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_sale_order_line__sale_grouped_display_name +msgid "" +"Technical field, used to display matrix with web_widget_x2many_2d_matrix " +"module." +msgstr "" +"Champ technique, utilisé pour afficher la matrice de produit, via le module " +"web_widget_x2many_2d_matrix." + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_production_assistant_wizard +msgid "" +"These products are sold but don't have any Bill Of Material, so they can't be\n" +" in this Production assistant:" +msgstr "" +"Ces produits sont dans une vente mais n'ont pas de Fiche technique, donc\n" +" ils ne peuvent pas être dans l'assistant de production:" + +#. module: mrp_sale_grouped +#: model:product.template,name:mrp_sale_grouped.demo_product_tomato_tart_product_template +msgid "Tomato pie" +msgstr "Tarte à la tomate" + +#. module: mrp_sale_grouped +#: model:product.template,name:mrp_sale_grouped.demo_product_tomato_product_template +msgid "Tomatoes" +msgstr "Tomates" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Total Tax Excluded" +msgstr "Total taxes exclus" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Total Tax Included" +msgstr "Total taxes inclus" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Total expected duration" +msgstr "" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Total real duration" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "UoM" +msgstr "UdM" + +#. module: mrp_sale_grouped +#: model:ir.actions.act_window,name:mrp_sale_grouped.action_grouped_sale_2_wizard_x2m_matrix +msgid "Update Values" +msgstr "Mettre à jour les valeurs" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_mrp_sale_grouped__website_message_ids +msgid "Website Messages" +msgstr "Messages du site web" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,help:mrp_sale_grouped.field_mrp_sale_grouped__website_message_ids +msgid "Website communication history" +msgstr "Historique de communication du site web" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "Week n°63 production" +msgstr "Ex : Semaine n°63 de production" + +#. module: mrp_sale_grouped +#: model:ir.model.fields,field_description:mrp_sale_grouped.field_sale_grouped_wizard_line__wizard_id +msgid "Wizard" +msgstr "Assistant" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_sale_grouped_wizard +msgid "Wizard for printing Production Plan" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_bom_print_purchase_list_wizard +msgid "Wizard for printing bill of materials" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_sale_grouped_wizard_line +msgid "Wizard line for printing purchase list from selected bill of materials" +msgstr "" + +#. module: mrp_sale_grouped +#: model:ir.model,name:mrp_sale_grouped.model_x2m_matrix_grouped_sales_wizard +msgid "X2Many Matrix Grouped Sales Wizard" +msgstr "" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.x2many_2d_matrix_grouped_sales +msgid "ℹ️ Only show draft and sent quotations (not confirmed quotations)" +msgstr "" +"ℹ️ Affiche seulement les devis brouillons et envoyées (pas les ventes " +"confirmés)" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "⤷ Associated Manufacturing Orders" +msgstr "⤷ Ordres de fabrication associés" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_sale_grouped_wizard_form +msgid "📈 Sales concerned" +msgstr "📈 Ventes concernées" + +#. module: mrp_sale_grouped +#: model_terms:ir.ui.view,arch_db:mrp_sale_grouped.view_grouped_sale_production_form +msgid "" +"📌 After giving a name, fill in the sales by adding a line bellow and " +"selecting one sale." +msgstr "" +"📌 Après avoir donné un nom, remplissez les ventes liées en cliquant sur " +"'ajouter une ligne' et en séléctionnant une ou plusieurs ventes." \ No newline at end of file diff --git a/mrp_sale_grouped/models/__init__.py b/mrp_sale_grouped/models/__init__.py new file mode 100644 index 00000000..712fc00f --- /dev/null +++ b/mrp_sale_grouped/models/__init__.py @@ -0,0 +1,4 @@ +from . import mrp_sale_grouped +from . import mrp_production +from . import sale_order +from . import sale_order_line diff --git a/mrp_sale_grouped/models/mrp_production.py b/mrp_sale_grouped/models/mrp_production.py new file mode 100644 index 00000000..2dc8454b --- /dev/null +++ b/mrp_sale_grouped/models/mrp_production.py @@ -0,0 +1,22 @@ +# Copyright (C) 2024 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class MrpProduction(models.Model): + _inherit = "mrp.production" + + def open_mo(self): + self.ensure_one() + result = self.env["ir.actions.act_window"]._for_xml_id( + "mrp.action_mrp_production_form" + ) + form_view = self.env.ref("mrp.mrp_production_form_view") + result["views"] = [(form_view.id, "form")] + result["res_id"] = self.id + result["context"] = { + "form_view_initial_mode": "edit", + } + return result diff --git a/mrp_sale_grouped/models/mrp_sale_grouped.py b/mrp_sale_grouped/models/mrp_sale_grouped.py new file mode 100644 index 00000000..0f8e4dc5 --- /dev/null +++ b/mrp_sale_grouped/models/mrp_sale_grouped.py @@ -0,0 +1,161 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class MrpSaleGrouped(models.Model): + _name = "mrp.sale.grouped" + _description = "Grouped Sale Production" + _inherit = ["mail.thread", "mail.activity.mixin"] + + _SALES_STATE_SELECTION = [ + ("no_sale", "No Sale"), + ("sales_in_progress", "Sales in progress"), + ("all_sales_confirmed", "Sales confirmed"), + ] + + _PROD_STATE_SELECTION = [ + ("no_production", "No Production"), + ("prod_in_progress", "Production in progress"), + ("all_production_done", "Production done"), + ] + + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + default=lambda s: s._default_company_id(), + ) + + name = fields.Char( + required=True, + ) + date = fields.Date( + string="Production date", + help="For example, it will be used for production wizard PDF.", + ) + notes = fields.Char() + + sales_state = fields.Selection( + selection=_SALES_STATE_SELECTION, + default="no_sale", + compute="_compute_sales_state", + ) + + productions_state = fields.Selection( + selection=_PROD_STATE_SELECTION, + default="no_sale", + compute="_compute_productions_state", + ) + + orders_qty = fields.Integer( + string="Sale Order Quantity", + compute="_compute_orders_qty", + help="Number of Sales associated in the grouped Sale Production", + ) + + order_ids = fields.One2many( + string="Sales Orders", + comodel_name="sale.order", + inverse_name="mrp_sale_grouped_id", + ) + + # Quick access to MRP Production Orders + production_ids = fields.One2many( + comodel_name="mrp.production", + compute="_compute_production_ids", + ) + + production_qty = fields.Integer( + compute="_compute_production_qty", + ) + + # Quick access to Products without any BoM + product_wo_bom_ids = fields.One2many( + comodel_name="product.product", + compute="_compute_product_wo_bom_ids", + ) + + product_wo_bom_qty = fields.Integer( + compute="_compute_product_wo_bom_qty", + ) + + # Default methods + @api.model + def _default_company_id(self): + return self.env.company + + # SALES + @api.depends("order_ids") + def _compute_sales_state(self): + for mrp_sale_grouped in self: + if not mrp_sale_grouped.order_ids: + mrp_sale_grouped.sales_state = "no_sale" + elif any( + order.state in ["draft", "sent"] + for order in mrp_sale_grouped.mapped("order_ids") + ): + mrp_sale_grouped.sales_state = "sales_in_progress" + else: + mrp_sale_grouped.sales_state = "all_sales_confirmed" + + @api.depends("order_ids") + def _compute_orders_qty(self): + for mrp_sale_grouped in self: + mrp_sale_grouped.orders_qty = len(mrp_sale_grouped.order_ids) + + # PRODUCTIONS + @api.depends("production_ids") + def _compute_productions_state(self): + for mrp_sale_grouped in self: + if not mrp_sale_grouped.production_ids: + mrp_sale_grouped.productions_state = "no_production" + elif any( + prods.state not in ["done", "cancel"] + for prods in mrp_sale_grouped.mapped("production_ids") + ): + mrp_sale_grouped.productions_state = "prod_in_progress" + else: + mrp_sale_grouped.productions_state = "all_production_done" + + @api.depends("order_ids", "order_ids.mrp_production_ids") + def _compute_production_ids(self): + for grouped_prod in self: + grouped_prod.production_ids = grouped_prod.order_ids.mapped( + "mrp_production_ids" + ) + + @api.depends("production_ids") + def _compute_production_qty(self): + for grouped_prod in self: + grouped_prod.production_qty = len(grouped_prod.production_ids) + + # Methods for Products without any BoM + @api.depends("order_ids") + def _compute_product_wo_bom_ids(self): + for grouped_prod in self: + grouped_prod.product_wo_bom_ids = grouped_prod.mapped( + "order_ids.order_line.product_id" + ).filtered(lambda r: r.bom_count == 0) + + @api.depends("product_wo_bom_ids") + def _compute_product_wo_bom_qty(self): + for grouped_prod in self: + grouped_prod.product_wo_bom_qty = len(grouped_prod.product_wo_bom_ids) + + def confirm_all_sale_order(self): + for sale_grouped in self: + sale_grouped.mapped("order_ids").action_confirm() + + # Action View + def action_view_production(self): + action = self.env.ref("mrp.mrp_production_action").read()[0] + if self.production_qty > 1: + action["domain"] = [("id", "in", self.production_ids.ids)] + else: + action["views"] = [ + (self.env.ref("mrp.mrp_production_form_view").id, "form") + ] + action["res_id"] = self.production_ids.id + return action diff --git a/mrp_sale_grouped/models/sale_order.py b/mrp_sale_grouped/models/sale_order.py new file mode 100644 index 00000000..b9c95d22 --- /dev/null +++ b/mrp_sale_grouped/models/sale_order.py @@ -0,0 +1,15 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + mrp_sale_grouped_id = fields.Many2one( + comodel_name="mrp.sale.grouped", + string="Grouped Sale Production", + index=True, + ) diff --git a/mrp_sale_grouped/models/sale_order_line.py b/mrp_sale_grouped/models/sale_order_line.py new file mode 100644 index 00000000..839daf26 --- /dev/null +++ b/mrp_sale_grouped/models/sale_order_line.py @@ -0,0 +1,23 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + sale_grouped_display_name = fields.Char( + compute="_compute_sale_grouped_display_name", + help="Technical field, used to display matrix" + " with web_widget_x2many_2d_matrix module.", + ) + + def _compute_sale_grouped_display_name(self): + for sale_order_line in self: + sale_order_line.sale_grouped_display_name = ( + sale_order_line.order_id.name + + " " + + sale_order_line.order_partner_id.name + ) diff --git a/mrp_sale_grouped/readme/CONFIGURE.rst b/mrp_sale_grouped/readme/CONFIGURE.rst new file mode 100644 index 00000000..bbe243d1 --- /dev/null +++ b/mrp_sale_grouped/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ +* To use wizard production assistant, you need to create BoMs for your products + +To handle Manufacturing orders with sales : +* Since v13, you need to unarchive Route `Replenish on Order (MTO)` +* Select MTO and Manufacture in your Products Routes (tab Inventory) \ No newline at end of file diff --git a/mrp_sale_grouped/readme/CONTRIBUTORS.rst b/mrp_sale_grouped/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..9ed47062 --- /dev/null +++ b/mrp_sale_grouped/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Quentin Dupont (quentin.dupont@grap.coop) diff --git a/mrp_sale_grouped/readme/DESCRIPTION.rst b/mrp_sale_grouped/readme/DESCRIPTION.rst new file mode 100644 index 00000000..7f981ef9 --- /dev/null +++ b/mrp_sale_grouped/readme/DESCRIPTION.rst @@ -0,0 +1,22 @@ +Quickly manage sales and linked manufacturing orders. +For example : a Week Production for a Bakery based on its Grocery sales. + +.. figure:: ../static/description/mrp_sale_grouped.png + +1 : Choose or create sales +2 : Act on sales : adjust quantities with Matrix2D view, and confirme +all at once +3 : Quick buttons : Products without BoM, Sales, Manufacturing Orders +4 : Print PDF that sum up sales by product and sale +5 : Act on Manufacturing Orders +6 : Handle your production with wizard assistant, you can handle what components +you have to buy, intermediate and finished products you have to produce. +See dedicated module mrp_wizard_production + +.. figure:: ../static/description/report_sales_sum_up.png +.. figure:: ../static/description/mrp_sale_grouped_sales_matrix2d.png +.. figure:: ../static/description/mrp_sale_grouped.jpeg + +Quickly see what's going on for your sales and production on tree view + +.. figure:: ../static/description/mrp_sale_grouped_tree_view.jpeg diff --git a/mrp_sale_grouped/report/__init__.py b/mrp_sale_grouped/report/__init__.py new file mode 100644 index 00000000..c607334f --- /dev/null +++ b/mrp_sale_grouped/report/__init__.py @@ -0,0 +1 @@ +from . import report_sale_grouped diff --git a/mrp_sale_grouped/report/ir_actions_report.xml b/mrp_sale_grouped/report/ir_actions_report.xml new file mode 100644 index 00000000..31679d06 --- /dev/null +++ b/mrp_sale_grouped/report/ir_actions_report.xml @@ -0,0 +1,20 @@ + + + + + + + + report.sale.grouped + Print sales sum up PROUT + qweb-pdf + mrp_sale_grouped.report_sale_grouped + False + + + + diff --git a/mrp_sale_grouped/report/report_sale_grouped.py b/mrp_sale_grouped/report/report_sale_grouped.py new file mode 100644 index 00000000..6ecb0b05 --- /dev/null +++ b/mrp_sale_grouped/report/report_sale_grouped.py @@ -0,0 +1,110 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo import api, models + + +class ReportSaleGrouped(models.AbstractModel): + _name = "report.mrp_sale_grouped.report_sale_grouped" + _description = "Report for Grouped Sale and Production" + + @api.model + def _get_report_values(self, docids, data=None): + ( + all_sale_order, + list_sale_order_product_quantity, + ) = self._prepare_data_to_matrix_customer_product(data) + docargs = { + "all_sale_order": all_sale_order, + "list_sale_order_product_quantity": list_sale_order_product_quantity, + } + return docargs + + @api.model + def calculate_qty_for_one_product( + self, bom_line_product_qty, bom_qty, desired_qty, digits + ): + _bom_qty = max(1, bom_qty) + return round(bom_line_product_qty * desired_qty / _bom_qty, digits) + + # Data is sale.order ids + # First returns list sale_order for head of PDF table + # Second return dict of product_id and quantities sort by sale_order + @api.model + def _prepare_data_to_matrix_customer_product(self, data): + all_sale_order_ids = data["line_data"] + all_sale_order = self.env["sale.order"].search( + [("id", "in", all_sale_order_ids)] + ) + sale_order_lines = self.env["sale.order.line"].search( + [("order_id", "in", all_sale_order_ids)] + ) + + all_products = sale_order_lines.mapped("product_id") + all_products_ids = all_products.ids + product_names = ( + self.env["product.product"].browse(all_products_ids).mapped("name") + ) + + all_customers = sale_order_lines.mapped("order_partner_id") + len(all_customers) + + # Create template with zero to be prepare to matrix product / sale_order + line_template = {} + for order_id in all_sale_order_ids: + line_template[order_id] = 0 + line_template["subtotal"] = 0 + line_template["uom"] = "" + + dict_sale_order_prod_qty = {} + # Browse each sale_order + for order in all_sale_order: + # Browse each sale_order_line + for line in order.order_line: + product = line.product_id + # Get quantity with product uom + # (convert if uom of sale_order_line is different) + quantity = line.product_uom._compute_quantity( + line.product_uom_qty, + line.product_id.uom_id, + rounding_method="HALF-UP", + ) + if product.id not in dict_sale_order_prod_qty: + # if not dict_sale_order_prod_qty.get(product.id): + # New product → new line that we fill with zero + dict_sale_order_prod_qty[product.id] = line_template.copy() + dict_sale_order_prod_qty[product.id][order.id] = quantity + dict_sale_order_prod_qty[product.id]["subtotal"] = quantity + dict_sale_order_prod_qty[product.id][ + "uom" + ] = line.product_id.uom_id.name + else: + # Product already created, add quantity to the right colmun + dict_sale_order_prod_qty[product.id][order.id] += quantity + dict_sale_order_prod_qty[product.id]["subtotal"] += quantity + + # Converting the dict into an array of arrays + # Get : {10: {22: 24.0, 16: 0, 'subtotal': 24.0, 'uom': 'Unit(s)'}, + # 28: {22: 0, 16: 3.0, 'subtotal': 3.0, 'uom': 'Unit(s)'},} + # Convert into : [['Customizable Desk', 24.0, ' ', '24.0 Unit(s)'], + # ['Acoustic Bloc Screens', ' ', 3.0, '3.0 Unit(s)'], ] + list_sale_order_product_quantity = [ + [product_names[i]] + + list(dict_sale_order_prod_qty[all_products_ids[i]].values()) + for i in range(len(all_products_ids)) + ] + # Replace 0 by blank + list_sale_order_product_quantity = [ + [val if val != 0 else " " for val in sublist] + for sublist in list_sale_order_product_quantity + ] + # Join last two elements for UoM text in PDF + list_sale_order_product_quantity = [ + sublist[:-2] + [" ".join(map(str, sublist[-2:]))] + for sublist in list_sale_order_product_quantity + ] + + # Return all customers and prepared lines + return all_sale_order, list_sale_order_product_quantity diff --git a/mrp_sale_grouped/report/report_sale_grouped.xml b/mrp_sale_grouped/report/report_sale_grouped.xml new file mode 100644 index 00000000..6ad006d6 --- /dev/null +++ b/mrp_sale_grouped/report/report_sale_grouped.xml @@ -0,0 +1,46 @@ + + + + + + + diff --git a/mrp_sale_grouped/security/ir.model.access.csv b/mrp_sale_grouped/security/ir.model.access.csv new file mode 100644 index 00000000..bb52dabc --- /dev/null +++ b/mrp_sale_grouped/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mrp_sale_grouped_all,MRP Grouped Sale Production All,mrp_sale_grouped.model_mrp_sale_grouped,mrp.group_mrp_user,1,1,1,1 +mrp_sale_grouped.access_sale_grouped_wizard,access_sale_grouped_wizard,mrp_sale_grouped.model_sale_grouped_wizard,base.group_user,1,1,1,1 +mrp_sale_grouped.access_sale_grouped_wizard_line,access_sale_grouped_wizard_line,mrp_sale_grouped.model_sale_grouped_wizard_line,base.group_user,1,1,1,1 +mrp_sale_grouped.access_x2m_matrix_grouped_sales_wizard,access_x2m_matrix_grouped_sales_wizard,mrp_sale_grouped.model_x2m_matrix_grouped_sales_wizard,base.group_user,1,1,1,1 diff --git a/mrp_sale_grouped/security/ir_rule.xml b/mrp_sale_grouped/security/ir_rule.xml new file mode 100644 index 00000000..7f8ea404 --- /dev/null +++ b/mrp_sale_grouped/security/ir_rule.xml @@ -0,0 +1,21 @@ + + + + + + + MRP Sale Grouped Rule + + + [ + '|', + ('company_id', '=', user.company_id.id), + ('company_id','=',False) + ] + + + diff --git a/mrp_sale_grouped/static/description/index.html b/mrp_sale_grouped/static/description/index.html new file mode 100644 index 00000000..a67efd8d --- /dev/null +++ b/mrp_sale_grouped/static/description/index.html @@ -0,0 +1,423 @@ + + + + + + +MRP BoM Print + + + +
+

MRP BoM Print

+ + +

Beta License: AGPL-3 quentinDupont/grap-odoo-custom

+

Manage the various useful prints for Bill of Materials +Printing allergens for selected or all Bill of Materials

+
+https://raw.githubusercontent.com/quentinDupont/grap-odoo-custom/12.0_MRP_Grap_prod/mrp_bom_print/static/description/bom_printing_allergens_with_options.gif +
+

Table of contents

+ +
+

Known issues / Roadmap

+

Allergens table works only when using product product on Bill Of Material.

+
+
+

Bug Tracker

+

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

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • GRAP
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the quentinDupont/grap-odoo-custom project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/mrp_sale_grouped/static/description/mrp_sale_grouped.jpeg b/mrp_sale_grouped/static/description/mrp_sale_grouped.jpeg new file mode 100644 index 00000000..e8047432 Binary files /dev/null and b/mrp_sale_grouped/static/description/mrp_sale_grouped.jpeg differ diff --git a/mrp_sale_grouped/static/description/mrp_sale_grouped_sales_matrix2d.png b/mrp_sale_grouped/static/description/mrp_sale_grouped_sales_matrix2d.png new file mode 100644 index 00000000..78ecf7ed Binary files /dev/null and b/mrp_sale_grouped/static/description/mrp_sale_grouped_sales_matrix2d.png differ diff --git a/mrp_sale_grouped/static/description/mrp_sale_grouped_tree_view.jpeg b/mrp_sale_grouped/static/description/mrp_sale_grouped_tree_view.jpeg new file mode 100644 index 00000000..194652bc Binary files /dev/null and b/mrp_sale_grouped/static/description/mrp_sale_grouped_tree_view.jpeg differ diff --git a/mrp_sale_grouped/static/description/report_sales_sum_up.png b/mrp_sale_grouped/static/description/report_sales_sum_up.png new file mode 100644 index 00000000..5540a660 Binary files /dev/null and b/mrp_sale_grouped/static/description/report_sales_sum_up.png differ diff --git a/mrp_sale_grouped/static/img/tomato_pie.png b/mrp_sale_grouped/static/img/tomato_pie.png new file mode 100644 index 00000000..959070ce Binary files /dev/null and b/mrp_sale_grouped/static/img/tomato_pie.png differ diff --git a/mrp_sale_grouped/tests/__init__.py b/mrp_sale_grouped/tests/__init__.py new file mode 100644 index 00000000..7a96e73b --- /dev/null +++ b/mrp_sale_grouped/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_sale_grouped diff --git a/mrp_sale_grouped/tests/test_mrp_sale_grouped.py b/mrp_sale_grouped/tests/test_mrp_sale_grouped.py new file mode 100644 index 00000000..721b2936 --- /dev/null +++ b/mrp_sale_grouped/tests/test_mrp_sale_grouped.py @@ -0,0 +1,62 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Quentin DUPONT (quentin.dupont@grap.coop) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestMrpSaleGrouped(TransactionCase): + def setUp(self): + super().setUp() + # Objects + self.mrp_sale_grouped_obj = self.env["mrp.sale.grouped"] + self.wizard_obj = self.env["sale.grouped.wizard"] + self.wizard_purchase_list_obj = self.env["bom.print.purchase.list.wizard"] + # Demo datas + self.sale_order_gemini = self.env.ref("mrp_sale_grouped.demo_sale_gemini") + self.sale_order_ready_mat = self.env.ref("mrp_sale_grouped.demo_sale_ready_mat") + self.sale_order_with_boms = self.env.ref( + "mrp_sale_grouped.demo_sale_deco_addict" + ) + self.bom_pie = self.env.ref("mrp_sale_grouped.demo_bom_tomato_tart") + + # Create one sale_grouped with sales + self.mrp_sale_grouped_1 = self.mrp_sale_grouped_obj.create( + {"name": "TEST Grouped Sale"} + ) + self.mrp_sale_grouped_with_boms = self.mrp_sale_grouped_obj.create( + {"name": "TEST with BoMs"} + ) + # Add grouped to sales + self.sale_order_gemini.mrp_sale_grouped_id = self.mrp_sale_grouped_1 + self.sale_order_ready_mat.mrp_sale_grouped_id = self.mrp_sale_grouped_1 + self.sale_order_with_boms.mrp_sale_grouped_id = self.mrp_sale_grouped_with_boms + + def test_01_create_grouped_check_sale_linked(self): + self.assertEqual(self.mrp_sale_grouped_1.orders_qty, 2) + + def test_02_grouped_sales_check_confirm_quotations(self): + self.assertEqual(self.mrp_sale_grouped_1.order_ids[0].state, "draft") + self.assertEqual(self.mrp_sale_grouped_1.order_ids[1].state, "draft") + self.assertEqual(self.mrp_sale_grouped_1.sales_state, "sales_in_progress") + self.mrp_sale_grouped_1.confirm_all_sale_order() + self.assertEqual(self.mrp_sale_grouped_1.order_ids[0].state, "sale") + self.assertEqual(self.mrp_sale_grouped_1.order_ids[1].state, "sale") + self.mrp_sale_grouped_1._compute_sales_state() + self.assertEqual(self.mrp_sale_grouped_1.sales_state, "all_sales_confirmed") + + def test_03_check_mrp_grouped_productions_state(self): + self.assertEqual(self.mrp_sale_grouped_1.productions_state, "no_production") + + def test_04_sale_grouped_report(self): + # Launch wizard and report action + wizard = self.wizard_obj.with_context( + active_ids=[self.mrp_sale_grouped_1.id] + ).create({}) + wizard.print_sale_sum_up() + + def test_05_check_wizard_production_with_boms(self): + self.wizard_purchase_list_obj.with_context( + active_ids=[self.mrp_sale_grouped_with_boms.id], + active_model="mrp.sale.grouped", + ).create({}) diff --git a/mrp_sale_grouped/views/action.xml b/mrp_sale_grouped/views/action.xml new file mode 100644 index 00000000..a42f2982 --- /dev/null +++ b/mrp_sale_grouped/views/action.xml @@ -0,0 +1,20 @@ + + + + + + + + Print sales sum up + sale.grouped.wizard + form + new + + report + + + diff --git a/mrp_sale_grouped/views/menu.xml b/mrp_sale_grouped/views/menu.xml new file mode 100644 index 00000000..27fb8eb4 --- /dev/null +++ b/mrp_sale_grouped/views/menu.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/mrp_sale_grouped/views/view_mrp_sale_grouped.xml b/mrp_sale_grouped/views/view_mrp_sale_grouped.xml new file mode 100644 index 00000000..fc24ab70 --- /dev/null +++ b/mrp_sale_grouped/views/view_mrp_sale_grouped.xml @@ -0,0 +1,211 @@ + + + + + + Update Values + ir.actions.act_window + x2m.matrix.grouped.sales.wizard + form + new + + + + Sale Orders + sale.order + tree,form + [('mrp_sale_grouped_id', 'in', active_ids)] + + + + Products + product.product + tree,form + [('id', 'in', context.get('ctx_product_wo_bom_ids',False))] + + + + Production Orders + mrp.production + tree,form + [('id', 'in', context.get('ctx_production_ids'))] + + + + mrp.sale.grouped + +
+
+
+ +
+ + + + + + + +
+
+ 📌 After giving a name, fill in the sales by adding a line bellow and selecting one sale. +
+
+
+ + + + +
+ + +

Sales

+
+ +