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 @@
width: 90%;
}
/* product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
margin: auto;
}
.npa-options label {
display: block;
}
/* - Orders created screen */
.order_created_header {
......
......@@ -688,35 +688,32 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */
/**
* Update 'purchase_ok' of a product
*
* @param {int} p_id product id
* @param {Boolean} npa value to set purchase_ok to
*/
function set_product_npa(p_id, npa) {
openModal();
const data = {
product_tmpl_id: p_id,
purchase_ok: !npa
};
// Fetch supplier products
function save_products_npa_minimal_stock(product, inputs) {
let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name};
inputs.each(function (i,e) {
const input = $(e)
if (input.attr('type') == 'checkbox') {
if (input.prop('checked') == true) {
actions.npa.push(input.val())
}
} else if (input.attr('name') == "minimal_stock") {
actions.minimal_stock = input.val()
}
});
$.ajax({
type: "POST",
url: "/products/update_product_purchase_ok",
url: "/products/update_npa_and_minimal_stock",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
data: JSON.stringify(actions),
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
setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify(
"Produit passé en NPA !",
"Actions enregistrées !",
{
elementPosition:"bottom right",
className: "success",
......@@ -724,19 +721,21 @@ function set_product_npa(p_id, npa) {
}
);
}, 500);
// Remove NPA products
products.splice(index, 1);
update_main_screen();
update_cdb_order();
products[index].minimal_stock = actions.minimal_stock;
if (actions.npa.length > 0) {
// Remove NPA products
products.splice(index, 1);
update_main_screen();
update_cdb_order();
}
closeModal();
},
error: function(data) {
let msg = "erreur serveur lors de la sauvegarde du NPA".
msg += ` (product_tmpl_id: ${p_id})`;
let msg = "erreur serveur lors de la sauvegarde".
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') {
err.msg += ' : ' + data.responseJSON.error;
}
......@@ -747,8 +746,10 @@ function set_product_npa(p_id, npa) {
update_main_screen();
}
});
}
/* - INVENTORY */
/**
......@@ -1327,8 +1328,8 @@ function prepare_datatable_columns() {
{
data: "id",
title: `<div id="table_header_select_all" class="txtcenter">
<span class="select_all_text">Sélectionner</span>
<label for="select_all_products_cb">- Tout</label>
<!--<span class="select_all_text">Sélectionner</span>-->
<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">
</div>`,
className: "dt-body-center",
......@@ -1453,12 +1454,11 @@ function prepare_datatable_columns() {
});
columns.push({
data: "purchase_ok",
title: `NPA`,
title: ``,
className: "dt-body-center",
orderable: false,
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%"
});
......@@ -1625,7 +1625,6 @@ function display_products(params) {
// On arrow up pressed, focus next row input
let next_input = $(this).closest("tr").prev().find(".product_qty_input");
next_input;
next_input.focus();
// Scroll to a position where the target input is not hidden by the sticky suppliers container
......@@ -1652,6 +1651,31 @@ function display_products(params) {
// On enter pressed, focus previous row input
$(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
......@@ -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;
}
......
......@@ -7,6 +7,7 @@ import csv
import tempfile
import pymysql.cursors
import datetime
import re
vcats = []
......@@ -252,7 +253,39 @@ class CagetteProduct(models.Model):
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):
"""Initially used to make massive barcode update."""
......@@ -578,7 +611,8 @@ class CagetteProducts(models.Model):
"uom_id",
"purchase_ok",
"supplier_taxes_id",
"product_variant_ids"
"product_variant_ids",
"minimal_stock"
]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
products_t = api.search_read('product.template', c, f)
......
......@@ -11,6 +11,7 @@ urlpatterns = [
url(r'^update_product_stock$', views.update_product_stock),
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_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'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print),
url(r'^shelf_labels$', views.shelf_labels), # massive print
......
......@@ -3,6 +3,7 @@
from outils.common_imports import *
from outils.for_view_imports import *
from members.models import CagetteUser
from products.models import CagetteProduct
from products.models import CagetteProducts
from inventory.models import CagetteInventory
......@@ -105,25 +106,51 @@ def update_product_stock(request):
def update_product_purchase_ok(request):
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):
return JsonResponse(res, status=500)
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else:
return JsonResponse({"res": res})
return JsonResponse(res, status=403)
def update_product_internal_ref(request):
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):
return JsonResponse(res, status=500)
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
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):
"""Generate files to put in DAV directory to be retrieved by scales app."""
......
......@@ -275,14 +275,21 @@
<hr/>
</div>
<div id="modal_product_npa">
<h3>Attention !</h3>
<div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3>
<p>
Vous vous apprêtez à passer le produit <span class="product_name"></span> en <span class="product_npa"></span>.<br/>
Dès que vous aurez cliqué sur "Valider", le produit sera retiré du tableau et l'information sera enregistrée dans Odoo.
<h4>NPA</h4>
<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>Êtez-vous sûr ?</p>
<hr/>
<p>
<h4>Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</p>
</div>
<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