[IMP] point_of_sale: manage lot/serial number from POS front-end
Frontend: - When product has lot/serial tracking open popup for accept lot/serial number when it selected. - Add icon in order line to show related lot/serial number and it indicate status of product lot - User can add or remove product by lot from current lot dialog box - Order line quantity don't match with lot line quantity show warning message Back End: - Added model `pos.pack.operation.lot` to manage product lot related with pos order - Added state button in pos session form view to show pickings which are not in 'Done' stage
This commit is contained in:
committed by
Joren Van Onder
parent
06e28ce2a4
commit
8f39c652c6
@@ -589,9 +589,7 @@ class PosOrder(models.Model):
|
||||
if picking_id:
|
||||
picking_id.action_confirm()
|
||||
picking_id.force_assign()
|
||||
# Mark pack operations as done
|
||||
for pack in picking_id.pack_operation_ids:
|
||||
pack.write({'qty_done': pack.product_qty})
|
||||
order.set_pack_operation_lot()
|
||||
picking_id.action_done()
|
||||
elif Move:
|
||||
Move.action_confirm()
|
||||
@@ -599,6 +597,34 @@ class PosOrder(models.Model):
|
||||
Move.action_done()
|
||||
return True
|
||||
|
||||
def set_pack_operation_lot(self):
|
||||
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
|
||||
|
||||
StockProductionLot = self.env['stock.production.lot']
|
||||
PosPackOperationLot = self.env['pos.pack.operation.lot']
|
||||
|
||||
for order in self:
|
||||
for pack_operation in order.picking_id.pack_operation_ids:
|
||||
qty = 0
|
||||
qty_done = 0
|
||||
pack_lots = []
|
||||
pos_pack_lots = PosPackOperationLot.search([('order_id', '=', order.id), ('product_id', '=', pack_operation.product_id.id)])
|
||||
pack_lot_names = [pos_pack.lot_name for pos_pack in pos_pack_lots]
|
||||
|
||||
if pack_lot_names:
|
||||
for lot_name in list(set(pack_lot_names)):
|
||||
stock_production_lot = StockProductionLot.search([('name', '=', lot_name), ('product_id', '=', pack_operation.product_id.id)])
|
||||
if stock_production_lot:
|
||||
if stock_production_lot.product_id.tracking == 'lot':
|
||||
qty = pack_lot_names.count(lot_name)
|
||||
else:
|
||||
qty = 1.0
|
||||
qty_done += qty
|
||||
pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
|
||||
else:
|
||||
qty_done = pack_operation.product_qty
|
||||
pack_operation.write({'pack_lot_ids': map(lambda x: (0, 0, x), pack_lots), 'qty_done': qty_done})
|
||||
|
||||
def add_payment(self, data):
|
||||
"""Create a new payment for the order"""
|
||||
args = {
|
||||
@@ -704,6 +730,7 @@ class PosOrderLine(models.Model):
|
||||
create_date = fields.Datetime(string='Creation Date', readonly=True)
|
||||
tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True)
|
||||
tax_ids_after_fiscal_position = fields.Many2many('account.tax', compute='_get_tax_ids_after_fiscal_position', string='Taxes')
|
||||
pack_lot_ids = fields.One2many('pos.pack.operation.lot', 'pos_order_line_id', string='Lot/serial Number')
|
||||
|
||||
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
|
||||
def _compute_amount_line_all(self):
|
||||
@@ -752,3 +779,13 @@ class PosOrderLine(models.Model):
|
||||
def _get_tax_ids_after_fiscal_position(self):
|
||||
for line in self:
|
||||
line.tax_ids_after_fiscal_position = line.order_id.fiscal_position_id.map_tax(line.tax_ids)
|
||||
|
||||
|
||||
class PosOrderLineLot(models.Model):
|
||||
_name = "pos.pack.operation.lot"
|
||||
_description = "Specify product lot/serial number in pos order line"
|
||||
|
||||
pos_order_line_id = fields.Many2one('pos.order.line')
|
||||
order_id = fields.Many2one('pos.order', related="pos_order_line_id.order_id")
|
||||
lot_name = fields.Char('Lot Name')
|
||||
product_id = fields.Many2one('product.product', related='pos_order_line_id.product_id')
|
||||
|
||||
@@ -88,9 +88,25 @@ class PosSession(models.Model):
|
||||
string='Available Payment Methods')
|
||||
order_ids = fields.One2many('pos.order', 'session_id', string='Orders')
|
||||
statement_ids = fields.One2many('account.bank.statement', 'pos_session_id', string='Bank Statement', readonly=True)
|
||||
picking_count = fields.Integer(compute='_compute_picking_count')
|
||||
|
||||
_sql_constraints = [('uniq_name', 'unique(name)', _("The name of this POS Session must be unique !"))]
|
||||
|
||||
@api.multi
|
||||
def _compute_picking_count(self):
|
||||
for pos in self:
|
||||
pickings = pos.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
|
||||
pos.picking_count = len(pickings.ids)
|
||||
|
||||
@api.multi
|
||||
def action_stock_picking(self):
|
||||
pickings = self.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
|
||||
action_picking = self.env.ref('stock.action_picking_tree_ready')
|
||||
action = action_picking.read()[0]
|
||||
action['context'] = {}
|
||||
action['domain'] = [('id', 'in', pickings.ids)]
|
||||
return action
|
||||
|
||||
@api.depends('cash_control', 'cash_journal_id', 'config_id.cash_control')
|
||||
def _compute_cash_all(self):
|
||||
for session in self:
|
||||
|
||||
@@ -497,7 +497,9 @@ td {
|
||||
.pos .oe_icon.oe_orange {
|
||||
color: rgb(239, 153, 65);
|
||||
}
|
||||
|
||||
.pos .oe_link_icon{
|
||||
cursor:pointer;
|
||||
}
|
||||
/* ********* Contains everything below the bar ********* */
|
||||
|
||||
.pos .pos-content {
|
||||
@@ -2223,6 +2225,15 @@ td {
|
||||
box-sizing: border-box;
|
||||
width: 80%;
|
||||
}
|
||||
.pos .popup .packlot-lines{
|
||||
overflow: auto;
|
||||
height: 250px;
|
||||
margin: 10px;
|
||||
}
|
||||
.pos .popup .packlot-line-input {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.pos .popup-number .popup-input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,14 @@ var Gui = core.Class.extend({
|
||||
// example loading a 'product_details' screen for a specific product.
|
||||
// - refresh: if you want the screen to cycle trough show / hide even
|
||||
// if you are already on the same screen.
|
||||
show_screen: function(screen_name,params,refresh) {
|
||||
show_screen: function(screen_name,params,refresh,skip_close_popup) {
|
||||
var screen = this.screen_instances[screen_name];
|
||||
if (!screen) {
|
||||
console.error("ERROR: show_screen("+screen_name+") : screen not found");
|
||||
}
|
||||
|
||||
this.close_popup();
|
||||
|
||||
if (!skip_close_popup){
|
||||
this.close_popup();
|
||||
}
|
||||
var order = this.pos.get_order();
|
||||
if (order) {
|
||||
var old_screen_name = order.get_screen_data('screen');
|
||||
|
||||
@@ -309,7 +309,7 @@ exports.PosModel = Backbone.Model.extend({
|
||||
model: 'product.product',
|
||||
fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'barcode', 'default_code',
|
||||
'to_weight', 'uom_id', 'description_sale', 'description',
|
||||
'product_tmpl_id'],
|
||||
'product_tmpl_id','tracking'],
|
||||
order: ['sequence','default_code','name'],
|
||||
domain: [['sale_ok','=',true],['available_in_pos','=',true]],
|
||||
context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
|
||||
@@ -1063,6 +1063,7 @@ exports.Orderline = Backbone.Model.extend({
|
||||
return;
|
||||
}
|
||||
this.product = options.product;
|
||||
this.set_product_lot(this.product)
|
||||
this.price = options.product.price;
|
||||
this.set_quantity(1);
|
||||
this.discount = 0;
|
||||
@@ -1076,11 +1077,18 @@ exports.Orderline = Backbone.Model.extend({
|
||||
if (!this.product) {
|
||||
console.error('ERROR: attempting to recover product not available in the point of sale');
|
||||
}
|
||||
this.set_product_lot(this.product)
|
||||
this.price = json.price_unit;
|
||||
this.set_discount(json.discount);
|
||||
this.set_quantity(json.qty);
|
||||
this.id = json.id;
|
||||
orderline_id = Math.max(this.id+1,orderline_id);
|
||||
var pack_lot_lines = json.pack_lot_ids;
|
||||
for (var i = 0; i < pack_lot_lines.length; i++) {
|
||||
var packlotline = pack_lot_lines[i][2];
|
||||
var pack_lot_line = new exports.Packlotline({}, {'json': _.extend(packlotline, {'order_line':this})});
|
||||
this.pack_lot_lines.add(pack_lot_line);
|
||||
}
|
||||
},
|
||||
clone: function(){
|
||||
var orderline = new exports.Orderline({},{
|
||||
@@ -1097,6 +1105,10 @@ exports.Orderline = Backbone.Model.extend({
|
||||
orderline.selected = false;
|
||||
return orderline;
|
||||
},
|
||||
set_product_lot: function(product){
|
||||
this.has_product_lot = product.tracking !== 'none';
|
||||
this.pack_lot_lines = this.has_product_lot && new PacklotlineCollection(null, {'order_line': this});
|
||||
},
|
||||
// sets a discount [0,100]%
|
||||
set_discount: function(discount){
|
||||
var disc = Math.min(Math.max(parseFloat(discount) || 0, 0),100);
|
||||
@@ -1156,6 +1168,31 @@ exports.Orderline = Backbone.Model.extend({
|
||||
return this.quantityStr;
|
||||
}
|
||||
},
|
||||
|
||||
compute_lot_lines: function(){
|
||||
var pack_lot_lines = this.pack_lot_lines;
|
||||
var lines = pack_lot_lines.length;
|
||||
if(this.quantity > lines){
|
||||
for(var i=0; i<this.quantity - lines; i++){
|
||||
pack_lot_lines.add(new exports.Packlotline({}, {'order_line': this}));
|
||||
}
|
||||
}
|
||||
if(this.quantity < lines){
|
||||
var to_remove = lines - this.quantity;
|
||||
var lot_lines = pack_lot_lines.sortBy('lot_name').slice(0, to_remove);
|
||||
pack_lot_lines.remove(lot_lines);
|
||||
}
|
||||
return this.pack_lot_lines;
|
||||
},
|
||||
|
||||
has_valid_product_lot: function(){
|
||||
if(!this.has_product_lot){
|
||||
return true;
|
||||
}
|
||||
var valid_product_lot = this.pack_lot_lines.get_valid_lots();
|
||||
return this.quantity === valid_product_lot.length;
|
||||
},
|
||||
|
||||
// return the unit of measure of the product
|
||||
get_unit: function(){
|
||||
var unit_id = this.product.uom_id;
|
||||
@@ -1203,6 +1240,12 @@ exports.Orderline = Backbone.Model.extend({
|
||||
this.set_quantity(this.get_quantity() + orderline.get_quantity());
|
||||
},
|
||||
export_as_JSON: function() {
|
||||
var pack_lot_ids = [];
|
||||
if (this.has_product_lot){
|
||||
this.pack_lot_lines.each(_.bind( function(item) {
|
||||
return pack_lot_ids.push([0, 0, item.export_as_JSON()]);
|
||||
}, this));
|
||||
}
|
||||
return {
|
||||
qty: this.get_quantity(),
|
||||
price_unit: this.get_unit_price(),
|
||||
@@ -1210,6 +1253,7 @@ exports.Orderline = Backbone.Model.extend({
|
||||
product_id: this.get_product().id,
|
||||
tax_ids: [[6, false, _.map(this.get_applicable_taxes(), function(tax){ return tax.id; })]],
|
||||
id: this.id,
|
||||
pack_lot_ids: pack_lot_ids
|
||||
};
|
||||
},
|
||||
//used to create a json of the ticket, to be sent to the printer
|
||||
@@ -1438,6 +1482,76 @@ var OrderlineCollection = Backbone.Collection.extend({
|
||||
model: exports.Orderline,
|
||||
});
|
||||
|
||||
exports.Packlotline = Backbone.Model.extend({
|
||||
defaults: {
|
||||
lot_name: null
|
||||
},
|
||||
initialize: function(attributes, options){
|
||||
this.order_line = options.order_line;
|
||||
if (options.json) {
|
||||
this.init_from_JSON(options.json);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
init_from_JSON: function(json) {
|
||||
this.order_line = json.order_line;
|
||||
this.set_lot_name(json.lot_name);
|
||||
},
|
||||
|
||||
set_lot_name: function(name){
|
||||
this.set({lot_name : _.str.trim(name) || null});
|
||||
},
|
||||
|
||||
get_lot_name: function(){
|
||||
return this.get('lot_name');
|
||||
},
|
||||
|
||||
export_as_JSON: function(){
|
||||
return {
|
||||
lot_name: this.get_lot_name(),
|
||||
};
|
||||
},
|
||||
|
||||
add: function(){
|
||||
var order_line = this.order_line,
|
||||
index = this.collection.indexOf(this);
|
||||
var new_lot_model = new exports.Packlotline({}, {'order_line': this.order_line});
|
||||
this.collection.add(new_lot_model, {at: index + 1});
|
||||
return new_lot_model;
|
||||
},
|
||||
|
||||
remove: function(){
|
||||
this.collection.remove(this);
|
||||
}
|
||||
});
|
||||
|
||||
var PacklotlineCollection = Backbone.Collection.extend({
|
||||
model: exports.Packlotline,
|
||||
initialize: function(models, options) {
|
||||
this.order_line = options.order_line;
|
||||
},
|
||||
|
||||
get_empty_model: function(){
|
||||
return this.findWhere({'lot_name': null});
|
||||
},
|
||||
|
||||
remove_empty_model: function(){
|
||||
this.remove(this.where({'lot_name': null}));
|
||||
},
|
||||
|
||||
get_valid_lots: function(){
|
||||
return this.filter(function(model){
|
||||
return model.get('lot_name');
|
||||
});
|
||||
},
|
||||
|
||||
set_quantity_by_lot: function() {
|
||||
var valid_lots = this.get_valid_lots();
|
||||
this.order_line.set_quantity(valid_lots.length);
|
||||
}
|
||||
});
|
||||
|
||||
// Every Paymentline contains a cashregister and an amount of money.
|
||||
exports.Paymentline = Backbone.Model.extend({
|
||||
initialize: function(attributes, options) {
|
||||
@@ -1861,6 +1975,10 @@ exports.Order = Backbone.Model.extend({
|
||||
this.orderlines.add(line);
|
||||
}
|
||||
this.select_orderline(this.get_last_orderline());
|
||||
|
||||
if(line.has_product_lot){
|
||||
this.display_lot_popup();
|
||||
}
|
||||
},
|
||||
get_selected_orderline: function(){
|
||||
return this.selected_orderline;
|
||||
@@ -1884,6 +2002,19 @@ exports.Order = Backbone.Model.extend({
|
||||
this.selected_orderline = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
display_lot_popup: function() {
|
||||
var order_line = this.get_selected_orderline();
|
||||
if (order_line){
|
||||
var pack_lot_lines = order_line.compute_lot_lines();
|
||||
this.pos.gui.show_popup('packlotline', {
|
||||
'title': _t('Lot/Serial Number(s) Required'),
|
||||
'pack_lot_lines': pack_lot_lines,
|
||||
'order': this
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/* ---- Payment Lines --- */
|
||||
add_paymentline: function(cashregister) {
|
||||
this.assert_editable();
|
||||
|
||||
@@ -197,6 +197,75 @@ var TextAreaPopupWidget = TextInputPopupWidget.extend({
|
||||
});
|
||||
gui.define_popup({name:'textarea', widget: TextAreaPopupWidget});
|
||||
|
||||
var PackLotLinePopupWidget = PopupWidget.extend({
|
||||
template: 'PackLotLinePopupWidget',
|
||||
events: _.extend({}, PopupWidget.prototype.events, {
|
||||
'click .remove-lot': 'remove_lot',
|
||||
'keydown': 'add_lot',
|
||||
'blur .packlot-line-input': 'lose_input_focus'
|
||||
}),
|
||||
|
||||
show: function(options){
|
||||
this._super(options);
|
||||
this.focus();
|
||||
},
|
||||
|
||||
click_confirm: function(){
|
||||
var pack_lot_lines = this.options.pack_lot_lines;
|
||||
this.$('.packlot-line-input').each(function(index, el){
|
||||
var cid = $(el).attr('cid'),
|
||||
lot_name = $(el).val();
|
||||
var pack_line = pack_lot_lines.get({cid: cid});
|
||||
pack_line.set_lot_name(lot_name);
|
||||
});
|
||||
pack_lot_lines.remove_empty_model();
|
||||
pack_lot_lines.set_quantity_by_lot();
|
||||
this.options.order.save_to_db();
|
||||
this.gui.close_popup();
|
||||
},
|
||||
|
||||
add_lot: function(ev) {
|
||||
if (ev.keyCode === $.ui.keyCode.ENTER){
|
||||
var pack_lot_lines = this.options.pack_lot_lines,
|
||||
$input = $(ev.target),
|
||||
cid = $input.attr('cid'),
|
||||
lot_name = $input.val();
|
||||
|
||||
var lot_model = pack_lot_lines.get({cid: cid});
|
||||
lot_model.set_lot_name(lot_name); // First set current model then add new one
|
||||
if(!pack_lot_lines.get_empty_model()){
|
||||
var new_lot_model = lot_model.add();
|
||||
this.focus_model = new_lot_model;
|
||||
}
|
||||
pack_lot_lines.set_quantity_by_lot();
|
||||
this.renderElement();
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
|
||||
remove_lot: function(ev){
|
||||
var pack_lot_lines = this.options.pack_lot_lines,
|
||||
$input = $(ev.target).prev(),
|
||||
cid = $input.attr('cid');
|
||||
var lot_model = pack_lot_lines.get({cid: cid});
|
||||
lot_model.remove();
|
||||
pack_lot_lines.set_quantity_by_lot();
|
||||
this.renderElement();
|
||||
},
|
||||
|
||||
lose_input_focus: function(ev){
|
||||
var $input = $(ev.target),
|
||||
cid = $input.attr('cid');
|
||||
var lot_model = this.options.pack_lot_lines.get({cid: cid});
|
||||
lot_model.set_lot_name($input.val());
|
||||
},
|
||||
|
||||
focus: function(){
|
||||
this.$("input[autofocus]").focus();
|
||||
this.focus_model = false; // after focus clear focus_model on widget
|
||||
}
|
||||
});
|
||||
gui.define_popup({name:'packlotline', widget:PackLotLinePopupWidget});
|
||||
|
||||
var NumberPopupWidget = PopupWidget.extend({
|
||||
template: 'NumberPopupWidget',
|
||||
|
||||
@@ -61,7 +61,7 @@ var ScreenWidget = PosBaseWidget.extend({
|
||||
var self = this;
|
||||
if (self.pos.scan_product(code)) {
|
||||
if (self.barcode_product_screen) {
|
||||
self.gui.show_screen(self.barcode_product_screen);
|
||||
self.gui.show_screen(self.barcode_product_screen, null, null, true);
|
||||
}
|
||||
} else {
|
||||
this.barcode_error_action(code);
|
||||
@@ -402,7 +402,21 @@ var ActionpadWidget = PosBaseWidget.extend({
|
||||
var self = this;
|
||||
this._super();
|
||||
this.$('.pay').click(function(){
|
||||
self.gui.show_screen('payment');
|
||||
var order = self.pos.get_order();
|
||||
var has_valid_product_lot = _.every(order.orderlines.models, function(line){
|
||||
return line.has_valid_product_lot();
|
||||
});
|
||||
if(!has_valid_product_lot){
|
||||
self.gui.show_popup('confirm',{
|
||||
'title': _t('Empty Serial/Lot Number'),
|
||||
'body': _t('One or more product(s) required serial/lot number.'),
|
||||
confirm: function(){
|
||||
self.gui.show_screen('payment');
|
||||
},
|
||||
});
|
||||
}else{
|
||||
self.gui.show_screen('payment');
|
||||
}
|
||||
});
|
||||
this.$('.set-customer').click(function(){
|
||||
self.gui.show_screen('clientlist');
|
||||
@@ -439,6 +453,8 @@ var OrderWidget = PosBaseWidget.extend({
|
||||
this.pos.get_order().select_orderline(orderline);
|
||||
this.numpad_state.reset();
|
||||
},
|
||||
|
||||
|
||||
set_value: function(val) {
|
||||
var order = this.pos.get_order();
|
||||
if (order.get_selected_orderline()) {
|
||||
@@ -495,6 +511,12 @@ var OrderWidget = PosBaseWidget.extend({
|
||||
el_node = el_node.childNodes[0];
|
||||
el_node.orderline = orderline;
|
||||
el_node.addEventListener('click',this.line_click_handler);
|
||||
var el_lot_icon = el_node.querySelector('.line-lot-icon');
|
||||
if(el_lot_icon){
|
||||
el_lot_icon.addEventListener('click', (function() {
|
||||
this.show_product_lot(orderline);
|
||||
}.bind(this)));
|
||||
}
|
||||
|
||||
orderline.node = el_node;
|
||||
return el_node;
|
||||
@@ -559,6 +581,11 @@ var OrderWidget = PosBaseWidget.extend({
|
||||
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
|
||||
},
|
||||
show_product_lot: function(orderline){
|
||||
this.pos.get_order().select_orderline(orderline);
|
||||
var order = this.pos.get_order();
|
||||
order.display_lot_popup();
|
||||
},
|
||||
});
|
||||
|
||||
/* ------ The Product Categories ------ */
|
||||
|
||||
@@ -941,6 +941,35 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="PackLotLinePopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-text">
|
||||
<p class="title"><t t-esc=" widget.options.title || '' " /></p>
|
||||
<div class='packlot-lines'>
|
||||
<t t-if="widget.options.pack_lot_lines">
|
||||
<t t-set="focus_lot_line" t-value="widget.focus_model || widget.options.pack_lot_lines.get_empty_model()"/>
|
||||
<t t-foreach='widget.options.pack_lot_lines.models' t-as='lot_line'>
|
||||
<input type="text" t-att-cid="lot_line.cid"
|
||||
t-att-value="lot_line.get('lot_name')"
|
||||
t-att-autofocus="lot_line === focus_lot_line ? 'autofocus': undefined"
|
||||
class="popup-input packlot-line-input"
|
||||
placeholder="Serial/Lot Number"/>
|
||||
<i class="oe_link_icon remove-lot fa fa-trash-o"></i>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="button confirm">
|
||||
Ok
|
||||
</div>
|
||||
<div class="button cancel">
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="TextAreaPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-textinput">
|
||||
@@ -1094,6 +1123,9 @@
|
||||
<li t-attf-class="orderline #{ line.selected ? 'selected' : '' }">
|
||||
<span class="product-name">
|
||||
<t t-esc="line.get_product().display_name"/>
|
||||
<t t-if="line.get_product().tracking!=='none'">
|
||||
<i t-attf-class="oe_link_icon fa fa-list oe_icon line-lot-icon #{line.has_valid_product_lot() ? 'oe_green' : 'oe_red' }"/>
|
||||
</t>
|
||||
</span>
|
||||
<span class="price">
|
||||
<t t-esc="widget.format_currency(line.get_display_price())"/>
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
type="action" context="{'balance': 'end', 'bank_statement_id': cash_register_id, 'default_pos_id': config_id}">
|
||||
<span class="o_stat_text">Set Closing Balance</span>
|
||||
</button>
|
||||
<button class="oe_stat_button" name="action_stock_picking" type="object" icon="fa-arrows-v" attrs="{'invisible':[('picking_count', '=', 0)]}">
|
||||
<field name="picking_count" widget="statinfo" string="Picking Errors"/>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="oe_title">
|
||||
Session:
|
||||
|
||||
Reference in New Issue
Block a user