[IMP] stock_picking_ui: new menu to select the picking by type or barcode + correctly restrict picking navigation by type in main screen
bzr revid: fva@openerp.com-20131003094904-65t4r9f0git7pl7q
This commit is contained in:
parent
d001d2fb0a
commit
5467c93bc5
|
@ -48,7 +48,7 @@
|
|||
.oe_pick_widget .oe_pick_button{
|
||||
display: inline-block;
|
||||
min-width: 64px;
|
||||
padding: 5px;
|
||||
padding: 5px 10px;
|
||||
font-size: 18px;
|
||||
border-radius: 3px;
|
||||
margin: 10px 5px;
|
||||
|
@ -70,7 +70,8 @@
|
|||
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):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;
|
||||
|
@ -101,6 +102,19 @@
|
|||
color: rgb(168, 6, 6);
|
||||
}
|
||||
|
||||
.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 *
|
||||
* ----------------------- */
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
console.log('Executing picking module');
|
||||
|
||||
openerp.stock = function(openerp){
|
||||
|
||||
console.log('Loading stock picking module');
|
||||
|
||||
openerp.stock = instance.stock || {};
|
||||
openerp_picking_widgets(openerp);
|
||||
openerp.web.client_actions.add('stock.ui', 'instance.stock.PickingMainWidget');
|
||||
};
|
||||
|
||||
|
|
@ -65,6 +65,5 @@ openerp.stock = function(openerp) {
|
|||
|
||||
openerp.stock = openerp.stock || {};
|
||||
openerp_picking_widgets(openerp);
|
||||
openerp.web.client_actions.add('stock.ui', 'instance.stock.PickingMainWidget');
|
||||
|
||||
};
|
||||
|
|
|
@ -97,6 +97,104 @@ function openerp_picking_widgets(instance){
|
|||
},
|
||||
});
|
||||
|
||||
module.PickingMenuWidget = instance.web.Widget.extend({
|
||||
template: 'PickingMenuWidget',
|
||||
init: function(parent, params){
|
||||
this._super(parent,params);
|
||||
var self = this;
|
||||
|
||||
this.picking_types = [];
|
||||
this.loaded = this.load();
|
||||
this.scanning_type = 0;
|
||||
this.barcode_scanner = new module.BarcodeScanner();
|
||||
this.pickings_by_type = {};
|
||||
|
||||
},
|
||||
load: function(){
|
||||
var self = this;
|
||||
return new instance.web.Model('stock.picking.type').get_func('search_read')([],[])
|
||||
.then(function(types){
|
||||
self.picking_types = types;
|
||||
|
||||
for(var i = 0; i < types.length; i++){
|
||||
self.pickings_by_type[types[i].id] = [];
|
||||
}
|
||||
self.pickings_by_type[0] = [];
|
||||
|
||||
return new instance.web.Model('stock.picking').call('search_read',[ [['state','in',['confirmed','assigned']]], [] ], {context: new instance.web.CompoundContext()});
|
||||
|
||||
}).then(function(pickings){
|
||||
self.pickings = pickings;
|
||||
|
||||
for(var i = 0; i < pickings.length; i++){
|
||||
var picking = pickings[i];
|
||||
self.pickings_by_type[picking.picking_type_id[0]].push(picking);
|
||||
}
|
||||
});
|
||||
},
|
||||
renderElement: function(){
|
||||
this._super();
|
||||
var self = this;
|
||||
this.$('.js_pick_quit').click(function(){ self.quit(); });
|
||||
this.$('.js_pick_scan').click(function(){ self.scan_picking($(this).data('id')); });
|
||||
this.$('.js_pick_last').click(function(){ self.goto_picking($(this).data('id')); });
|
||||
},
|
||||
start: function(){
|
||||
var self = this;
|
||||
this.barcode_scanner.connect(function(barcode){
|
||||
self.on_scan(barcode);
|
||||
});
|
||||
this.loaded.then(function(){
|
||||
self.renderElement();
|
||||
});
|
||||
},
|
||||
goto_picking: function(type_id){
|
||||
this.do_action({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'stock.ui',
|
||||
target: 'current',
|
||||
context: { active_id: type_id },
|
||||
},{
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
},
|
||||
scan_picking: function(id){
|
||||
this.$('.js_pick_scan.oe_active').text(_t('Scan')).removeClass('oe_active');
|
||||
if(id !== this.scanning_type){
|
||||
this.$('.js_pick_scan[data-id='+id+']').text(_t('Please scan a barcode ...')).addClass('oe_active');
|
||||
this.scanning_type = id;
|
||||
}else{
|
||||
this.scanning_type = 0;
|
||||
}
|
||||
},
|
||||
on_scan: function(barcode){
|
||||
for(var i = 0, len = this.pickings.length; i < len; i++){
|
||||
var picking = this.pickings[i];
|
||||
if(picking.picking_type_id[0] === this.scanning_type && picking.name.toUpperCase() === barcode.toUpperCase()){
|
||||
this.do_action({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'stock.ui',
|
||||
target: 'current',
|
||||
context: { picking_id: picking.id },
|
||||
},{
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.$('.js_pick_scan.oe_active').text(_t('Scanned picking not found'));
|
||||
},
|
||||
quit: function(){
|
||||
instance.webclient.set_content_full_screen(false);
|
||||
window.location = '/'; // FIXME Ask niv how to do it correctly
|
||||
},
|
||||
destroy: function(){
|
||||
this._super();
|
||||
this.barcode_scanner.disconnect();
|
||||
instance.webclient.set_content_full_screen(false);
|
||||
},
|
||||
});
|
||||
openerp.web.client_actions.add('stock.menu', 'instance.stock.PickingMenuWidget');
|
||||
|
||||
module.PickingMainWidget = instance.web.Widget.extend({
|
||||
template: 'PickingMainWidget',
|
||||
init: function(parent,params){
|
||||
|
@ -108,29 +206,29 @@ function openerp_picking_widgets(instance){
|
|||
this.movelines = null;
|
||||
this.operations = null;
|
||||
this.packages = null;
|
||||
this.scan_timestamp = 0;
|
||||
this.numpad_buffer = [];
|
||||
this.barcode_scanner = new module.BarcodeScanner();
|
||||
this.picking_type_id = params.context.active_id || 0;
|
||||
|
||||
window.pickwidget = this;
|
||||
|
||||
console.log('Action params:', params);
|
||||
console.log('Session:',instance.session);
|
||||
|
||||
this.loaded = this.load();
|
||||
if(params.context.picking_id){
|
||||
this.loaded = this.load(params.context.picking_id);
|
||||
}else{
|
||||
this.loaded = this.load();
|
||||
}
|
||||
},
|
||||
|
||||
// load the picking data from the server. If picking_id is undefined, it will take the first picking
|
||||
// belonging to the category
|
||||
load: function(picking_id){
|
||||
var self = this;
|
||||
|
||||
console.log('LOADING DATA FROM SERVER');
|
||||
|
||||
function load_picking_list(type_id){
|
||||
var pickings = new $.Deferred();
|
||||
|
||||
if(picking_id){
|
||||
var picking = new instance.web.Model('stock.picking').call('read',[[picking_id], []]);
|
||||
}else{
|
||||
var picking = new $.Deferred();
|
||||
var pickings = new instance.web.Model('stock.picking')
|
||||
.call('get_picking_for_packing_ui')
|
||||
new instance.web.Model('stock.picking')
|
||||
.call('get_picking_for_packing_ui',[{'default_picking_type_id':type_id}])
|
||||
.then(function(picking_ids){
|
||||
console.log('Picking Ids',picking_ids);
|
||||
if(!picking_ids || picking_ids.length === 0){
|
||||
(new instance.web.Dialog(self,{
|
||||
title: _t('No Picking Available'),
|
||||
|
@ -142,32 +240,48 @@ function openerp_picking_widgets(instance){
|
|||
}]
|
||||
}, _t('<p>We could not find a picking to display.</p>'))).open();
|
||||
|
||||
picking.reject();
|
||||
pickings.reject();
|
||||
}else{
|
||||
self.pickings = picking_ids;
|
||||
new instance.web.Model('stock.picking').call('read',[[picking_ids[0]],[]])
|
||||
.then(function(pick){
|
||||
picking.resolve(pick);
|
||||
});
|
||||
pickings.resolve(picking_ids);
|
||||
}
|
||||
});
|
||||
|
||||
return pickings;
|
||||
}
|
||||
|
||||
// if we have a specified picking id, we load that one, and we load the picking of the same type as the active list
|
||||
if( picking_id ){
|
||||
var loaded_picking = new instance.web.Model('stock.picking')
|
||||
.call('read',[[picking_id], [], new instance.web.CompoundContext()])
|
||||
.then(function(picking){
|
||||
self.picking = picking[0];
|
||||
|
||||
return load_picking_list(self.picking.picking_type_id[0]);
|
||||
});
|
||||
}else{
|
||||
// if we don't have a specified picking id, we load the pickings belong to the specified type, and then we take
|
||||
// the first one of that list as the active picking
|
||||
var loaded_picking = new $.Deferred();
|
||||
load_picking_list(self.picking_type_id)
|
||||
.then(function(){
|
||||
return new instance.web.Model('stock.picking').call('read',[self.pickings[0],[], new instance.web.CompoundContext()]);
|
||||
})
|
||||
.then(function(picking){
|
||||
self.picking = picking;
|
||||
loaded_picking.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
var loaded = picking.then(function(picking){
|
||||
self.picking = picking instanceof Array ? picking[0] : picking;
|
||||
console.log('Picking:',self.picking);
|
||||
console.log('User Context:', instance.session.user_context);
|
||||
console.log('Context:', new instance.web.CompoundContext().eval());
|
||||
var loaded = loaded_picking.then(function(){
|
||||
|
||||
return new instance.web.Model('stock.move').call('read',[self.picking.move_lines, [], new instance.web.CompoundContext()]);
|
||||
}).then(function(movelines){
|
||||
self.movelines = movelines;
|
||||
console.log('Move Lines:',movelines);
|
||||
|
||||
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;
|
||||
console.log('Operations:',self.operations);
|
||||
|
||||
var package_ids = [];
|
||||
|
||||
|
@ -177,26 +291,29 @@ function openerp_picking_widgets(instance){
|
|||
}
|
||||
}
|
||||
|
||||
console.log('Package ids:',package_ids);
|
||||
|
||||
return new instance.web.Model('stock.quant.package').call('read',[package_ids, [], new instance.web.CompoundContext()]);
|
||||
}).then(function(packages){
|
||||
self.packages = packages;
|
||||
console.log('Packages:', self.packages);
|
||||
});
|
||||
|
||||
return loaded;
|
||||
|
||||
},
|
||||
start: function(){
|
||||
var self = this;
|
||||
instance.webclient.set_content_full_screen(true);
|
||||
this.connect_barcode_scanner_and_numpad();
|
||||
this.connect_numpad();
|
||||
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_prev').click(function(){ self.picking_prev(); });
|
||||
this.$('.js_pick_next').click(function(){ self.picking_next(); });
|
||||
this.$('.js_pick_menu').click(function(){ self.menu(); });
|
||||
|
||||
$.when(this.loaded).done(function(){
|
||||
self.picking_editor = new module.PickingEditorWidget(self);
|
||||
|
@ -231,7 +348,6 @@ function openerp_picking_widgets(instance){
|
|||
var self = this;
|
||||
return this.load(picking_id)
|
||||
.then(function(){
|
||||
console.log('REFRESHING UI');
|
||||
self.picking_editor.renderElement();
|
||||
self.package_editor.renderElement();
|
||||
self.package_selector.renderElement();
|
||||
|
@ -248,45 +364,47 @@ function openerp_picking_widgets(instance){
|
|||
}
|
||||
});
|
||||
},
|
||||
menu: function(){
|
||||
this.do_action({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'stock.menu',
|
||||
target: 'current',
|
||||
},{
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
|
||||
},
|
||||
scan: function(ean){
|
||||
var self = this;
|
||||
console.log('Scan: ',ean);
|
||||
new instance.web.Model('stock.picking')
|
||||
.call('get_barcode_and_return_todo_stuff', [self.picking.id, ean])
|
||||
.then(function(){
|
||||
return self.refresh_ui(self.picking.id);
|
||||
});
|
||||
this.scan_timestamp = new Date().getTime();
|
||||
},
|
||||
pack: function(){
|
||||
var self = this;
|
||||
console.log('Pack');
|
||||
new instance.web.Model('stock.picking')
|
||||
.call('action_pack',[[[self.picking.id]]])
|
||||
.then(function(){
|
||||
instance.session.user_context.current_package_id = false;
|
||||
console.log('Context Reset');
|
||||
|
||||
return self.refresh_ui(self.picking.id);
|
||||
});
|
||||
},
|
||||
done: function(){
|
||||
var self = this;
|
||||
console.log('Done');
|
||||
new instance.web.Model('stock.picking')
|
||||
.call('action_done_from_packing_ui',[self.picking.id])
|
||||
.then(function(new_picking_id){
|
||||
console.log('New picking id:',new_picking_id);
|
||||
return self.refresh_ui(new_picking_id);
|
||||
});
|
||||
},
|
||||
print_package: function(package_id){
|
||||
var self = this;
|
||||
console.log('Print Package:',package_id);
|
||||
new instance.web.Model('stock.quant.package')
|
||||
.call('action_print',[[package_id]])
|
||||
.then(function(action){
|
||||
console.log('Print Package Repport Action:',action);
|
||||
return self.do_action(action);
|
||||
});
|
||||
},
|
||||
|
@ -312,7 +430,6 @@ function openerp_picking_widgets(instance){
|
|||
},
|
||||
copy_package: function(package_id){
|
||||
var self = this;
|
||||
console.log('Copy Package:',package_id);
|
||||
new instance.web.Model('stock.quant.package')
|
||||
.call('copy',[[package_id]])
|
||||
.then(function(){
|
||||
|
@ -321,7 +438,6 @@ function openerp_picking_widgets(instance){
|
|||
},
|
||||
delete_package: function(package_id){
|
||||
var self = this;
|
||||
console.log('Delete Package:',package_id);
|
||||
new instance.web.Model('stock.quant.package')
|
||||
.call('unlink',[[package_id]])
|
||||
.then(function(){
|
||||
|
@ -329,13 +445,11 @@ function openerp_picking_widgets(instance){
|
|||
});
|
||||
},
|
||||
deselect_package: function(){
|
||||
console.log('Deselect Package');
|
||||
instance.session.user_context.current_package_id = false;
|
||||
this.package_editor.renderElement();
|
||||
this.package_selector.renderElement();
|
||||
},
|
||||
select_package: function(package_id){
|
||||
console.log('Select Package:',package_id);
|
||||
instance.session.user_context.current_package_id = package_id;
|
||||
this.package_editor.renderElement();
|
||||
this.package_selector.renderElement();
|
||||
|
@ -375,15 +489,12 @@ function openerp_picking_widgets(instance){
|
|||
var op = ops[ops.length-1];
|
||||
|
||||
if(quantity === '++'){
|
||||
console.log('Increase quantity!');
|
||||
quantity = op.product_qty + 1;
|
||||
}else if(quantity === '--'){
|
||||
console.log('Decrease quantity :(');
|
||||
quantity = op.product_qty - 1;
|
||||
}
|
||||
|
||||
if(typeof quantity === 'number' && quantity >= 0){
|
||||
console.log('Set quantity: ',quantity);
|
||||
new instance.web.Model('stock.pack.operation')
|
||||
.call('write',[[op.id],{'product_qty': quantity }])
|
||||
.then(function(){
|
||||
|
@ -392,27 +503,14 @@ function openerp_picking_widgets(instance){
|
|||
}
|
||||
|
||||
},
|
||||
connect_barcode_scanner_and_numpad: function(){
|
||||
connect_numpad: function(){
|
||||
var self = this;
|
||||
var numbers = [];
|
||||
var timestamp = 0;
|
||||
var numpad = [];
|
||||
var numpad_timestamp;
|
||||
// it is important to catch the keypress event and not keyup/keydown as keypress normalizes the input codes :)
|
||||
$('body').delegate('','keyup',function(e){
|
||||
//console.log('Key:',e.keyCode);
|
||||
if (e.keyCode >= 48 && e.keyCode < 58){
|
||||
if(timestamp + 30 < new Date().getTime()){
|
||||
numbers = [];
|
||||
}
|
||||
numbers.push(e.keyCode - 48);
|
||||
timestamp = new Date().getTime();
|
||||
if(numbers.length === 13){
|
||||
self.scan(numbers.join(''));
|
||||
numbers = [];
|
||||
}
|
||||
}else{
|
||||
numbers = [];
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
@ -436,16 +534,59 @@ function openerp_picking_widgets(instance){
|
|||
}
|
||||
numpad_timestamp = new Date().getTime();
|
||||
}
|
||||
});
|
||||
};
|
||||
$('body').on('keypress', this.numpad_handler);
|
||||
},
|
||||
disconnect_barcode_scanner_and_numpad: function(){
|
||||
$('body').undelegate('', 'keyup')
|
||||
disconnect_numpad: function(){
|
||||
$('body').off('keypress', this.numpad_handler);
|
||||
},
|
||||
quit: function(){
|
||||
console.log('Quit');
|
||||
this.disconnect_barcode_scanner_and_numpad();
|
||||
instance.webclient.set_content_full_screen(false);
|
||||
this.destroy();
|
||||
window.location = '/'; // FIXME Ask niv how to do it correctly
|
||||
},
|
||||
destroy: function(){
|
||||
this._super();
|
||||
this.disconnect_numpad();
|
||||
this.barcode_scanner.disconnect();
|
||||
instance.webclient.set_content_full_screen(false);
|
||||
},
|
||||
});
|
||||
openerp.web.client_actions.add('stock.ui', 'instance.stock.PickingMainWidget');
|
||||
|
||||
module.BarcodeScanner = instance.web.Class.extend({
|
||||
connect: function(callback){
|
||||
var code = "";
|
||||
var timeStamp = 0;
|
||||
var timeout = null;
|
||||
|
||||
this.handler = function(e){
|
||||
if(e.which === 13){ //ignore returns
|
||||
return;
|
||||
}
|
||||
|
||||
if(timeStamp + 50 < new Date().getTime()){
|
||||
code = "";
|
||||
}
|
||||
|
||||
timeStamp = new Date().getTime();
|
||||
clearTimeout(timeout);
|
||||
|
||||
code += String.fromCharCode(e.which);
|
||||
|
||||
timeout = setTimeout(function(){
|
||||
if(code.length >= 3){
|
||||
callback(code);
|
||||
}
|
||||
code = "";
|
||||
},100);
|
||||
};
|
||||
|
||||
$('body').on('keypress', this.handler);
|
||||
|
||||
},
|
||||
disconnect: function(){
|
||||
$('body').off('keypress', this.handler);
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -154,7 +154,40 @@
|
|||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name='PickingMenuWidget'>
|
||||
<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>
|
||||
|
||||
</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>
|
||||
|
||||
<t t-foreach="widget.picking_types" t-as="type">
|
||||
<h4 class='oe_pick_app_subtitle'> <t t-esc="type.name" /> </h4>
|
||||
<t t-if="widget.pickings_by_type[type.id].length > 0">
|
||||
<span class='oe_pick_button oe_medium js_pick_last' t-att-data-id="type.id">Last Picking</span>
|
||||
<span class='oe_pick_button oe_medium js_pick_scan' t-att-data-id="type.id">Scan</span>
|
||||
</t>
|
||||
<t t-if="widget.pickings_by_type[type.id].length === 0">
|
||||
<p class='oe_pick_app_info'>Nothing to process.</p>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="PickingMainWidget">
|
||||
|
@ -169,14 +202,12 @@
|
|||
</div>
|
||||
|
||||
<div class='oe_pick_toolbar'>
|
||||
<div class='oe_pick_button oe_small oe_left js_pick_prev'><</div>
|
||||
<div class='oe_pick_button oe_small oe_disabled oe_left js_pick_prev'><</div>
|
||||
<div class='oe_pick_button js_pick_pack'> Pack </div>
|
||||
<!-- <div class='oe_pick_button js_pick_reset'> Reset </div> -->
|
||||
<div class='oe_pick_button js_pick_done'> Done </div>
|
||||
<div class='oe_pick_button oe_small oe_right js_pick_next'>></div>
|
||||
<div class='oe_pick_button oe_small oe_disabled oe_right js_pick_next'>></div>
|
||||
</div>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr class='oe_pick_body_row'>
|
||||
|
|
|
@ -1006,8 +1006,8 @@ class stock_picking(osv.osv):
|
|||
|
||||
# Methods for the barcode UI
|
||||
|
||||
def get_picking_for_packing_ui(self, cr, uid, context=None):
|
||||
return self.search(cr, uid, [('state', 'in', ('confirmed', 'assigned'))], context=context)
|
||||
def get_picking_for_packing_ui(self, cr, uid, context={}):
|
||||
return self.search(cr, uid, [('state', 'in', ('confirmed', 'assigned')),('picking_type_id','=', context.get('default_picking_type_id'))], context=context)
|
||||
|
||||
def action_done_from_packing_ui(self, cr, uid, picking_id, only_split_lines=False, context=None):
|
||||
self.do_partial(cr, uid, picking_id, only_split_lines, context=context)
|
||||
|
|
Loading…
Reference in New Issue