Commit f2dff3c8 by François C.

Merge branch '7747-inventaire-par-article' into 'migration-v12'

several changes in inventaire par article app

See merge request !284
parents 6963f5a9 2123aa7b
Pipeline #4185 canceled with stage
...@@ -1918,9 +1918,19 @@ function display_products(params) { ...@@ -1918,9 +1918,19 @@ function display_products(params) {
cell.addClass('product_ref_cell'); cell.addClass('product_ref_cell');
} }
} }
} },
dom: 'lrtip', // Remove the search input from that table
});
/* In order to remove the accents in the input, we could attach a keyup event to the default
* search field generated by datatables, but then two search events would be triggered on keyup :
* the default datatable event, and ours.
* To fix this problem we create our own input search so that only our event fires.
*/
$(document).on('keyup', '#custom_products_table_filter input[type="search"]', function() {
products_table
.search(jQuery.fn.DataTable.ext.type.search.string(this.value))
.draw();
}); });
products_table.search('');
$('.main').show(); $('.main').show();
$('#main_content_footer').show(); $('#main_content_footer').show();
$('#do_inventory').show(); $('#do_inventory').show();
......
...@@ -397,6 +397,13 @@ class CagetteProducts(models.Model): ...@@ -397,6 +397,13 @@ class CagetteProducts(models.Model):
return api.search_read('product.product', cond, fields) return api.search_read('product.product', cond, fields)
@staticmethod @staticmethod
def get_all_active_products():
api = OdooAPI()
cond = [['active', '=', True]]
fields = ['id', 'uom_id', 'name', 'qty_available', 'barcode']
return api.search_read('product.product', cond, fields, limit=False)
@staticmethod
def get_vrac_products_from_cats(cats, withCandidate=False, fields=[]): def get_vrac_products_from_cats(cats, withCandidate=False, fields=[]):
api = OdooAPI() api = OdooAPI()
cond = [['active', '=', True], cond = [['active', '=', True],
......
.main, .barcode_search_area {
margin-top: 20px;
}
#barcode_selector {
border: 1px solid #555;
border-radius:15px;
}
.stock_edit_input { .stock_edit_input {
max-width: 50px; max-width: 100px;
} }
.dataTables_filter {
display: block !important; /* S'assure qu'elle est bien affichée */
}
\ No newline at end of file
/* /*
* Récupération d'un produit par scan et affichage des informations du produits. * Récupération d'un produit par scan et affichage des informations du produits.
* Les informations actuellements récupérées sont : * Les informations actuellements récupérées sont :
* - Numéro de Rayon
* - Stock théorique * - Stock théorique
* *
* Les informations actuellement modifiables sont : * Les informations actuellement modifiables sont :
...@@ -9,143 +8,127 @@ ...@@ -9,143 +8,127 @@
*/ */
var products = [], var products = [],
products_table = null, products_table = null;
search_chars = [];
function init(products) {
function reset_focus() { // create table, show panel
$('#barcode_selector').val(''); products_table = $('#products_table').DataTable({
$('#barcode_selector').focus(); data: products,
} columns: [
{data: "id", title: "id", visible: false},
function add_product(product) { {
try { data: "name",
// Add to list title: "Produit",
products.push(product); width: "50%",
render: function (data, type, full, meta) {
if (products_table == null) { // Add tooltip with barcode over product name
// create table, show panel let display_barcode = "Aucun";
products_table = $('#products_table').DataTable({
data: products, if ('barcode' in full) {
columns:[ display_barcode = full.barcode;
{data:"id", title: "id", visible: false}, }
{
data:"name",
title:"Produit",
width: "50%",
render: function (data, type, full, meta) {
// Add tooltip with barcode over product name
let display_barcode = "Aucun";
if ('barcode' in full) { return '<div class="tooltip">' + data
display_barcode = full.barcode; + ' <span class="tooltiptext tt_twolines">Code barre : '
} + display_barcode + '</span> </div>';
}
},
{
data: "uom_id.1",
title: "Unité de vente",
className: "dt-body-center",
orderable: false
},
{
data: "qty_available",
title: "Stock théorique",
className: "dt-body-center",
render: function (data, type, full, meta) {
return '<input type="number" class="stock_edit_input" value="' + data + '">'
+ ' <button type="button" class="stock_edit_button btn--primary"><i class="fas fa-lg fa-check"></i></button>';
}
}
],
order: [
[
0,
"asc"
]
],
pageLength: 10,
deferRender: true,
processing: true,
searching: true,
language: {url: '/static/js/datatables/french.json'},
dom: 'lrtip', // Remove the search input from that table
});
/* In order to remove the accents in the input, we could attach a keyup event to the default
* search field generated by datatables, but then two search events would be triggered on keyup :
* the default datatable event, and ours.
* To fix this problem we create our own input search so that only our event fires.
*/
$(document).on('keyup', '#custom_products_table_filter input[type="search"]', function() {
products_table
.search(jQuery.fn.DataTable.ext.type.search.string(this.value))
.draw();
});
return '<div class="tooltip">' + data // Listener on 'Update product stock' button
+ ' <span class="tooltiptext tt_twolines">Code barre : ' $('#products_table tbody').on('click', 'button.stock_edit_button', function () {
+ display_barcode + '</span> </div>'; var row = products_table.row($(this).parents('tr'));
} var row_data = row.data();
},
{data:"shelf_sortorder", title: "Rayon"}, // Data validation
{ qty = $(this).parents('tr')
data:"uom_id.1", .find('input')
title: "Unité de vente", .val();
className:"dt-body-center", if (row_data.uom_id[0] == 1) {
orderable: false if (qty != 0 && qty / parseInt(qty) != 1) {
}, $.notify(
"Ce produit est vendu à l'unité : la valeur doit être un nombre entier !",
{ {
data:"qty_available", globalPosition: "top left",
title: "Stock théorique", className: "error"
className:"dt-body-center",
render: function (data, type, full, meta) {
return '<input type="number" class="stock_edit_input" value="' + data + '">'
+ ' <button type="button" class="stock_edit_button btn--primary"><i class="fas fa-lg fa-check"></i></button>';
}
} }
], );
order: [
[
0,
"asc"
]
],
paging: false,
dom: 'lrtip', // Remove the search input from that table
language: {url : '/static/js/datatables/french.json'}
});
// Listener on 'Update product stock' button
$('#products_table tbody').on('click', 'button.stock_edit_button', function () {
var row = products_table.row($(this).parents('tr'));
var row_data = row.data();
// Data validation
qty = $(this).parents('tr')
.find('input')
.val();
if (row_data.uom_id[0] == 1) {
if (qty/parseInt(qty) != 1) {
$.notify(
"Ce produit est vendu à l'unité : la valeur doit être un nombre entier !",
{
globalPosition:"top left",
className: "error"
}
);
return 0; return 0;
} }
qty = parseInt(qty); // product by unit qty = parseInt(qty); // product by unit
} else { } else {
qty = parseFloat(qty); qty = parseFloat(qty);
} }
// Value is valid // Value is valid
if (!isNaN(qty)) { if (!isNaN(qty)) {
row_data.qty = $(this).parents('tr') row_data.qty = $(this).parents('tr')
.find('input') .find('input')
.val(); .val();
if (row_data.qty != row_data.qty_available) { if (row_data.qty != row_data.qty_available) {
update_product_stock(row_data); update_product_stock(row_data);
} else { } else {
$.notify( $.notify(
"Valeur inchangée.", "Valeur inchangée.",
{ {
globalPosition:"top left", globalPosition: "top left",
className: "info" className: "info"
}
);
} }
} else { );
$.notify( $("#products_table_filter input").focus()
"Valeur invalide.", }
{
globalPosition:"top left",
className: "error"
}
);
}
});
} else { } else {
// Add row to table $.notify(
var rowNode = products_table.row.add(product).draw(false) "Valeur invalide.",
.node(); {
let onAnimationEnd = function() { globalPosition: "top left",
rowNode.classList.remove('blink_me'); className: "error"
}; }
);
// Handle blinking effect for newly added row $("#products_table_filter input").focus()
$(rowNode).addClass('blink_me');
rowNode.addEventListener('animationend', onAnimationEnd);
rowNode.addEventListener('webkitAnimationEnd', onAnimationEnd);
} }
} catch (e) { });
err = {msg: e.name + ' : ' + e.message, ctx: 'add_product'};
console.error(err);
report_JS_error(err, 'produits');
}
} }
function update_product_stock(p_data) { function update_product_stock(p_data) {
...@@ -162,6 +145,7 @@ function update_product_stock(p_data) { ...@@ -162,6 +145,7 @@ function update_product_stock(p_data) {
delete p_data.qty; delete p_data.qty;
closeModal(); closeModal();
$("#products_table_filter input").focus()
$.notify( $.notify(
"Stock mis à jour !", "Stock mis à jour !",
{ {
...@@ -169,8 +153,6 @@ function update_product_stock(p_data) { ...@@ -169,8 +153,6 @@ function update_product_stock(p_data) {
className: "success" className: "success"
} }
); );
reset_focus();
}, },
error: function(data) { error: function(data) {
// server error // server error
...@@ -189,47 +171,28 @@ function update_product_stock(p_data) { ...@@ -189,47 +171,28 @@ function update_product_stock(p_data) {
} }
// Fetch a product when barcode is read // Fetch a product when barcode is read
function fetch_product_from_bc(barcode) { function fetch_products() {
if (barcode == '') { openModal();
reset_focus();
return 0;
}
for (p of products) {
if (p.barcode == barcode) {
$.notify(
"Produit déjà récupéré !",
{
globalPosition:"top left",
className: "info"
}
);
reset_focus();
return 0;
}
}
$.ajax({ $.ajax({
url: "/products/get_product_data?barcode=" + barcode, url: "/products/get_all_active_products",
success: function(data) { success: function(data) {
reset_focus(); init(data.products)
closeModal();
if (data.product.active == false) { // Attendre que le DOM soit complètement prêt avant de donner le focus
alert('Ce produit est archivé !'); setTimeout(function() {
// Vérifier si l'input de la recherche est disponible
return 0; var searchInput = $("#products_table_filter input");
} if (searchInput.length) {
searchInput.focus(); // Mettre le focus sur le champ de recherche
add_product(data.product); }
}, 100); // Attendre un court instant pour s'assurer que DataTables est complètement initialisé
}, },
error: function(data) { error: function(data) {
closeModal();
if (data.status == 404) { if (data.status == 404) {
// Product not found (shouldn't rise) // Product not found (shouldn't rise)
$.notify( $.notify(
"Aucun produit trouvé avec ce code barre !", "Aucun produit trouvé !",
{ {
globalPosition:"top left", globalPosition:"top left",
className: "error" className: "error"
...@@ -238,7 +201,7 @@ function fetch_product_from_bc(barcode) { ...@@ -238,7 +201,7 @@ function fetch_product_from_bc(barcode) {
reset_focus(); reset_focus();
} else { } else {
// server error // server error
err = {msg: "erreur serveur lors de la récupération du produit", ctx: 'fetch_product_from_bc'}; err = {msg: "erreur serveur lors de la récupération du produit", ctx: 'fetch_products'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error; err.msg += ' : ' + data.responseJSON.error;
} }
...@@ -252,28 +215,5 @@ function fetch_product_from_bc(barcode) { ...@@ -252,28 +215,5 @@ function fetch_product_from_bc(barcode) {
$(document).ready(function() { $(document).ready(function() {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
fetch_products('');
reset_focus();
$('#button_barcode_selector').on('click', function () {
bc = $('#barcode_selector').val();
fetch_product_from_bc(bc);
});
// Barcode reader: listen for 13 digits read in a very short time
$('#search_input').keypress(function(e) {
if (e.which >= 48 && e.which <= 57) {
search_chars.push(String.fromCharCode(e.which));
}
if (search_chars.length >= 13) {
var barcode = search_chars.join("");
if (!isNaN(barcode)) {
search_chars = [];
setTimeout(function() {
fetch_product_from_bc(barcode);
}, 300);
}
}
});
}); });
...@@ -7,6 +7,7 @@ urlpatterns = [ ...@@ -7,6 +7,7 @@ urlpatterns = [
url(r'^simple_list$', views.get_simple_list), url(r'^simple_list$', views.get_simple_list),
url(r'^get_product_for_order_helper$', views.get_product_for_order_helper), url(r'^get_product_for_order_helper$', views.get_product_for_order_helper),
url(r'^get_product_data$', views.get_product_data), url(r'^get_product_data$', views.get_product_data),
url(r'^get_all_active_products$', views.get_all_active_products),
url(r'^get_products_stdprices$', views.get_products_stdprices), url(r'^get_products_stdprices$', views.get_products_stdprices),
url(r'^update_product_stock$', views.update_product_stock), url(r'^update_product_stock$', views.update_product_stock),
url(r'^update_product_purchase_ok$', views.update_product_purchase_ok), url(r'^update_product_purchase_ok$', views.update_product_purchase_ok),
......
...@@ -55,6 +55,14 @@ def get_product_for_order_helper(request): ...@@ -55,6 +55,14 @@ def get_product_for_order_helper(request):
else: else:
return JsonResponse(res, safe=False) return JsonResponse(res, safe=False)
def get_all_active_products(request):
products = CagetteProducts.get_all_active_products()
if not products:
return JsonResponse({"product": products}, status=404)
else:
return JsonResponse({"products": products})
def get_product_data(request): def get_product_data(request):
barcode = request.GET['barcode'] barcode = request.GET['barcode']
res = CagetteProduct.get_product_from_barcode(barcode) res = CagetteProduct.get_product_from_barcode(barcode)
...@@ -63,6 +71,12 @@ def get_product_data(request): ...@@ -63,6 +71,12 @@ def get_product_data(request):
return JsonResponse({"product": res}, status=404) return JsonResponse({"product": res}, status=404)
p = res[0] p = res[0]
add_shelf_sortorder(p)
return JsonResponse({"product": p})
def add_shelf_sortorder(p):
if p['shelf_id'] is not False: if p['shelf_id'] is not False:
shelfs_sortorder = Shelfs.get_shelfs_sortorder([p['shelf_id'][0]]) shelfs_sortorder = Shelfs.get_shelfs_sortorder([p['shelf_id'][0]])
...@@ -73,7 +87,6 @@ def get_product_data(request): ...@@ -73,7 +87,6 @@ def get_product_data(request):
else: else:
p['shelf_sortorder'] = 'X' p['shelf_sortorder'] = 'X'
return JsonResponse({"product": p})
def get_products_stdprices(request): def get_products_stdprices(request):
ids = json.loads(request.body.decode()) ids = json.loads(request.body.decode())
......
<div id="custom_products_table_wrapper" class="dataTables_wrapper no-footer">
<div id="custom_products_table_filter" class="dataTables_filter">
<label for="custom_product_search_input">Recherche :
<input type="search" id="custom_product_search_input" class="form-control" placeholder="" aria-controls="products_table"/>
</label>
</div>
</div>
\ No newline at end of file
...@@ -125,6 +125,7 @@ ...@@ -125,6 +125,7 @@
<div class="main" style="display:none;"> <div class="main" style="display:none;">
<div class="table_area"> <div class="table_area">
{% include "common/custom_search_input_replacing_datatable_default.html" %}
<table id="products_table" class="display" cellspacing="0" width="100%"></table> <table id="products_table" class="display" cellspacing="0" width="100%"></table>
</div> </div>
</div> </div>
......
...@@ -15,17 +15,12 @@ ...@@ -15,17 +15,12 @@
{% block content %} {% block content %}
<div class="header txtcenter"> <div class="header txtcenter">
<h1>Informations sur les produits du magasin</h1> <h1>Inventaire par produits</h1>
</div>
<div class="barcode_search_area txtcenter">
<p><i>Passez un produit sous la douchette ! Si rien ne se passe, cliquez sur le champ ci-dessous puis ré-essayez.</i></p>
<input type="text" id="barcode_selector" name="barcode_selector" placeholder="Codebarre">
<button type="button" class="btn--primary" id="button_barcode_selector" name="button">Recherche</button>
</div> </div>
<div class="main"> <div class="main">
<table id="products_table" class="display" cellspacing="0" ></table> {% include "common/custom_search_input_replacing_datatable_default.html" %}
<table id="products_table" class="display" cellspacing="0" ></table>
</div> </div>
<script src="{% static "js/all_common.js" %}?v=1651853225"></script> <script src="{% static "js/all_common.js" %}?v=1651853225"></script>
......
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