Commit 8a14e906 by François C.

Add change product shelf function

parent 7dfcbf2f
Pipeline #2188 passed with stage
in 1 minute 32 seconds
...@@ -505,11 +505,14 @@ class Shelf(models.Model): ...@@ -505,11 +505,14 @@ class Shelf(models.Model):
class Shelfs(models.Model): class Shelfs(models.Model):
def get_all(): def get_all(precision='full'):
res = [] res = []
try: try:
api = OdooAPI() api = OdooAPI()
res = api.execute('product.shelfs', 'get', {}) if precision == 'simple':
res = api.search_read('product.shelfs', [], ['name', 'sort_order'], order='sort_order asc')
else:
res = api.execute('product.shelfs', 'get', {})
except Exception as e: except Exception as e:
coop_logger.error("Rayons, get_all : %s", str(e)) coop_logger.error("Rayons, get_all : %s", str(e))
return res return res
...@@ -529,3 +532,18 @@ class Shelfs(models.Model): ...@@ -529,3 +532,18 @@ class Shelfs(models.Model):
except Exception as e: except Exception as e:
coop_logger.error("Rayons, get_shelfs_sortorder : %s", str(e)) coop_logger.error("Rayons, get_shelfs_sortorder : %s", str(e))
return res return res
@staticmethod
def make_products_shelf_links(data):
res = {}
try:
api = OdooAPI()
res['done'] = []
for elt in data:
f = {'shelf_id': elt['shelf_id']}
if api.update('product.product', [elt['product_id']], f):
res['done'].append(elt['product_id'])
except Exception as e:
res['error'] = str(e)
coop_logger.error("Rayons, make_products_shelf_links : %s", str(e))
return res
\ No newline at end of file
...@@ -139,6 +139,13 @@ table.dataTable { ...@@ -139,6 +139,13 @@ table.dataTable {
padding: 5px; padding: 5px;
} }
#header_container_left {
float: left;
}
#change_shelf_btn {
float: right;
}
div#container_edition { div#container_edition {
padding: 8px; padding: 8px;
background-color: #e7e9ed; background-color: #e7e9ed;
...@@ -288,3 +295,8 @@ hr { ...@@ -288,3 +295,8 @@ hr {
opacity: 1; opacity: 1;
} }
/* Change shelf modal */
.shelf_selection {
max-width: 240px;
}
...@@ -12,7 +12,9 @@ var validation_msg = $('#validation_msg'), ...@@ -12,7 +12,9 @@ var validation_msg = $('#validation_msg'),
process_all_items_msg = $('#process_all_items_msg'), process_all_items_msg = $('#process_all_items_msg'),
faq_content = $("#FAQ_modal_content"), faq_content = $("#FAQ_modal_content"),
issues_reporting = $("#issues_reporting"), issues_reporting = $("#issues_reporting"),
add_product_form = $("#add_product_form"); add_product_form = $("#add_product_form"),
change_shelf_form = $("#change_shelf_form"),
change_shelf_btn = $('#change_shelf_btn');
var shelf = null, var shelf = null,
parent_location = '/shelfs', parent_location = '/shelfs',
...@@ -28,7 +30,9 @@ var shelf = null, ...@@ -28,7 +30,9 @@ var shelf = null,
adding_product = false, // True if modal to add a product is open. adding_product = false, // True if modal to add a product is open.
barcodes = null, // Barcodes stored locally barcodes = null, // Barcodes stored locally
// datetime for which shelf's ongoing_inv_start_datetime is considered null // datetime for which shelf's ongoing_inv_start_datetime is considered null
default_inventory_start_datetime = "0001-01-01 00:00:00"; default_inventory_start_datetime = "0001-01-01 00:00:00",
selected_products_for_shelf_change = [],
all_shelfs = null; // Use get_all_shelfs to access it's value
/* UTILS */ /* UTILS */
...@@ -297,6 +301,25 @@ function validateEdition() { ...@@ -297,6 +301,25 @@ function validateEdition() {
} }
} }
/**
* Unselect all rows from datatable
* Only for table_to_process
*/
function unselect_all_rows() {
$("#select_all_products_cb").prop("checked", false);
table_to_process.rows().every(function() {
const node = $(this.node());
node.removeClass('selected');
node.find(".select_product_cb").first()
.prop("checked", false);
return 0;
});
selected_rows = [];
}
/* /*
* Update a product info and add it to processed items. * Update a product info and add it to processed items.
* If 'value' is set, use it as new value. * If 'value' is set, use it as new value.
...@@ -323,6 +346,102 @@ function editProductInfo (productToEdit, value = null) { ...@@ -323,6 +346,102 @@ function editProductInfo (productToEdit, value = null) {
return true; return true;
} }
/* Change shelf process */
/*
data should be an array of {product_id: xx, shelf_id: yy}
*/
function record_products_shelf_on_server(data) {
return new Promise(resolve => {
$.ajax({
type: "POST",
url: "/shelfs/change_products_shelfs",
dataType: "json",
data: JSON.stringify(data),
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
if (typeof data.res !== "undefined" && typeof data.res.done !== "undefined")
resolve(data.res.done);
else
resolve(null);
},
error: function() {
alert("Impossible de mettre à jour les données");
resolve(null);
}
});
});
}
// call on change_shelf_btn click action
async function open_change_shelf_modal() {
selected_products_for_shelf_change = [];
$('.select_product_cb:checked').each(function(idx,elt){
const row = $(elt).closest('tr');
selected_products_for_shelf_change.push(table_to_process.row(row).data())
});
if (selected_products_for_shelf_change.length > 0) {
/*
As button is not shown if no product is selected, should be always true
But, with CSS changes, it could happen that length == 0
*/
const shelfs = await get_all_shelfs();
if (shelfs !== null) {
let modal_content = $('#templates #change_shelf_form').clone(),
shelf_selector = $('<select>').addClass('shelf_selection'),
table = modal_content.find('table tbody').empty();
//construct shelfs selector
shelfs.forEach(
(shelf) => {
let option = $('<option>')
.val(shelf.id)
.text(shelf.name + ' (' + shelf.sort_order + ')');
shelf_selector.append(option);
});
selected_products_for_shelf_change.forEach(
(product) => {
let tr = $('<tr>').attr('data-id',product.id)
.append($('<td>').text(product.name))
.append($('<td>').append(shelf_selector.clone()));
table.append(tr);
});
openModal(
modal_content.html(),
() => {
if (is_time_to('change_product_shelf', 500)) {
make_change = async () => {
// Prepare data to be transmitted to server to be recorded
let data = [];
$('.overlay-content table tbody tr').each(function(idx,e){
data.push({
product_id : $(e).data('id'),
shelf_id : $(e).find('select').val()
});
});
const update_result = await record_products_shelf_on_server(data);
if (update_result !== null) {
update_result.forEach(
(product_id) => {
remove_from_toProcess(table_to_process.row($('tr#'+product_id)));
});
}
}
make_change();
}
},
'Changer les produits de rayons'
);
} else {
alert("Les informations des autres rayons n'ont pas pu être récupérées.")
}
}
}
/* LISTS HANDLING */ /* LISTS HANDLING */
...@@ -342,8 +461,24 @@ function initLists() { ...@@ -342,8 +461,24 @@ function initLists() {
} }
// Init table for items to process // Init table for items to process
var columns_to_process = [
{data:"id", title: "id", visible: false}, var columns_to_process = [];
if (shelf.inventory_status !== "") {
columns_to_process.push({data:"id", title: "id", visible: false});
} else {
columns_to_process.push({
title: `<div id="table_header_select_all" class="txtcenter">
<input type="checkbox" id="select_all_products_cb" name="select_all_products_cb" value="all">
</div>`,
className: "dt-body-center",
orderable: false,
render: function (data) {
return `<input type="checkbox" class="select_product_cb" />`;
},
width: "4%"});
}
columns_to_process = columns_to_process.concat([
{data:"name", title:"Produit", width: "60%"}, {data:"name", title:"Produit", width: "60%"},
{data:"uom_id.1", title:"Unité de mesure", className:"dt-body-center"}, {data:"uom_id.1", title:"Unité de mesure", className:"dt-body-center"},
{ {
...@@ -351,7 +486,7 @@ function initLists() { ...@@ -351,7 +486,7 @@ function initLists() {
defaultContent: "<a class='btn' id='process_item' href='#'><i class='far fa-edit'></i></a>", "className":"dt-body-center", defaultContent: "<a class='btn' id='process_item' href='#'><i class='far fa-edit'></i></a>", "className":"dt-body-center",
orderable: false orderable: false
} }
]; ]);
if (originView == 'custom_list') { if (originView == 'custom_list') {
columns_to_process.splice(1, 0, {data:"shelf_sortorder", title:"Rayon", className:"dt-body-center"}); columns_to_process.splice(1, 0, {data:"shelf_sortorder", title:"Rayon", className:"dt-body-center"});
...@@ -451,6 +586,41 @@ function initLists() { ...@@ -451,6 +586,41 @@ function initLists() {
clearLineEdition(); clearLineEdition();
} }
}); });
// Select row(s) on checkbox change (copied from orders_helper.js -only table_to_process changed-)
$(table_to_process.table().header()).on('click', 'th #select_all_products_cb', function () {
if (this.checked) {
selected_rows = [];
table_to_process.rows().every(function() {
const node = $(this.node());
node.addClass('selected');
node.find(".select_product_cb").first()
.prop("checked", true);
// Save selected rows in case the table is updated
selected_rows.push(this.data().id);
return 0;
});
change_shelf_btn.show();
} else {
unselect_all_rows();
change_shelf_btn.hide();
}
});
$(table_to_process.table().body()).on('click', '.select_product_cb', function () {
if (this.checked) {
change_shelf_btn.show();
} else {
// must hide change_shelf_btn only if no other product is selected
if ($('.select_product_cb:checked').length === 0) {
change_shelf_btn.hide();
}
}
});
change_shelf_btn.click(open_change_shelf_modal);
} }
// Add a line to the 'items to process' list // Add a line to the 'items to process' list
...@@ -766,6 +936,37 @@ function saveIssuesReport() { ...@@ -766,6 +936,37 @@ function saveIssuesReport() {
/* INIT */ /* INIT */
// (for shelf change)
function get_all_shelfs() {
return new Promise(resolve => {
if (all_shelfs !== null) {
resolve(all_shelfs);
} else {
$.ajax({
type: 'GET',
url: "/shelfs/all/simple",
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
shelfs = null;
if (typeof data.res !== "undefined" && data.res.length > 0)
shelfs = data.res;
resolve(shelfs);
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des rayons", ctx: 'get_all_shelfs'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'shelf_inventory');
resolve(null);
}
});
}
});
}
// Get shelf data from server if not in local storage // Get shelf data from server if not in local storage
function get_shelf_data() { function get_shelf_data() {
var url = (originView == 'shelf') ? '../' + shelf.id : '../get_custom_list_data?id=' + shelf.id; var url = (originView == 'shelf') ? '../' + shelf.id : '../get_custom_list_data?id=' + shelf.id;
......
...@@ -9,8 +9,9 @@ urlpatterns = [ ...@@ -9,8 +9,9 @@ urlpatterns = [
url(r'^shelf_view/([0-9]+)$', views.shelf_view), url(r'^shelf_view/([0-9]+)$', views.shelf_view),
url(r'^shelf_inventory/([0-9]+)$', views.shelf_inventory), url(r'^shelf_inventory/([0-9]+)$', views.shelf_inventory),
url(r'^inventory_process_state/([0-9]+)$', views.inventory_process_state), url(r'^inventory_process_state/([0-9]+)$', views.inventory_process_state),
url(r'^all$', views.all), url(r'^all/?([a-z]*)$', views.all),
url(r'^get_shelves_extra_data$', views.get_shelves_extra_data), url(r'^get_shelves_extra_data$', views.get_shelves_extra_data),
url(r'^change_products_shelfs$', views.change_products_shelfs),
url(r'^(?P<shelf_id>\d+)$', views.shelf_data), url(r'^(?P<shelf_id>\d+)$', views.shelf_data),
url(r'^(?P<shelf_id>\d+)/products$', views.products), 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+)/add_product$', views.add_product),
......
...@@ -71,9 +71,9 @@ def set_begin_inventory_datetime(request, shelf_id): ...@@ -71,9 +71,9 @@ def set_begin_inventory_datetime(request, shelf_id):
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def all(request): def all(request, precision):
"""Get all shelves data""" """Get all shelves data"""
return JsonResponse({'res': Shelfs.get_all()}) return JsonResponse({'res': Shelfs.get_all(precision)})
def get_shelves_extra_data(request): def get_shelves_extra_data(request):
"""Get data that need calculation, so long execution time""" """Get data that need calculation, so long execution time"""
...@@ -130,6 +130,19 @@ def inventory_process_state(request, shelf_id): ...@@ -130,6 +130,19 @@ def inventory_process_state(request, shelf_id):
else: else:
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def change_products_shelfs(request):
res = {}
try:
data = json.loads(request.body.decode())
res = Shelfs.make_products_shelf_links(data)
except Exception as e:
res['error'] = str(e)
coop_logger.error("change_products_shelfs : %s", str(e))
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def do_shelf_inventory(request): def do_shelf_inventory(request):
"""Process shelf inventory""" """Process shelf inventory"""
""" """
......
...@@ -91,6 +91,9 @@ ...@@ -91,6 +91,9 @@
<div class="container_products" id="container_left"> <div class="container_products" id="container_left">
<h4 id="header_container_left">Produits à compter</h4> <h4 id="header_container_left">Produits à compter</h4>
<button style="display:none;" id="change_shelf_btn" class="btn btn--primary">
Changer de rayon
</button>
<table id="table_to_process" class="display" cellspacing="0"></table> <table id="table_to_process" class="display" cellspacing="0"></table>
</div> </div>
<div class="container_products" id="container_right"> <div class="container_products" id="container_right">
...@@ -167,6 +170,19 @@ ...@@ -167,6 +170,19 @@
<input autocomplete="off" type="text" placeholder="Code barre du produit" class="add_product_input"> <input autocomplete="off" type="text" placeholder="Code barre du produit" class="add_product_input">
<hr /> <hr />
</div> </div>
<div id="change_shelf_form">
<h3>Changement de rayons</h3>
<hr />
<table>
<thead>
<tr>
<th>Produit</th>
<th>Rayon</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
......
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