[MERGE] merged csn branch with barcode interface greatly improved

bzr revid: qdp-launchpad@openerp.com-20140327132221-ylk22ykso1wyq1ca
This commit is contained in:
Quentin (OpenERP) 2014-03-27 14:22:21 +01:00
commit a7fdd2371f
11 changed files with 1037 additions and 829 deletions

View File

@ -27,3 +27,5 @@ import report
import wizard
import res_config
import controllers

View File

@ -104,7 +104,6 @@ Dashboard / Reports for Warehouse Management will include:
'application': True,
'auto_install': False,
'css': [
'static/src/css/picking.css',
'static/src/css/stock.css',
],
'js': [

View File

@ -0,0 +1 @@
import main

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
import logging
import simplejson
import os
import openerp
import time
import random
from openerp import http
from openerp.http import request
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
_logger = logging.getLogger(__name__)
html_template = """<!DOCTYPE html>
<html>
<head>
<title>Barcode Scanner</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta http-equiv="content-type" content="text/html, charset=utf-8" />
<meta name="viewport" content=" width=1024, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link rel="shortcut icon" sizes="80x51" href="/stock/static/src/img/scan.png">
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/stock/static/src/css/barcode.css" />
<link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css" />
<link rel="stylesheet" href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css" />
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css" />
%(js)s
<script type="text/javascript">
$(function() {
var s = new openerp.init(%(modules)s);
%(init)s
});
</script>
</head>
<body>
<!--[if lte IE 8]>
<script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>CFInstall.check({mode: "overlay"});</script>
<![endif]-->
</body>
</html>
"""
class BarcodeController(http.Controller):
@http.route(['/barcode/web/'], type='http', auth='user')
def a(self, debug=False, **k):
if not request.session.uid:
return http.local_redirect('/web/login?redirect=/barcode/web')
js_list = manifest_list('js',db=request.db, debug=debug)
css_list = manifest_list('css',db=request.db, debug=debug)
js = "\n".join('<script type="text/javascript" src="%s"></script>' % i for i in js_list)
#css = "\n".join('<link rel="stylesheet" href="%s">' % i for i in css_list)
r = html_template % {
'js': js,
# 'css': css,
'modules': simplejson.dumps(module_boot(request.db)),
'init': """
var wc = new s.web.WebClient();
wc.show_application = function(){
wc.action_manager.do_action("stock.ui", {});
};
wc.do_push_state = function(state){};
wc.appendTo($(document.body));
"""
}
return r

View File

@ -0,0 +1,134 @@
.in_container_hidden {
display: none;
}
.in_container {
}
.oe_pick_app_header{
margin-top: 0;
}
.oe_picking {
cursor: pointer;
}
.oe_kanban.oe_picking {
min-height: 80px;
margin-bottom: 10px;
border: 1px solid rgba(0, 0, 0, 0.16);
border-bottom-color: rgba(0, 0, 0, 0.3);
-webkit-transition: -webkit-transform, -webkit-box-shadow, border 200ms linear;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
padding: 5px;
}
.oe_kanban_color_0 {
background-color: white;
color: #5a5a5a;
}
.oe_kanban_color_1 {
background-color: #cccccc;
color: #424242;
}
.oe_kanban_color_2 {
background-color: #ffc7c7;
color: #7a3737;
}
.oe_kanban_color_3 {
background-color: #fff1c7;
color: #756832;
}
.oe_kanban_color_4 {
background-color: #e3ffc7;
color: #5d6937;
}
.oe_kanban_color_5 {
background-color: #c7ffd5;
color: #1a7759;
}
.oe_kanban_color_6 {
background-color: #c7ffff;
color: #1a5d83;
}
.oe_kanban_color_7 {
background-color: #c7d5ff;
color: #3b3e75;
}
.oe_kanban_color_8 {
background-color: #e3c7ff;
color: #4c3668;
}
.oe_kanban_color_9 {
background-color: #ffc7f1;
color: #6d2c70;
}
/*Blinking text*/
.blink_me {
-webkit-animation-name: blinker;
-webkit-animation-duration: 1s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: 2;
-moz-animation-name: blinker;
-moz-animation-duration: 1s;
-moz-animation-timing-function: linear;
-moz-animation-iteration-count: 2;
animation-name: blinker;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: 2;
}
@-moz-keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.0; }
100% { opacity: 1.0; }
}
@-webkit-keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.0; }
100% { opacity: 1.0; }
}
@keyframes blinker {
0% { opacity: 1.0; }
50% { opacity: 0.0; }
100% { opacity: 1.0; }
}
/*hide OpenERP leftbar, table should use all width by default and display vertical scrollbar if needed*/
.oe_leftbar {
display: none;
}
table.oe_webclient.oe_content_full_screen{
width: 100%;
}
body{
overflow-y: visible !important;
}
/* --- Styling of OpenERP Elements ---
Needed for loading and error box */
/* Increase z-index value to insure that loading goes above navbar of bootstrap*/
.openerp .oe_loading {
display: none;
z-index: 1000;
position: fixed;
top: 0;
right: 50%;
padding: 4px 12px;
background: #a61300;
color: white;
text-align: center;
border: 1px solid #990000;
border-top: none;
-moz-border-radius-bottomright: 8px;
-moz-border-radius-bottomleft: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}

View File

@ -1,291 +0,0 @@
/* ----------------------- *
* PICKING WIDGET LAYOUT *
* ----------------------- */
.oe_pick_widget {
position: absolute;
top: 0px; right: 0px; bottom: 0px; left: 0px;
color: #444444;
background: #686868;
text-shadow: none;
font-family: 'Lato', 'Open Sans', Arial, Helvetica, sans-serif;
}
.oe_pick_widget .oe_pick_layout {
width: 100%;
height: 100%;
}
.oe_pick_widget .oe_hidden{
display: none !important;
}
.oe_pick_widget .oe_pick_header{
background: #454343;
background-image: linear-gradient(#646060,#262626);
}
.oe_pick_widget .oe_pick_body_cont{
height: 100%;
}
.oe_pick_widget .oe_pick_body{
width: 100%;
height: 100%;
background-color: white;
background: url('/web/static/src/img/form_sheetbg.png');
box-shadow: 0px 9px 5px -5px rgba(0, 0, 0, 0.16) inset;
overflow: hidden;
overflow-y: auto;
}
.oe_pick_widget .oe_pick_app{
width: 600px;
background: white;
margin: 40px auto;
padding: 20px;
border: solid 1px rgb(228, 228, 255);
box-shadow: 0px 2px 3px rgba(114, 114, 175, 0.11);
}
.oe_pick_widget .oe_pick_toolbar{
width: 600px;
margin: auto;
text-align: center;
}
.oe_pick_widget .oe_pick_button{
display: inline-block;
min-width: 64px;
padding: 5px 10px;
font-size: 18px;
border-radius: 3px;
margin: 10px 5px;
text-align: center;
line-height: 30px;
background: #b2b3d7;
background: linear-gradient(#b2b3d7, #7f82ac);
color: #273072;
border: 1px solid #353A7E;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
cursor: pointer;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
}
.oe_pick_widget .oe_pick_button.oe_disabled {
cursor: default;
background-color: #5A5A5A;
background: linear-gradient(#666666, #474747);
border: solid 1px rgb(44, 44, 44);
color: rgb(53, 53, 53);
box-shadow: 0px 1px 1px rgba(255, 255, 255, 0.32) inset;
}
.oe_pick_widget .oe_pick_button:not(.oe_disabled):active,
.oe_pick_widget .oe_pick_button:not(.oe_disabled).oe_active{
background: rgb(92, 84, 133);
box-shadow: 0px 1px 0px 1px rgba(0,0,0,0.3) inset;
margin: 11px 6px;
border: none;
color: white;
text-shadow: 0px 1px rgba(0,0,0, 0.3);
}
.oe_pick_widget .oe_pick_button.oe_small{
min-width: 0px;
width: 32px;
}
.oe_pick_widget .oe_pick_right_toolbar{
float: right;
border-left: solid 1px rgba(0,0,0,0.3);
padding-left: 8px;
padding-right: 8px;
margin-left: 8px;
}
.oe_pick_widget .oe_pick_col_small{ width: 60px; }
.oe_pick_widget .oe_pick_col_medium{ width: 100px; }
.oe_pick_widget .oe_pick_col_big{ width: 200px; }
.oe_pick_widget .oe_pick_col_expand{ width: 100%; }
.oe_pick_widget .oe_centeralign{ text-align: center; }
.oe_pick_widget .oe_invalid{
background: rgb(255, 226, 226);
color: rgb(168, 6, 6);
}
.oe_pick_widget .oe_pick_app_header{
font-size: 24px;
margin-left: 5px;
margin-bottom: 10px;
}
.oe_pick_widget .oe_pick_app_title{
font-size: 20px;
margin-top: 0px;
}
.oe_pick_widget .oe_pick_app_subtitle{
font-size: 16px;
font-weight: normal;
}
.oe_pick_widget .oe_pick_app_info{
margin-left: 4px;
opacity: 0.5;
}
/* ----------------------- *
* PICKING TABLES *
* ----------------------- */
.oe_pick_widget .oe_pick_list{
margin: 0px 0px 20px 0px;
padding: 5px;
}
.oe_pick_widget .oe_pick_list_header{
font-size: 20px;
margin-bottom: 5px;
}
.oe_pick_widget .oe_pick_list_table{
background: whitesmoke;
width: 100%;
table-layout: fixed;
font-size: 16px;
border-radius: 3px;
}
.oe_pick_widget .oe_pick_list_table tbody tr:nth-child(odd){
background: rgb(250,250,250);
}
.oe_pick_widget .oe_pick_list_table tbody tr.oe_selected{
background: rgb(236, 236, 247);
outline: solid 1px rgba(79, 107, 255, 0.25);
}
.oe_pick_widget .oe_pick_list_table td,
.oe_pick_widget .oe_pick_list_table th {
padding: 5px;
overflow: hidden;
text-overflow: ellipsis;
}
.oe_pick_widget .oe_pick_list_table thead{
background: rgb(224, 225, 235);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.oe_pick_widget .oe_pick_list_table th {
background: none;
border: none;
font-weight: bold;
}
.oe_pick_widget .oe_row_button{
display: inline-block;
background: rgb(238, 237, 248);
padding: 2px 4px;
line-height: 14px;
color: rgb(137, 127, 173);
border-radius: 3px;
border: solid 1px rgba(0, 0, 0, 0.09);
cursor: pointer;
}
.oe_pick_widget .oe_expanded{
text-align: center;
min-width: 32px;
}
.oe_pick_widget .js_pack_select,
.oe_pick_widget .js_pack_op{
cursor: pointer;
}
/* ----------------------- *
* MAIN PICKING MENU *
* ----------------------- */
.oe_pick_widget .oe_picking{
background: rgb(223, 226, 246);
padding: 10px;
margin-bottom: 3px;
border-radius: 3px;
border-bottom: solid 2px rgb(189, 189, 241);
cursor: pointer;
}
.oe_pick_widget .oe_picking.oe_empty {
border: solid 1px rgb(228, 226, 241);
background: white;
cursor: default;
}
.oe_pick_widget .oe_picking > .oe_picking_name{
display: inline-block;
min-width: 100px;
margin-right: 10px;
}
.oe_pick_widget .oe_search_empty{
padding: 10px;
border-radius: 3px;
background: rgb(228, 226, 241);
text-align: center;
}
/* ----------------------- *
* SEARCH BOX *
* ----------------------- */
.oe_pick_widget .oe_searchbox{
margin-top: 13px;
}
.oe_pick_widget .oe_searchbox input{
padding: 8px;
}
.oe_pick_widget .oe_picking_not_found{
padding: 8px;
margin: 24px 0px;
text-align: center;
background: rgb(239, 240, 247);
border-radius: 3px;
color: rgb(119, 77, 77);
}
/* ----------------------- *
* RESPONSIVENESS *
* ----------------------- */
@media screen and (max-width:642px){
.oe_pick_widget .oe_pick_body{
background: white;
background-image: none;
box-shadow: none;
}
.oe_pick_widget .oe_pick_app{
width: auto;
max-width: 600px;
margin: 0 auto;
border: none;
box-shadow: none;
padding-left: 5px;
padding-right: 5px;
}
.oe_pick_widget .oe_pick_toolbar{
width: auto;
}
.oe_pick_widget .oe_pick_right_toolbar{
float: none;
text-align: center;
border-left: none;
margin-left: 0px;
}
.oe_pick_widget .oe_pick_button{
margin-top: 3px;
margin-bottom: 3px;
}
}
@media screen and (max-width:450px){
.oe_pick_widget .oe_pick_list_header{
font-size: 16px;
}
.oe_pick_widget .oe_pick_list_table{
font-size: 12px;
}
.oe_pick_widget .oe_pick_col_small{
width: 50px;
}
}
@media screen and (max-width:326px){
.oe_pick_widget .oe_pick_toolbar{
width: 326px;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="TactileListWidget">
<!-- <t t-name="TactileListWidget">
<div class='oe_tactlist js_width' t-att-style="'width:' + widget.get_width() + 'px'">
<div class='oe_tactlist_header'>
<t t-esc='widget.get_title()' />
@ -44,228 +44,228 @@
<span class='oe_hresizer js_hresizer'></span>
</div>
</div>
</t>
</t> -->
<t t-name='PickingEditorWidget'>
<div class='oe_pick_list'>
<div class='oe_pick_list_header'>
Operations To Process
</div>
<table class='oe_pick_list_table'>
<colgroup>
<col class='oe_pick_col_expand'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_expand'></col>
<col class='oe_pick_col_small'></col>
</colgroup>
<thead>
<tr>
<th>Product</th>
<th class='oe_centeralign'>Qty</th>
<th class='oe_centeralign'>Rem</th>
<th class='oe_centeralign'>UoM</th>
<th>Location</th>
<th>Scan</th>
</tr>
</thead>
<tbody>
<t t-foreach="widget.get_rows()" t-as="row">
<tr t-att-class="row.classes">
<td> <t t-esc="row.cols.product" /> </td>
<td class='oe_centeralign'> <t t-esc="row.cols.qty" /> </td>
<td class='oe_centeralign'> <t t-esc="row.cols.rem" /> </td>
<td class='oe_centeralign'> <t t-esc="row.cols.uom" /> </td>
<td> <t t-esc="row.cols.loc" /> </td>
<td><span class='oe_row_button oe_expanded js_pack_scan' t-att-op-id='row.cols.id'></span></td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-name='PackageEditorWidget'>
<div class='oe_pick_list'>
<div class='oe_pick_list_header'> <t t-esc="widget.get_header()" /> </div>
<table class='oe_pick_list_table'>
<colgroup>
<col class='oe_pick_col_expand'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_small'></col>
</colgroup>
<thead>
<tr>
<th>Product</th>
<th class='oe_centeralign'>Qty</th>
<th class='oe_centeralign'>UoM</th>
</tr>
</thead>
<tbody>
<t t-foreach="widget.get_rows()" t-as="row">
<tr t-att-class="row.classes" t-att-op-id="row.att_op_id">
<td> <t t-esc="row.cols.product" /> </td>
<td class='oe_centeralign'> <t t-esc="row.cols.qty" /> </td>
<td class='oe_centeralign'> <t t-esc="row.cols.uom" /> </td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-name='PackageSelectorWidget'>
<div class='oe_pick_list'>
<div class='oe_pick_list_header'><t t-esc="widget.get_header()" /></div>
<table class='oe_pick_list_table'>
<colgroup>
<col class='oe_pick_col_expand'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_small'></col>
<col class='oe_pick_col_small'></col>
</colgroup>
<thead>
<tr>
<th>Package</th> <th></th> <th></th> <th></th>
</tr>
</thead>
<tbody>
<t t-foreach="widget.get_rows()" t-as="row">
<tr t-att-class="'js_pack_row' + row.classes" t-att-pack-id="row.id">
<td class='js_pack_select'> <t t-esc="row.cols.pack" /> </td>
<td class='oe_centeralign'> <span class='oe_row_button js_pack_print'>Print</span> </td>
<td class='oe_centeralign'> <span class='oe_row_button js_pack_plus'>Copy</span> </td>
<td class='oe_centeralign'> <span class='oe_row_button js_pack_minus'>Delete</span> </td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-name="PickingSearchResults">
<t t-if="results.length === 0">
<div class='oe_search_empty'>
No picking found.
</div>
</t>
<t t-if="results.length > 0">
<t t-foreach="results" t-as="picking">
<div class="oe_picking" t-att-data-id="picking.id">
<span class='oe_picking_name'><t t-esc="picking.name" /></span>
<div class="modal fade" id="js_LocationChooseModal" tabindex="-1" role="dialog" aria-labelledby="LocationChooseModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<h4 class="modal-title" id="myModalLabel">Choose a location</h4>
</div>
<div class="modal-body">
<p>Scan a location or select it in the list below</p>
<select id="js_loc_select" class="form-control">
<option class="js_loc_option" data-loc-id="false"></option>
<t t-foreach="widget.get_location()" t-as="loc">
<option class="js_loc_option" t-att-data-loc-id="loc.id"><t t-esc="loc.name"/></option>
</t>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary js_validate_location">Change Location</button>
</div>
</div>
</div>
</div>
<div class="row">
<div>
<div class="col-sm-4 col-xs-6">
<h2 class="oe_pick_app_header" />
</div>
<div class="col-sm-8 col-xs⁻6 text-right">
<button type="button" class='btn btn-default js_pick_done'> Create backorder </button>
<button type="button" class='btn btn-default js_pick_print'> Print </button>
</div>
</div>
</div>
<div class="row">
<div>
<div class="col-md-3 col-sm-4 col-xs-6">
<h3><strong>
<select id="js_select" class="form-control">
<option value="ToDo" id="js_select_todo">Operations ToDo</option>
<option value="Processed" id="js_select_processed">Operations Processed</option>
</select>
</strong>
</h3>
</div>
<div class="col-md-3 col-sm-4 col-xs-6">
<h2>
<div class="input-group">
<input type="text" class="form-control oe_searchbox" placeholder="Filter by location..."/>
<span class="input-group-btn">
<button class="btn btn-danger js_clear_search" type="button">x</button>
</span>
</div>
</h2>
</div>
<div class="col-md-6 col-sm-4 col-xs-12 text-right">
<h3>
<button type="button" class='btn btn-default js_pick_pack'> Put in Pack </button>
<button type="button" class='btn btn-danger js_drop_down'> Process </button>
</h3>
</div>
</div>
</div>
<div>
<table class='table table-condensed js_op_table_todo'>
<thead>
<tr>
<th class="text-left">Product</th>
<th class='text-center' width="150">Scanned</th>
<th class='text-center'>Todo</th>
<th>From</th>
<th>To</th>
</tr>
</thead>
<tbody>
<t t-foreach="widget.get_rows()" t-as="row">
<tr t-att-class="row.classes + 'js_pack_op_line'" t-att-data-id="row.cols.id" t-att-data-container-id="row.cols.container_id">
<td t-att-class="'text-left' + row.cols.head_container ? ' js_unfold' : ''">
<t t-if="!row.cols.head_container &amp;&amp; row.cols.container"><span class="fa fa-level-up fa-rotate-90" style="margin-left:10px;margin-right:10px;"></span></t>
<t t-esc="row.cols.product" />
</td>
<td class='text-center js_row_qty'>
<t t-if="row.cols.processed == 'false' &amp;&amp; !row.cols.container">
<div class="input-group">
<span class="input-group-addon js_minus input-sm" t-att-data-product-id='row.cols.product_id'><a href="#"><i class="fa fa-minus"></i></a></span>
<form class="js_submit_value">
<input type="text" class="form-control text-center js_qty" t-att-placeholder="row.cols.rem"></input>
<!-- <input type="submit" class="hidden"></input> -->
</form>
<span class="input-group-addon js_plus input-sm" t-att-data-product-id='row.cols.product_id'><a href="#"><i class="fa fa-plus"></i></a></span>
</div>
</t>
<t t-if="(row.cols.processed == 'true' || row.cols.container)">
<t t-esc="row.cols.rem" />
</t>
</td>
<td class="text-center">
<t t-esc="row.cols.qty"/> <t t-esc="row.cols.uom" />
</td>
<td class="js_loc">
<t t-esc="row.cols.loc" />
<t t-if="row.cols.pack" ><span> : <t t-esc="row.cols.pack" /></span></t>
<t t-if="row.cols.lot" ><span> : <t t-esc="row.cols.lot" /></span></t>
</td>
<td class="js_loc"> <t t-esc="row.cols.dest" />
<div class="pull-right btn-group">
<button type="button" class="btn btn-default dropdown-toggle fa fa-cog" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<!-- <t t-if="!row.cols.lot &amp;&amp; row.cols.product_id">
<li><a class="js_create_lot" href="#">Create Lot</a></li>
</t> -->
<t t-if="!row.cols.head_container &amp;&amp; !row.cols.container">
<li><a class="js_change_src" href="#">Change source location</a></li>
<li><a class="js_change_dst" href="#">Change destination location</a></li>
</t>
<t t-if="row.cols.head_container">
<li><a class="js_pack_change_dst" href="#">Change destination location</a></li>
<li class="divider"></li>
<li><a class="js_delete_pack" href="#">Remove from package</a></li>
<li><a class="js_print_pack" href="#">Print package label</a></li>
</t>
</ul>
</div>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-name="PickingSearchResults">
<div class="panel-heading">
<h3 class="panel-title">Search Results</h3>
</div>
<div class="panel-body">
<t t-if="results.length === 0">
<strong>No picking found.</strong>
</t>
</t>
<t t-if="results.length > 0">
<t t-foreach="results" t-as="picking">
<div class="col-lg-3 col-md-4">
<div class="panel panel-default oe_picking" t-att-data-id="picking.id">
<div class="panel-body">
<strong class='oe_picking_name'><t t-esc="picking.name" /></strong>
</div>
</div>
</div>
</t>
</t>
</div>
</t>
<t t-name="PickingMenuWidget">
<div class='oe_pick_widget'>
<table class='oe_pick_layout'>
<tr class='oe_pick_header_row'>
<td class='oe_pick_header'>
<div class='oe_pick_right_toolbar'>
<div class='oe_pick_button js_pick_quit'> Quit </div>
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header navbar-form navbar-left">
<input type='text' class="oe_searchbox form-control pull-left" placeholder='Search'/>
</div>
<div class="navbar-header navbar-form navbar-right">
<button type="button" class="btn btn-danger js_pick_quit pull-right">Quit</button>
</div>
</div>
</div>
<div class="container">
<h1 class="js_title_label">Select your operation</h1>
<div class='js_picking_not_found alert alert-warning hidden'>
Scanned picking could not be found
</div>
<div class='js_picking_search_results panel panel-info hidden'>
</div>
<div class="row js_picking_categories">
<t t-foreach="widget.picking_types" t-as="type">
<div class="col-lg-3 col-md-4">
<div t-att-class="'oe_kanban oe_picking oe_kanban_color_' + type.color + ' ' + (widget.pickings_by_type[type.id].length === 0 ? 'oe_empty':'js_pick_last') "
t-att-data-id="type.id">
<t t-if="type.code == 'incoming'" ><span class="fa fa-sign-in fa-2x"></span></t>
<t t-if="type.code == 'outgoing'" ><span class="fa fa-truck fa-2x fa-flip-horizontal"></span></t>
<t t-if="type.code == 'internal'" ><span class="fa fa-retweet fa-2x"></span></t>
<strong><span><t t-esc="type.complete_name"/></span></strong>
<div><t t-if="widget.pickings_by_type[type.id].length > 0">
<span class='badge'><t t-esc="widget.pickings_by_type[type.id].length" /> picking(s) </span>
</t></div>
</div>
<div class='oe_pick_toolbar'>
<div class='oe_searchbox oe_left'>
<input type='text' placeholder='Search'/>
</div>
</div>
</td>
</tr>
<tr class='oe_pick_body_row'>
<td class='oe_pick_body_cont'>
<div class='oe_pick_body'>
<div class='oe_pick_app'>
<h3 class='oe_pick_app_title'>Pickings</h3>
<div class='oe_picking_not_found oe_hidden'>
Scanned picking could not be found
</div>
<div class='oe_picking_search_results'>
</div>
<div class='oe_picking_categories'>
<p>
Select the type of picking you want to process.
</p>
<t t-foreach="widget.picking_types" t-as="type">
<div t-att-class="'oe_picking ' + (widget.pickings_by_type[type.id].length === 0 ? 'oe_empty':'js_pick_last') "
t-att-data-id="type.id">
<span class='oe_picking_name'><t t-esc="type.complete_name" /></span>
<t t-if="widget.pickings_by_type[type.id].length === 0">
<span class='oe_pick_app_info'>Nothing to do</span>
</t>
<t t-if="widget.pickings_by_type[type.id].length === 1">
<span class='oe_pick_app_info'>1 picking</span>
</t>
<t t-if="widget.pickings_by_type[type.id].length > 1">
<span class='oe_pick_app_info'><t t-esc="widget.pickings_by_type[type.id].length" /> pickings </span>
</t>
</div>
</t>
</div>
</div>
</div>
</td>
</tr>
</table>
</div>
</t>
</div>
</div>
</t>
<t t-name="PickingMainWidget">
<div class='oe_pick_widget'>
<table class='oe_pick_layout'>
<tr class='oe_pick_header_row'>
<td class='oe_pick_header'>
<div class='oe_pick_right_toolbar'>
<div class='oe_pick_button js_pick_menu'> Menu </div>
</div>
<div class='oe_pick_toolbar'>
<div class='oe_pick_button oe_disabled js_pick_prev'>&lt; Previous</div>
<div class='oe_pick_button oe_disabled js_pick_next'>Next &gt;</div>
</div>
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-left">
<button type="button" class="btn btn-primary navbar-btn js_pick_menu">Menu</button>
</div>
<div class="navbar-right">
<button type="button" class="btn btn-default navbar-btn js_pick_prev">&lt; Previous</button>
<button type="button" class="btn btn-default navbar-btn js_pick_next">Next &gt;</button>
</div>
</div>
</div>
</td>
</tr>
<tr class='oe_pick_body_row'>
<td class='oe_pick_body_cont'>
<div class='oe_pick_body'>
<div class='oe_pick_app'>
<div class='oe_pick_button js_pick_done'> Done </div>
<div class='oe_pick_button js_pick_print'> Print </div>
<div class='oe_pick_app_header'>
<t t-esc='widget.get_header()' />
</div>
<div class='oe_pick_button js_pick_pack'> Put in Pack </div>
<div class='oe_placeholder_picking_editor'></div>
<div class='oe_placeholder_package_editor'></div>
<div class='oe_placeholder_package_selector'></div>
<div class='oe_placeholder_menu'></div>
</div>
</div>
</td>
</tr>
</table>
<div class="container">
<div class='oe_placeholder_picking_editor'/>
</div>
</t>

View File

@ -131,6 +131,7 @@ class stock_location(osv.osv):
'scrap_location': fields.boolean('Is a Scrap Location?', help='Check this box to allow using this location to put scrapped/damaged goods.'),
'removal_strategy_ids': fields.one2many('product.removal', 'location_id', 'Removal Strategies'),
'putaway_strategy_ids': fields.one2many('product.putaway', 'location_id', 'Put Away Strategies'),
'loc_barcode': fields.char('Location barcode'),
}
_defaults = {
'active': True,
@ -141,6 +142,12 @@ class stock_location(osv.osv):
'posz': 0,
'scrap_location': False,
}
_sql_constraints = [('loc_barcode_company_uniq', 'unique (loc_barcode,company_id)', 'The barcode for a location must be unique per company !')]
def create(self, cr, uid, default, context=None):
if not default.get('loc_barcode', False):
default.update({'loc_barcode': default.get('complete_name', False)})
return super(stock_location,self).create(cr, uid, default, context=context)
def get_putaway_strategy(self, cr, uid, location, product, context=None):
pa = self.pool.get('product.putaway')
@ -1062,6 +1069,14 @@ class stock_picking(osv.osv):
'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
})
return vals
def open_barcode_interface(self, cr, uid, picking_ids, context=None):
final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
def do_partial_open_barcode(self, cr, uid, picking_ids, context=None):
self.do_prepare_partial(cr, uid, picking_ids, context=context)
return self.open_barcode_interface(cr, uid, picking_ids, context=context)
def do_prepare_partial(self, cr, uid, picking_ids, context=None):
context = context or {}
@ -1325,50 +1340,83 @@ class stock_picking(osv.osv):
def action_done_from_ui(self, cr, uid, picking_id, context=None):
""" called when button 'done' is pushed in the barcode scanner UI """
#write qty_done into field product_qty for every package_operation before doing the transfer
pack_op_obj = self.pool.get('stock.pack.operation')
for operation in self.browse(cr, uid, picking_id, context=context).pack_operation_ids:
pack_op_obj.write(cr, uid, operation.id, {'product_qty': operation.qty_done}, context=context)
self.do_transfer(cr, uid, [picking_id], context=context)
#return id of next picking to work on
return self.get_next_picking_for_ui(cr, uid, context=context)
def action_pack(self, cr, uid, picking_ids, context=None):
def action_pack(self, cr, uid, picking_ids, operation_filter_ids=None, context=None):
""" Create a package with the current pack_operation_ids of the picking that aren't yet in a pack.
Used in the barcode scanner UI and the normal interface as well. """
Used in the barcode scanner UI and the normal interface as well.
operation_filter_ids is used by barcode scanner interface to specify a subset of operation to pack"""
if operation_filter_ids == None:
operation_filter_ids = []
stock_operation_obj = self.pool.get('stock.pack.operation')
package_obj = self.pool.get('stock.quant.package')
stock_move_obj = self.pool.get('stock.move')
for picking_id in picking_ids:
operation_ids = stock_operation_obj.search(cr, uid, [('picking_id', '=', picking_id), ('result_package_id', '=', False)], context=context)
operation_search_domain = [('picking_id', '=', picking_id), ('result_package_id', '=', False)]
if operation_filter_ids != []:
operation_search_domain.append(('id', 'in', operation_filter_ids))
operation_ids = stock_operation_obj.search(cr, uid, operation_search_domain, context=context)
pack_operation_ids = []
if operation_ids:
for operation in stock_operation_obj.browse(cr, uid, operation_ids, context=context):
for record in operation.linked_move_operation_ids:
stock_move_obj.check_tracking(cr, uid, record.move_id, operation.package_id.id or operation.lot_id.id, context=context)
#If we haven't done all qty in operation, we have to split into 2 operation
op = operation
if (operation.qty_done < operation.product_qty):
new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context)
stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0}, context=context)
op = stock_operation_obj.browse(cr, uid, new_operation, context=context)
pack_operation_ids.append(op.id)
for record in op.linked_move_operation_ids:
stock_move_obj.check_tracking(cr, uid, record.move_id, op.package_id.id or op.lot_id.id, context=context)
package_id = package_obj.create(cr, uid, {}, context=context)
stock_operation_obj.write(cr, uid, operation_ids, {'result_package_id': package_id}, context=context)
stock_operation_obj.write(cr, uid, pack_operation_ids, {'result_package_id': package_id}, context=context)
return True
def process_product_id_from_ui(self, cr, uid, picking_id, product_id, context=None):
return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id)], context=context)
def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=True, context=None):
return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context)
def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, context=None):
def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None):
'''This function is called each time there barcode scanner reads an input'''
lot_obj = self.pool.get('stock.production.lot')
package_obj = self.pool.get('stock.quant.package')
product_obj = self.pool.get('product.product')
stock_operation_obj = self.pool.get('stock.pack.operation')
stock_location_obj = self.pool.get('stock.location')
answer = {'filter_loc': False, 'operation_id': False}
#check if the barcode correspond to a location
matching_location_ids = stock_location_obj.search(cr, uid, [('loc_barcode', '=', barcode_str)], context=context)
if matching_location_ids:
#if we have a location, return immediatly with the location name
location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
answer['filter_loc_id'] = matching_location_ids[0]
return answer
#check if the barcode correspond to a product
matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', barcode_str), ('default_code', '=', barcode_str)], context=context)
if matching_product_ids:
self.process_product_id_from_ui(cr, uid, picking_id, matching_product_ids[0], context=context)
op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
answer['operation_id'] = op_id
return answer
#check if the barcode correspond to a lot
matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
if matching_lot_ids:
lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], context=context)
op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
answer['operation_id'] = op_id
return answer
#check if the barcode correspond to a package
matching_package_ids = package_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
if matching_package_ids:
stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], context=context)
op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
answer['operation_id'] = op_id
return answer
return answer
class stock_production_lot(osv.osv):
@ -3616,6 +3664,7 @@ class stock_pack_operation(osv.osv):
'product_id': fields.many2one('product.product', 'Product', ondelete="CASCADE"), # 1
'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'qty_done': fields.float('Quantity Processed', digits_compute=dp.get_precision('Product Unit of Measure')),
'package_id': fields.many2one('stock.quant.package', 'Package'), # 2
'lot_id': fields.many2one('stock.production.lot', 'Lot/Serial Number'),
'result_package_id': fields.many2one('stock.quant.package', 'Container Package', help="If set, the operations are packed into this package", required=False, ondelete='cascade'),
@ -3628,10 +3677,13 @@ class stock_pack_operation(osv.osv):
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
'location_id': fields.many2one('stock.location', 'Location From', required=True),
'location_dest_id': fields.many2one('stock.location', 'Location To', required=True),
'processed': fields.selection([('true','Yes'), ('false','No')],'Has been processed?', required=True),
}
_defaults = {
'date': fields.date.context_today,
'qty_done': 0,
'processed': lambda *a: 'false',
}
def write(self, cr, uid, ids, vals, context=None):
@ -3651,8 +3703,32 @@ class stock_pack_operation(osv.osv):
self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context)
return res_id
def action_drop_down(self, cr, uid, ids, context=None):
''' Used by barcode interface to say that pack_operation has been moved from src location
to destination location, if qty_done is less than product_qty than we have to split the
operation in two to process the one with the qty moved
'''
processed_ids = []
for pack_op in self.browse(cr, uid, ids, context=None):
op = pack_op.id
if pack_op.qty_done < pack_op.product_qty:
# we split the operation in two
op = self.copy(cr, uid, pack_op.id, {'product_qty': pack_op.qty_done, 'qty_done': pack_op.qty_done}, context=context)
self.write(cr, uid, ids, {'product_qty': pack_op.product_qty - pack_op.qty_done, 'qty_done': 0}, context=context)
processed_ids.append(op)
self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
def create_and_assign_lot(self, cr, uid, id, context=None):
''' Used by barcode interface to create a new lot and assign it to the operation
'''
obj = self.browse(cr,uid,id,context)
product_id = obj.product_id.id
if not obj.lot_id:
new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, {'product_id': product_id}, context=context)
self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context)
#TODO: this function can be refactored
def _search_and_increment(self, cr, uid, picking_id, domain, context=None):
def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False ,visible_op_ids=False, increment=True, context=None):
'''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+1) otherwise create it
:param domain: list of tuple directly reusable as a domain
@ -3670,16 +3746,32 @@ class stock_pack_operation(osv.osv):
#if current_package_id is given in the context, we increase the number of items in this package
package_clause = [('result_package_id', '=', context.get('current_package_id', False))]
existing_operation_ids = self.search(cr, uid, [('picking_id', '=', picking_id)] + domain + package_clause, context=context)
todo_operation_ids = []
if existing_operation_ids:
if filter_visible:
todo_operation_ids = [val for val in existing_operation_ids if val in visible_op_ids]
else:
todo_operation_ids = existing_operation_ids
if todo_operation_ids:
#existing operation found for the given domain and picking => increment its quantity
operation_id = existing_operation_ids[0]
qty = self.browse(cr, uid, operation_id, context=context).product_qty + 1
self.write(cr, uid, [operation_id], {'product_qty': qty}, context=context)
operation_id = todo_operation_ids[0]
op_obj = self.browse(cr, uid, operation_id, context=context)
qty = op_obj.qty_done
if increment:
qty += 1
else:
qty -= 1 if qty >= 1 else 0
if qty == 0 and op_obj.product_qty == 0:
#we have a line with 0 qty set, so delete it
self.unlink(cr, uid, [operation_id], context=context)
return False
self.write(cr, uid, [operation_id], {'qty_done': qty}, context=context)
else:
#no existing operation found for the given domain and picking => create a new one
values = {
'picking_id': picking_id,
'product_qty': 1,
'product_qty': 0,
'qty_done': 1,
}
for key in domain:
var_name, dummy, value = key
@ -3691,7 +3783,7 @@ class stock_pack_operation(osv.osv):
update_dict['product_uom_id'] = uom_id
values.update(update_dict)
operation_id = self.create(cr, uid, values, context=context)
return True
return operation_id
class stock_move_operation_link(osv.osv):
@ -3857,6 +3949,11 @@ class stock_picking_type(osv.osv):
_description = "The picking type determines the picking view"
_order = 'sequence'
def open_barcode_interface(self, cr, uid, ids, context=None):
final_url="/barcode/web/#action=stock.ui&picking_type_id="+str(ids[0]) if len(ids) else '0'
return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
picking_obj = self.pool.get('stock.picking')
res = dict.fromkeys(ids, [])

View File

@ -375,6 +375,7 @@
<field name="posx"/>
<field name="posy"/>
<field name="posz"/>
<field name="loc_barcode"/>
</group>
</group>
<separator string="Removal Strategies" groups="stock.group_adv_location"/>
@ -721,9 +722,9 @@
<button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed,partially_available" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="do_transfer" states="assigned" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="do_transfer" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', False), ('state', '!=', 'partially_available')]}"/>
<button name="do_prepare_partial" string="Enter Transfer Details" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','not in',('assigned', 'partially_available'))]}"/>
<button name="do_transfer" states="assigned" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('pack_operation_exist', '=', True)]}"/>
<button name="do_partial_open_barcode" string="Enter Transfer Details" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','not in',('assigned', 'partially_available'))]}"/>
<button name="open_barcode_interface" string="Open Barcode interface" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','not in',('assigned', 'partially_available'))]}"/>
<button name="do_print_delivery" string="Print Delivery Order" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '!=', 'outgoing'), ('state', '!=', 'done')]}"/>
<button name="do_print_picking" string="Print Picking List" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '=', 'outgoing'), ('state', '!=', 'assigned')]}"/>
<button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
@ -757,6 +758,7 @@
<notebook>
<page string="Products">
<separator string="Stock Moves" attrs="{'invisible': [('pack_operation_exist', '=', False)]}"/>
<!--
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
<group>
<group col="3">
@ -782,6 +784,9 @@
<field name="result_package_id" groups="stock.group_tracking_lot"/>
</tree>
</field>
-->
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
<field name="pack_operation_exist" invisible="1"/>
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
</page>
<page string="Additional Info">
@ -1249,12 +1254,6 @@
<menuitem action="action_move_form2" id="menu_action_move_form2" parent="menu_traceability" sequence="3" groups="stock.group_locations"/>
<record id="action_stock_stock_ui" model="ir.actions.client">
<field name="name">Stock picking</field>
<field name="tag">stock.ui</field>
<field name="res_model">stock</field>
</record>
<!--
Reception Picking (By Stock Move)
From stock_partial_move_view
@ -1465,7 +1464,7 @@
<div class="oe_kanban_content">
<h4 class="oe_center"><field name="complete_name"/></h4>
<div class="oe_right">
<a name="%(action_stock_stock_ui)d" type="action">
<a name="open_barcode_interface" type="object">
<img src="/stock/static/src/img/scan.png"
alt="Click to launch the barcode interface"
class="oe_stock_scan_image" title="Click to launch the barcode interface"/>

View File

@ -22,7 +22,7 @@
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='do_prepare_partial']" position="after">
<xpath expr="//button[@name='do_partial_open_barcode']" position="after">
<button name="%(action_stock_invoice_onshipping)d" string="Create Invoice" attrs="{'invisible': ['|',('state','&lt;&gt;','done'),('invoice_state','&lt;&gt;','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/>
<button name="%(action_stock_invoice_onshipping)d" string="Refund Invoice" attrs="{'invisible': ['|',('state','&lt;&gt;','done'),('invoice_state','&lt;&gt;','invoiced')]}" type="action" class="oe_highlight" groups="base.group_user" context="{'inv_type': 'out_refund'}"/>
</xpath>