Skip to content

Commit

Permalink
[IMP] point_of_sale: Traceability
Browse files Browse the repository at this point in the history
1. ONLINE

    - Raise a warning when encoding the SN/LN to inform the user if this
      SN/LN cannot be delivered
      "This Serial number has already been delivered and is not available
      anymore, are you sure you have encoded the right number ?"
       (+ see if other mistakes are possible)

2. OFFLINE

    - No change

3. GENERAL

    - Log a next activity of type exception the PoS Session if some
      delivery orders could not be confirmed + with link to the pickings
      Responsible = PoS Session responsible
      "Some operations could not be confirmed due to exceptions :
        - WH/OUT/xxxx (clickable link)
        - WH/OUT/yyyy (clickable link)

Task #1906359
  • Loading branch information
Gert Pellin committed Jan 21, 2019
1 parent 89d473b commit 16658c2
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 45 deletions.
38 changes: 31 additions & 7 deletions addons/point_of_sale/models/pos_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ def create_from_ui(self, orders):
existing_references = set([o['pos_reference'] for o in existing_orders])
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
order_ids = []
wrong_stock_pickings = dict()

for tmp_order in orders_to_save:
to_invoice = tmp_order['to_invoice']
Expand All @@ -715,7 +716,11 @@ def create_from_ui(self, orders):
order_ids.append(pos_order.id)

try:
pos_order.action_pos_order_paid()
for session_id, value in pos_order.action_pos_order_paid().items():
if session_id in wrong_stock_pickings:
wrong_stock_pickings[session_id].extend(value)
else:
wrong_stock_pickings[session_id] = value
except psycopg2.OperationalError:
# do not hide transactional errors, the order(s) won't be saved!
raise
Expand All @@ -726,6 +731,10 @@ def create_from_ui(self, orders):
pos_order.action_pos_order_invoice()
pos_order.invoice_id.sudo().action_invoice_open()
pos_order.account_move = pos_order.invoice_id.move_id
if wrong_stock_pickings:
for session_id, stock_pickings in wrong_stock_pickings.items():
session = self.env['pos.session'].browse(session_id)
session.alert_wrong_lots(stock_pickings)
return order_ids

def test_paid(self):
Expand All @@ -744,6 +753,7 @@ def create_picking(self):
Picking = self.env['stock.picking']
Move = self.env['stock.move']
StockWarehouse = self.env['stock.warehouse']
wrong_stock_pickings = dict()
for order in self:
if not order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
continue
Expand Down Expand Up @@ -808,31 +818,41 @@ def create_picking(self):
order.write({'picking_id': order_picking.id or return_picking.id})

if return_picking:
order._force_picking_done(return_picking)
for session_id, value in order._force_picking_done(return_picking).items():
if session_id in wrong_stock_pickings:
wrong_stock_pickings[session_id].append(value)
else:
wrong_stock_pickings[session_id] = [value]
if order_picking:
order._force_picking_done(order_picking)
for session_id, value in order._force_picking_done(order_picking).items():
if session_id in wrong_stock_pickings:
wrong_stock_pickings[session_id].extend(value)
else:
wrong_stock_pickings[session_id] = value

# when the pos.config has no picking_type_id set only the moves will be created
if moves and not return_picking and not order_picking:
moves._action_assign()
moves.filtered(lambda m: m.product_id.tracking == 'none')._action_done()

return True
return wrong_stock_pickings

def _force_picking_done(self, picking):
"""Force picking in order to be set as done."""
self.ensure_one()
picking.action_assign()
wrong_lots = self.set_pack_operation_lot(picking)
if not wrong_lots:
wrong_stock_pickings = self.set_pack_operation_lot(picking)
if not wrong_stock_pickings:
picking.action_done()
return wrong_stock_pickings

def set_pack_operation_lot(self, picking=None):
"""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']
has_wrong_lots = False
wrong_stock_pickings = dict()
for order in self:
for move in (picking or self.picking_id).move_lines:
picking_type = (picking or self.picking_id).picking_type_id
Expand All @@ -858,6 +878,10 @@ def set_pack_operation_lot(self, picking=None):
pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
else:
has_wrong_lots = True
if not order.session_id.id in wrong_stock_pickings:
wrong_stock_pickings[order.session_id.id] = [picking.id or self.picking_id.id]
else:
wrong_stock_pickings[order.session_id.id].append(picking.id or self.picking_id.id)
elif move.product_id.tracking == 'none' or not lots_necessary:
qty_done = move.product_uom_qty
else:
Expand All @@ -878,7 +902,7 @@ def set_pack_operation_lot(self, picking=None):
move.quantity_done = qty_done
else:
move._set_quantity_done(qty_done)
return has_wrong_lots
return wrong_stock_pickings

def _prepare_bank_statement_line_payment_values(self, data):
"""Create a new payment for the order"""
Expand Down
15 changes: 15 additions & 0 deletions addons/point_of_sale/models/pos_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class PosSession(models.Model):
_name = 'pos.session'
_order = 'id desc'
_description = 'Point of Sale Session'
_inherit = ['mail.thread', 'mail.activity.mixin']

POS_SESSION_STATE = [
('opening_control', 'Opening Control'), # method action_pos_session_open
Expand Down Expand Up @@ -334,3 +335,17 @@ def open_cashbox(self):
action['res_id'] = cashbox_id

return action

"""
Next activities
"""
def alert_wrong_lots(self, stock_picking_ids):
stock_pickings = self.env['stock.picking'].search([('id', 'in', stock_picking_ids)])
self.activity_schedule_with_view('mail.mail_activity_data_warning',
user_id=self.user_id.id,
views_or_xmlid='point_of_sale.exception_stock_moves_confirmation',
render_context={
'stock_pickings': stock_pickings,
}
)

125 changes: 122 additions & 3 deletions addons/point_of_sale/static/src/js/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,23 @@ exports.PosModel = Backbone.Model.extend({
}
return true;
},

is_serial_used: function(serial, cid) {
// Check if a unique serial number is already in use in all open orders
// disregard pack_lot_line with cid
return this.get_order_list().some(function(order) {
return order.is_serial_used(serial, cid);
});
},
get_lot_count: function(lot_name, cid) {
// return the quantity of articles of a pack reserved in all open orders
// disregard pack_lot_line with cid
var count = 0;
this.get_order_list().forEach(function(order) {
count += order.get_lot_count(lot_name, cid);
});
return count;
},

// Exports the paid orders (the ones waiting for internet connection)
export_paid_orders: function() {
Expand Down Expand Up @@ -1214,6 +1231,26 @@ exports.Product = Backbone.Model.extend({
initialize: function(attr, options){
_.extend(this, options);
},
get_lots: function(location_id){
var self = this;
var done = new $.Deferred();
var product_lots = {};
var domain = [["location_id", "=", location_id], ["lot_id", "!=", false], ["product_id", '=', this.id]];
var fields = ['lot_id', 'quantity'];
rpc.query({
model: "stock.quant",
method: "search_read",
args: [domain, fields]
}).then(function (result) {
result.forEach(function(lot){
product_lots[lot.lot_id[1]] = lot.quantity;
});
done.resolve(product_lots);
}, function() {
done.resolve(false);
});
return done;
},

// Port of get_product_price on product.pricelist.
//
Expand Down Expand Up @@ -1468,9 +1505,39 @@ exports.Orderline = Backbone.Model.extend({
has_valid_product_lot: function(){
if(!this.has_product_lot){
return true;
} else {
var valid_product_lot = this.pack_lot_lines.get_valid_lots();
return this.get_required_number_of_lots() === valid_product_lot.length;
}
var valid_product_lot = this.pack_lot_lines.get_valid_lots();
return this.get_required_number_of_lots() === valid_product_lot.length;
},

get_invalid_lot_lines: function(db_lots, pack_lot_lines) {
var self = this;
if (db_lots === false) return [];
var db_lots_set = new Set(Object.keys(db_lots));

var pack_lot_lines_nok = [];
function nok(cid, lot_name, reason, el) {
pack_lot_lines_nok.push({cid: cid, lot_name: lot_name, reason: reason, el: el});
};

pack_lot_lines.forEach(function(pack_lot_line){
var lot_name = pack_lot_line["lot_name"],
cid = pack_lot_line["cid"],
el = pack_lot_line["el"];
if (lot_name !=="") {
if (!db_lots_set.has(lot_name)) {
nok(cid, lot_name, "Not found", el);
} else if (self.product.tracking === "lot"
&& (self.quantity + self.pos.get_lot_count(lot_name, cid))> db_lots[lot_name]) {
nok(cid, lot_name, "Quantity", el);
} else if (self.product.tracking === "serial"
&& self.pos.is_serial_used(lot_name, cid)) {
nok(cid, lot_name, "Taken", el);
}
}
});
return pack_lot_lines_nok;
},

// return the unit of measure of the product
Expand Down Expand Up @@ -1869,6 +1936,9 @@ exports.Packlotline = Backbone.Model.extend({
},

remove: function(){
if (this.collection.length === 1){
this.add();
}
this.collection.remove(this);
}
});
Expand Down Expand Up @@ -1896,8 +1966,22 @@ var PacklotlineCollection = Backbone.Collection.extend({
set_quantity_by_lot: function() {
if (this.order_line.product.tracking == 'serial') {
var valid_lots = this.get_valid_lots();
this.order_line.set_quantity(valid_lots.length);
this.order_line.set_quantity(valid_lots.length >= 1 ? valid_lots.length : 1);
}
},

set_changed_lines: function(lots) {
var self = this;
lots.forEach(function(lot){
self.get({cid: lot["cid"]}).set_lot_name(lot["lot_name"]);
})
this.remove_empty_model();
this.set_quantity_by_lot();
},

line_exists: function(cid, lot_name) {
var to_return = this.get({cid: cid})["lot_name"] === _.str.trim(lot_name);
return to_return;
}
});

Expand Down Expand Up @@ -2437,6 +2521,41 @@ exports.Order = Backbone.Model.extend({
}
},

get_lot_count: function(lot_name, cid) {
// return the quantity of articles of a pack reserved in the order
// disregard pack_lot_line with cid
var self = this;
var order_lines = this.get_orderlines();
var count = 0;
order_lines.forEach(function(order_line) {
if (order_line.pack_lot_lines) {
var pack_lot_line = order_line.pack_lot_lines.models[0]
if (order_line.product.tracking === "lot"
&& pack_lot_line
&& pack_lot_line.cid !== cid
&& pack_lot_line.attributes.lot_name === lot_name) {
count += order_line.quantity;
}
}
});
return count;
},

is_serial_used: function(serial, cid) {
// Check if a unique serial number is already in use in the order
// disregard pack_lot_line with cid
var self = this;
var order_lines = this.get_orderlines();
return order_lines.some(function(order_line) {
if (order_line.product.tracking === "serial") {
return order_line.pack_lot_lines.models.some(function(pack_lot_line) {
return (pack_lot_line.cid !== cid && pack_lot_line.attributes.lot_name === serial);
});
}
return false;
});
},

/* ---- Payment Lines --- */
add_paymentline: function(cashregister) {
this.assert_editable();
Expand Down
Loading

0 comments on commit 16658c2

Please sign in to comment.