Commit 0d39f02b by François C.

Merge branch 'dev_cooperatic' into 'ticket_2828'

# Conflicts:
#   shelfs/static/js/shelf_inventory.js
parents ae224347 acd357d1
Pipeline #2175 passed with stage
in 1 minute 29 seconds
......@@ -58,7 +58,11 @@ def add_products(request):
try:
id = int(request.POST.get('shelf_id'))
barcodes = json.loads(request.POST.get('bc'))
result = Shelf(id).add_products_by_barcodes(barcodes)
m = Shelf(id)
result = m.add_products_by_barcodes(barcodes)
# Update shelf last product added date
result["update_last_product_added_date"] = m.update_last_product_added_date()
except Exception as e:
result['error'] = str(e)
else:
......
......@@ -6,7 +6,7 @@ from products.models import CagetteProducts
from inventory.models import CagetteInventory
import os
from datetime import date
from datetime import date, datetime
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font
from statistics import *
......@@ -14,6 +14,7 @@ from statistics import *
# Prefix for temp shelf inventory files
tmp_inv_file_prefix = 'temp/inventory_shelf_'
default_inventory_start_datetime = "0001-01-01 00:00:00"
def as_text(value):
""" Utils """
......@@ -106,6 +107,7 @@ class Shelf(models.Model):
# Inventory all done
f['inventory_status'] = ''
f['date_last_inventory'] = date.today().strftime("%Y-%m-%d")
f['ongoing_inv_start_datetime'] = default_inventory_start_datetime # Reset
if 'last_inventory_id' in params:
f['last_inventory_id'] = params['last_inventory_id']
......@@ -180,6 +182,18 @@ class Shelf(models.Model):
res['error'] = "L'enregistrement n'a pas pu se réaliser"
return res
def set_begin_inventory_datetime(self):
res = {}
now = datetime.now().isoformat()
f = {'ongoing_inv_start_datetime': now}
try:
res["update"] = self.o_api.update('product.shelfs', self.id, f)
res["inventory_begin_datetime"] = now
except Exception as e:
res['error'] = str(e)
return res
def save_tmp_inventory(self, inventory_data):
"""Save inventory data in a json temp file"""
......
......@@ -12,22 +12,23 @@ var validation_msg = $('#validation_msg'),
process_all_items_msg = $('#process_all_items_msg'),
faq_content = $("#FAQ_modal_content"),
issues_reporting = $("#issues_reporting"),
add_product_form = $("#add_product_form"),
add_product_input = $("#add_product_input");
add_product_form = $("#add_product_form");
var shelf,
var shelf = null,
parent_location = '/shelfs',
originView = "shelf", // or custom_list (create from order view)
list_to_process = [],
table_to_process,
table_processed,
table_to_process = null,
table_processed = null,
editing_item = null, // Store the item currently being edited
editing_origin, // Keep track of where editing_item comes from
editing_origin = "", // Keep track of where editing_item comes from
processed_row_counter = 0, // Keep count of the order the item were added in processed list
search_chars = [],
user_comments = '',
adding_product = false, // True if modal to add a product is open
barcodes = null; // Barcodes stored locally
adding_product = false, // True if modal to add a product is open.
barcodes = null, // Barcodes stored locally
// datetime for which shelf's ongoing_inv_start_datetime is considered null
default_inventory_start_datetime = "0001-01-01 00:00:00";
/* UTILS */
......@@ -104,10 +105,14 @@ function barcode_analyzer(chars) {
}
function reset_previous_value() {
if (editing_item != null) {
if (editing_item !== null) {
$('#edition_input').val(editing_item.qty);
}
}
function refresh() {
location.reload();
}
// Directly send a line to edition when barcode is read
function select_product_from_bc(barcode) {
var found = null,
......@@ -115,7 +120,7 @@ function select_product_from_bc(barcode) {
if (isValidEAN13(barcode)) {
var scannedProduct = barcodes.get_corresponding_odoo_product(barcode);
if (scannedProduct == null) {
if (scannedProduct === null) {
alert("Le code-barre " + barcode + " ne correspond à aucun article connu.");
return -1;
......@@ -127,7 +132,7 @@ function select_product_from_bc(barcode) {
}
}
if (editing_item == null) {
if (editing_item === null) {
$.each(list_to_process, function(i, e) {
if (e.barcode == barcode) {
......@@ -189,7 +194,7 @@ function handle_blinking_effect(element) {
element.addEventListener('animationend', onAnimationEnd);
element.addEventListener('webkitAnimationEnd', onAnimationEnd);
function onAnimationEnd(e) {
function onAnimationEnd() {
element.classList.remove('blink_me');
}
}
......@@ -279,7 +284,7 @@ function clearLineEdition() {
}
// Validate product edition
function validateEdition(form) {
function validateEdition() {
if (editing_item != null) {
if (editProductInfo(editing_item)) {
clearLineEdition();
......@@ -515,10 +520,12 @@ function confirmProcessAllItems() {
openModal();
// Iterate over all rows in table of items to process
table_to_process.rows().every(function (rowIdx, tableLoop, rowLoop) {
table_to_process.rows().every(function () {
var data = this.data();
editProductInfo(data, 0);
return 1;
});
// Reset data
......@@ -556,7 +563,6 @@ function send() {
var url = "../do_" + originView + "_inventory";
var call_begin_at = new Date().getTime();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({
type: "PUT",
url: url,
......@@ -579,18 +585,19 @@ function send() {
}
}
var msg = (originView == 'shelf') ? 'Retour à la liste des rayons' : 'Retour';
var msg = (originView == 'shelf') ? 'OK, je passe à la suite !' : 'Retour';
openModal(inventory_validated_msg.html(), back, msg, true, false);
// Go to step 2 if step 1 is validated and modal closed
openModal(inventory_validated_msg.html(), refresh, msg, true, false);
// Go back to list if modal closed
$('#modal_closebtn_top').on('click', back);
$('#modal_closebtn_bottom').on('click', back);
// Go to step 2 if modal is closed
$('#modal_closebtn_top').on('click', refresh);
$('#modal_closebtn_bottom').on('click', refresh);
// Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id);
},
error: function(jqXHR, textStatus) { // 500 error has been thrown or web server sent a timeout
error: function(jqXHR) { // 500 error has been thrown or web server sent a timeout
if (jqXHR.status == 504) {
/*
django is too long to respond.
......@@ -657,6 +664,35 @@ function exit_adding_product() {
adding_product = false;
}
/**
* Set the ongoing inventory start datetime.
* This operation is invisible to the user.
*/
function set_begin_inventory_datetime() {
if (originView === 'shelf' &&
(
shelf.ongoing_inv_start_datetime === default_inventory_start_datetime
|| shelf.ongoing_inv_start_datetime === undefined
)
) {
$.ajax({
type: "POST",
url: "/shelfs/"+shelf.id+"/set_begin_inventory_datetime",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
shelf.ongoing_inv_start_datetime = data.res.inventory_begin_datetime;
// Update local storage
localStorage.setItem(originView + "_" + shelf.id, JSON.stringify(shelf));
},
error: function() {
console.log("Impossible de mettre à jour la date de début d'inventaire");
}
});
}
}
// Add a product that's not in the list
function open_adding_product() {
if (originView == 'shelf') {
......@@ -673,7 +709,6 @@ function do_add_product() {
};
openModal();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({
type: "POST",
url: "../"+shelf.id+"/add_product",
......@@ -681,7 +716,7 @@ function do_add_product() {
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(prod_data),
success: function(data) {
success: function() {
exit_adding_product();
closeModal();
alert('Produit ajouté !');
......@@ -728,8 +763,6 @@ function saveIssuesReport() {
// Get shelf data from server if not in local storage
function get_shelf_data() {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
var url = (originView == 'shelf') ? '../' + shelf.id : '../get_custom_list_data?id=' + shelf.id;
$.ajax({
......@@ -741,6 +774,7 @@ function get_shelf_data() {
success: function(data) {
shelf = data.res;
init();
set_begin_inventory_datetime();
},
error: function(data) {
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
......@@ -756,7 +790,6 @@ function init() {
// Products passed at page loading
// TODO: get products by ajax for better ui experience (? -> warning js at loading)
// TODO : What happens if products are being put or removed from the self before the end of the inventory ?
//console.log(shelf)
list_to_process = products;
initLists();
......@@ -813,7 +846,7 @@ function init() {
$(document).on('click', 'button#add_product_to_shelf', open_adding_product);
$(document).on('click', 'button#open_issues_report', openIssuesReport);
$(document).on('click', 'button#open_faq', openFAQ);
$(document).on('click', 'button#process_all_items', function (e) {
$(document).on('click', 'button#process_all_items', function () {
openModal(process_all_items_msg.html(), confirmProcessAllItems, 'Confirmer', false);
});
......@@ -830,7 +863,7 @@ function init() {
handle_blinking_effect(container_edition);
// Disable mousewheel on an input number field when in focus
$('#edition_input').on('focus', function (e) {
$('#edition_input').on('focus', function () {
$(this).on('wheel.disableScroll', function (e) {
e.preventDefault();
/*
......@@ -851,7 +884,7 @@ function init() {
$("#reset_to_previous_qty").hide();
}
})
.on('blur', function (e) {
.on('blur', function () {
$(this).off('wheel.disableScroll');
});
......@@ -928,6 +961,8 @@ var get_barcodes = async function() {
$(document).ready(function() {
// Get Route parameter
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
var pathArray = window.location.pathname.split('/');
shelf = {id: pathArray[pathArray.length-1]};
......@@ -947,7 +982,9 @@ $(document).ready(function() {
if (stored_shelf != null) {
shelf = stored_shelf;
init();
set_begin_inventory_datetime();
} else {
// Get shelf info if not coming from shelves list
get_shelf_data();
......
......@@ -6,8 +6,8 @@ Informations affichées :
*/
var parent_location = '/shelfs',
shelf,
table_products,
shelf = null,
table_products = null,
search_chars = [];
/* UTILS */
......@@ -54,7 +54,7 @@ function initList() {
title:"Delta (dernier inv.)",
width: "10%",
className:"dt-body-center",
render: function (data, type, full, meta) {
render: function (data, type) {
if (type == "sort" || type == 'type')
return data;
......@@ -70,7 +70,7 @@ function initList() {
title:"Pertes (dernier inv.)",
width: "10%",
className:"dt-body-center",
render: function (data, type, full, meta) {
render: function (data, type) {
if (type == "sort" || type == 'type')
return data;
......
......@@ -6,7 +6,6 @@ var main_content = $('#main-content'),
shelf_sort_order = create_form.find('input[name="sort_order"]'),
shelf_name = create_form.find('input[name="name"]'),
description = create_form.find('textarea[name="description"]'),
eye = '<i class="fas fa-eye"></i>',
delete_icon = '<i class="fas fa-trash"></i>',
add_icon = '<i class="fas fa-plus-circle"></i>',
edit_icon = '<i class="fas fa-edit"></i>',
......@@ -136,6 +135,8 @@ var update_shelf = function() {
rData.res.p_nb = data.p_nb;
this.data(rData.res).draw();
}
return 1;
});
closeModal();
} else alert(rData.res.error);
......@@ -202,12 +203,6 @@ var downloadInventoryReport = function() {
}
};
var rowUpdate = function (row, rdata) {
//console.log(row, rdata)
};
// TODO put datatable common methods such as following in a file useable for all modules
var rowGetData = function(clicked) {
var row = shelfs_table.row(clicked.parents('tr'));
......@@ -216,7 +211,7 @@ var rowGetData = function(clicked) {
return row.data();
};
function coop_init_datatable(params, data, domsel, cols, action_btn) {
function coop_init_datatable(params, data, domsel, cols) {
var buttons = [];
var columns = [];
......@@ -277,10 +272,6 @@ function coop_init_datatable(params, data, domsel, cols, action_btn) {
rowId : "id",
data : data,
language: {url : '/static/js/datatables/french.json'},
createdRow: function(row, rdata, index) {
rowUpdate(row, rdata);
},
initComplete: function() {
/*
if (! coop_is_connected())
......@@ -317,7 +308,7 @@ var init_and_fill_selfs_list = function() {
data:"last_inventory_id",
title:"Rapport dernier inventaire",
className: "action",
render: function (data, type, full, meta) {
render: function (data) {
if (typeof data != "undefined" && data != 0) {
return download_icon;
} else {
......
......@@ -13,11 +13,46 @@ function init_datatable() {
className:"dt-body-center"
},
{data:"name", title:"Nom"},
{data:"description", title:"Description", orderable: false},
// {data:"description", title:"Description", orderable: false},
{
data:"ongoing_inv_start_datetime",
title:"Début inventaire en cours",
render: function (data, type) {
// Sort on data, not rendering
if (type == "sort" || type == 'type')
return data;
if (data == '0001-01-01 00:00:00')
return "";
else {
var date = new Date(data);
return `${date.toLocaleDateString('fr-FR')} ${date.toLocaleTimeString('fr-FR')}`;
}
}
},
{
data:"date_last_product_added",
title:"Dernier ajout produit",
render: function (data, type) {
// Sort on data, not rendering
if (type == "sort" || type == 'type')
return data;
if (data == '0001-01-01')
return "";
else {
var date = new Date(data);
return date.toLocaleDateString('fr-FR');
}
}
},
{
data:"date_last_inventory",
title:"Date dernier inventaire",
render: function (data, type, full, meta) {
title:"Dernier inventaire",
render: function (data, type) {
// Sort on data, not rendering
if (type == "sort" || type == 'type')
return data;
......@@ -36,7 +71,7 @@ function init_datatable() {
{
data:"shelf_value",
title:"Valeur théorique du rayon",
render: function (data, type, full, meta) {
render: function (data, type) {
if (type == "sort" || type == 'type')
return data;
......@@ -51,44 +86,45 @@ function init_datatable() {
width: "5%",
className:"dt-body-center"
},
{
data:"last_inv_delta_percentage",
title:"Delta (dernier inv.)",
width: "5%",
className:"dt-body-center",
render: function (data, type, full, meta) {
if (type == "sort" || type == 'type')
return data;
if (data == -99999999) {
return '/';
} else {
return data + ' %';
}
}
},
{
data:"last_inv_losses_percentage",
title:"Pertes (dernier inv.)",
width: "5%",
className:"dt-body-center",
render: function (data, type, full, meta) {
if (type == "sort" || type == 'type')
return data;
if (data == -99999999) {
return '/';
} else {
return data + ' %';
}
}
},
/* NOT IN USE */
// {
// data:"last_inv_delta_percentage",
// title:"Delta (dernier inv.)",
// width: "5%",
// className:"dt-body-center",
// render: function (data, type) {
// if (type == "sort" || type == 'type')
// return data;
// if (data == -99999999) {
// return '/';
// } else {
// return data + ' %';
// }
// }
// },
// {
// data:"last_inv_losses_percentage",
// title:"Pertes (dernier inv.)",
// width: "5%",
// className:"dt-body-center",
// render: function (data, type) {
// if (type == "sort" || type == 'type')
// return data;
// if (data == -99999999) {
// return '/';
// } else {
// return data + ' %';
// }
// }
// },
{
data:"inventory_status",
title:"Inventaire à faire",
className:"dt-body-center",
width: "15%",
render: function (data, type, full, meta) {
render: function (data) {
if (data == '')
return "<button class='btn--primary do_shelf_inventory'>Inventaire en rayon</button>";
else
......@@ -144,11 +180,13 @@ function get_shelfs_extra_data() {
}
function set_null_to_extra_data() {
shelfs_table.rows().every(function (rowIdx, tableLoop, rowLoop) {
shelfs_table.rows().every(function () {
var d = this.data();
d.shelf_value = -2;
this.invalidate(); // invalidate the data DataTables has cached for this row
return 1;
});
shelfs_table.draw();
......@@ -157,7 +195,6 @@ function set_null_to_extra_data() {
var getRowData = function(clicked) {
var row = shelfs_table.row(clicked.parents('tr'));
return row.data();
};
......
......@@ -17,7 +17,7 @@ function init_datatable() {
{
data:"date_last_inventory",
title:"Date dernier inventaire",
render: function (data, type, full, meta) {
render: function (data, type) {
// Sort on data, not rendering
if (type == "sort" || type == 'type')
return data;
......@@ -37,7 +37,7 @@ function init_datatable() {
title:"",
className:"dt-body-center",
width: "15%",
render: function (data, type, full, meta) {
render: function () {
return "<button class='btn--success do_export_sales_data'>Export Ventes</button>";
}
}
......
......@@ -14,6 +14,7 @@ urlpatterns = [
url(r'^(?P<shelf_id>\d+)$', views.shelf_data),
url(r'^(?P<shelf_id>\d+)/products$', views.products),
url(r'^(?P<shelf_id>\d+)/add_product$', views.add_product),
url(r'^(?P<shelf_id>\d+)/set_begin_inventory_datetime$', views.set_begin_inventory_datetime),
url(r'^do_shelf_inventory$', views.do_shelf_inventory),
url(r'^(?P<shelf_id>\d+)/last_inventory_report$', views.get_last_inventory_report),
url(r'^shelf_inventory_FAQ', views.shelf_inventory_FAQ),
......
......@@ -61,6 +61,15 @@ def shelf_data(request, shelf_id):
else:
return JsonResponse({'res': shelf})
def set_begin_inventory_datetime(request, shelf_id):
""" Set the ongoing inventory start datetime. Set it to now. """
res = Shelf(shelf_id).set_begin_inventory_datetime()
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def all(request):
"""Get all shelves data"""
......
......@@ -114,14 +114,14 @@
</div>
<div id="validation_msg">
<h3>Attention !</h3>
<p>Vous vous apprêtez valider le comptage des produits<span class="validation_msg_step2" style="display:none;"> en réserve</span> de ce rayon.</p>
<p>Vous vous apprêtez à valider le comptage des produits<span class="validation_msg_step2" style="display:none;"> en réserve</span> de ce rayon.</p>
<p class="validation_msg_step2" style="display:none;"><i>Cette opération pourra pendre un peu de temps. (ex: 5min pour 120 produits)</i><br/><br/></p>
<p>Êtez-vous sûr ?</p>
<hr />
</div>
<div id="inventory_validated">
<div class="txtcenter">
<h3>Bravo, l'inventaire de ce rayon est terminé !</h3>
<h3>Bravo, la première partie de l'inventaire de ce rayon est terminé !</h3>
<div id="products_missed_container" style="display:none;">
<br />
<h4>Attention, les produits suivants n'ont pas pu être inventoriés : </h4>
......@@ -132,7 +132,8 @@
</div>
<div id="step1_validated" style="display:none;">
<br/>
<p>Vous pouvez continuer l'inventaire en allant comptabiliser les produits en réserve, si ce n'est pas déjà fait.</p>
<p><strong>Vous allez maintenent passer à l'inventaire en réserve.</strong></p>
<p>Si vous avez un doute sur ce qu'est l'inventaire en réserve, la présence de stock ou non, demandez au.à la salarié.e responsable.</p>
</div>
</div>
<hr />
......
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