Commit b36613dc by François C.

Modifications pour rendre inventaire plus robuste, pour générer des informations…

Modifications pour rendre inventaire plus robuste, pour générer des informations supplémentaires pour étiquettes rayons cartonnées
parent 802ac8d4
......@@ -153,7 +153,7 @@ class CagetteInventory(models.Model):
'products': ids
}
# Crate inventory file, name is timestamp of creation
# Create inventory file, name is timestamp of creation
timestamp = int(time.time())
filename = custom_list_file_path + str(timestamp) + '.json'
with open(filename, 'w+') as outfile:
......
......@@ -97,7 +97,7 @@ def do_custom_list_inventory(request):
else:
return JsonResponse({'res': res})
@csrf_exempt
@csrf_exempt
def generate_inventory_list(request):
"""Responding to Odoo ajax call (no csrf)."""
res = {}
......
......@@ -46,6 +46,7 @@ class CagetteProduct(models.Model):
return res
@staticmethod
def get_product_info_for_label_from_template_id(template_id):
"""Get product info for label."""
......@@ -54,14 +55,26 @@ class CagetteProduct(models.Model):
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight']
fields += getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
return api.search_read('product.product', cond, fields)
additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
fields += additionnal_fields
product_data = api.search_read('product.product', cond, fields)
if product_data and 'suppliers' in additionnal_fields:
cond = [['product_tmpl_id.id', '=', template_id]]
fields = ['name']
suppliers = api.search_read('product.supplierinfo', cond, fields)
if suppliers:
suppliers_name = []
for s in suppliers:
suppliers_name.append(s['name'][1])
product_data[0]['suppliers'] = ', '.join(list(set(suppliers_name)))
return product_data
@staticmethod
def generate_label_for_printing(templ_id, directory, price=None, nb=None):
res = {}
try:
p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id)
if (p and p[0]['product_tmpl_id'][0] == int(templ_id)):
product = p[0]
txt = ''
......@@ -91,6 +104,18 @@ class CagetteProduct(models.Model):
coop_logger.error("Generate label : %s %s", templ_id, str(e))
return res
@staticmethod
def register_start_supplier_shortage(product_id, partner_id, date_start):
"""Start a supplier shortage for a product"""
api = OdooAPI()
f = {
'product_id' : product_id,
'partner_id' : partner_id,
'date_start' : date_start,
}
res = api.create('product.supplier.shortage', f)
return res
class CagetteProducts(models.Model):
"""Initially used to make massive barcode update."""
......@@ -166,6 +191,8 @@ class CagetteProducts(models.Model):
def get_products_for_label_appli(withCandidate=False):
fields = ['sale_ok', 'uom_id', 'barcode',
'name', 'display_name', 'list_price', 'categ_id', 'image_medium']
if getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False) is True:
fields.append('pos_categ_id')
to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields)
if len(vcats) > 0:
vrac = CagetteProducts.get_vrac_products_from_cats(vcats, withCandidate, fields)
......@@ -182,6 +209,19 @@ class CagetteProducts(models.Model):
fields = ['uom_id', 'display_name','barcode']
return api.search_read('product.product', cond, fields)
@staticmethod
def get_pos_categories():
api = OdooAPI()
fields = ['name', 'parent_id', 'sequence', 'image_small']
try:
res = api.search_read('pos.category', [], fields)
except Exception as e:
coop_logger.error('Getting POS categories : %s', str(e))
res = []
return res
@staticmethod
def get_all_barcodes():
"""Needs lacagette_products Odoo module to be active."""
......@@ -195,6 +235,12 @@ class CagetteProducts(models.Model):
for p in res['list']:
# transcode result to compact format (for bandwith save and browser memory)
# real size / 4 (for 2750 products)
# following 2 lines is only useful for La Cagette (changing uom_id in Database has cascade effects...)
# TODO : Use mapping list in config.py
if p['uom_id'] == 3:
p['uom_id'] = 21
if p['uom_id'] == 20:
p['uom_id'] = 1
result['pdts'][p['barcode']] = [
p['display_name'],
p['sale_ok'],
......@@ -202,6 +248,7 @@ class CagetteProducts(models.Model):
p['available_in_pos'],
p['id'],
p['standard_price'],
p['list_price'],
p['uom_id']]
if 'uoms' in res and 'list' in res['uoms']:
result['uoms'] = res['uoms']['list']
......@@ -264,18 +311,31 @@ class CagetteProducts(models.Model):
@staticmethod
def get_barcode_rules():
c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight' ]], ['barcode_nomenclature_id','=', 1]]
rules = OdooAPI().search_read('barcode.rule', c, ['pattern'], order="sequence ASC")
# As rules are ordered by sequence, let's find where to stop (.* pattern)
stop_idx = len(rules) - 1
i = 0
for r in rules:
if r['pattern'] == ".*":
stop_idx = i
i += 1
if stop_idx > 0:
rules = rules[:stop_idx - 1]
return rules
result = {'patterns': [], 'aliases': {}}
try:
import re
c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight', 'alias']], ['barcode_nomenclature_id','=', 1]]
rules = OdooAPI().search_read('barcode.rule', c, ['pattern', 'type', 'alias'], order="sequence ASC")
# As rules are ordered by sequence, let's find where to stop (.* pattern)
stop_idx = len(rules) - 1
i = 0
for r in rules:
if r['pattern'] == ".*":
stop_idx = i
i += 1
if stop_idx > 0:
rules = rules[:stop_idx - 1]
for r in rules:
if r['type'] == 'alias':
alias_bc = re.sub('[^0-9]', '', r['pattern'])
if len(alias_bc) > 0:
result['aliases'][alias_bc] = r['alias']
elif '{' in r['pattern'] or '.' in r['pattern']:
result['patterns'].append(r)
except Exception as e:
result['error'] = str(e)
coop_logger.error("Get Barcode Rules : %s", str(e))
return result
@staticmethod
......
......@@ -13,11 +13,12 @@ IFCBarcodes = {
closeModal();
if (typeof bc_data.res.error == "undefined") {
this.patterns = bc_data.res.patterns;
this.aliases = bc_data.res.aliases;
this.codes = bc_data.res.list.pdts;
this.uoms = bc_data.res.list.uoms;
this.keys = bc_data.res.keys;
} else {
this.errors.push(bc_data.error);
this.errors.push(bc_data.res.error);
}
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'retrieve barcodes'};
......@@ -33,26 +34,33 @@ IFCBarcodes = {
},
get_corresponding_odoo_product: function(bc) {
//console.log('To analyze :' + bc)
var odoo_product = null;
var index = 0,
pattern_found = false,
encoded_value = '';
is_alias = false,
encoded_value = '',
pattern_type = '',
odoo_product = null,
product_data = null;
// Let's find out if it matches a pattern
while (index < this.patterns.length -1 && pattern_found === false) {
var pattern = this.patterns[index];
var pattern = this.patterns[index].pattern;
var significant_prefix = pattern.replace(/[^0-9]/g, ''); //remove all but figures
if (bc.indexOf(significant_prefix) === 0) {
// console.log(pattern)
/*
For example,
bc = 0493213018809
pattern = 0493...{NNDDD}
*/
//console.log(pattern)
// console.log(bc)
//0493...{NNDDD} (pattern)
//0493213018809 (bc)
odoo_bc = '';
pattern_found = true;
pattern_type = this.patterns[index].type;
pattern = pattern.replace(/[^0-9.ND]/, '');
bc = bc.slice(0, -1); // remove original check figure
odoo_bc = '';
// Read pattern character by character
for (var i = 0; i < pattern.length; i++) {
if (/[0-9]/.exec(pattern[i])) {
......@@ -72,13 +80,39 @@ IFCBarcodes = {
index++;
}
// let's seek "normalized" bc in codes array
for (code in this.codes) {
if (code == bc) {
odoo_product = {barcode: code, data: this.codes[code], value: encoded_value};
// let's seek "normalized" bc in codes array or alias map
for (alias in this.aliases) {
if (bc == alias) {
is_alias = true;
for (barcode in this.codes) {
if (barcode == this.aliases[alias]) {
product_data = this.codes[barcode];
}
}
}
}
if (is_alias === false) {
for (code in this.codes) {
if (code == bc) {
product_data = this.codes[code];
}
}
}
if (product_data !== null) {
p_uom = (this.uoms)[product_data[this.keys.uom_id]];
if (encoded_value.length > 0 && !isNaN(encoded_value)) {
if (p_uom == 'Unit(s)' || p_uom == 'unité') {
encoded_value = parseInt(encoded_value, 10);
} else {
encoded_value = parseFloat(encoded_value);
}
}
odoo_product = {barcode: bc, data: product_data, rule: pattern_type, value: encoded_value};
}
//console.log(odoo_product)
return odoo_product;
}
};
......@@ -93,5 +127,6 @@ init_barcodes = async function() {
else
result = ifcb;
// console.log(result.patterns)
return result;
};
......@@ -83,7 +83,13 @@ def labels_appli_csv(request, params):
try:
if (params == '/wc'):
withCandidate = True
with_pos_categories = getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False)
products = CagetteProducts.get_products_for_label_appli(withCandidate)
if with_pos_categories is True:
pos_categories = CagetteProducts.get_pos_categories()
else:
pos_categories = []
rows = []
for p in products:
if (p['sale_ok'] is True):
......@@ -96,28 +102,31 @@ def labels_appli_csv(request, params):
barcode = ''
if not (barcode.isnumeric()):
barcode = ''
rows.append([p['id'], p['display_name'], barcode,
p['list_price'],
# p['categ_id'][1],
p['categ'],
uom,
# p['image'].replace(img_temp_folder, ""),
p['image'].replace("\n", ""),
# p['available_in_pos'] # ,p['sale_ok']
])
p_row = [p['id'], p['display_name'], barcode,
p['list_price'],
p['categ'],
uom,
p['image'].replace("\n", "")]
if with_pos_categories is True:
if p['pos_categ_id']:
p_row.append(p['pos_categ_id'][0])
else:
p_row.append('')
rows.append(p_row)
header = ['id', 'nom', 'code-barre', 'prix',
'categorie', 'unite', 'image'
# 'en vente', 'sale_ok'
]
if with_pos_categories is True and len(pos_categories) > 0:
header.append('id_categorie_pos')
with open(settings.DAV_PATH + '/pos_categories.json', 'w') as outfile:
json.dump(pos_categories, outfile)
os_file = settings.DAV_PATH + '/flv.csv'
file_copies = []
nb = 1
if hasattr(settings, 'FLV_CSV_NB'):
nb = int(settings.FLV_CSV_NB)
nb = int(getattr(settings, 'FLV_CSV_NB', 1))
for i in range(1, nb + 1):
file_copies.append(settings.DAV_PATH + '/flv_' + str(i) + '.csv')
......@@ -133,6 +142,8 @@ def labels_appli_csv(request, params):
file.close()
for c in file_copies:
copyfile(os_file, c)
except Exception as e:
res['error'] = str(e)
return JsonResponse({'res': res})
......@@ -176,14 +187,14 @@ def get_all_barcodes(request):
'available_in_pos': 3,
'id': 4,
'standard_price': 5,
'uom_id': 6
'list_price': 6,
'uom_id': 7
}
rules = CagetteProducts.get_barcode_rules()
res['patterns'] = rules['patterns']
res['aliases'] = rules['aliases']
res['time'] = int(round(time.time() * 1000)) - start
res['patterns'] = []
for r in rules:
if '{' in r['pattern'] or '.' in r['pattern']:
res['patterns'].append(r['pattern'])
except Exception as e:
coop_logger.error("products_barcodes : %s", str(e))
res['error'] = str(e)
......
......@@ -27,7 +27,7 @@ class Shelf(models.Model):
self.o_api = OdooAPI()
def get(self):
res ={}
res = {}
try:
c = [['id', '=', self.id]]
f = []
......@@ -200,6 +200,12 @@ class Shelf(models.Model):
filename = tmp_inv_file_prefix + str(self.id) + '.json'
os.remove(filename)
lockfilename = tmp_inv_file_prefix + str(self.id) + '.lock'
try:
os.remove(lockfilename)
except Exception as e:
pass
return True
except Exception as e:
return False
......@@ -220,6 +226,24 @@ class Shelf(models.Model):
first_inventory = json.load(json_file)
except Exception as e:
coop_logger.error("Unable to process first step file : %s", e)
import errno
raise FileExistsError(errno.ENOENT, os.strerror(errno.ENOENT), filename)
lockfilename = tmp_inv_file_prefix + str(self.id) + '.lock'
# Look for lock file: if exists, first step file is being processed so stop here
try:
with open(lockfilename) as lock_file:
return {'error': 'First step file busy', 'busy': True}
except Exception as e:
pass
# Verification passed, create the lock file to indicate first step file is being processed
try:
with open(lockfilename, 'w') as lock_file:
json.dump({}, lock_file)
except Exception as e:
coop_logger.error("Unable to create lock file : %s", e)
if first_inventory:
# if poducts in saved data
......
......@@ -106,14 +106,18 @@ function handle_blinking_effect(element) {
// When edition event is fired
function edit_event(clicked) {
// Remove from origin table
var row_data = null;
if (editing_origin == 'to_process') {
let row = table_to_process.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_toProcess(row);
} else {
let row = table_processed.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_processed(row);
}
......@@ -430,7 +434,7 @@ function pre_send() {
// Proceed with inventory: send the request to the server
function send() {
if (is_time_to('submit_inv_qties')) {
// Loading on
// Loading on
var wz = $('#main-waiting-zone').clone();
wz.find('.msg').text("Patience, cela peut prendre de nombreuses minutes s'il y a une centaine de produits");
......@@ -440,6 +444,7 @@ function send() {
shelf.user_comments = user_comments;
var url = "../do_" + originView + "_inventory";
var call_begin_at = new Date().getTime();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({
......@@ -475,14 +480,59 @@ function send() {
// Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id);
},
error: function(data) { // 500 error has been thrown
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
console.log(data.responseJSON.error);
error: function(jqXHR, textStatus) { // 500 error has been thrown or web server sent a timeout
if (jqXHR.status == 504) {
/*
django is to long to respond.
Let it the same time laps before asking if the process is well done
*/
var now = new Date().getTime();
setTimeout(
function() {
$.ajax({
type: 'GET',
url: '../inventory_process_state/' + shelf.id,
success: function(rData) {
if ('res' in rData && 'state' in rData.res) {
if (shelf.inventory_status == 'step1_done' && rData.res.state != 'step1_done') {
// shelf inventory has been already done
localStorage.removeItem(originView + '_' + shelf.id);
closeModal();
back();
} else {
console.log('Still in process : need to call recursively to make other calls');
}
} else {
console.log(rData);
}
}
});
}
, now - call_begin_at
);
} else if (jqXHR.status == 500) {
var message = "Erreur lors de la sauvegarde des données. " +
"Pas de panique, les données de l'inventaire n'ont pas été perdues ! " +
"Merci de contacter un salarié et de réessayer plus tard.";
if (typeof jqXHR.responseJSON != 'undefined' && typeof jqXHR.responseJSON.error != 'undefined') {
//console.log(jqXHR.responseJSON.error);
if ('busy' in jqXHR.responseJSON) {
message = "Inventaire en cours de traitement.";
} else if (jqXHR.responseJSON.error == 'FileExistsError') {
//step1 file has been found => previous request succeeded
message = "Les données avaient déjà été transmises....";
// Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id);
}
}
closeModal();
alert(message);
back();
}
closeModal();
alert("Erreur lors de la sauvegarde des données. " +
"Pas de panique, les données de l'inventaire n'ont pas été perdues ! " +
"Merci de contacter un salarié et de réessayer plus tard.");
}
});
} else {
......
......@@ -8,6 +8,7 @@ urlpatterns = [
url(r'^$', views.index),
url(r'^shelf_view/([0-9]+)$', views.shelf_view),
url(r'^shelf_inventory/([0-9]+)$', views.shelf_inventory),
url(r'^inventory_process_state/([0-9]+)$', views.inventory_process_state),
url(r'^all$', views.all),
url(r'^get_shelves_extra_data$', views.get_shelves_extra_data),
url(r'^(?P<shelf_id>\d+)$', views.shelf_data),
......
......@@ -13,7 +13,7 @@ def index(request):
"""Main shelf page"""
shelfs = Shelfs.get_all()
# TODO : Make the distinction beetween active and inactive products
for s in shelfs :
for s in shelfs:
s['shelf_value'] = -1
context = {'title': 'Rayons',
......@@ -107,8 +107,29 @@ def add_product(request, shelf_id):
return JsonResponse({'res': res})
def inventory_process_state(request, shelf_id):
res = {}
try:
s = Shelf(shelf_id)
s_data = s.get()
res['state'] = s_data['inventory_status']
except Exception as e:
res['error'] = str(e)
coop_logger.error("Inventory process state : %s", str(e))
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def do_shelf_inventory(request):
"""Process shelf inventory"""
"""
If many products are implied, the whole process could last many minutes.
During this time, user can submit data again.
This is managed with 'busy' message returned by Shelf.get_full_inventory_data method
Web server can also return a timeout message during this time.
This is managed by sending a query to above "get_process_state" (within browser ajax error capture)
"""
res = {}
# TODO : manage error strings array instead of one string
try:
......@@ -121,7 +142,8 @@ def do_shelf_inventory(request):
'name': shelf_data['name'] + ' - ' + inventory_date.strftime("%d/%m/%Y"),
'shelf_id': shelf_data['id'],
'user_comments': shelf_data['user_comments'],
'products': shelf_data['list_processed']
'products': shelf_data['list_processed'],
'status': shelf_data['inventory_status']
}
try:
filename = 'data/inventories_backup/'
......@@ -129,20 +151,26 @@ def do_shelf_inventory(request):
filename += "__" + str(shelf_data['id']) + '.json'
with open(filename, 'w') as outfile:
json.dump(shelf_data, outfile)
except:
pass
except Exception as serr:
coop_logger.error("Inventory backup failure : %s", str(serr))
try:
if shelf_data['inventory_status'] == '' :
if shelf_data['inventory_status'] == '':
# First step: save first products count in temp file
res = m.save_tmp_inventory(inventory_data)
else :
else:
inventory_data['date'] = inventory_date
inventory_data['shelf_name'] = shelf_data['name']
inventory_data['shelf_num'] = shelf_data['sort_order']
# Get data from step 1
full_inventory_data = m.get_full_inventory_data(inventory_data)
if 'error' in full_inventory_data:
res['error'] = full_inventory_data['error']
if 'busy' in full_inventory_data:
res['busy'] = True
return JsonResponse(res, status=500)
# Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
......@@ -173,7 +201,7 @@ def do_shelf_inventory(request):
except Exception as e:
# Don't validate if error anywhere in inventory process
res['error'] = {'inventory' : str(e)}
res['error'] = type(e).__name__
coop_logger.error("Shelf inv. : %s", str(e))
except Exception as err_json:
res['error'] = "Unable to parse received JSON"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment