Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

115
sale_manual_delivery/README.rst Executable file
View File

@@ -0,0 +1,115 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
====================
Sale Manual Delivery
====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:08bd49944495269b3be34ce7a56655f20ad1c2048c290be903c842989ac528fd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_manual_delivery
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_manual_delivery
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
The goal of this module is to allow the manual creation of delivery
slips. If activated, a sale order won't directly impact the stock. It
will not make a reservation on the stock. It allows the delivery and the
impact on stock to be done manually when needed. The goal is to be used
on long term projects where not all the material is shipped at once. As
you make many shipments, every time you make a new delivery, you can
choose an new carrier as well as a planned date. A wizard helps you to
chose what to deliver by showing you how much you already planned to
ship.
|Field|
|Wizard|
.. |Field| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_manual_delivery/static/description/field.png
.. |Wizard| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_manual_delivery/static/description/wizard.png
**Table of contents**
.. contents::
:local:
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/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 <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_manual_delivery%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Camptocamp SA
Contributors
------------
- Denis Leemann <denis.leemann@camptocamp.com>
- Joel Grand-Guillaume <joel.grandguillaume@camptocamp.com>
- Phuc Tran Thanh <phuc@trobz.com>
- Iván Todorovich <ivan.todorovich@gmail.com>
- `Trobz <https://trobz.com>`__:
- nguyenhk <nguyenhk@trobz.com>
- `360ERP <https://www.360erp.com>`__:
- Andrea Stirpe
Other credits
-------------
The migration of this module from 14.0 to 16.0 was financially supported
by Camptocamp
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_manual_delivery>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,5 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard
from .hook import pre_init_hook

View File

@@ -0,0 +1,22 @@
# Copyright 2017 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Sale Manual Delivery",
"category": "Sale",
"author": "Camptocamp SA, Odoo Community Association (OCA)",
"license": "AGPL-3",
"version": "18.0.1.0.1",
"website": "https://github.com/OCA/sale-workflow",
"summary": "Create manually your deliveries",
"depends": ["stock_delivery", "sale_stock", "sales_team"],
"data": [
"security/ir.model.access.csv",
"views/crm_team.xml",
"views/sale_order.xml",
"wizard/manual_delivery.xml",
],
"installable": True,
"application": False,
"auto_install": False,
"pre_init_hook": "pre_init_hook",
}

65
sale_manual_delivery/hook.py Executable file
View File

@@ -0,0 +1,65 @@
def pre_init_hook(env):
cr = env.cr
cr.execute(
"""
ALTER TABLE sale_order_line ADD COLUMN IF NOT EXISTS qty_procured numeric;
COMMENT ON COLUMN sale_order_line.qty_procured IS 'Quantity Procured';
"""
)
cr.execute(
"""
ALTER TABLE sale_order_line ADD COLUMN IF NOT EXISTS qty_to_procure numeric;
COMMENT ON COLUMN sale_order_line.qty_to_procure IS 'Quantity to Procure"';
"""
)
cr.execute(
"""
update sale_order_line as sol set qty_procured = r.qty_procured,
qty_to_procure = sol.product_uom_qty - r.qty_procured
from (select sol.id, sum(
case
when (
sl.usage = 'customer'
and sm.origin_returned_move_id is null
or (
sm.origin_returned_move_id is not null and sm.to_refund
)) then
ROUND(
((sm.product_uom_qty / sm_product_uom.factor) * sol_product_uom.factor),
SCALE(sol_product_uom.rounding)
)
when (
sl.usage != 'customer'
and sm.to_refund
) then
ROUND(
((sm.product_uom_qty / sm_product_uom.factor) * sol_product_uom.factor),
SCALE(sol_product_uom.rounding)
) * -1
else 0
end)
AS qty_procured
from
sale_order_line as sol
inner join (
select sol.id, sm.id as move_id, sm.location_id, sm.location_dest_id
from sale_order_line as sol
left join stock_move as sm on (
sm.state != 'cancel'
and sm.scrapped = false
and sol.product_id = sm.product_id
and sm.sale_line_id = sol.id
)
) as q on q.id = sol.id
left join stock_move as sm on sm.id = q.move_id
left join product_product as pp on pp.id = sol.product_id
left join product_template as pt on pt.id = pp.product_tmpl_id
left join stock_location as sl on sl.id = q.location_dest_id
LEFT JOIN uom_uom sm_product_uom ON sm.product_uom = sm_product_uom.id
LEFT JOIN uom_uom sol_product_uom ON sol.product_uom = sol_product_uom.id
group by sol.id, sm.product_uom, sol.product_uom
) as r
where r.id = sol.id
"""
)

339
sale_manual_delivery/i18n/es.po Executable file
View File

@@ -0,0 +1,339 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_manual_delivery
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-07-25 12:10+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Cancel"
msgstr "Cancelar"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__commercial_partner_id
msgid "Commercial Partner"
msgstr "Socio comercial"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Confirm"
msgstr "Confirmar"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_order_form
msgid "Create Delivery"
msgstr "Crear entrega"
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery_line
msgid "Create Manual Delivery"
msgstr "Crear entrega manual"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Create Manually Delivery"
msgstr "Crear entrega manual"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_date
msgid "Created on"
msgstr "Creado en"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__date_planned
#: model:ir.model.fields,field_description:sale_manual_delivery.field_procurement_group__date_planned
msgid "Date Planned"
msgstr "Fecha planificada"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__partner_id
msgid "Delivery Address"
msgstr "Dirección de entrega"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__carrier_id
msgid "Delivery Method"
msgstr "Método de entrega"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__has_pending_delivery
msgid "Delivery pending?"
msgstr "¿Entrega pendiente?"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__name
msgid "Description"
msgstr "Descripción"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__display_name
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__display_name
msgid "Display Name"
msgstr "Nombre para mostrar"
#. module: sale_manual_delivery
#: model_terms:ir.actions.act_window,help:sale_manual_delivery.action_order_line_delivery_tree
msgid "Here is a list of each sales order line to be invoiced."
msgstr ""
"A continuación, se muestra una lista de cada línea de pedido de venta que se "
"facturará."
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__id
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__id
msgid "ID"
msgstr "ID"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order__manual_delivery
msgid ""
"If enabled, the deliveries are not created at SO confirmation. You need to "
"use the Create Delivery button in order to reserve and ship the goods."
msgstr ""
"Si se activa, las entregas no se crean en la confirmación de SO. Debe "
"utilizar el botón Crear entrega para reservar y enviar la mercancía."
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_uid
msgid "Last Updated by"
msgstr "Actualizado por última vez por"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_date
msgid "Last Updated on"
msgstr "Ultima actualización en"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery__route_id
msgid "Leave it blank to use the same route that is in the sale line"
msgstr "Déjelo en blanco para usar la misma ruta que está en la línea de venta"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__line_ids
msgid "Lines to validate"
msgstr "Líneas para validar"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__manual_delivery
msgid "Manual Delivery"
msgstr "Entrega manual"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery_line
msgid "Manual Delivery Line"
msgstr "Línea de entrega manual"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_ordered
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Ordered"
msgstr "Encargado"
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery.py:0
#, python-format
msgid "Please select one partner at a time"
msgstr "Seleccione un socio a la vez"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Procured"
msgstr "Adquirido"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_procurement_group
msgid "Procurement Group"
msgstr "Grupo de adquisiciones"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__product_id
msgid "Product"
msgstr "Producto"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__quantity
msgid "Quantity"
msgstr "Cantidad"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity Procured"
msgstr "Cantidad adquirida"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity already planned or shipped (stock movements already created)"
msgstr "Cantidad ya prevista o enviada (movimientos de existencias ya creados)"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_ordered
msgid "Quantity ordered in the related Sale Order"
msgstr "Cantidad ordenada en la orden de venta relacionada"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "Quantity to Procure"
msgstr "Cantidad a adquirir"
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_order_line_delivery_tree
#: model:ir.ui.menu,name:sale_manual_delivery.menu_delivery_sales_order_lines
msgid "Sale Lines to Deliver"
msgstr "Líneas de venta para entregar"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__order_line_id
msgid "Sale Order Line"
msgstr "Línea de orden de venta"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order
msgid "Sales Order"
msgstr "Orden de Venta"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order_line
msgid "Sales Order Line"
msgstr "Línea de orden de venta"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid ""
"Sales Order Lines that are confirmed, done or in exception state and no "
"delivery is created to satisfy them"
msgstr ""
"Líneas de orden de venta que están confirmadas, realizadas o en estado de "
"excepción y no se crea ninguna entrega para satisfacerlas"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_crm_team
msgid "Sales Team"
msgstr "Equipo de Venta"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Search Undelivered Lines"
msgstr "Buscar líneas no entregadas"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_stock_move
msgid "Stock Move"
msgstr "Movimiento de stock"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "There is Pending qty to add to a delivery"
msgstr "Hay una cantidad pendiente para agregar a una entrega"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Unplanned delivery"
msgstr "Entrega no planificada"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__route_id
msgid "Use specific Route"
msgstr "Usar ruta específica"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__manual_delivery_id
msgid "Wizard"
msgstr "Asistente"
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery_line.py:0
#, python-format
msgid ""
"You can not deliver more than the remaining quantity. If you need to do so, "
"please edit the sale order first."
msgstr ""
"No puede entregar más de la cantidad restante. Si necesita hacerlo, primero "
"edite la orden de venta."
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/models/sale_order.py:0
#, python-format
msgid ""
"You can only change to/from manual delivery in a quote, not a confirmed order"
msgstr ""
"Solo puede cambiar a / desde la entrega manual en un presupuesto, no en un "
"pedido confirmado"
#~ msgid "Last Modified on"
#~ msgstr "Última modificación el"
#~ msgid "Create Picking"
#~ msgstr "Crear picking"
#~ msgid "Existing quantity"
#~ msgstr "Cantidad existente"
#~ msgid ""
#~ "If Manual, the deliveries are not created at SO confirmation. You "
#~ "need to use the Create Delivery button in order to reserve and "
#~ "ship the goods."
#~ msgstr ""
#~ "Si es Manual, las entregas no se crean en la confirmación del SO. Debe "
#~ "utilizar el botón Crear entrega para reservar y enviar las mercancías."
#~ msgid "Ordered quantity"
#~ msgstr "Cantidad ordenada"
#~ msgid "Pending to deliver"
#~ msgstr "Pendiente de entregar"
#~ msgid ""
#~ "Quantity already planned or shipped (stock movements already "
#~ "created)"
#~ msgstr "Cantidad ya planificada o enviada (movimientos de stock ya creados)"
#~ msgid "Quantity to Ship"
#~ msgstr "Cantidad a enviar"
#~ msgid "Remaining quantity"
#~ msgstr "Cantidad restante"
#~ msgid "Remaining quantity available to deliver"
#~ msgstr "Cantidad restante disponible para entregar"
#~ msgid "Sale Order"
#~ msgstr "Orden de venta"
#~ msgid "Sales Channels"
#~ msgstr "Canales de venta"
#~ msgid "Sales Order Lines"
#~ msgstr "Líneas de orden de venta"
#~ msgid "Wizard manual procurement"
#~ msgstr "Adquisición manual del asistente"

294
sale_manual_delivery/i18n/it.po Executable file
View File

@@ -0,0 +1,294 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_manual_delivery
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-06-05 12:35+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Cancel"
msgstr "Annulla"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__commercial_partner_id
msgid "Commercial Partner"
msgstr "Partner commerciale"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Confirm"
msgstr "Conferma"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_order_form
msgid "Create Delivery"
msgstr "Crea consegna"
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery_line
msgid "Create Manual Delivery"
msgstr "Crea consegna manuale"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Create Manually Delivery"
msgstr "Crea consegna manualmente"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_date
msgid "Created on"
msgstr "Creato il"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__date_planned
#: model:ir.model.fields,field_description:sale_manual_delivery.field_procurement_group__date_planned
msgid "Date Planned"
msgstr "Data pianificata"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__partner_id
msgid "Delivery Address"
msgstr "Indirizzo di consegna"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__carrier_id
msgid "Delivery Method"
msgstr "Metodo di consegna"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__has_pending_delivery
msgid "Delivery pending?"
msgstr "Consegna in attesa?"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__name
msgid "Description"
msgstr "Descrizione"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__display_name
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: sale_manual_delivery
#: model_terms:ir.actions.act_window,help:sale_manual_delivery.action_order_line_delivery_tree
msgid "Here is a list of each sales order line to be invoiced."
msgstr "Ecco un elenco di ogni riga di ordine di vendita da fatturare."
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__id
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__id
msgid "ID"
msgstr "ID"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order__manual_delivery
msgid ""
"If enabled, the deliveries are not created at SO confirmation. You need to "
"use the Create Delivery button in order to reserve and ship the goods."
msgstr ""
"Se abilitata, le consegne non vengono create alla conferma del SO. È "
"necessario utilizzare il pulsante Crea consegna per prenotare e spedire la "
"merce."
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery__route_id
msgid "Leave it blank to use the same route that is in the sale line"
msgstr ""
"Lasciare in bianco per utilizzare lo stesso percorso che è nella riga di "
"vendita"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__line_ids
msgid "Lines to validate"
msgstr "Righe da convalidare"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__manual_delivery
msgid "Manual Delivery"
msgstr "Consegna manuale"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery_line
msgid "Manual Delivery Line"
msgstr "Riga consegna manuale"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_ordered
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Ordered"
msgstr "Ordinato"
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery.py:0
#, python-format
msgid "Please select one partner at a time"
msgstr "Si prega di selezionare un partner alla volta"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Procured"
msgstr "Approggigionato"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_procurement_group
msgid "Procurement Group"
msgstr "Gruppo di approvvigionamento"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__product_id
msgid "Product"
msgstr "Prodotto"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__quantity
msgid "Quantity"
msgstr "Quantità"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity Procured"
msgstr "Quantità approvvigionata"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity already planned or shipped (stock movements already created)"
msgstr "Quantità già programmata o spedita (movimenti di magazzino già creati)"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_ordered
msgid "Quantity ordered in the related Sale Order"
msgstr "Quantità ordinata nel relativo ordine di vendita"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "Quantity to Procure"
msgstr "Quantità da approvvigionare"
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_order_line_delivery_tree
#: model:ir.ui.menu,name:sale_manual_delivery.menu_delivery_sales_order_lines
msgid "Sale Lines to Deliver"
msgstr "Righe di vendita da consegnare"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__order_line_id
msgid "Sale Order Line"
msgstr "Riga ordine di vendita"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order_line
msgid "Sales Order Line"
msgstr "Riga ordine di vendita"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid ""
"Sales Order Lines that are confirmed, done or in exception state and no "
"delivery is created to satisfy them"
msgstr ""
"Righe di ordine di vendita che sono confermate, evase o in stato di "
"eccezione e nessuna consegna è creata per soddisfarle"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_crm_team
msgid "Sales Team"
msgstr "Team di vendita"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Search Undelivered Lines"
msgstr "Ricerca righe non consegnate"
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_stock_move
msgid "Stock Move"
msgstr "Movimento di magazzino"
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "There is Pending qty to add to a delivery"
msgstr "C'è una quantità in sospeso da aggiungere a una consegna"
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Unplanned delivery"
msgstr "Consegna non pianificata"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__route_id
msgid "Use specific Route"
msgstr "Usa un percorso specifico"
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__manual_delivery_id
msgid "Wizard"
msgstr "Procedura guidata"
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery_line.py:0
#, python-format
msgid ""
"You can not deliver more than the remaining quantity. If you need to do so, "
"please edit the sale order first."
msgstr ""
"Non è possibile consegnare più della quantità rimanente. Se è necessario, "
"modificare prima l'ordine di vendita."
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/models/sale_order.py:0
#, python-format
msgid ""
"You can only change to/from manual delivery in a quote, not a confirmed order"
msgstr ""
"È possibile abilitare/disabilitare consegna manuale solo in un preventivo, "
"non un ordine già confermato"
#~ msgid "Last Modified on"
#~ msgstr "Ultima modifica il"

View File

@@ -0,0 +1,275 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_manual_delivery
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \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: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Cancel"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__commercial_partner_id
msgid "Commercial Partner"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Confirm"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_order_form
msgid "Create Delivery"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_wizard_manual_delivery_line
msgid "Create Manual Delivery"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Create Manually Delivery"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_uid
msgid "Created by"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__create_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__create_date
msgid "Created on"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__date_planned
#: model:ir.model.fields,field_description:sale_manual_delivery.field_procurement_group__date_planned
msgid "Date Planned"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__partner_id
msgid "Delivery Address"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__carrier_id
msgid "Delivery Method"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__has_pending_delivery
msgid "Delivery pending?"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__name
msgid "Description"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__display_name
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__display_name
msgid "Display Name"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.actions.act_window,help:sale_manual_delivery.action_order_line_delivery_tree
msgid "Here is a list of each sales order line to be invoiced."
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__id
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__id
msgid "ID"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order__manual_delivery
msgid ""
"If enabled, the deliveries are not created at SO confirmation. You need to "
"use the Create Delivery button in order to reserve and ship the goods."
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_uid
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__write_date
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery__route_id
msgid "Leave it blank to use the same route that is in the sale line"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__line_ids
msgid "Lines to validate"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_crm_team__manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order__manual_delivery
msgid "Manual Delivery"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_manual_delivery_line
msgid "Manual Delivery Line"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_ordered
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Ordered"
msgstr ""
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery.py:0
msgid "Please select one partner at a time"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.manual_delivery_wizard_form
msgid "Procured"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_procurement_group
msgid "Procurement Group"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__product_id
msgid "Product"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__quantity
msgid "Quantity"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity Procured"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_procured
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_procured
msgid "Quantity already planned or shipped (stock movements already created)"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_manual_delivery_line__qty_ordered
msgid "Quantity ordered in the related Sale Order"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "Quantity to Procure"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.actions.act_window,name:sale_manual_delivery.action_order_line_delivery_tree
#: model:ir.ui.menu,name:sale_manual_delivery.menu_delivery_sales_order_lines
msgid "Sale Lines to Deliver"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__order_line_id
msgid "Sale Order Line"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_sale_order_line
msgid "Sales Order Line"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid ""
"Sales Order Lines that are confirmed, done or in exception state and no "
"delivery is created to satisfy them"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_crm_team
msgid "Sales Team"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Search Undelivered Lines"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model,name:sale_manual_delivery.model_stock_move
msgid "Stock Move"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,help:sale_manual_delivery.field_sale_order_line__qty_to_procure
msgid "There is Pending qty to add to a delivery"
msgstr ""
#. module: sale_manual_delivery
#: model_terms:ir.ui.view,arch_db:sale_manual_delivery.view_sales_order_undelivered_line_filter
msgid "Unplanned delivery"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery__route_id
msgid "Use specific Route"
msgstr ""
#. module: sale_manual_delivery
#: model:ir.model.fields,field_description:sale_manual_delivery.field_manual_delivery_line__manual_delivery_id
msgid "Wizard"
msgstr ""
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/wizard/manual_delivery_line.py:0
msgid ""
"You can not deliver more than the remaining quantity. If you need to do so, "
"please edit the sale order first."
msgstr ""
#. module: sale_manual_delivery
#. odoo-python
#: code:addons/sale_manual_delivery/models/sale_order.py:0
msgid ""
"You can only change to/from manual delivery in a quote, not a confirmed "
"order"
msgstr ""

View File

@@ -0,0 +1,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import crm_team
from . import sale_order
from . import sale_order_line
from . import stock_move
from . import procurement_group

View File

@@ -0,0 +1,14 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class CrmTeam(models.Model):
_inherit = "crm.team"
manual_delivery = fields.Boolean(
help="If enabled, the deliveries are not created at SO confirmation. "
"You need to use the Create Delivery button in order to reserve and "
"ship the goods.",
)

View File

@@ -0,0 +1,11 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ProcurementGroup(models.Model):
_inherit = "procurement.group"
date_planned = fields.Date()

View File

@@ -0,0 +1,49 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = "sale.order"
manual_delivery = fields.Boolean(
help="If enabled, the deliveries are not created at SO confirmation. "
"You need to use the Create Delivery button in order to reserve "
"and ship the goods.",
)
has_pending_delivery = fields.Boolean(
string="Delivery pending?",
compute="_compute_delivery_pending",
)
def _compute_delivery_pending(self):
for rec in self:
lines_pending = rec.order_line.filtered(
lambda x: x.product_id.type != "service" and x.qty_to_procure > 0
)
rec.has_pending_delivery = bool(lines_pending)
@api.onchange("team_id")
def _onchange_team_id(self):
self.manual_delivery = self.team_id.manual_delivery
def action_manual_delivery_wizard(self):
self.ensure_one()
xmlid = "sale_manual_delivery.action_wizard_manual_delivery"
action = self.env["ir.actions.actions"]._for_xml_id(xmlid)
action["context"] = {"default_carrier_id": self.carrier_id.id}
return action
@api.constrains("manual_delivery")
def _check_manual_delivery(self):
if any(rec.state not in ["draft", "sent"] for rec in self):
raise UserError(
_(
"You can only change to/from manual delivery"
" in a quote, not a confirmed order"
)
)

View File

@@ -0,0 +1,148 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
qty_procured = fields.Float(
string="Quantity Procured",
help="Quantity already planned or shipped (stock movements already created)",
compute="_compute_qty_procured",
readonly=True,
store=True,
)
qty_to_procure = fields.Float(
string="Quantity to Procure",
help="There is Pending qty to add to a delivery",
compute="_compute_qty_to_procure",
store=True,
readonly=True,
)
@api.depends(
"move_ids.state",
"move_ids.scrapped",
"move_ids.product_uom_qty",
"move_ids.product_uom",
"move_ids.location_id",
"move_ids.location_dest_id",
)
def _compute_qty_procured(self):
"""
Computes the already planned quantities for the given sale order lines,
based on the existing stock.moves
"""
for line in self:
qty_procured = 0
if line.qty_delivered_method == "stock_move":
qty_procured = line._get_qty_procurement(previous_product_uom_qty=False)
line.qty_procured = qty_procured
@api.depends("product_uom_qty", "qty_procured")
def _compute_qty_to_procure(self):
"""Computes the remaining quantity to plan on sale order lines"""
for line in self:
line.qty_to_procure = line.product_uom_qty - line.qty_procured
def _get_procurement_group(self):
# Overload to get the procurement.group for the right date / partner
# Note: sale_manual_delivery is expected to be a manual.delivery record
manual_delivery = self.env.context.get("sale_manual_delivery")
if manual_delivery:
domain = [
("sale_id", "=", self.order_id.id),
("partner_id", "=", manual_delivery.partner_id.id),
]
if manual_delivery.date_planned:
domain += [
("date_planned", "=", manual_delivery.date_planned),
]
return self.env["procurement.group"].search(domain, limit=1)
else:
return super()._get_procurement_group()
def _prepare_procurement_group_vals(self):
# Overload to add manual.delivery fields to procurement.group
# Note: sale_manual_delivery is expected to be a manual.delivery record
res = super()._prepare_procurement_group_vals()
manual_delivery = self.env.context.get("sale_manual_delivery")
if manual_delivery:
res["partner_id"] = manual_delivery.partner_id.id
res["date_planned"] = manual_delivery.date_planned
return res
def _prepare_procurement_values(self, group_id=False):
# Overload to handle manual delivery date planned and route
# This method ultimately prepares stock.move vals as its result is sent
# to StockRule._get_stock_move_values.
# Note: sale_manual_delivery is expected to be a manual.delivery record
res = super()._prepare_procurement_values(group_id=group_id)
manual_delivery = self.env.context.get("sale_manual_delivery")
if manual_delivery:
if manual_delivery.date_planned:
res["date_planned"] = manual_delivery.date_planned
if manual_delivery.route_id:
# `_get_stock_move_values` expects a recordset
res["route_ids"] = manual_delivery.route_id
return res
def _action_launch_stock_rule_manual(self, previous_product_uom_qty=False):
# Note: sale_manual_delivery is expected to be a manual.delivery record
manual_delivery = self.env.context.get("sale_manual_delivery")
procurements = []
for line in self:
if line.state != "sale" or line.product_id.type not in ("consu", "product"):
continue
# Qty comes from the manual delivery wizard
# This is different than the original method
manual_line = manual_delivery.line_ids.filtered(
lambda mdl, ln=line: mdl.order_line_id == ln
)
if not manual_line.quantity:
continue
group_id = line._get_procurement_group()
if not group_id:
group_id = self.env["procurement.group"].create(
line._prepare_procurement_group_vals()
)
else:
# In case the procurement group is already created and the order was
# cancelled, we need to update certain values of the group.
# This part is different than the original method
if group_id.move_type != line.order_id.picking_policy:
group_id.write({"move_type": line.order_id.picking_policy})
values = line._prepare_procurement_values(group_id=group_id)
line_uom = line.product_uom
quant_uom = line.product_id.uom_id
product_qty, procurement_uom = line_uom._adjust_uom_quantities(
manual_line.quantity, quant_uom
)
procurements.append(
self.env["procurement.group"].Procurement(
line.product_id,
product_qty,
procurement_uom,
line.order_id.partner_shipping_id.property_stock_customer,
line.name,
line.order_id.name,
line.order_id.company_id,
values,
)
)
if procurements:
self.env["procurement.group"].run(procurements)
return True
def _action_launch_stock_rule(self, previous_product_uom_qty=False):
# Overload to skip launching stock rules on manual delivery lines
# We only launch them when this is called from the manual delivery wizard
# Note: sale_manual_delivery is expected to be a manual.delivery record
manual_delivery_lines = self.filtered("order_id.manual_delivery")
lines_to_launch = self - manual_delivery_lines
return super(SaleOrderLine, lines_to_launch)._action_launch_stock_rule(
previous_product_uom_qty=previous_product_uom_qty
)

View File

@@ -0,0 +1,37 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
def _get_new_picking_values(self):
# Overload to set carrier_id from the manual delivery wizard
# Note: sale_manual_delivery is expected to be a manual.delivery record
res = super()._get_new_picking_values()
manual_delivery = self.env.context.get("sale_manual_delivery")
if manual_delivery:
if manual_delivery.partner_id:
res["partner_id"] = manual_delivery.partner_id.id
if manual_delivery.carrier_id:
res["carrier_id"] = manual_delivery.carrier_id.id
return res
def _search_picking_for_assignation(self):
# Overload to filter carrier_id
# Note: sale_manual_delivery is expected to be a manual.delivery record
manual_delivery = self.env.context.get("sale_manual_delivery")
if manual_delivery:
# original domain used in super()
domain = self._search_picking_for_assignation_domain()
# Filter on carrier
if manual_delivery.carrier_id:
domain += [
("carrier_id", "=", manual_delivery.carrier_id.id),
]
return self.env["stock.picking"].search(domain, limit=1)
else:
return super()._search_picking_for_assignation()

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,13 @@
- Denis Leemann \<<denis.leemann@camptocamp.com>\>
- Joel Grand-Guillaume \<<joel.grandguillaume@camptocamp.com>\>
- Phuc Tran Thanh \<<phuc@trobz.com>\>
- Iván Todorovich \<<ivan.todorovich@gmail.com>\>
- [Trobz](https://trobz.com):
- nguyenhk \<<nguyenhk@trobz.com>\>
- [360ERP](https://www.360erp.com):
- Andrea Stirpe

View File

@@ -0,0 +1,2 @@
The migration of this module from 14.0 to 16.0 was financially supported
by Camptocamp

View File

@@ -0,0 +1,13 @@
The goal of this module is to allow the manual creation of delivery
slips. If activated, a sale order won't directly impact the stock. It
will not make a reservation on the stock. It allows the delivery and the
impact on stock to be done manually when needed. The goal is to be used
on long term projects where not all the material is shipped at once. As
you make many shipments, every time you make a new delivery, you can
choose an new carrier as well as a planned date. A wizard helps you to
chose what to deliver by showing you how much you already planned to
ship.
![Field](../static/description/field.png)
![Wizard](../static/description/wizard.png)

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_manual_delivery_all,access_manual_delivery_all,model_manual_delivery,stock.group_stock_user,1,1,1,1
access_manual_delivery_line_all,access_manual_delivery_line_all,model_manual_delivery_line,stock.group_stock_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_manual_delivery_all access_manual_delivery_all model_manual_delivery stock.group_stock_user 1 1 1 1
3 access_manual_delivery_line_all access_manual_delivery_line_all model_manual_delivery_line stock.group_stock_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,456 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="sale-manual-delivery">
<h1>Sale Manual Delivery</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:08bd49944495269b3be34ce7a56655f20ad1c2048c290be903c842989ac528fd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_manual_delivery"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_manual_delivery"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>The goal of this module is to allow the manual creation of delivery
slips. If activated, a sale order wont directly impact the stock. It
will not make a reservation on the stock. It allows the delivery and the
impact on stock to be done manually when needed. The goal is to be used
on long term projects where not all the material is shipped at once. As
you make many shipments, every time you make a new delivery, you can
choose an new carrier as well as a planned date. A wizard helps you to
chose what to deliver by showing you how much you already planned to
ship.</p>
<p><img alt="Field" src="https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_manual_delivery/static/description/field.png" /></p>
<p><img alt="Wizard" src="https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_manual_delivery/static/description/wizard.png" /></p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-4">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-5">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_manual_delivery%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-2">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-3">Authors</a></h3>
<ul class="simple">
<li>Camptocamp SA</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
<ul class="simple">
<li>Denis Leemann &lt;<a class="reference external" href="mailto:denis.leemann&#64;camptocamp.com">denis.leemann&#64;camptocamp.com</a>&gt;</li>
<li>Joel Grand-Guillaume &lt;<a class="reference external" href="mailto:joel.grandguillaume&#64;camptocamp.com">joel.grandguillaume&#64;camptocamp.com</a>&gt;</li>
<li>Phuc Tran Thanh &lt;<a class="reference external" href="mailto:phuc&#64;trobz.com">phuc&#64;trobz.com</a>&gt;</li>
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;gmail.com">ivan.todorovich&#64;gmail.com</a>&gt;</li>
<li><a class="reference external" href="https://trobz.com">Trobz</a>:<ul>
<li>nguyenhk &lt;<a class="reference external" href="mailto:nguyenhk&#64;trobz.com">nguyenhk&#64;trobz.com</a>&gt;</li>
</ul>
</li>
<li><a class="reference external" href="https://www.360erp.com">360ERP</a>:<ul>
<li>Andrea Stirpe</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-5">Other credits</a></h3>
<p>The migration of this module from 14.0 to 16.0 was financially supported
by Camptocamp</p>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_manual_delivery">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -0,0 +1 @@
from . import test_manual_delivery

View File

@@ -0,0 +1,531 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
from odoo.addons.sale.tests.common import TestSaleCommonBase
class TestSaleStock(TestSaleCommonBase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env.ref("base.res_partner_1")
cls.product = cls.env.ref("product.product_delivery_01")
cls.product2 = cls.env.ref("product.product_delivery_02")
cls.product3 = cls.env.ref("product.product_order_01")
cls.carrier1 = cls.env.ref("delivery.delivery_carrier")
cls.carrier2 = cls.env.ref("delivery.delivery_local_delivery")
cls.stock_location = cls.env.ref("stock.stock_location_stock")
cls.env["stock.quant"]._update_available_quantity(
cls.product, cls.stock_location, 100
)
cls.env.user_demo = cls.env.ref("base.user_demo")
def _manual_delivery_wizard(self, records, vals=None):
if not vals:
vals = {}
return (
self.env["manual.delivery"]
.with_context(
active_model=records._name,
active_ids=records.ids,
)
.create(vals)
)
def test_00_sale_manual_delivery(self):
"""
Test SO's manual delivery; we do it with a user without admin rights
"""
model_user_order = self.env["sale.order"].with_user(self.env.user_demo)
order = model_user_order.create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 5.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
)
],
"manual_delivery": True,
}
)
# confirm our standard so, check the picking
order.action_confirm()
self.assertFalse(
order.picking_ids,
'No picking should be created for "manual delivery" orders',
)
# Raise error when user try to modify sale order in confirmd stage.
with self.assertRaises(UserError):
order.write({"manual_delivery": False})
# open the manual delivery wizard
action = order.action_manual_delivery_wizard()
self.assertEqual(action["res_model"], "manual.delivery")
# create a manual delivery for all ordered quantity
self._manual_delivery_wizard(order).confirm()
# check picking is created
self.assertTrue(
order.picking_ids,
'Picking should be created after "manual delivery" wizard call',
)
# create a manual delivery, nothing left to ship
wizard = self._manual_delivery_wizard(order)
self.assertFalse(
wizard.line_ids,
"After picking creation for all products, "
"no lines should be left in the wizard",
)
wizard.confirm()
self.assertEqual(
len(order.picking_ids), 1.0, "Picking number should remain 1.0"
)
def test_01_sale_standard_delivery(self):
"""
Test SO's standard delivery
"""
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 5.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
)
],
"manual_delivery": False,
}
)
# confirm our standard so, check the picking
order.action_confirm()
self.assertTrue(
order.picking_ids,
'Picking should be created for "standard delivery" orders',
)
# deliver completely
pick = order.picking_ids
pick.action_assign()
pick.move_line_ids.write({"quantity": 5})
pick.button_validate()
# Check quantity delivered
del_qty = sum(sol.qty_delivered for sol in order.order_line)
self.assertEqual(del_qty, 5.0, "Delivery quantity doesn't match")
def test_02_sale_various_manual_delivery(self):
"""
Test SO's various manual delivery
"""
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 5.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
)
],
"manual_delivery": True,
}
)
# confirm our standard so, check the picking
order.action_confirm()
self.assertFalse(
order.picking_ids,
'No picking should be created for "manual delivery" orders',
)
# create a manual delivery for part of ordered quantity
wizard = self._manual_delivery_wizard(order)
wizard.line_ids.write({"quantity": 2.0})
wizard.confirm()
# checking has_pending_delivery for Create Delivery button to appear
self.assertTrue(order.has_pending_delivery)
# check picking is created
self.assertEqual(
len(order.picking_ids),
1,
'Picking should be created after "manual delivery" wizard call',
)
# deliver completely
pick = order.picking_ids
pick.action_assign()
pick.move_line_ids.write({"quantity": 2})
pick.button_validate()
# Check quantity delivered
del_qty = sum(sol.qty_delivered for sol in order.order_line)
self.assertEqual(del_qty, 2.0, "Delivery quantity doesn't match")
# a manual delivery with qty 0 shouldn't do anything
wizard = self._manual_delivery_wizard(order)
wizard.line_ids.write({"quantity": 0.0})
wizard.confirm()
self.assertEqual(
len(order.picking_ids),
1.0,
"No picking should've been created",
)
# try to create a manual delivery with more quantity than the ordered
wizard = self._manual_delivery_wizard(order)
with self.assertRaises(UserError):
wizard.line_ids.write({"quantity": 10.0})
wizard.confirm()
# create a manual delivery, 3.0 left to ship
wizard = self._manual_delivery_wizard(order)
wizard.line_ids.write({"quantity": 3.0})
wizard.confirm()
# checking has_pending_delivery for Create Delivery button to hide
self.assertFalse(order.has_pending_delivery)
self.assertEqual(
len(order.picking_ids),
2.0,
"Picking number doesn't match",
)
def test_03_sale_selected_lines(self):
"""
Test SO's various manual delivery
"""
order1 = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 1.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
)
],
"manual_delivery": True,
}
)
order2 = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product2.name,
"product_id": self.product2.id,
"product_uom_qty": 2.0,
"product_uom": self.product2.uom_id.id,
"price_unit": self.product2.list_price,
},
)
],
"manual_delivery": True,
}
)
order3 = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product3.name,
"product_id": self.product.id,
"product_uom_qty": 3.0,
"product_uom": self.product3.uom_id.id,
"price_unit": self.product3.list_price,
},
)
],
"manual_delivery": True,
}
)
# confirm our standard so, check the picking
order1.action_confirm()
order2.action_confirm()
order3.action_confirm()
some_lines = order1.order_line | order3.order_line
all_lines = order1.order_line | order2.order_line | order3.order_line
# create a manual delivery for part of ordered quantity
wizard = self._manual_delivery_wizard(some_lines)
self.assertEqual(sum(wizard.line_ids.mapped("quantity")), 4.0)
wizard.confirm()
# check picking is created
self.assertTrue(
order3.picking_ids,
'Picking should be created after "manual delivery" wizard call',
)
self.assertEqual(
len(order3.picking_ids.move_ids),
1,
"Different sales orders should still create different pickings",
)
self.assertFalse(
order2.picking_ids,
'Picking should not be created after "manual delivery" wizard call',
)
# test action undelivered
undelivered = self.env["sale.order.line"].search(
[
("qty_to_procure", ">", 0),
("state", "=", "sale"),
("id", "in", all_lines.ids),
]
)
self.assertEqual(
undelivered,
order2.order_line,
"Bad pending qty to deliver filter",
)
def test_03_sale_multi_delivery(self):
self.env["stock.quant"]._update_available_quantity(
self.product2, self.stock_location, 100
)
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 10.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
),
(
0,
0,
{
"name": self.product2.name,
"product_id": self.product2.id,
"product_uom_qty": 10.0,
"product_uom": self.product2.uom_id.id,
"price_unit": self.product2.list_price,
},
),
],
"manual_delivery": True,
}
)
# confirm our standard so, check the picking
order.action_confirm()
self.assertFalse(
order.picking_ids,
'No picking should be created for "manual delivery" orders',
)
# create a manual delivery for part of ordered quantity
date_now = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
wizard = self._manual_delivery_wizard(
order.order_line[0],
{"carrier_id": order.carrier_id.id, "date_planned": date_now},
)
wizard.line_ids.write({"quantity": 2.0})
wizard.confirm()
# check picking is created
self.assertEqual(
len(order.picking_ids),
1,
'Picking should be created after "manual delivery" wizard call',
)
first_picking = order.picking_ids
self.assertEqual(
first_picking.scheduled_date.replace(
hour=0, minute=0, second=0, microsecond=0
),
date_now,
)
# create a second manual delivery for next week
date_next_week = date_now + relativedelta(weeks=1)
wizard = self._manual_delivery_wizard(
order.order_line[1],
{"carrier_id": order.carrier_id.id, "date_planned": date_next_week},
)
wizard.line_ids.write({"quantity": 3.0})
wizard.confirm()
self.assertEqual(
len(order.picking_ids),
2,
"Sale Manual Delivery: second picking should be created after"
' "manual delivery" wizard call with different date',
)
second_picking = order.picking_ids - first_picking
self.assertEqual(
second_picking.scheduled_date.replace(
hour=0, minute=0, second=0, microsecond=0
),
date_next_week,
)
# create a third manual delivery for today (should be mixed with first)
new_date_now = datetime.now()
wizard = self._manual_delivery_wizard(
order.order_line[0],
{"carrier_id": order.carrier_id.id, "date_planned": new_date_now},
)
wizard.line_ids.write({"quantity": 5.0})
wizard.confirm()
self.assertEqual(
len(order.picking_ids),
2,
"Sale Manual Delivery: new moves should be merged in first picking"
' after "manual delivery" wizard call with same date',
)
self.assertEqual(
sum(first_picking.mapped("move_ids.product_uom_qty")),
7,
)
def test_04_sale_single_picking(self):
"""
Test SO's various manual delivery
"""
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 1.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
),
(
0,
0,
{
"name": self.product2.name,
"product_id": self.product2.id,
"product_uom_qty": 2.0,
"product_uom": self.product2.uom_id.id,
"price_unit": self.product2.list_price,
},
),
],
"manual_delivery": True,
}
)
# confirm our standard so, check the picking
order.action_confirm()
# create a manual delivery for part of ordered quantity
wizard = self._manual_delivery_wizard(order.order_line)
wizard.confirm()
self.assertEqual(
len(order.picking_ids), 1.0, "Delivery: picking number should be 1.0"
)
def test_05_sale_multi_carrier(self):
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"partner_invoice_id": self.partner.id,
"partner_shipping_id": self.partner.id,
"order_line": [
(
0,
0,
{
"name": self.product.name,
"product_id": self.product.id,
"product_uom_qty": 10.0,
"product_uom": self.product.uom_id.id,
"price_unit": self.product.list_price,
},
),
],
"manual_delivery": True,
"carrier_id": self.carrier1.id,
}
)
# confirm our standard so, check the picking
order.action_confirm()
# create a manual delivery for part of ordered quantity
wizard = self._manual_delivery_wizard(order, {"carrier_id": self.carrier1.id})
wizard.line_ids.write({"quantity": 2.0})
wizard.confirm()
# check picking is created
self.assertEqual(
len(order.picking_ids),
1,
'Picking should be created after "manual delivery" wizard call',
)
first_picking = order.picking_ids
self.assertEqual(
first_picking.carrier_id,
order.carrier_id,
"Picking carrier should be the one in the order",
)
# create a second manual delivery with a different carrier
wizard = self._manual_delivery_wizard(order, {"carrier_id": self.carrier2.id})
wizard.line_ids.write({"quantity": 2.0})
wizard.confirm()
self.assertEqual(
len(order.picking_ids),
2,
"A different picking should've been created, as the carrier is different",
)
second_picking = order.picking_ids - first_picking
self.assertEqual(
second_picking.carrier_id,
self.carrier2,
"Picking carrier should be the one selected",
)
# create a third manual delivery for (should be mixed with first)
wizard = self._manual_delivery_wizard(order, {"carrier_id": self.carrier1.id})
wizard.line_ids.write({"quantity": 2.0})
wizard.confirm()
self.assertEqual(
len(order.picking_ids),
2,
"The first picking should be re-used",
)

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="crm_team_view_form" model="ir.ui.view">
<field name="name">crm.team.form</field>
<field name='model'>crm.team</field>
<field name='inherit_id' ref="sales_team.crm_team_view_form" />
<field name='arch' type="xml">
<group name="left" position="inside">
<field name='manual_delivery' />
</group>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.form</field>
<field name="model">sale.order</field>
<field name='inherit_id' ref="sale.view_order_form" />
<field name='arch' type="xml">
<button name="action_quotation_send" position="before">
<button
type="object"
name="action_manual_delivery_wizard"
class="btn btn-primary"
string="Create Delivery"
context="{'active_model': 'sale.order', 'active_id': id, 'active_ids': [id]}"
invisible="not has_pending_delivery or not manual_delivery or state != 'sale'"
/>
</button>
<group name="sale_shipping" position="inside">
<field name="manual_delivery" widget="boolean_toggle" />
<field name="has_pending_delivery" invisible="0" />
</group>
</field>
</record>
<record id="view_order_line_tree" model="ir.ui.view">
<field name="name">sale.order.line.tree</field>
<field name="model">sale.order.line</field>
<field name="inherit_id" ref="sale.view_order_line_tree" />
<field name="arch" type="xml">
<field name="qty_delivered" position="before">
<field name="qty_procured" />
<field name="qty_to_procure" />
</field>
</field>
</record>
<record id="view_sales_order_undelivered_line_filter" model="ir.ui.view">
<field name="name">sale.order.undelivered.line</field>
<field name="model">sale.order.line</field>
<field name="arch" type="xml">
<search string="Search Undelivered Lines">
<field name="order_id" />
<separator />
<filter
string="Unplanned delivery"
name="filter_unplanned"
domain="[('qty_to_procure', '!=', 0), ('state', '=', 'sale')]"
help="Sales Order Lines that are confirmed, done or in exception state and no delivery is created to satisfy them"
/>
</search>
</field>
</record>
<record id="action_order_line_delivery_tree" model="ir.actions.act_window">
<field name="name">Sale Lines to Deliver</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order.line</field>
<field name="view_mode">list</field>
<field name="search_view_id" ref="view_sales_order_undelivered_line_filter" />
<field name="context">{"search_default_filter_unplanned":1}</field>
<field name="filter" eval="True" />
<field name="help" type="html">
<p>Here is a list of each sales order line to be invoiced.</p>
</field>
</record>
<menuitem
id="menu_delivery_sales_order_lines"
parent="stock.menu_stock_warehouse_mgmt"
action="action_order_line_delivery_tree"
sequence="99"
groups="stock.group_stock_user"
/>
</odoo>

View File

@@ -0,0 +1,2 @@
from . import manual_delivery
from . import manual_delivery_line

View File

@@ -0,0 +1,91 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class ManualDelivery(models.TransientModel):
_name = "manual.delivery"
_description = "Manual Delivery"
_order = "create_date desc"
@api.model
def default_get(self, fields):
res = super().default_get(fields)
# Get lines from active_model if it's a sale.order / sale.order.line
sale_lines = self.env["sale.order.line"]
active_model = self.env.context["active_model"]
if active_model == "sale.order.line":
sale_ids = self.env.context["active_ids"] or []
sale_lines = self.env["sale.order.line"].browse(sale_ids)
elif active_model == "sale.order":
sale_ids = self.env.context["active_ids"] or []
sale_lines = self.env["sale.order"].browse(sale_ids).mapped("order_line")
if len(sale_lines.mapped("order_id.partner_id")) > 1:
raise UserError(_("Please select one partner at a time"))
if sale_lines:
# Get partner from those lines
partner = sale_lines.mapped("order_id.partner_id")
res["partner_id"] = partner.id
res["commercial_partner_id"] = partner.commercial_partner_id.id
# Convert to manual.delivery.lines
res["line_ids"] = [
(
0,
0,
{
"order_line_id": line.id,
"name": line.name,
"product_id": line.product_id.id,
"qty_ordered": line.product_uom_qty,
"qty_procured": line.qty_procured,
"quantity": line.qty_to_procure,
},
)
for line in sale_lines
if line.qty_to_procure and line.product_id.type != "service"
]
return res
commercial_partner_id = fields.Many2one(
"res.partner",
required=True,
readonly=True,
ondelete="cascade",
)
partner_id = fields.Many2one(
"res.partner",
string="Delivery Address",
domain="['|',"
"('id', '=', commercial_partner_id),"
"('parent_id', '=', commercial_partner_id)]",
ondelete="cascade",
)
carrier_id = fields.Many2one(
"delivery.carrier",
string="Delivery Method",
ondelete="cascade",
)
route_id = fields.Many2one(
"stock.route",
string="Use specific Route",
domain=[("sale_selectable", "=", True)],
ondelete="cascade",
help="Leave it blank to use the same route that is in the sale line",
)
line_ids = fields.One2many(
"manual.delivery.line",
"manual_delivery_id",
string="Lines to validate",
)
date_planned = fields.Datetime()
def confirm(self):
"""Creates the manual procurements"""
self.ensure_one()
sale_order_lines = self.line_ids.mapped("order_line_id")
sale_order_lines.with_context(
sale_manual_delivery=self
)._action_launch_stock_rule_manual()

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id='manual_delivery_wizard_form' model='ir.ui.view'>
<field name="name">manual.delivery.wizard.form</field>
<field name="model">manual.delivery</field>
<field name='arch' type='xml'>
<form string="Create Manually Delivery" name="manual_procurment_form">
<sheet>
<group>
<group>
<field
name="commercial_partner_id"
invisible="1"
force_save="1"
/>
<field name="partner_id" />
<field name="date_planned" />
</group>
<group>
<field name="carrier_id" />
<field name="route_id" groups="stock.group_adv_location" />
</group>
</group>
<field name='line_ids'>
<list editable="bottom" create="0">
<field
name="order_line_id"
column_invisible="1"
force_save="1"
/>
<field name="product_id" />
<field name="name" />
<field name="qty_ordered" string="Ordered" />
<field name="qty_procured" string="Procured" />
<field name="quantity" />
</list>
</field>
</sheet>
<footer>
<button
type="object"
name="confirm"
string="Confirm"
class="btn-primary"
/>
<button string="Cancel" class="btn-default" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_wizard_manual_delivery" model="ir.actions.act_window">
<field name="name">Create Manual Delivery</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">manual.delivery</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="sale.model_sale_order" />
</record>
<record id="action_wizard_manual_delivery_line" model="ir.actions.act_window">
<field name="name">Create Manual Delivery</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">manual.delivery</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="sale.model_sale_order_line" />
</record>
</odoo>

View File

@@ -0,0 +1,56 @@
# Copyright 2017 Denis Leemann, Camptocamp SA
# Copyright 2021 Iván Todorovich, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import float_compare
class ManualDeliveryLine(models.TransientModel):
_name = "manual.delivery.line"
_description = "Manual Delivery Line"
manual_delivery_id = fields.Many2one(
"manual.delivery",
string="Wizard",
ondelete="cascade",
required=True,
readonly=True,
)
order_line_id = fields.Many2one(
"sale.order.line",
string="Sale Order Line",
required=True,
readonly=True,
ondelete="cascade",
)
product_id = fields.Many2one(related="order_line_id.product_id")
name = fields.Text(related="order_line_id.name")
qty_ordered = fields.Float(
string="Ordered",
related="order_line_id.product_uom_qty",
help="Quantity ordered in the related Sale Order",
readonly=True,
)
qty_procured = fields.Float(related="order_line_id.qty_procured")
quantity = fields.Float()
@api.constrains("quantity")
def _check_quantity(self):
"""Prevent delivering more than the ordered quantity"""
if any(
float_compare(
line.quantity,
line.qty_ordered - line.qty_procured,
precision_rounding=line.product_id.uom_id.rounding,
)
> 0.00
for line in self
):
raise ValidationError(
_(
"You can not deliver more than the remaining quantity. "
"If you need to do so, please edit the sale order first."
)
)