[MERGE] branch with the necessary fixes of the barcode scanner UI. Further improvements related to the syle and usability are planned but all features should be fully working by now.

bzr revid: qdp-launchpad@openerp.com-20140213094446-lg1bevdrlv3tvyjn
This commit is contained in:
Quentin (OpenERP) 2014-02-13 10:44:46 +01:00
commit 1b01820265
6 changed files with 158 additions and 168 deletions

View File

@ -107,9 +107,6 @@ Dashboard / Reports for Warehouse Management will include:
'static/src/css/stock.css',
],
'js': [
'static/lib/sparkline/jquery.sparkline.js',
'static/lib/justgage.js',
'static/src/js/stock_picking_type.js',
'static/src/js/widgets.js',
],
'qweb': ['static/src/xml/picking.xml'],

View File

@ -223,7 +223,7 @@
* ----------------------- */
.oe_pick_widget .oe_searchbox{
float: right;
margin-top: 13px;
}
.oe_pick_widget .oe_searchbox input{
padding: 8px;

View File

@ -54,6 +54,9 @@ function openerp_picking_widgets(instance){
console.log('Id:',id);
self.getParent().scan_product_id(id);
});
//remove navigtion bar from default openerp GUI
$('td.navbar').html('<div></div>');
},
});
@ -68,7 +71,6 @@ function openerp_picking_widgets(instance){
var model = this.getParent();
var rows = [];
var ops = model.get_current_operations();
_.each( ops, function(op){
rows.push({
cols: {
@ -118,10 +120,9 @@ function openerp_picking_widgets(instance){
var model = this.getParent();
this.$('.js_pack_row').each(function(){
var pack_id = parseInt($(this).attr('pack-id'));
$('.js_pack_print', this).click(function(){ model.print_package(pack_id); });
$('.js_pack_plus', this).click(function(){ model.copy_package(pack_id); });
$('.js_pack_minus', this).click(function(){ model.delete_package(pack_id); });
$('.js_pack_plus', this).click(function(){ model.copy_package_op(pack_id); });
$('.js_pack_minus', this).click(function(){ model.delete_package_op(pack_id); });
$('.js_pack_select', this).click(function(){
if(model.get_selected_package() && model.get_selected_package().id === pack_id){
model.deselect_package();
@ -159,7 +160,7 @@ function openerp_picking_widgets(instance){
}
self.pickings_by_type[0] = [];
return new instance.web.Model('stock.picking').call('search_read',[ [['state','in',['confirmed','assigned']]], [] ], {context: new instance.web.CompoundContext()});
return new instance.web.Model('stock.picking').call('search_read',[ [['state','in', ['assigned', 'partially_available']]], [] ], {context: new instance.web.CompoundContext()});
}).then(function(pickings){
self.pickings = pickings;
@ -182,6 +183,8 @@ function openerp_picking_widgets(instance){
this.$('.oe_searchbox input').keyup(function(event){
self.on_searchbox($(this).val());
});
//remove navigtion bar from default openerp GUI
$('td.navbar').html('<div></div>');
},
start: function(){
this._super();
@ -214,6 +217,7 @@ function openerp_picking_widgets(instance){
});
},
search_picking: function(barcode){
//TODO don't crash if a not supported char is given
var re = RegExp("([0-9]+):.*?"+barcode.toUpperCase(),"gi");
var results = [];
for(var i = 0; i < 100; i++){
@ -234,7 +238,7 @@ function openerp_picking_widgets(instance){
for(var i = 0, len = this.pickings.length; i < len; i++){
var picking = this.pickings[i];
if(picking.name.toUpperCase() === barcode.toUpperCase()){
if(picking.name.toUpperCase() === $.trim(barcode.toUpperCase())){
this.goto_picking(picking.id);
break;
}
@ -294,7 +298,6 @@ function openerp_picking_widgets(instance){
this.packages = null;
this.barcode_scanner = new module.BarcodeScanner();
this.picking_type_id = params.context.active_id || 0;
if(params.context.picking_id){
this.loaded = this.load(params.context.picking_id);
@ -312,7 +315,6 @@ function openerp_picking_widgets(instance){
function load_picking_list(type_id){
var pickings = new $.Deferred();
new instance.web.Model('stock.picking')
.call('get_next_picking_for_ui',[{'default_picking_type_id':type_id}])
.then(function(picking_ids){
@ -322,7 +324,7 @@ function openerp_picking_widgets(instance){
buttons: [{
text:_t('Ok'),
click: function(){
self.quit();
self.menu();
}
}]
}, _t('<p>We could not find a picking to display.</p>'))).open();
@ -356,11 +358,12 @@ function openerp_picking_widgets(instance){
})
.then(function(picking){
self.picking = picking;
self.picking_type_id = picking.picking_type_id[0];
loaded_picking.resolve();
});
}
var loaded = loaded_picking.then(function(){
return loaded_picking.then(function(){
return new instance.web.Model('stock.move').call('read',[self.picking.move_lines, [], new instance.web.CompoundContext()]);
}).then(function(movelines){
@ -369,23 +372,20 @@ function openerp_picking_widgets(instance){
return new instance.web.Model('stock.pack.operation').call('read',[self.picking.pack_operation_ids, [], new instance.web.CompoundContext()]);
}).then(function(operations){
self.operations = operations;
var package_ids = [];
for(var i = 0; i < operations.length; i++){
if(!_.contains(package_ids,operations[i].result_package_id[0])){
package_ids.push(operations[i].result_package_id[0]);
if (operations[i].result_package_id[0]){
package_ids.push(operations[i].result_package_id[0]);
}
}
}
return new instance.web.Model('stock.quant.package').call('read',[package_ids, [], new instance.web.CompoundContext()]);
}).then(function(packages){
self.packages = packages;
});
return loaded;
},
start: function(){
this._super();
@ -395,10 +395,12 @@ function openerp_picking_widgets(instance){
this.barcode_scanner.connect(function(ean){
self.scan(ean);
});
this.$('.js_pick_quit').click(function(){ self.quit(); });
this.$('.js_pick_pack').click(function(){ self.pack(); });
this.$('.js_pick_done').click(function(){ self.done(); });
this.$('.js_pick_print').click(function(){ self.print_picking(); });
this.$('.js_pick_prev').click(function(){ self.picking_prev(); });
this.$('.js_pick_next').click(function(){ self.picking_next(); });
this.$('.js_pick_menu').click(function(){ self.menu(); });
@ -437,10 +439,8 @@ function openerp_picking_widgets(instance){
self.$('.oe_pick_app_header').text(self.get_header());
});
}).fail(function(error) {console.log(error);});
return this._super();
},
// reloads the data from the provided picking and refresh the ui.
// (if no picking_id is provided, gets the first picking in the db)
@ -463,15 +463,14 @@ function openerp_picking_widgets(instance){
}else{
self.$('.js_pick_next').removeClass('oe_disabled');
}
self.$('.oe_pick_app_header').text(self.get_header());
});
},
get_header: function(){
if(this.picking){
return _t('Picking:') +' '+this.picking.name;
return this.picking.name;
}else{
return _t('Picking:');
return '';
}
},
menu: function(){
@ -515,9 +514,14 @@ function openerp_picking_widgets(instance){
done: function(){
var self = this;
new instance.web.Model('stock.picking')
.call('action_done_from_ui',[self.picking.id])
.then(function(new_picking_id){
return self.refresh_ui(new_picking_id);
.call('action_done_from_ui',[self.picking.id, {'default_picking_type_id': self.picking_type_id}])
.then(function(new_picking_ids){
if (new_picking_ids){
return self.refresh_ui(new_picking_ids[0]);
}
else {
return 0;
}
});
},
print_package: function(package_id){
@ -528,6 +532,24 @@ function openerp_picking_widgets(instance){
return self.do_action(action);
});
},
print_picking: function(){
var self = this;
new instance.web.Model('stock.picking.type').call('read', [[self.picking_type_id], ['code'], new instance.web.CompoundContext()])
.then(function(pick_type){
if (pick_type[0]['code'] == 'outgoing'){
new instance.web.Model('stock.picking').call('do_print_delivery',[[self.picking.id]])
.then(function(action){
return self.do_action(action);
});
}
else {
new instance.web.Model('stock.picking').call('do_print_picking',[[self.picking.id]])
.then(function(action){
return self.do_action(action);
});
}
});
},
picking_next: function(){
for(var i = 0; i < this.pickings.length; i++){
if(this.pickings[i] === this.picking.id){
@ -548,20 +570,21 @@ function openerp_picking_widgets(instance){
}
}
},
copy_package: function(package_id){
copy_package_op: function(pack_id){
var self = this;
new instance.web.Model('stock.quant.package')
.call('copy',[[package_id]])
new instance.web.Model('stock.quant.package').call('copy_pack',[pack_id])
.then(function(){
return self.refresh_ui(self.picking.id);
});
},
delete_package: function(package_id){
delete_package_op: function(pack_id){
var self = this;
new instance.web.Model('stock.quant.package')
.call('unlink',[[package_id]])
.then(function(){
return self.refresh_ui(self.picking.id);
new instance.web.Model('stock.pack.operation').call('search', [[['result_package_id', '=', pack_id]]])
.then(function(op_ids) {
new instance.web.Model('stock.pack.operation').call('unlink', [op_ids])
.then(function() {
return self.refresh_ui(self.picking.id);
});
});
},
deselect_package: function(){
@ -631,15 +654,9 @@ function openerp_picking_widgets(instance){
return;
}
if(quantity === '++'){
quantity = op.product_qty + 1;
}else if(quantity === '--'){
quantity = op.product_qty - 1;
}
if(typeof quantity === 'number' && quantity >= 0){
new instance.web.Model('stock.pack.operation')
.call('write',[[op.id],{'product_qty': quantity }])
.call('write',[[op],{'product_qty': quantity }])
.then(function(){
self.refresh_ui(self.picking.id);
});
@ -652,31 +669,23 @@ function openerp_picking_widgets(instance){
var numpad_timestamp;
this.numpad_handler = function(e){
// upper row numbers are reserved for the barcode scanner
if( e.keyCode < 48 && e.keyCode >= 58){
if(numpad_timestamp + 1500 < new Date().getTime()){
numpad = [];
}
if(e.keyCode === 27 || e.keyCode === 8){ // ESC or BACKSPACE
numpad = [];
}else if(e.keyCode >= 96 && e.keyCode <= 105){ // NUMPAD NUMBERS
numpad.push(e.keyCode - 96);
}else if(e.keyCode === 13){ // ENTER
if(numpad.length > 0){
self.set_operation_quantity(parseInt(numpad.join('')));
}
numpad = [];
}else if(e.keyCode === 107){ // NUMPAD +
self.set_operation_quantity('++');
numpad = [];
}else if(e.keyCode === 109){ // NUMPAD -
self.set_operation_quantity('--');
numpad = [];
}else{
numpad = [];
}
numpad_timestamp = new Date().getTime();
if(numpad_timestamp + 1500 < new Date().getTime()){
numpad = [];
}
if(e.keyCode === 27 || e.keyCode === 8){ // ESC or BACKSPACE
numpad = [];
}else if(e.keyCode >= 48 && e.keyCode <= 57){ // NUMPAD NUMBERS
numpad.push(e.keyCode - 48);
}else if(e.keyCode === 13){ // ENTER
console.log('enter');
if(numpad.length > 0){
self.set_operation_quantity(parseInt(numpad.join('')));
}
numpad = [];
}else{
numpad = [];
}
numpad_timestamp = new Date().getTime();
};
$('body').on('keypress', this.numpad_handler);
},
@ -734,3 +743,11 @@ function openerp_picking_widgets(instance){
});
}
openerp.stock = function(openerp) {
openerp.stock = openerp.stock || {};
openerp_picking_widgets(openerp);
}

View File

@ -175,11 +175,14 @@
<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>
<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'>
@ -187,10 +190,6 @@
<div class='oe_pick_body'>
<div class='oe_pick_app'>
<div class='oe_searchbox'>
<input type='text' placeholder='Search'/>
</div>
<h3 class='oe_pick_app_title'>Pickings</h3>
<div class='oe_picking_not_found oe_hidden'>
@ -208,7 +207,7 @@
<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.name" /></span>
<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>
@ -229,22 +228,19 @@
</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 class='oe_pick_button js_pick_quit'> Quit </div>
</div>
<div class='oe_pick_toolbar'>
<div class='oe_pick_button oe_small oe_disabled oe_left js_pick_prev'>&lt;</div>
<div class='oe_pick_button js_pick_pack'> Pack </div>
<div class='oe_pick_button js_pick_done'> Done </div>
<div class='oe_pick_button oe_small oe_disabled oe_right js_pick_next'>&gt;</div>
<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>
</td>
@ -253,10 +249,13 @@
<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>

View File

@ -596,7 +596,7 @@ class stock_picking(osv.osv):
_name = "stock.picking"
_inherit = ['mail.thread']
_description = "Picking List"
_order = "priority desc, date desc, id desc"
_order = "priority desc, date asc, id desc"
def _set_min_date(self, cr, uid, id, field, value, arg, context=None):
move_obj = self.pool.get("stock.move")
@ -1010,31 +1010,11 @@ class stock_picking(osv.osv):
self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context)
def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None):
def _create_link_for_product(product_id, qty):
qty_to_assign = qty
for move in picking.move_lines:
if move.product_id.id == product_id and move.state not in ['done', 'cancel']:
qty_on_link = min(move.remaining_qty, qty_to_assign)
link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context)
qty_to_assign -= qty_on_link
move.refresh()
if qty_to_assign <= 0:
break
link_obj = self.pool.get('stock.move.operation.link')
uom_obj = self.pool.get('product.uom')
package_obj = self.pool.get('stock.quant.package')
pack_op_obj = self.pool.get('stock.pack.operation')
for picking in self.browse(cr, uid, picking_ids, context=context):
for op in picking.pack_operation_ids:
to_unlink_ids = [x.id for x in op.linked_move_operation_ids]
if to_unlink_ids:
link_obj.unlink(cr, uid, to_unlink_ids, context=context)
if op.product_id:
normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.product_qty, op.product_id.uom_id.id)
_create_link_for_product(op.product_id.id, normalized_qty)
elif op.package_id:
for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items():
_create_link_for_product(product_id, qty)
op_ids = [op.id for op in picking.pack_operation_ids]
if op_ids:
pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context)
def _create_extra_moves(self, cr, uid, picking, context=None):
'''This function creates move lines on a picking, at the time of do_transfer, based on
@ -1129,13 +1109,13 @@ class stock_picking(osv.osv):
""" returns the next pickings to process. Used in the barcode scanner UI"""
if context is None:
context = {}
domain = [('state', 'in', ('confirmed', 'assigned'))]
domain = [('state', 'in', ('assigned', 'partially_available'))]
if context.get('default_picking_type_id'):
domain.append(('picking_type_id', '=', context['default_picking_type_id']))
return self.search(cr, uid, domain, context=context)
def action_done_from_ui(self, cr, uid, picking_id, context=None):
""" called when button 'done' in pused in the barcode scanner UI """
""" called when button 'done' is pushed in the barcode scanner UI """
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)
@ -3319,6 +3299,24 @@ class stock_package(osv.osv):
res[quant.product_id.id] += quant.qty
return res
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
if not default.get('name'):
default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
return super(stock_package, self).copy(cr, uid, id, default, context=context)
def copy_pack(self, cr, uid, id, default_pack_values=None, default=None, context=None):
stock_pack_operation_obj = self.pool.get('stock.pack.operation')
if default is None:
default = {}
new_package_id = self.copy(cr, uid, id, default_pack_values, context=context)
default['result_package_id'] = new_package_id
op_ids = stock_pack_operation_obj.search(cr, uid, [('result_package_id', '=', id)], context=context)
for op_id in op_ids:
stock_pack_operation_obj.copy(cr, uid, op_id, default, context=context)
class stock_pack_operation(osv.osv):
_name = "stock.pack.operation"
_description = "Packing Operation"
@ -3407,6 +3405,42 @@ class stock_pack_operation(osv.osv):
'date': fields.date.context_today,
}
def write(self, cr, uid, ids, vals, context=None):
res = super(stock_pack_operation, self).write(cr, uid, ids, vals, context=context)
self.recompute_rem_qty_from_operation(cr, uid, ids, context=context)
return res
def create(self, cr, uid, vals, context=None):
res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context)
self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context)
return res_id
def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None):
def _create_link_for_product(product_id, qty):
qty_to_assign = qty
for move in op.picking_id.move_lines:
if move.product_id.id == product_id and move.state not in ['done', 'cancel']:
qty_on_link = min(move.remaining_qty, qty_to_assign)
link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context)
qty_to_assign -= qty_on_link
move.refresh()
if qty_to_assign <= 0:
break
link_obj = self.pool.get('stock.move.operation.link')
uom_obj = self.pool.get('product.uom')
package_obj = self.pool.get('stock.quant.package')
for op in self.browse(cr, uid, op_ids, context=context):
to_unlink_ids = [x.id for x in op.linked_move_operation_ids]
if to_unlink_ids:
link_obj.unlink(cr, uid, to_unlink_ids, context=context)
if op.product_id:
normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.product_qty, op.product_id.uom_id.id)
_create_link_for_product(op.product_id.id, normalized_qty)
elif op.package_id:
for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items():
_create_link_for_product(product_id, qty)
def process_packaging(self, cr, uid, operation, quants, context=None):
''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found
a quant already has been with the good package information so we don't consider that case in this method'''
@ -3447,7 +3481,7 @@ class stock_pack_operation(osv.osv):
#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)
self.write(cr, uid, [operation_id], {'product_qty': qty}, context=context)
else:
#no existing operation found for the given domain and picking => create a new one
values = {
@ -3622,39 +3656,6 @@ class stock_picking_type(osv.osv):
_description = "The picking type determines the picking view"
_order = 'sequence'
def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
""" Generic method to generate data for bar chart values using SparklineBarWidget.
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
:param obj: the target model (i.e. crm_lead)
:param domain: the domain applied to the read_group
:param list read_fields: the list of fields to read in the read_group
:param str value_field: the field used to compute the value of the bar slice
:param str groupby_field: the fields used to group
:return list section_result: a list of dicts: [
{ 'value': (int) bar_column_value,
'tootip': (str) bar_column_tooltip,
}
]
"""
month_begin = date.today().replace(day=1)
section_result = [{
'value': 0,
'tooltip': (month_begin + relativedelta.relativedelta(months=i)).strftime('%B'),
} for i in range(-2, 2, 1)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
for group in group_obj:
group_begin_date = datetime.strptime(group['__domain'][0][2], DEFAULT_SERVER_DATE_FORMAT)
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
section_result[-month_delta.months + 2] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
inner_groupby = (group.get('__context', {})).get('group_by',[])
if inner_groupby:
groupby_picking = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context)
for groupby in groupby_picking:
section_result[-month_delta.months + 2]['value'] = groupby.get(value_field, 0)
return section_result
def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
picking_obj = self.pool.get('stock.picking')
res = dict.fromkeys(ids, [])
@ -3672,24 +3673,6 @@ class stock_picking_type(osv.osv):
res[picking_type_id] = tristates
return res
def _get_monthly_pickings(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('stock.picking')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
groupby_begin = (month_begin + relativedelta.relativedelta(months=-2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
groupby_end = (month_begin + relativedelta.relativedelta(months=2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
created_domain = [
('picking_type_id', '=', id),
('state', '=', 'done'),
('date', '>=', groupby_begin),
('date', '<', groupby_end),
]
res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date', 'picking_type_id'], 'picking_type_id_count', ['date', 'picking_type_id'], context=context)
return res
def _get_picking_count(self, cr, uid, ids, field_names, arg, context=None):
obj = self.pool.get('stock.picking')
domains = {
@ -3799,9 +3782,6 @@ class stock_picking_type(osv.osv):
'active': fields.boolean('Active'),
# Statistics for the kanban view
'monthly_picking': fields.function(_get_monthly_pickings,
type='string',
string='Done Pickings per Month'),
'last_done_picking': fields.function(_get_tristate_values,
type='string',
string='Last 10 Done Pickings'),

View File

@ -1494,9 +1494,6 @@
</div>
<div>
<a name="%(action_picking_tree)d" type="action">All Operations</a>
<!--<a name="%(action_picking_tree_done_grouped)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_picking" widget="sparkline_bar">Monthly Done Operations</field>
</a>-->
</div>
</div>
<div class="oe_picking_type_gauge">