[IMP] (purchase_)stock: suppliers information in replenishment

Infobox to help the user with the replenishment. Allow those actions:
- See the different supplier with their lead time, price and quantity
- Be able to select a supplier and set it on the orderpoint
⁻ See the last delivery by mmonth for the product
- Last purchase date for each supplier

Technicaly it use a wizard and a fields char with JSON used by
a widget to display a static template. Also it creates directly
the wizard in backend instead of just let the view manage the new
object since we need a button on a one2many and if the records do
not have an id, it's not possilbe to use it.

closes odoo/odoo#72039

Task: 2519761
Related: odoo/upgrade#2596
Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
This commit is contained in:
Arnold Moyaux
2021-06-29 07:41:12 +00:00
parent 0ed38652a4
commit 6eaa4a2ae3
21 changed files with 318 additions and 49 deletions
+2 -2
View File
@@ -111,11 +111,11 @@ class StockRule(models.Model):
date_planned = date_planned - relativedelta(hours=1)
return date_planned
def _get_lead_days(self, product):
def _get_lead_days(self, product, **values):
"""Add the product and company manufacture delay to the cumulative delay
and cumulative description.
"""
delay, delay_description = super()._get_lead_days(product)
delay, delay_description = super()._get_lead_days(product, **values)
bypass_delay_description = self.env.context.get('bypass_delay_description')
manufacture_rule = self.filtered(lambda r: r.action == 'manufacture')
if not manufacture_rule:
+1
View File
@@ -4,6 +4,7 @@
from . import models
from . import report
from . import populate
from . import wizard
from odoo import api, SUPERUSER_ID
+1
View File
@@ -25,6 +25,7 @@
'report/purchase_report_templates.xml',
'report/report_stock_forecasted.xml',
'report/report_stock_rule.xml',
'wizard/stock_replenishment_info.xml'
],
'demo': [
'data/purchase_stock_demo.xml',
+46
View File
@@ -69,3 +69,49 @@ class ProductProduct(models.Model):
qty_by_product_location[(product.id, location.id)] += product_qty
qty_by_product_wh[(product.id, location.warehouse_id.id)] += product_qty
return qty_by_product_location, qty_by_product_wh
class SupplierInfo(models.Model):
_inherit = 'product.supplierinfo'
last_purchase_date = fields.Date('Last Purchase', compute='_compute_last_purchase_date')
show_set_supplier_button = fields.Boolean(
'Show Set Supplier Button', compute='_compute_show_set_supplier_button')
def _compute_last_purchase_date(self):
self.last_purchase_date = False
purchases = self.env['purchase.order'].search([
('state', 'in', ('purchase', 'done')),
('order_line.product_id', 'in',
self.product_tmpl_id.product_variant_ids.ids),
('partner_id', 'in', self.name.ids),
], order='date_order')
for supplier in self:
products = supplier.product_tmpl_id.product_variant_ids
for purchase in purchases:
if purchase.partner_id != supplier.name:
continue
if not (products & purchase.order_line.product_id):
continue
supplier.last_purchase_date = purchase.date_order
break
def _compute_show_set_supplier_button(self):
self.show_set_supplier_button = True
orderpoint_id = self.env.context.get('default_orderpoint_id')
orderpoint = self.env['stock.warehouse.orderpoint'].browse(orderpoint_id)
if orderpoint_id:
self.filtered(
lambda s: s.id == orderpoint.supplier_id.id
).show_set_supplier_button = False
def action_set_supplier(self):
self.ensure_one()
orderpoint_id = self.env.context.get('orderpoint_id')
orderpoint = self.env['stock.warehouse.orderpoint'].browse(orderpoint_id)
if not orderpoint:
return
orderpoint.route_id = self.env['stock.rule'].search([('action', '=', 'buy')], limit=1).route_id.id
orderpoint.supplier_id = self
if orderpoint.qty_to_order < self.min_qty:
orderpoint.qty_to_order = self.min_qty
+10
View File
@@ -193,6 +193,10 @@ class Orderpoint(models.Model):
""" Extend to add more depends values """
return super()._compute_qty()
@api.depends('supplier_id')
def _compute_lead_days(self):
return super()._compute_lead_days()
@api.depends('route_id')
def _compute_show_suppplier(self):
buy_route = []
@@ -216,6 +220,12 @@ class Orderpoint(models.Model):
return result
def _get_lead_days_values(self):
values = super()._get_lead_days_values()
if self.supplier_id:
values['supplierinfo'] = self.supplier_id
return values
def _get_replenishment_order_notification(self):
self.ensure_one()
order = self.env['purchase.order.line'].search([
+3 -3
View File
@@ -141,16 +141,16 @@ class StockRule(models.Model):
procurement.values, po))
self.env['purchase.order.line'].sudo().create(po_line_values)
def _get_lead_days(self, product):
def _get_lead_days(self, product, **values):
"""Add the company security lead time, days to purchase and the supplier
delay to the cumulative delay and cumulative description. The days to
purchase and company lead time are always displayed for onboarding
purpose in order to indicate that those options are available.
"""
delay, delay_description = super()._get_lead_days(product)
delay, delay_description = super()._get_lead_days(product, **values)
bypass_delay_description = self.env.context.get('bypass_delay_description')
buy_rule = self.filtered(lambda r: r.action == 'buy')
seller = product.with_company(buy_rule.company_id)._select_seller()
seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller()
if not buy_rule or not seller:
return delay, delay_description
buy_rule.ensure_one()
+3 -3
View File
@@ -30,10 +30,10 @@
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable"/>
<field name="arch" type="xml">
<field name="route_id" position="after">
<xpath expr="//button[@name='action_stock_replenishment_info']" position="before">
<field name="show_supplier" invisible="1"/>
<field name="supplier_id" optional="hide" attrs="{'invisible': [('show_supplier', '=', False)]}" options="{'no_create': True}"/>
</field>
<field name="supplier_id" string="Vendor" optional="show" attrs="{'invisible': [('show_supplier', '=', False)]}" options="{'no_create': True}"/>
</xpath>
</field>
</record>
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_replenishment_info
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockReplenishmentInfo(models.TransientModel):
_inherit = 'stock.replenishment.info'
_description = 'Stock supplier replenishment information'
supplierinfo_id = fields.Many2one(related='orderpoint_id.supplier_id')
supplierinfo_ids = fields.Many2many(
'product.supplierinfo', compute='_compute_supplierinfo_ids',
store=True)
@api.depends('orderpoint_id')
def _compute_supplierinfo_ids(self):
for replenishment_info in self:
replenishment_info.supplierinfo_ids = replenishment_info.product_id.seller_ids
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_supplierinfo_replenishment_tree_view" model="ir.ui.view">
<field name="name">product.supplierinfo.replenishment.tree.view</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_tree_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<field name="delay" position="after">
<field name="show_set_supplier_button" invisible="1"/>
<field name="last_purchase_date"/>
<button name="action_set_supplier" type="object" string="Set as Supplier" class="btn btn-link"
context="{'orderpoint_id': parent.orderpoint_id, 'stock_replenishment_info_id': parent.id}"
attrs="{'invisible': [('show_set_supplier_button', '=', False)]}"/>
</field>
<xpath expr="//tree" position="attributes">
<attribute name="decoration-bf">parent.supplierinfo_id == id</attribute>
</xpath>
<field name="min_qty" position="attributes">
<attribute name="decoration-danger">min_qty &gt; parent.qty_to_order</attribute>
</field>
<field name="company_id" position="attributes">
<attribute name="optional">hide</attribute>
</field>
<field name="delay" position="attributes">
<attribute name="optional">show</attribute>
</field>
</field>
</record>
<record id="view_stock_replenishment_info_stock_purchase_inherit" model="ir.ui.view">
<field name="name">stock.replenishment.information.purchase.stock.inherit</field>
<field name="model">stock.replenishment.info</field>
<field name="inherit_id" ref="stock.view_stock_replenishment_info"/>
<field name="arch" type="xml">
<xpath expr="//footer" position="before">
<field name="supplierinfo_id" invisible="1"/>
<field name="supplierinfo_ids" readonly="1" context="{'tree_view_ref': 'purchase_stock.product_supplierinfo_replenishment_tree_view'}"/>
</xpath>
</field>
</record>
</odoo>
+1
View File
@@ -54,6 +54,7 @@
'wizard/stock_backorder_confirmation_views.xml',
'wizard/stock_quantity_history.xml',
'wizard/stock_request_count.xml',
'wizard/stock_replenishment_info.xml',
'wizard/stock_rules_report_views.xml',
'wizard/stock_warn_insufficient_qty_views.xml',
'wizard/product_replenish_views.xml',
+28 -26
View File
@@ -6,7 +6,6 @@ from collections import defaultdict
from datetime import datetime, time
from dateutil import relativedelta
from itertools import groupby
from json import dumps
from psycopg2 import OperationalError
from odoo import SUPERUSER_ID, _, api, fields, models, registry
@@ -92,7 +91,6 @@ class StockWarehouseOrderpoint(models.Model):
allowed_location_ids = fields.One2many(comodel_name='stock.location', compute='_compute_allowed_location_ids')
rule_ids = fields.Many2many('stock.rule', string='Rules used', compute='_compute_rules')
json_lead_days_popover = fields.Char(compute='_compute_json_popover')
lead_days_date = fields.Date(compute='_compute_lead_days')
allowed_route_ids = fields.Many2many('stock.location.route', compute='_compute_allowed_route_ids')
route_id = fields.Many2one(
@@ -127,36 +125,14 @@ class StockWarehouseOrderpoint(models.Model):
])
self.allowed_route_ids = route_by_product.ids
@api.depends('rule_ids', 'product_id.seller_ids', 'product_id.seller_ids.delay')
def _compute_json_popover(self):
for orderpoint in self:
if not orderpoint.product_id or not orderpoint.location_id:
orderpoint.json_lead_days_popover = False
continue
dummy, lead_days_description = orderpoint.rule_ids._get_lead_days(orderpoint.product_id)
orderpoint.json_lead_days_popover = dumps({
'title': _('Replenishment'),
'icon': 'fa-area-chart',
'popoverTemplate': 'stock.leadDaysPopOver',
'lead_days_date': fields.Date.to_string(orderpoint.lead_days_date),
'lead_days_description': lead_days_description,
'today': fields.Date.to_string(fields.Date.today()),
'trigger': orderpoint.trigger,
'qty_forecast': orderpoint.qty_forecast,
'qty_to_order': orderpoint.qty_to_order,
'product_min_qty': orderpoint.product_min_qty,
'product_max_qty': orderpoint.product_max_qty,
'product_uom_name': orderpoint.product_uom_name,
'virtual': orderpoint.trigger == 'manual' and orderpoint.create_uid.id == SUPERUSER_ID,
})
@api.depends('rule_ids', 'product_id.seller_ids', 'product_id.seller_ids.delay')
def _compute_lead_days(self):
for orderpoint in self:
if not orderpoint.product_id or not orderpoint.location_id:
orderpoint.lead_days_date = False
continue
lead_days, dummy = orderpoint.rule_ids._get_lead_days(orderpoint.product_id)
values = orderpoint._get_lead_days_values()
lead_days, dummy = orderpoint.rule_ids._get_lead_days(orderpoint.product_id, **values)
lead_days_date = fields.Date.today() + relativedelta.relativedelta(days=lead_days)
orderpoint.lead_days_date = lead_days_date
@@ -212,10 +188,32 @@ class StockWarehouseOrderpoint(models.Model):
raise UserError(_("Changing the company of this record is forbidden at this point, you should rather archive it and create a new one."))
return super().write(vals)
def action_product_forecast_report(self):
self.ensure_one()
action = self.product_id.action_product_forecast_report()
action['context'] = {
'active_id': self.product_id.id,
'active_model': 'product.product',
}
warehouse = self.warehouse_id
if warehouse:
action['context']['warehouse'] = warehouse.id
return action
@api.model
def action_open_orderpoints(self):
return self._get_orderpoint_action()
def action_stock_replenishment_info(self):
self.ensure_one()
action = self.env['ir.actions.actions']._for_xml_id('stock.action_stock_replenishment_info')
action['name'] = _('Replenishment Information for %s in %s', self.product_id.display_name, self.warehouse_id.display_name)
res = self.env['stock.replenishment.info'].create({
'orderpoint_id': self.id,
})
action['res_id'] = res.id
return action
def action_replenish(self):
self._procure_orderpoint_confirm(company_id=self.env.company)
notification = False
@@ -288,6 +286,10 @@ class StockWarehouseOrderpoint(models.Model):
orderpoints = self.filtered(lambda o: o.location_id.id == g['location_id'][0])
orderpoints.route_id = g['route_id']
def _get_lead_days_values(self):
self.ensure_one()
return dict()
def _get_product_context(self):
"""Used to call `virtual_available` when running an orderpoint."""
self.ensure_one()
+1 -1
View File
@@ -320,7 +320,7 @@ class StockRule(models.Model):
move_values[field] = values.get(field)
return move_values
def _get_lead_days(self, product):
def _get_lead_days(self, product, **values):
"""Returns the cumulative delay and its description encountered by a
procurement going through the rules in `self`.
@@ -87,3 +87,4 @@ access_stock_inventory_conflict,stock.inventory.conflict,model_stock_inventory_c
access_stock_inventory_warning,stock.inventory.warning,model_stock_inventory_warning,stock.group_stock_manager,1,1,1,0
access_stock_inventory_adjustment_name,stock.inventory.adjustment.name,model_stock_inventory_adjustment_name,stock.group_stock_manager,1,1,1,0
access_stock_request_count,stock.request.count,model_stock_request_count,stock.group_stock_manager,1,1,1,0
access_stock_replenishment_info,stock.replenishment.info,model_stock_replenishment_info,stock.group_stock_manager,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
87 access_stock_inventory_warning stock.inventory.warning model_stock_inventory_warning stock.group_stock_manager 1 1 1 0
88 access_stock_inventory_adjustment_name stock.inventory.adjustment.name model_stock_inventory_adjustment_name stock.group_stock_manager 1 1 1 0
89 access_stock_request_count stock.request.count model_stock_request_count stock.group_stock_manager 1 1 1 0
90 access_stock_replenishment_info stock.replenishment.info model_stock_replenishment_info stock.group_stock_manager 1 1 1 0
@@ -70,7 +70,21 @@ const ForecastWidgetField = AbstractField.extend({
},
});
const JsonWidget = AbstractField.extend({
supportedFieldTypes: ['char'],
_render: function () {
var value = JSON.parse(this.value);
if (!value || !value.template) {
this.$el.html('');
return;
}
$(QWeb.render(value.template, value)).appendTo(this.$el);
},
});
fieldRegistry.add('forecast_widget', ForecastWidgetField);
fieldRegistry.add('json_widget', JsonWidget);
return ForecastWidgetField;
});
@@ -3,6 +3,7 @@ odoo.define('stock.StockOrderpointListController', function (require) {
var core = require('web.core');
var ListController = require('web.ListController');
var FormView = require('web.FormView');
var qweb = core.qweb;
@@ -33,7 +34,7 @@ var StockOrderpointListController = ListController.extend({
// -------------------------------------------------------------------------
_onButtonClicked: function (ev) {
if (ev.data.attrs.class.split(' ').includes('o_replenish_buttons')) {
if (ev.data.attrs.class && ev.data.attrs.class.split(' ').includes('o_replenish_buttons')) {
ev.stopPropagation();
var self = this;
this._callButtonAction(ev.data.attrs, ev.data.record).then(function () {
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<div t-name="stock.leadDaysPopOver">
<h2>Lead Times</h2>
<hr/>
<p>
The forecasted stock on the <t t-esc="lead_days_date"/>
is <t t-if="qty_to_order &lt;= 0"><t t-esc="qty_forecast"/> <t t-esc="product_uom_name"/></t><t t-else="">
below the minimum inventory of <t t-esc="product_min_qty"/> <t t-esc="product_uom_name"/>
: <t t-esc="qty_to_order"/> <t t-esc="product_uom_name"/> should be replenished to reach the maximum of
The forecasted stock on the <b><t t-esc="lead_days_date"/></b>
is <t t-if="qty_to_order &lt;= 0"><b><t t-esc="qty_forecast"/> <t t-esc="product_uom_name"/></b></t><t t-else="">
below the inventory <b>minimum of <t t-esc="product_min_qty"/> <t t-esc="product_uom_name"/>
: <t t-esc="qty_to_order"/> <t t-esc="product_uom_name"/> should be replenished</b> to reach the maximum of
<t t-esc="product_max_qty"/> <t t-esc="product_uom_name"/>.</t>
</p>
<table t-if="lead_days_description" class="table table-borderless">
<table t-if="lead_days_description" class="table table-borderless table-sm">
<tbody>
<tr>
<td>
@@ -29,11 +31,6 @@
</tr>
</tbody>
</table>
<button class="text-left btn btn-link action_open_forecast"
type="button">
<i class="fa fa-fw o_button_icon fa-arrow-right"></i>
View Forecast
</button>
</div>
<t t-name="StockOrderpoint.Buttons">
@@ -47,4 +44,18 @@
</span>
</t>
<div t-name="stock.replenishmentHistory">
<h2>Sales History</h2>
<hr/>
<table t-if="replenishment_history" class="table table-borderless">
<tbody>
<t t-foreach="replenishment_history" t-as="line">
<tr>
<td><t t-esc="line.name"/></td>
<td class="text-right text-nowrap" t-esc="line.quantity + ' ' + line.uom_name"/>
</tr>
</t>
</tbody>
</table>
</div>
</templates>
@@ -48,8 +48,9 @@
<field name="warehouse_id" options="{'no_create': True}" groups="stock.group_stock_multi_warehouses" optional="hide"/>
<field name="qty_on_hand" force_save="1"/>
<field name="qty_forecast" force_save="1"/>
<field name="json_lead_days_popover" optional="show" nolabel="1" string="Forecast Description" width="10px" widget="popover_widget" attrs="{'invisible': [('id', '=', False)]}"/>
<button name="action_product_forecast_report" type="object" icon="fa-area-chart" attrs="{'invisible': [('id', '=', False)]}"/>
<field name="route_id" options="{'no_create': True, 'no_open': True}"/>
<button name="action_stock_replenishment_info" type="object" icon="fa-info" attrs="{'invisible': [('id', '=', False)]}"/>
<field name="trigger" optional="hide"/>
<field name="product_min_qty" optional="show"/>
<field name="product_max_qty" optional="show"/>
@@ -83,7 +84,7 @@
<field name="product_min_qty" optional="show"/>
<field name="product_max_qty" optional="show"/>
<field name="qty_multiple" optional="show"/>
<field name="json_lead_days_popover" optional="show" nolabel="1" string="Forecast Description" width="10px" widget="popover_widget" attrs="{'invisible': [('id', '=', False)]}"/>
<button name="stock.action_stock_replenishment_info" string="Forecast Description" type="action" icon="fa-area-chart" attrs="{'invisible': [('id', '=', False)]}"/>
<field name="product_uom_name" string="UoM" groups="uom.group_uom"/>
<field name="company_id" optional="hide" readonly="1" groups="base.group_multi_company"/>
</tree>
@@ -160,7 +161,7 @@
<div class="o_row">
<field name="product_min_qty"/>
<field name="product_uom_name"/>
<field name="json_lead_days_popover" widget="popover_widget" attrs="{'invisible': [('id', '=', False)]}"/>
<button name="stock.action_stock_replenishment_info" string="Forecast Description" type="action" icon="fa-area-chart" attrs="{'invisible': [('id', '=', False)]}"/>
</div>
<label for="product_max_qty"/>
<div class="o_row">
+1
View File
@@ -18,3 +18,4 @@ from . import stock_track_confirmation
from . import stock_package_destination
from . import stock_orderpoint_snooze
from . import stock_request_count
from . import stock_replenishment_info
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from json import dumps
from datetime import datetime, time
from odoo import api, fields, models, SUPERUSER_ID
from odoo.osv.expression import AND
from odoo.tools.date_utils import get_month, subtract
class StockReplenishmentInfo(models.TransientModel):
_name = 'stock.replenishment.info'
_description = 'Stock supplier replenishment information'
_rec_name = 'orderpoint_id'
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint')
product_id = fields.Many2one('product.product', related='orderpoint_id.product_id')
qty_to_order = fields.Float(related='orderpoint_id.qty_to_order')
json_lead_days = fields.Char(compute='_compute_json_lead_days')
json_replenishment_history = fields.Char(compute='_compute_json_replenishment_history')
@api.depends('orderpoint_id')
def _compute_json_lead_days(self):
for replenishment_report in self:
if not replenishment_report.orderpoint_id.product_id or not replenishment_report.orderpoint_id.location_id:
continue
orderpoint = replenishment_report.orderpoint_id
orderpoints_values = orderpoint._get_lead_days_values()
dummy, lead_days_description = orderpoint.rule_ids._get_lead_days(
orderpoint.product_id, **orderpoints_values)
replenishment_report.json_lead_days = dumps({
'template': 'stock.leadDaysPopOver',
'lead_days_date': fields.Date.to_string(replenishment_report.orderpoint_id.lead_days_date),
'lead_days_description': lead_days_description,
'today': fields.Date.to_string(fields.Date.today()),
'trigger': orderpoint.trigger,
'qty_forecast': orderpoint.qty_forecast,
'qty_to_order': orderpoint.qty_to_order,
'product_min_qty': orderpoint.product_min_qty,
'product_max_qty': orderpoint.product_max_qty,
'product_uom_name': orderpoint.product_uom_name,
'virtual': orderpoint.trigger == 'manual' and orderpoint.create_uid.id == SUPERUSER_ID,
})
@api.depends('orderpoint_id')
def _compute_json_replenishment_history(self):
for replenishment_report in self:
replenishment_history = []
today = fields.Datetime.now()
first_month = subtract(today, month=2)
date_from, dummy = get_month(first_month)
dummy, date_to = get_month(today)
domain = [
('product_id', '=', replenishment_report.product_id.id),
('date', '>=', date_from),
('date', '<=', datetime.combine(date_to, time.max)),
('state', '=', 'done'),
('company_id', '=', replenishment_report.orderpoint_id.company_id.id)
]
quantity_by_month_out = self.env['stock.move'].read_group(
AND([domain, [('location_dest_id.usage', '=', 'customer')]]),
['date', 'product_qty'], ['date:month'])
quantity_by_month_returned = self.env['stock.move'].read_group(
AND([domain, [('location_id.usage', '=', 'customer')]]),
['date', 'product_qty'], ['date:month'])
quantity_by_month_returned = {
g['date:month']: g['product_qty'] for g in quantity_by_month_returned}
for group in quantity_by_month_out:
month = group['date:month']
replenishment_history.append({
'name': month,
'quantity': group['product_qty'] - quantity_by_month_returned.get(month, 0),
'uom_name': replenishment_report.product_id.uom_id.display_name,
})
replenishment_report.json_replenishment_history = dumps({
'template': 'stock.replenishmentHistory',
'replenishment_history': replenishment_history
})
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_stock_replenishment_info" model="ir.ui.view">
<field name="name">Stock Replenishment Information</field>
<field name="model">stock.replenishment.info</field>
<field name="arch" type="xml">
<form>
<field name="orderpoint_id" invisible="1"/>
<field name="qty_to_order" invisible="1"/>
<div class="row ml8">
<group class="col-6">
<field nolabel="1" name="json_lead_days" widget="json_widget"/>
</group>
<group class="col-6">
<field nolabel="1" name="json_replenishment_history" widget="json_widget"/>
</group>
</div>
<footer>
<button string="Close" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_stock_replenishment_info" model="ir.actions.act_window">
<field name="name">Replenishment Information</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.replenishment.info</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_stock_replenishment_info"/>
<field name="context">{'default_orderpoint_id': active_id}</field>
<field name="target">new</field>
</record>
</odoo>