Commit 799fe052 by François C.

#1929 #1934 : Allow user to set minimal stock and set NPA / FDS for each product…

#1929 #1934 : Allow user to set minimal stock and set NPA / FDS for each product in Orders Helper interface
parent a325f466
...@@ -367,6 +367,15 @@ ...@@ -367,6 +367,15 @@
width: 90%; width: 90%;
} }
/* product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
margin: auto;
}
.npa-options label {
display: block;
}
/* - Orders created screen */ /* - Orders created screen */
.order_created_header { .order_created_header {
......
...@@ -688,35 +688,32 @@ function _compute_total_values_by_supplier() { ...@@ -688,35 +688,32 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */ /* - PRODUCT */
/** function save_products_npa_minimal_stock(product, inputs) {
* Update 'purchase_ok' of a product let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name};
* inputs.each(function (i,e) {
* @param {int} p_id product id const input = $(e)
* @param {Boolean} npa value to set purchase_ok to if (input.attr('type') == 'checkbox') {
*/ if (input.prop('checked') == true) {
function set_product_npa(p_id, npa) { actions.npa.push(input.val())
openModal(); }
} else if (input.attr('name') == "minimal_stock") {
const data = { actions.minimal_stock = input.val()
product_tmpl_id: p_id, }
purchase_ok: !npa });
};
// Fetch supplier products
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/products/update_product_purchase_ok", url: "/products/update_npa_and_minimal_stock",
dataType: "json", dataType: "json",
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(data), data: JSON.stringify(actions),
success: () => { success: () => {
const index = products.findIndex(p => p.id == p_id); const index = products.findIndex(p => p.id == product.id);
// Give time for modal to fade // Give time for modal to fade
setTimeout(function() { setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify( $(".actions_buttons_area .right_action_buttons").notify(
"Produit passé en NPA !", "Actions enregistrées !",
{ {
elementPosition:"bottom right", elementPosition:"bottom right",
className: "success", className: "success",
...@@ -724,19 +721,21 @@ function set_product_npa(p_id, npa) { ...@@ -724,19 +721,21 @@ function set_product_npa(p_id, npa) {
} }
); );
}, 500); }, 500);
products[index].minimal_stock = actions.minimal_stock;
// Remove NPA products if (actions.npa.length > 0) {
products.splice(index, 1); // Remove NPA products
update_main_screen(); products.splice(index, 1);
update_cdb_order(); update_main_screen();
update_cdb_order();
}
closeModal(); closeModal();
}, },
error: function(data) { error: function(data) {
let msg = "erreur serveur lors de la sauvegarde du NPA". let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${p_id})`; msg += ` (product_tmpl_id: ${product.id})`;
err = {msg: msg, ctx: 'set_product_npa'}; err = {msg: msg, ctx: 'save_products_npa_minimal_stock'};
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;
} }
...@@ -747,8 +746,10 @@ function set_product_npa(p_id, npa) { ...@@ -747,8 +746,10 @@ function set_product_npa(p_id, npa) {
update_main_screen(); update_main_screen();
} }
}); });
} }
/* - INVENTORY */ /* - INVENTORY */
/** /**
...@@ -1327,8 +1328,8 @@ function prepare_datatable_columns() { ...@@ -1327,8 +1328,8 @@ function prepare_datatable_columns() {
{ {
data: "id", data: "id",
title: `<div id="table_header_select_all" class="txtcenter"> title: `<div id="table_header_select_all" class="txtcenter">
<span class="select_all_text">Sélectionner</span> <!--<span class="select_all_text">Sélectionner</span>-->
<label for="select_all_products_cb">- Tout</label> <label for="select_all_products_cb">Tout</label>
<input type="checkbox" class="select_product_cb" id="select_all_products_cb" name="select_all_products_cb" value="all"> <input type="checkbox" class="select_product_cb" id="select_all_products_cb" name="select_all_products_cb" value="all">
</div>`, </div>`,
className: "dt-body-center", className: "dt-body-center",
...@@ -1453,12 +1454,11 @@ function prepare_datatable_columns() { ...@@ -1453,12 +1454,11 @@ function prepare_datatable_columns() {
}); });
columns.push({ columns.push({
data: "purchase_ok", title: ``,
title: `NPA`,
className: "dt-body-center", className: "dt-body-center",
orderable: false, orderable: false,
render: function (data) { render: function (data) {
return `<input type="checkbox" class="product_npa_cb" value="purchase_ok" ${data ? '' : 'checked'}>`; return `<button type="button" class="btn--primary product_actions">Actions</button>`;
}, },
width: "4%" width: "4%"
}); });
...@@ -1625,7 +1625,6 @@ function display_products(params) { ...@@ -1625,7 +1625,6 @@ function display_products(params) {
// On arrow up pressed, focus next row input // On arrow up pressed, focus next row input
let next_input = $(this).closest("tr").prev().find(".product_qty_input"); let next_input = $(this).closest("tr").prev().find(".product_qty_input");
next_input;
next_input.focus(); next_input.focus();
// Scroll to a position where the target input is not hidden by the sticky suppliers container // Scroll to a position where the target input is not hidden by the sticky suppliers container
...@@ -1652,6 +1651,31 @@ function display_products(params) { ...@@ -1652,6 +1651,31 @@ function display_products(params) {
// On enter pressed, focus previous row input // On enter pressed, focus previous row input
$(this).closest("tr").next().find(".product_qty_input").focus(); $(this).closest("tr").next().find(".product_qty_input").focus();
} }
})
.on('click', 'tbody td .product_actions', function(e){
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const product = products.find(p => p.id == p_id);
let modal_product_actions = $('#templates #modal_product_actions');
modal_product_actions.find(".product_name").text(product.name);
//modal_product_actions.find(".product_npa").text(null ? 'Ne Pas Acheter' : 'Peut Être Acheté');
openModal(
modal_product_actions.html(),
() => {
if (is_time_to('validate_product_actions')) {
save_products_npa_minimal_stock(product, modal.find('input'));
}
},
'Valider',
false
);
modal.find('input[name="minimal_stock"]').val(product.minimal_stock)
}); });
// Associate product to supplier on click on 'X' in the table // Associate product to supplier on click on 'X' in the table
...@@ -1783,34 +1807,7 @@ function display_products(params) { ...@@ -1783,34 +1807,7 @@ function display_products(params) {
} }
}); });
// Set product is NPA (Ne Pas Acheter)
$('#products_table').on('click', 'tbody td .product_npa_cb', function () {
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const npa = this.checked;
const product = products.find(p => p.id == p_id);
let modal_product_npa = $('#templates #modal_product_npa');
modal_product_npa.find(".product_name").text(product.name);
modal_product_npa.find(".product_npa").text(npa ? 'Ne Pas Acheter' : 'Peut Être Acheté');
openModal(
modal_product_npa.html(),
() => {
if (is_time_to('validate_set_product_npa')) {
set_product_npa(p_id, npa);
}
},
'Valider',
false,
true,
() => {
this.checked = !this.checked;
}
);
});
return 0; return 0;
} }
......
...@@ -7,6 +7,7 @@ import csv ...@@ -7,6 +7,7 @@ import csv
import tempfile import tempfile
import pymysql.cursors import pymysql.cursors
import datetime import datetime
import re
vcats = [] vcats = []
...@@ -252,7 +253,39 @@ class CagetteProduct(models.Model): ...@@ -252,7 +253,39 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod
def update_npa_and_minimal_stock(data):
"""Update NPA (ne pas acheter) and minimal stock data"""
res = {}
try:
api = OdooAPI()
f = {'minimal_stock': data['minimal_stock']}
if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']:
# Add [NPA] in product name if needed
f['name'] = data['name'] if ('[NPA]' in data['name']) else data['name'] + " [NPA]"
f['purchase_ok'] = 0
elif '[NPA]' in data['name']:
# Remove [NPA] from name
f['name'] = re.sub(r'( \[NPA\])', '', data['name'])
current_name = data['name'] if ('name' not in f) else f['name']
if 'fds-in-name' in data['npa']:
f['name'] = current_name if '[FDS]' in data['name'] else current_name + " [FDS]"
f['purchase_ok'] = 0
elif '[FDS]' in current_name:
f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0:
f['purchase_ok'] = 1
res["update"] = api.update('product.template', data['id'], f)
except Exception as e:
res["error"] = str(e)
coop_logger.error("update_npa_and_minimal_stock : %s %s", str(e), str(data))
return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -578,7 +611,8 @@ class CagetteProducts(models.Model): ...@@ -578,7 +611,8 @@ class CagetteProducts(models.Model):
"uom_id", "uom_id",
"purchase_ok", "purchase_ok",
"supplier_taxes_id", "supplier_taxes_id",
"product_variant_ids" "product_variant_ids",
"minimal_stock"
] ]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]] c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
products_t = api.search_read('product.template', c, f) products_t = api.search_read('product.template', c, f)
......
...@@ -11,6 +11,7 @@ urlpatterns = [ ...@@ -11,6 +11,7 @@ urlpatterns = [
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),
url(r'^update_product_internal_ref$', views.update_product_internal_ref), url(r'^update_product_internal_ref$', views.update_product_internal_ref),
url(r'^update_npa_and_minimal_stock$', views.update_npa_and_minimal_stock),
url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'), url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'),
url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print), url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print),
url(r'^shelf_labels$', views.shelf_labels), # massive print url(r'^shelf_labels$', views.shelf_labels), # massive print
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from outils.common_imports import * from outils.common_imports import *
from outils.for_view_imports import * from outils.for_view_imports import *
from members.models import CagetteUser
from products.models import CagetteProduct from products.models import CagetteProduct
from products.models import CagetteProducts from products.models import CagetteProducts
from inventory.models import CagetteInventory from inventory.models import CagetteInventory
...@@ -105,25 +106,51 @@ def update_product_stock(request): ...@@ -105,25 +106,51 @@ def update_product_stock(request):
def update_product_purchase_ok(request): def update_product_purchase_ok(request):
res = {} res = {}
data = json.loads(request.body.decode()) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"]) res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"])
if ('error' in res): if ('error' in res):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else: else:
return JsonResponse({"res": res}) return JsonResponse(res, status=403)
def update_product_internal_ref(request): def update_product_internal_ref(request):
res = {} res = {}
data = json.loads(request.body.decode()) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"]) res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"])
if ('error' in res): if ('error' in res):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else: else:
return JsonResponse({"res": res}) return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request):
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
try:
data = json.loads(request.body.decode())
res = CagetteProduct.update_npa_and_minimal_stock(data)
except Exception as e:
res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error'])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else:
return JsonResponse(res, status=403)
def labels_appli_csv(request, params): def labels_appli_csv(request, params):
"""Generate files to put in DAV directory to be retrieved by scales app.""" """Generate files to put in DAV directory to be retrieved by scales app."""
......
...@@ -275,14 +275,21 @@ ...@@ -275,14 +275,21 @@
<hr/> <hr/>
</div> </div>
<div id="modal_product_npa"> <div id="modal_product_actions">
<h3>Attention !</h3> Actions sur <h3><span class="product_name"></span></h3>
<p> <p>
Vous vous apprêtez à passer le produit <span class="product_name"></span> en <span class="product_npa"></span>.<br/> <h4>NPA</h4>
Dès que vous aurez cliqué sur "Valider", le produit sera retiré du tableau et l'information sera enregistrée dans Odoo. <div class="npa-options">
<label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label>
<label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label>
</div>
</p> </p>
<p>Êtez-vous sûr ?</p> <p>
<hr/> <h4>Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</p>
</div> </div>
<div id="modal_create_order"> <div id="modal_create_order">
......
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