mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[ADD] stock_request_kanban
This commit is contained in:
committed by
Bernat Puig Font
parent
d08f2eace0
commit
7416737d3f
83
stock_request_kanban/README.rst
Normal file
83
stock_request_kanban/README.rst
Normal file
@@ -0,0 +1,83 @@
|
||||
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
|
||||
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
====================
|
||||
Stock Request Kanban
|
||||
====================
|
||||
|
||||
On most companies there is products that must be purchased often but cannot be
|
||||
stored as a usual product because no consumption moves are made.
|
||||
Usually, they are stored as consumables or putaway rules are defined.
|
||||
In both cases, reordering rules cannot be used.
|
||||
This module allows to use stock request as reordering rules for this kind of
|
||||
products.
|
||||
|
||||
It is created following the concept of lean kanban cards.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Creation
|
||||
--------
|
||||
* Go to 'Stock Requests / Stock Requests Kanban' and create a new Kanban.
|
||||
* Indicate a product, quantity and location.
|
||||
* Press 'Save'.
|
||||
* Print the kanban and put it in the storage of the product
|
||||
|
||||
Request kanban
|
||||
--------------
|
||||
|
||||
This should be used if you want to create the kanban when the card is consumed.
|
||||
|
||||
* Once the product is consumed, take the card
|
||||
* Go to 'Stock Requests / Order Kanban Card'
|
||||
* Scan the card
|
||||
* The stock request is created
|
||||
|
||||
Request kanban batch
|
||||
--------------------
|
||||
|
||||
This should be used when you will store the cards and create request orders
|
||||
for kanbans later.
|
||||
|
||||
* Once the product is consumed, take the card and store it
|
||||
* Create a store request order
|
||||
* Press the scan button
|
||||
* Scan all the pending kanban cards
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/153/11.0
|
||||
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/stock-logistics-warehouse/issues>`_. In case of
|
||||
trouble, please check there if your issue has already been reported. If you
|
||||
spotted it first, help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Enric Tobella <etobella@creublanca.es>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
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.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
5
stock_request_kanban/__init__.py
Normal file
5
stock_request_kanban/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
28
stock_request_kanban/__manifest__.py
Normal file
28
stock_request_kanban/__manifest__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
{
|
||||
'name': 'Stock Request kanban',
|
||||
'version': '11.0.1.0.0',
|
||||
'category': 'Reporting',
|
||||
'website': 'https://github.com/eficent/cb-addons',
|
||||
'author': 'Creu Blanca, Eficent, Odoo Community Association (OCA)',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Adds a stock request order, and takes stock requests as lines',
|
||||
'depends': [
|
||||
'stock_request',
|
||||
'barcodes',
|
||||
],
|
||||
'data': [
|
||||
'data/stock_request_sequence_data.xml',
|
||||
'report/report_paper_format.xml',
|
||||
'wizard/wizard_stock_request_kanban_views.xml',
|
||||
'wizard/wizard_stock_request_order_kanban_views.xml',
|
||||
'views/stock_request_order_views.xml',
|
||||
'views/stock_request_kanban_views.xml',
|
||||
'report/stock_request_kanban_templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
14
stock_request_kanban/data/stock_request_sequence_data.xml
Normal file
14
stock_request_kanban/data/stock_request_sequence_data.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="seq_stock_request_kanban" model="ir.sequence">
|
||||
<field name="name">Stock Request Kanban</field>
|
||||
<field name="code">stock.request.kanban</field>
|
||||
<field name="prefix">KB</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
5
stock_request_kanban/models/__init__.py
Normal file
5
stock_request_kanban/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import stock_request
|
||||
from . import stock_request_kanban
|
||||
10
stock_request_kanban/models/stock_request.py
Normal file
10
stock_request_kanban/models/stock_request.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class StockRequest(models.Model):
|
||||
_inherit = 'stock.request'
|
||||
|
||||
kanban_id = fields.Many2one('stock.request.kanban', readonly=True)
|
||||
19
stock_request_kanban/models/stock_request_kanban.py
Normal file
19
stock_request_kanban/models/stock_request_kanban.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import api, models, fields
|
||||
|
||||
|
||||
class StockRequestKanban(models.Model):
|
||||
_name = 'stock.request.kanban'
|
||||
_description = 'Stock Request Kanban'
|
||||
_inherit = 'stock.request.abstract'
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name', '/') == '/':
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code(
|
||||
'stock.request.kanban')
|
||||
return super().create(vals)
|
||||
15
stock_request_kanban/report/report_paper_format.xml
Normal file
15
stock_request_kanban/report/report_paper_format.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="kanban_paper_format" model="report.paperformat">
|
||||
<field name="name">Kanban format</field>
|
||||
<field name="format">A4</field>
|
||||
<field name="orientation">Portrait</field>
|
||||
<field name="margin_top">0.0</field>
|
||||
<field name="margin_bottom">0.0</field>
|
||||
<field name="margin_left">0.0</field>
|
||||
<field name="margin_right">0.0</field>
|
||||
<field name="header_spacing">0</field>
|
||||
<field name="dpi">72</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
<template id="report_simple_label">
|
||||
<div class="col-xs-3" style="padding:0;height:350px;">
|
||||
<div class="col-xs-6"
|
||||
style="height:30%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<strong t-field="o.product_id.default_code"
|
||||
t-if="o.product_id.default_code"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6"
|
||||
style="height:30%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<strong t-field="o.location_id.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 align-middle"
|
||||
style="height:40%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<strong t-field="o.product_id.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6"
|
||||
style="height:10%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<span t-esc="float(o.product_qty)"/>
|
||||
<span t-field="o.product_id.uom_id.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6"
|
||||
style="height:10%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<span t-esc="float(o.product_uom_qty)"/>
|
||||
<span t-field="o.product_uom_id.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12"
|
||||
style="height:10%;border:2px solid black;text-align:center;vertical-align:middle;display:table;">
|
||||
<div style="display: table-cell; vertical-align: middle;">
|
||||
<span t-field="o.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12" style="height:10%;">
|
||||
<img t-att-src="'/report/barcode/%s/%s?width=%s&height=%s' % ('Standard39', o.name, 320, 20)"
|
||||
style="height:80%; width: 100%;"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="report_kanban_label">
|
||||
<t t-call="web.basic_layout">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
|
||||
<div class="page">
|
||||
<t t-call="stock_request_kanban.report_simple_label">
|
||||
<t t-set="o" t-value="o"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<report
|
||||
id="action_report_kanban"
|
||||
string="Print kanban"
|
||||
model="stock.request.kanban"
|
||||
report_type="qweb-pdf"
|
||||
name="stock_request_kanban.report_kanban_label"
|
||||
file="stock_request_kanban.report_kanban_label"
|
||||
paperformat="stock_request_kanban.kanban_paper_format"
|
||||
menu="True"/>
|
||||
</odoo>
|
||||
3
stock_request_kanban/security/ir.model.access.csv
Normal file
3
stock_request_kanban/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_stock_request_kanban_user,stock request kanban user,model_stock_request_kanban,stock_request.group_stock_request_user,1,1,1,
|
||||
access_stock_request_kanban_manager,stock request kanban manager,model_stock_request_kanban,stock_request.group_stock_request_manager,1,1,1,1
|
||||
|
BIN
stock_request_kanban/static/description/icon.png
Normal file
BIN
stock_request_kanban/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
1
stock_request_kanban/tests/__init__.py
Normal file
1
stock_request_kanban/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_kanban
|
||||
166
stock_request_kanban/tests/test_kanban.py
Normal file
166
stock_request_kanban/tests/test_kanban.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# Copyright 2017 Creu Blanca
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from reportlab.graphics.barcode import getCodes
|
||||
|
||||
|
||||
class TestKanban(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.main_company = self.env.ref('base.main_company')
|
||||
self.warehouse = self.env.ref('stock.warehouse0')
|
||||
self.categ_unit = self.env.ref('product.product_uom_categ_unit')
|
||||
|
||||
# common data
|
||||
self.company_2 = self.env['res.company'].create({
|
||||
'name': 'Comp2',
|
||||
})
|
||||
self.wh2 = self.env['stock.warehouse'].search(
|
||||
[('company_id', '=', self.company_2.id)], limit=1)
|
||||
|
||||
self.ressuply_loc = self.env['stock.location'].create({
|
||||
'name': 'Ressuply',
|
||||
'location_id': self.warehouse.view_location_id.id,
|
||||
})
|
||||
self.route = self.env['stock.location.route'].create({
|
||||
'name': 'Transfer',
|
||||
'product_categ_selectable': False,
|
||||
'product_selectable': True,
|
||||
'company_id': self.main_company.id,
|
||||
'sequence': 10,
|
||||
})
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Product',
|
||||
'route_ids': [(4, self.route.id)],
|
||||
'company_id': False,
|
||||
})
|
||||
self.uom_dozen = self.env['product.uom'].create({
|
||||
'name': 'Test-DozenA',
|
||||
'category_id': self.categ_unit.id,
|
||||
'factor_inv': 12,
|
||||
'uom_type': 'bigger',
|
||||
'rounding': 0.001})
|
||||
|
||||
self.env['procurement.rule'].create({
|
||||
'name': 'Transfer',
|
||||
'route_id': self.route.id,
|
||||
'location_src_id': self.ressuply_loc.id,
|
||||
'location_id': self.warehouse.lot_stock_id.id,
|
||||
'action': 'move',
|
||||
'picking_type_id': self.warehouse.int_type_id.id,
|
||||
'procure_method': 'make_to_stock',
|
||||
'warehouse_id': self.warehouse.id,
|
||||
'company_id': self.main_company.id,
|
||||
'propagate': 'False',
|
||||
})
|
||||
|
||||
def test_onchanges(self):
|
||||
kanban = self.env['stock.request.kanban'].new({})
|
||||
kanban.product_id = self.product
|
||||
kanban.onchange_product_id()
|
||||
kanban.company_id = self.main_company
|
||||
kanban.onchange_company_id()
|
||||
self.assertTrue(kanban.warehouse_id)
|
||||
kanban.warehouse_id = self.wh2
|
||||
kanban.onchange_warehouse_id()
|
||||
self.assertEqual(kanban.company_id, self.company_2)
|
||||
kanban.location_id = self.warehouse.view_location_id
|
||||
kanban.onchange_location_id()
|
||||
self.assertEqual(kanban.company_id, self.main_company)
|
||||
self.assertEqual(kanban.warehouse_id, self.warehouse)
|
||||
|
||||
def test_create(self):
|
||||
kanban = self.env['stock.request.kanban'].new({})
|
||||
kanban.product_id = self.product
|
||||
kanban.onchange_product_id()
|
||||
kanban.product_uom_qty = 1
|
||||
kanban = kanban.create(kanban._convert_to_write(kanban._cache))
|
||||
self.assertTrue(kanban.company_id)
|
||||
self.assertEqual(kanban.route_ids, self.route)
|
||||
|
||||
def test_order_barcodes(self):
|
||||
kanban_1 = self.env['stock.request.kanban'].create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
kanban_2 = self.env['stock.request.kanban'].create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
kanban_3 = self.env['stock.request.kanban'].create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'company_id': self.company_2.id,
|
||||
'warehouse_id': self.wh2.id,
|
||||
'location_id': self.wh2.lot_stock_id.id,
|
||||
})
|
||||
order = self.env['stock.request.order'].create({
|
||||
'company_id': self.main_company.id,
|
||||
'warehouse_id': self.warehouse.id,
|
||||
'location_id': self.warehouse.lot_stock_id.id,
|
||||
})
|
||||
wizard = self.env['wizard.stock.request.order.kanban'].with_context(
|
||||
default_order_id=order.id
|
||||
).create({})
|
||||
with self.assertRaises(ValidationError):
|
||||
wizard.on_barcode_scanned(kanban_1.name)
|
||||
self.pass_code(wizard, kanban_1.name)
|
||||
self.assertEqual(wizard.status_state, 0)
|
||||
self.assertTrue(order.stock_request_ids.filtered(
|
||||
lambda r: r.kanban_id == kanban_1
|
||||
))
|
||||
self.pass_code(wizard, kanban_2.name)
|
||||
self.assertTrue(order.stock_request_ids.filtered(
|
||||
lambda r: r.kanban_id == kanban_2
|
||||
))
|
||||
self.assertEqual(wizard.status_state, 0)
|
||||
self.pass_code(wizard, kanban_1.name)
|
||||
self.assertEqual(wizard.status_state, 1)
|
||||
self.pass_code(wizard, kanban_2.name+kanban_1.name)
|
||||
self.assertEqual(wizard.status_state, 1)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.pass_code(wizard, kanban_3.name)
|
||||
|
||||
def test_barcodes(self):
|
||||
kanban_1 = self.env['stock.request.kanban'].create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
kanban_2 = self.env['stock.request.kanban'].create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
wizard = self.env['wizard.stock.request.kanban'].with_context(
|
||||
).create({})
|
||||
with self.assertRaises(ValidationError):
|
||||
wizard.on_barcode_scanned(kanban_1.name)
|
||||
self.assertFalse(self.env['stock.request'].search(
|
||||
[('kanban_id', '=', kanban_1.id)])
|
||||
)
|
||||
self.pass_code(wizard, kanban_1.name)
|
||||
self.assertEqual(wizard.status_state, 0)
|
||||
self.assertTrue(self.env['stock.request'].search(
|
||||
[('kanban_id', '=', kanban_1.id)])
|
||||
)
|
||||
self.assertFalse(self.env['stock.request'].search(
|
||||
[('kanban_id', '=', kanban_2.id)])
|
||||
)
|
||||
self.pass_code(wizard, kanban_2.name)
|
||||
self.assertTrue(self.env['stock.request'].search(
|
||||
[('kanban_id', '=', kanban_2.id)])
|
||||
)
|
||||
|
||||
def pass_code(self, wizard, code):
|
||||
bcc = getCodes()[wizard.get_barcode_format()](value=code)
|
||||
bcc.validate()
|
||||
bcc.encode()
|
||||
wizard.on_barcode_scanned(bcc.encoded[1:-1])
|
||||
146
stock_request_kanban/views/stock_request_kanban_views.xml
Normal file
146
stock_request_kanban/views/stock_request_kanban_views.xml
Normal file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record id="view_stock_request_kanban_tree" model="ir.ui.view">
|
||||
<field name="name">stock.request.kanban.tree</field>
|
||||
<field name="model">stock.request.kanban</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Stock Requests" decoration-muted="active == 'false'" decoration-bf="message_needaction==True">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="warehouse_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="route_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_id"
|
||||
options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="stock_request_kanban_search">
|
||||
<field name="name">stock.request.kanban.search</field>
|
||||
<field name="model">stock.request.kanban</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Stock Requests Kanban">
|
||||
<field name="name" string="Stock Requests Kanban"/>
|
||||
<field name="warehouse_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="product_id"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Warehouse" domain="[]" context="{'group_by':'warehouse_id'}"/>
|
||||
<filter string="Location" domain="[]" context="{'group_by':'location_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_request_kanban_form" model="ir.ui.view">
|
||||
<field name="name">stock.request.kanban.form</field>
|
||||
<field name="model">stock.request.kanban</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Stock Requests">
|
||||
<header/>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<div class="oe_title">
|
||||
<label string="Stock Request Kanban" />
|
||||
<h1>
|
||||
<field name="name" readonly="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id" widget="selection" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="product_uom_id"
|
||||
options="{'no_open': True, 'no_create': True}" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="route_id"
|
||||
options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="route_ids" invisible="1"/>
|
||||
<field name="procurement_group_id"
|
||||
groups="stock.group_adv_location"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group name="quantities">
|
||||
<field name="product_uom_qty"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="activity_ids" widget="mail_activity"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Partner Kanban View -->
|
||||
<record model="ir.ui.view" id="stock_request_kanban_kanban_view">
|
||||
<field name="name">stock.request.kanban.kanban</field>
|
||||
<field name="model">stock.request.kanban</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_res_partner_kanban">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="display_name"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="product_qty"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_res_partner_kanban">
|
||||
|
||||
<div class="o_kanban_image">
|
||||
<img t-att-src="kanban_image('product.product', 'image_small', record.product_id.raw_value)"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong class="o_kanban_record_title"><field name="display_name"/></strong>
|
||||
<div class="o_kanban_tags_section"/>
|
||||
<ul>
|
||||
<li t-if="record.product_id.raw_value"><field name="product_id"/></li>
|
||||
<li t-if="record.warehouse_id.raw_value" groups="stock.group_stock_multi_locations"><field name="warehouse_id"/></li>
|
||||
<li t-if="record.location_id.raw_value" groups="stock.group_stock_multi_locations"><field name="location_id"/></li>
|
||||
<li t-if="record.product_qty"><field name="product_qty"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="stock_request_kanban_action" model="ir.actions.act_window">
|
||||
<field name="name">Stock Request Kanbans</field>
|
||||
<field name="res_model">stock.request.kanban</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a Stock Request Kanban.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="stock_request_kanban_menu"
|
||||
name="Stock Requests Kanbans"
|
||||
parent="stock_request.menu_stock_request_root"
|
||||
action="stock_request_kanban_action"
|
||||
groups="stock_request.group_stock_request_manager"
|
||||
sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
21
stock_request_kanban/views/stock_request_order_views.xml
Normal file
21
stock_request_kanban/views/stock_request_order_views.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="stock_request_order_form">
|
||||
<field name="name">stock.request.order.form</field>
|
||||
<field name="model">stock.request.order</field>
|
||||
<field name="inherit_id" ref="stock_request.stock_request_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button name="%(stock_request_kanban.wizard_stock_request_order_kanban_action)d"
|
||||
type="action"
|
||||
icon="fa-barcode"
|
||||
string="Scan"
|
||||
states="draft"
|
||||
class="oe_read_only"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
6
stock_request_kanban/wizard/__init__.py
Normal file
6
stock_request_kanban/wizard/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2018 Creu Blanca
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import wizard_stock_request_kanban_abstract
|
||||
from . import wizard_stock_request_kanban
|
||||
from . import wizard_stock_request_order_kanban
|
||||
14
stock_request_kanban/wizard/wizard_stock_request_kanban.py
Normal file
14
stock_request_kanban/wizard/wizard_stock_request_kanban.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright 2017 Creu Blanca
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class WizardStockRequestOrderKanbanAbstract(models.TransientModel):
|
||||
_name = "wizard.stock.request.kanban"
|
||||
_inherit = "wizard.stock.request.kanban.abstract"
|
||||
|
||||
def barcode_ending(self):
|
||||
self.stock_request_id.action_confirm()
|
||||
return
|
||||
@@ -0,0 +1,90 @@
|
||||
# Copyright 2017 Creu Blanca
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from reportlab.graphics.barcode import getCodes
|
||||
|
||||
|
||||
class WizardStockRequestOrderKanbanAbstract(models.AbstractModel):
|
||||
_name = "wizard.stock.request.kanban.abstract"
|
||||
_inherit = "barcodes.barcode_events_mixin"
|
||||
|
||||
kanban_id = fields.Many2one(
|
||||
'stock.request.kanban',
|
||||
readonly=True,
|
||||
)
|
||||
stock_request_id = fields.Many2one(
|
||||
'stock.request',
|
||||
readonly=True,
|
||||
)
|
||||
status = fields.Text(
|
||||
readonly=True,
|
||||
default="Start scanning",
|
||||
)
|
||||
status_state = fields.Integer(
|
||||
default=0,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
def get_barcode_format(self):
|
||||
return 'Standard39'
|
||||
|
||||
def validate_barcode(self, barcode):
|
||||
bcc = getCodes()[self.get_barcode_format()](value=barcode[:-1])
|
||||
bcc.validate()
|
||||
bcc.encode()
|
||||
if bcc.encoded[1:-1] != barcode:
|
||||
raise ValidationError(_('CRC is not valid'))
|
||||
return barcode[:-1]
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
barcode = self.validate_barcode(barcode)
|
||||
|
||||
self.kanban_id = self.env['stock.request.kanban'].search([
|
||||
('name', '=', barcode)
|
||||
])
|
||||
if not self.kanban_id:
|
||||
self.status = _("Barcode %s does not correspond to any "
|
||||
"Kanban. Try with another barcode or "
|
||||
"press Close to finish scanning.") % barcode
|
||||
self.status_state = 1
|
||||
return
|
||||
if self.validate_kanban(barcode):
|
||||
self.stock_request_id = self.env['stock.request'].create(
|
||||
self.stock_request_kanban_values()
|
||||
)
|
||||
self.status_state = 0
|
||||
|
||||
self.status = _('Added kanban %s for product %s' % (
|
||||
self.stock_request_id.kanban_id.name,
|
||||
self.stock_request_id.product_id.display_name
|
||||
))
|
||||
self.barcode_ending()
|
||||
return
|
||||
|
||||
def barcode_ending(self):
|
||||
return
|
||||
|
||||
def validate_kanban(self, barcode):
|
||||
'''
|
||||
It must return True if the kanban is valid, False otherwise
|
||||
:param barcode:
|
||||
:return:
|
||||
'''
|
||||
return True
|
||||
|
||||
def stock_request_kanban_values(self):
|
||||
return {
|
||||
'company_id': self.kanban_id.company_id.id,
|
||||
'procurement_group_id':
|
||||
self.kanban_id.procurement_group_id.id or False,
|
||||
'location_id': self.kanban_id.location_id.id or False,
|
||||
'warehouse_id': self.kanban_id.warehouse_id.id or False,
|
||||
'product_id': self.kanban_id.product_id.id,
|
||||
'product_uom_id': self.kanban_id.product_uom_id.id or False,
|
||||
'route_id': self.kanban_id.route_id.id or False,
|
||||
'product_uom_qty': self.kanban_id.product_uom_qty,
|
||||
'kanban_id': self.kanban_id.id,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
Copyright 2017 Creu Blanca
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="wizard_stock_request_kanban_form" model="ir.ui.view">
|
||||
<field name="name">wizard.stock.request.kanban.form</field>
|
||||
<field name="model">wizard.stock.request.kanban</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Add Kanban">
|
||||
<div class="alert alert-success text-center o_form_header"
|
||||
role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible':[('status_state', '!=', 0)]}">
|
||||
<bold><field name="status"/></bold>
|
||||
</div>
|
||||
<div class="alert alert-danger alert-dismissable text-center"
|
||||
role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible':[('status_state', '=', 0)]}">
|
||||
<bold><field name="status"/></bold>
|
||||
</div>
|
||||
<field name="status_state" invisible="1"/>
|
||||
<field name="kanban_id" invisible="1"/>
|
||||
<field name="stock_request_id" invisible="1"/>
|
||||
<field name="_barcode_scanned" widget="barcode_handler"
|
||||
invisible="1"/>
|
||||
<footer>
|
||||
<button
|
||||
name="action_cancel"
|
||||
string="Close"
|
||||
class="oe_link"
|
||||
special="cancel"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window id="wizard_stock_request_kanban_action"
|
||||
name="Add Kanban"
|
||||
res_model="wizard.stock.request.kanban"
|
||||
view_mode="form"
|
||||
view_type="form"
|
||||
target="new"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_wizard_stock_request_kanban"
|
||||
action="wizard_stock_request_kanban_action"
|
||||
name="Order Kanban Card" parent="stock_request.menu_stock_request_root"
|
||||
sequence="20"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,53 @@
|
||||
# Copyright 2017 Creu Blanca
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class WizardStockRequestOrderKanban(models.TransientModel):
|
||||
_name = "wizard.stock.request.order.kanban"
|
||||
_inherit = "wizard.stock.request.kanban.abstract"
|
||||
|
||||
order_id = fields.Many2one(
|
||||
'stock.request.order',
|
||||
required=True
|
||||
)
|
||||
|
||||
def validate_kanban(self, barcode):
|
||||
res = super().validate_kanban(barcode)
|
||||
if self.order_id.stock_request_ids.filtered(
|
||||
lambda r: r.kanban_id == self.kanban_id
|
||||
):
|
||||
self.status = _("Barcode %s is on the order") % barcode
|
||||
self.status_state = 1
|
||||
return False
|
||||
if self.order_id.state != 'draft':
|
||||
raise ValidationError(_(
|
||||
'Lines only can be added on orders with draft state'))
|
||||
if not self.order_id.company_id:
|
||||
self.order_id.company_id = self.kanban_id.company_id
|
||||
elif self.order_id.company_id != self.kanban_id.company_id:
|
||||
raise ValidationError(_('Company must be the same'))
|
||||
if (
|
||||
self.kanban_id.procurement_group_id and
|
||||
self.order_id.procurement_group_id !=
|
||||
self.kanban_id.procurement_group_id
|
||||
):
|
||||
raise ValidationError(_('Procurement group must be the same'))
|
||||
if self.order_id.location_id != self.kanban_id.location_id:
|
||||
raise ValidationError(_('Location must be the same'))
|
||||
if self.order_id.warehouse_id != self.kanban_id.warehouse_id:
|
||||
raise ValidationError(_('Warehouse must be the same'))
|
||||
return res
|
||||
|
||||
def stock_request_kanban_values(self):
|
||||
res = super().stock_request_kanban_values()
|
||||
res['order_id'] = self.order_id.id,
|
||||
res['expected_date'] = self.order_id.expected_date,
|
||||
return res
|
||||
|
||||
def barcode_ending(self):
|
||||
super().barcode_ending()
|
||||
self.order_id = self.stock_request_id.order_id
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
Copyright 2017 Eficent Business and IT Consulting Services, S.L.
|
||||
Copyright 2017 Creu Blanca
|
||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="wizard_stock_request_order_kanban_form" model="ir.ui.view">
|
||||
<field name="name">wizard.stock.request.order.kanban.form</field>
|
||||
<field name="model">wizard.stock.request.order.kanban</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="wizard_stock_request_kanban_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="kanban_id" position="after">
|
||||
<field name="order_id" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window id="wizard_stock_request_order_kanban_action"
|
||||
name="Add Kanban"
|
||||
res_model="wizard.stock.request.order.kanban"
|
||||
view_mode="form"
|
||||
view_type="form"
|
||||
context="{'default_order_id': active_id}"
|
||||
target="new"/>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user