addons portados en el repo de ecocentral
This commit is contained in:
parent
f2194c5367
commit
65818b0155
72 changed files with 2474 additions and 3 deletions
1
stock_picking_report_undelivered_product/__init__.py
Normal file
1
stock_picking_report_undelivered_product/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
22
stock_picking_report_undelivered_product/__manifest__.py
Normal file
22
stock_picking_report_undelivered_product/__manifest__.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# Copyright 2026 Ecocentral, Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
"name": "Stock Picking Report Undelivered Product",
|
||||
"summary": "Display undelivered product lines on the delivery slip report",
|
||||
"version": "18.0.1.0.0",
|
||||
"development_status": "Beta",
|
||||
"author": "Tecnativa, Ecocentral, Criptomart, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/Ecocentral/ecocentral",
|
||||
"category": "Warehouse",
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"stock",
|
||||
],
|
||||
"data": [
|
||||
"views/product_views.xml",
|
||||
"views/res_partner_views.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
"views/report_deliveryslip.xml",
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from . import product_template
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
display_undelivered_in_picking = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
undelivered_product_slip_report_method = fields.Selection(
|
||||
related="company_id.undelivered_product_slip_report_method",
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
undelivered_product_slip_report_method = fields.Selection(
|
||||
[
|
||||
("all", "Display all undelivered product lines"),
|
||||
(
|
||||
"partially_undelivered",
|
||||
"Display only partially undelivered product lines",
|
||||
),
|
||||
(
|
||||
"completely_undelivered",
|
||||
"Display only completely undelivered product lines",
|
||||
),
|
||||
],
|
||||
string="Method to display undelivered product lines in report picking",
|
||||
default="all",
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
display_undelivered_in_picking = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
splitted_stock_move_orig_id = fields.Many2one(
|
||||
comodel_name="stock.move", string="Splitted from", readonly=True
|
||||
)
|
||||
|
||||
def _prepare_move_split_vals(self, qty):
|
||||
"""Store origin stock move which created the splitted move."""
|
||||
vals = super()._prepare_move_split_vals(qty)
|
||||
vals["splitted_stock_move_orig_id"] = self.id
|
||||
return vals
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright 2022 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = "stock.move.line"
|
||||
|
||||
def _get_aggregated_product_quantities(self, **kwargs):
|
||||
"""Odoo displays undelivered products in the main table too.
|
||||
|
||||
Remove them from the aggregated dict so they only show up in the
|
||||
bottom "remaining products" table.
|
||||
"""
|
||||
aggregated_move_lines = super()._get_aggregated_product_quantities(**kwargs)
|
||||
keys_to_remove = {
|
||||
key
|
||||
for key, values in aggregated_move_lines.items()
|
||||
if not values["quantity"]
|
||||
}
|
||||
for key in keys_to_remove:
|
||||
aggregated_move_lines.pop(key, None)
|
||||
return aggregated_move_lines
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Muestra en el informe de albarán (delivery slip) las líneas de producto que
|
||||
no se han servido: aquellas cuyo ``stock.move`` acaba cancelado (sin
|
||||
backorder) porque el producto pedido no llegó a entregarse, total o
|
||||
parcialmente.
|
||||
|
||||
Configurable por partner y por producto (casilla "Mostrar no servidos en el
|
||||
albarán", activada por defecto), y por compañía (mostrar todas las líneas no
|
||||
servidas, solo las parcialmente servidas, o solo las totalmente no servidas).
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
En Inventario > Configuración > Ajustes, sección "Report picking undelivered
|
||||
products", elegir el método (todas / solo parciales / solo completas).
|
||||
|
||||
En la ficha del partner o del producto, casilla "Display Undelivered In
|
||||
Picking" para excluirlo del bloque de no servidos.
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import test_stock_picking_report_undelivered_product
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
# Copyright 2020 Tecnativa - Sergio Teruel
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from odoo.tests import Form
|
||||
from odoo.tests import TransactionCase
|
||||
|
||||
|
||||
class TestStockPickingReportUndeliveredProduct(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.ResPartner = cls.env["res.partner"]
|
||||
cls.ProductProduct = cls.env["product.product"]
|
||||
cls.StockPicking = cls.env["stock.picking"]
|
||||
cls.StockQuant = cls.env["stock.quant"]
|
||||
cls.BackOrderWiz = cls.env["stock.backorder.confirmation"]
|
||||
cls.warehouse = cls.env.ref("stock.warehouse0")
|
||||
cls.picking_type_out = cls.env.ref("stock.picking_type_out")
|
||||
|
||||
cls.partner_display = cls.ResPartner.create(
|
||||
{"name": "Partner for test display", "display_undelivered_in_picking": True}
|
||||
)
|
||||
cls.partner_no_display = cls.ResPartner.create(
|
||||
{
|
||||
"name": "Partner for test on display",
|
||||
"display_undelivered_in_picking": False,
|
||||
}
|
||||
)
|
||||
|
||||
cls.product_display = cls.ProductProduct.create(
|
||||
{
|
||||
"name": "Test product undelivered display",
|
||||
"display_undelivered_in_picking": True,
|
||||
"type": "consu",
|
||||
"is_storable": True,
|
||||
}
|
||||
)
|
||||
cls.product_no_display = cls.ProductProduct.create(
|
||||
{
|
||||
"name": "Test product undelivered no display",
|
||||
"display_undelivered_in_picking": False,
|
||||
"type": "consu",
|
||||
"is_storable": True,
|
||||
}
|
||||
)
|
||||
cls.product_no_display_wo_stock = cls.ProductProduct.create(
|
||||
{
|
||||
"name": "Test product undelivered no display without stock",
|
||||
"display_undelivered_in_picking": False,
|
||||
"type": "consu",
|
||||
"is_storable": True,
|
||||
}
|
||||
)
|
||||
cls.StockQuant.create(
|
||||
{
|
||||
"product_id": cls.product_no_display.id,
|
||||
"location_id": cls.warehouse.lot_stock_id.id,
|
||||
"quantity": 2000,
|
||||
}
|
||||
)
|
||||
|
||||
def _create_picking(self, partner):
|
||||
picking_form = Form(self.StockPicking)
|
||||
picking_form.picking_type_id = self.picking_type_out
|
||||
picking_form.partner_id = partner
|
||||
|
||||
with picking_form.move_ids_without_package.new() as line:
|
||||
line.product_id = self.product_display
|
||||
line.product_uom_qty = 50.00
|
||||
with picking_form.move_ids_without_package.new() as line:
|
||||
line.product_id = self.product_no_display
|
||||
line.product_uom_qty = 20.00
|
||||
with picking_form.move_ids_without_package.new() as line:
|
||||
line.product_id = self.product_no_display_wo_stock
|
||||
line.product_uom_qty = 20.00
|
||||
return picking_form.save()
|
||||
|
||||
def _transfer_picking_no_backorder(self, picking):
|
||||
# Transfer picking with no create backorder option
|
||||
picking.move_line_ids.picked = True
|
||||
backorder_wizard = self.BackOrderWiz.create({"pick_ids": [(4, picking.id)]})
|
||||
backorder_wizard.with_context(
|
||||
button_validate_picking_ids=picking.id
|
||||
).process_cancel_backorder()
|
||||
|
||||
def _render(self, picking):
|
||||
return self.env["ir.actions.report"]._render_qweb_html(
|
||||
"stock.report_deliveryslip", picking.ids
|
||||
)
|
||||
|
||||
def test_displayed_customer(self):
|
||||
picking = self._create_picking(self.partner_display)
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
picking.move_line_ids.filtered(
|
||||
lambda ml: ml.product_id == self.product_display
|
||||
).quantity = 10.00
|
||||
self._transfer_picking_no_backorder(picking)
|
||||
res = self._render(picking)
|
||||
self.assertIn("undelivered_product", str(res[0]))
|
||||
|
||||
def test_no_displayed_customer(self):
|
||||
picking = self._create_picking(self.partner_no_display)
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
picking.move_line_ids.filtered(
|
||||
lambda ml: ml.product_id == self.product_display
|
||||
).quantity = 10.00
|
||||
self._transfer_picking_no_backorder(picking)
|
||||
res = self._render(picking)
|
||||
self.assertNotIn("undelivered_product", str(res[0]))
|
||||
|
||||
def test_no_displayed_product(self):
|
||||
picking = self._create_picking(self.partner_display)
|
||||
picking.move_ids.filtered(
|
||||
lambda move: move.product_id == self.product_display
|
||||
).unlink()
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
picking.move_line_ids.quantity = 10.00
|
||||
self._transfer_picking_no_backorder(picking)
|
||||
res = self._render(picking)
|
||||
self.assertNotIn("undelivered_product", str(res[0]))
|
||||
|
||||
def test_picking_report_method(self):
|
||||
product = self.ProductProduct.create(
|
||||
{
|
||||
"name": "test01",
|
||||
"display_undelivered_in_picking": True,
|
||||
"type": "consu",
|
||||
"is_storable": True,
|
||||
}
|
||||
)
|
||||
product2 = self.ProductProduct.create(
|
||||
{
|
||||
"name": "test02",
|
||||
"display_undelivered_in_picking": True,
|
||||
"type": "consu",
|
||||
"is_storable": True,
|
||||
}
|
||||
)
|
||||
self.StockQuant.create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"location_id": self.warehouse.lot_stock_id.id,
|
||||
"quantity": 2000,
|
||||
}
|
||||
)
|
||||
picking_form = Form(self.StockPicking)
|
||||
picking_form.picking_type_id = self.picking_type_out
|
||||
picking_form.partner_id = self.partner_display
|
||||
with picking_form.move_ids_without_package.new() as line:
|
||||
line.product_id = product
|
||||
line.product_uom_qty = 50.00
|
||||
with picking_form.move_ids_without_package.new() as line:
|
||||
line.product_id = product2
|
||||
line.product_uom_qty = 20.00
|
||||
picking = picking_form.save()
|
||||
|
||||
picking.action_confirm()
|
||||
picking.action_assign()
|
||||
picking.move_line_ids.filtered(lambda ml: ml.product_id == product).quantity = (
|
||||
10.00
|
||||
)
|
||||
self._transfer_picking_no_backorder(picking)
|
||||
|
||||
# Empty setting method field
|
||||
picking.company_id.undelivered_product_slip_report_method = False
|
||||
res = self._render(picking)
|
||||
self.assertIn("test02", str(res[0]))
|
||||
|
||||
# Print all undelivered lines, partial and completely lines
|
||||
picking.company_id.undelivered_product_slip_report_method = "all"
|
||||
res = self._render(picking)
|
||||
self.assertIn("test02", str(res[0]))
|
||||
|
||||
# Print only partial undelivered lines
|
||||
picking.company_id.undelivered_product_slip_report_method = (
|
||||
"partially_undelivered"
|
||||
)
|
||||
res = self._render(picking)
|
||||
self.assertNotIn("test02", str(res[0]))
|
||||
|
||||
# Print only completely undelivered lines
|
||||
picking.company_id.undelivered_product_slip_report_method = (
|
||||
"completely_undelivered"
|
||||
)
|
||||
res = self._render(picking)
|
||||
self.assertNotIn("partially_undelivered_line", str(res[0]))
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_template_property_form" model="ir.ui.view">
|
||||
<field
|
||||
name="name"
|
||||
>product.template.form.inherit.undelivered.product</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="stock.view_template_property_form" />
|
||||
<field name="arch" type="xml">
|
||||
<group name="group_lots_and_weight" position="inside">
|
||||
<field name="display_undelivered_in_picking" />
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<template id="undeliverd_product">
|
||||
<t
|
||||
t-if="o.partner_id.display_undelivered_in_picking and o.state == 'done'"
|
||||
>
|
||||
<!-- Get stock moves from backorder according to setting values -->
|
||||
<t
|
||||
t-if="not o.company_id.undelivered_product_slip_report_method or o.company_id.undelivered_product_slip_report_method == 'all'"
|
||||
>
|
||||
<t
|
||||
t-set="undelivered_moves"
|
||||
t-value="o.mapped('move_ids').filtered(lambda l: l.quantity == 0.0 and l.state == 'cancel' and l.product_id.display_undelivered_in_picking)"
|
||||
/>
|
||||
</t>
|
||||
<t
|
||||
t-if="o.company_id.undelivered_product_slip_report_method == 'partially_undelivered'"
|
||||
>
|
||||
<t
|
||||
t-set="undelivered_moves"
|
||||
t-value="o.mapped('move_ids').filtered(lambda l: l.state == 'cancel' and l.product_id.display_undelivered_in_picking and l.splitted_stock_move_orig_id)"
|
||||
/>
|
||||
</t>
|
||||
<t
|
||||
t-if="o.company_id.undelivered_product_slip_report_method == 'completely_undelivered'"
|
||||
>
|
||||
<t
|
||||
t-set="undelivered_moves"
|
||||
t-value="o.mapped('move_ids').filtered(lambda l: l.state == 'cancel' and l.product_id.display_undelivered_in_picking and not l.splitted_stock_move_orig_id)"
|
||||
/>
|
||||
</t>
|
||||
<table
|
||||
t-if="undelivered_moves"
|
||||
class="table table-sm table-bordered mt0"
|
||||
id="undelivered_product"
|
||||
>
|
||||
<thead>
|
||||
<tr><th colspan="7"><strong
|
||||
>Remaining products (not delivered yet)</strong></th></tr>
|
||||
<tr>
|
||||
<th><strong>Reference</strong></th>
|
||||
<th><strong>Product</strong></th>
|
||||
<th class="text-end"><strong>Quantity</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t
|
||||
t-foreach="undelivered_moves.sorted(key=lambda l: (l.product_id.default_code or '', l.product_id.name))"
|
||||
t-as="undelivered_move"
|
||||
>
|
||||
<tr
|
||||
t-att-class="'partially_undelivered_line' if undelivered_move.splitted_stock_move_orig_id else 'completely_undelivered_line'"
|
||||
>
|
||||
<td>
|
||||
<span
|
||||
t-field="undelivered_move.product_id.default_code"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
t-field="undelivered_move.product_id.name"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span
|
||||
t-field="undelivered_move.product_uom_qty"
|
||||
/> <span
|
||||
groups="uom.group_uom"
|
||||
t-field="undelivered_move.product_uom"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template
|
||||
id="report_delivery_document"
|
||||
inherit_id="stock.report_delivery_document"
|
||||
priority="200"
|
||||
>
|
||||
<xpath expr="//table[@name='stock_move_line_table']" position="after">
|
||||
<t
|
||||
t-call="stock_picking_report_undelivered_product.undeliverd_product"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field
|
||||
name="name"
|
||||
>res.config.settings.view.form.inherit.undelivered.product</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="stock.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<block name="operations_setting_container" position="inside">
|
||||
<setting
|
||||
id="undelivered_product_slip_report_method"
|
||||
string="Undelivered products"
|
||||
help="Method to display undelivered product lines in the delivery slip report"
|
||||
>
|
||||
<field
|
||||
name="undelivered_product_slip_report_method"
|
||||
class="o_light_label"
|
||||
widget="selection"
|
||||
/>
|
||||
</setting>
|
||||
</block>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_partner_stock_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.form.inherit.undelivered.product</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="stock.view_partner_stock_form" />
|
||||
<field name="arch" type="xml">
|
||||
<group name="inventory" position="inside">
|
||||
<field name="display_undelivered_in_picking" />
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue