Commit 9bc038d0 by François C.

Merge branch '1990-archive-product-from-alc' into 'dev_cooperatic'

1990 archive product from alc

See merge request !112
parents 6ca90bec a3b1e80f
Pipeline #1747 passed with stage
in 1 minute 35 seconds
......@@ -368,8 +368,8 @@ class CagetteInventory(models.Model):
return {'missed': missed, 'unchanged': unchanged, 'done': done}
@staticmethod
def update_stock_with_shelf_inventory_data(inventory_data):
"""Updates Odoo stock after a shelf inventory"""
def update_products_stock(inventory_data):
""" Updates Odoo stock after a shelf inventory or another action"""
TWOPLACES = Decimal(10) ** -2
api = OdooAPI()
......
......@@ -94,7 +94,7 @@ def do_custom_list_inventory(request):
full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data)
# Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
# remove file
CagetteInventory.remove_custom_inv_file(inventory_data['id'])
......
/* Comments : */
/* - Screens */
/* -- Sections */
/* - Common */
.page_body{
position: relative;
}
......@@ -9,8 +15,6 @@
right: 0;
}
/* - Common */
.pill {
border-radius: 30px;
min-width: 200px;
......@@ -198,7 +202,7 @@
}
#coverage_form > div {
display:inline-block;
display:block;
float:left;
}
......@@ -367,7 +371,7 @@
width: 90%;
}
/* product actions modal*/
/* -- Product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
......@@ -376,6 +380,27 @@
.npa-options label {
display: block;
}
.modal_product_actions_section {
margin: 1em 0;
}
.modal_product_actions_section .tooltip {
margin-left: 5px;
}
.modal_product_actions_title {
font-weight: bold;
font-size: 2.2rem;
margin-bottom: 10px;
}
.checkbox_action_disabled {
cursor: not-allowed;
opacity: .5;
}
/* - Orders created screen */
.order_created_header {
......
......@@ -266,8 +266,12 @@ function check_products_data() {
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
let loaded_products_ids = products.map(p => p.id);
// Going through products fetched from server
for (let product of data.res.products) {
const p_index = products.findIndex(p => p.id == product.id);
const p_id = product.id;
const p_index = products.findIndex(p => p.id == p_id);
if (p_index === -1) {
// Add product if it wasn't fetched before (made available since last access to order)
......@@ -288,9 +292,39 @@ function check_products_data() {
}
}
}
// Remove fetched product id from loaded products list
const loaded_p_index = loaded_products_ids.indexOf(p_id);
if (loaded_p_index > -1) {
loaded_products_ids.splice(loaded_p_index, 1);
}
}
$('.notifyjs-wrapper').trigger('notify-hide');
/**
* If loaded p_ids are remaining:
* these products were loaded but don't match the conditions to be fetched anymore.
* Remove them.
*/
if (loaded_products_ids.length > 0) {
for (pid of loaded_products_ids) {
const p_index = products.findIndex(p => p.id == pid);
const p_name = products[p_index].name;
products.splice(p_index, 1);
$.notify(
`Produit "${p_name}" retiré de la commande.\nIl a probablement été passé en archivé ou en NPA sur un autre poste.`,
{
globalPosition:"top left",
className: "info",
autoHideDelay: 12000,
clickToHide: false
}
);
}
}
resolve();
},
error: function(data) {
......@@ -693,23 +727,33 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */
function save_products_npa_minimal_stock(product, inputs) {
let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name};
function commit_actions_on_product(product, inputs) {
let actions = {
npa: [],
to_archive: false,
minimal_stock: 0,
id: product.id,
name: product.name
};
inputs.each(function (i,e) {
const input = $(e)
if (input.attr('type') == 'checkbox') {
if (input.attr('name') == 'npa-actions') {
if (input.prop('checked') == true) {
actions.npa.push(input.val())
actions.npa.push(input.val());
}
} else if (input.attr('name') == "minimal_stock") {
actions.minimal_stock = input.val()
actions.minimal_stock = input.val();
} else if (input.attr('name') == "archive-action") {
if (input.prop('checked') == true && product.incoming_qty === 0) {
actions.to_archive = true;
}
}
});
openModal();
$.ajax({
type: "POST",
url: "/products/update_npa_and_minimal_stock",
url: "/products/commit_actions_on_product",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
......@@ -718,8 +762,9 @@ function save_products_npa_minimal_stock(product, inputs) {
const index = products.findIndex(p => p.id == product.id);
products[index].minimal_stock = actions.minimal_stock;
if (actions.npa.length > 0) {
// Remove NPA products
if (actions.npa.length > 0 || actions.to_archive === true) {
// Remove NPA & archived products
products.splice(index, 1);
update_main_screen();
debounceFunction(update_cdb_order);
......@@ -748,18 +793,32 @@ function save_products_npa_minimal_stock(product, inputs) {
let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${product.id})`;
err = {msg: msg, ctx: 'save_products_npa_minimal_stock'};
err = {msg: msg, ctx: 'commit_actions_on_product'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
closeModal();
alert('Erreur lors de la sauvegarde de la donnée. Veuillez ré-essayer plus tard.');
try {
if (data.responseJSON.code === "archiving_with_incoming_qty") {
alert("Ce produit a des quantités entrantes, vous ne pouvez pas l'archiver.")
} else if (data.responseJSON.code === "error_stock_update") {
alert('Erreur lors de la mise à zéro du stock du produit archivé. Les actions ont bien été réalisées.');
} else {
alert('Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.');
}
} catch (error) {
alert('Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.');
}
check_products_data()
.then(() => {
update_cdb_order();
update_main_screen();
closeModal();
});
}
});
}
......@@ -1668,20 +1727,25 @@ function display_products(params) {
.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é');
const product_can_be_archived = product.incoming_qty === 0;
if (product_can_be_archived == true) {
modal_product_actions.find('input[name="archive-action"]').prop( "disabled", false );
modal_product_actions.find('input[name="archive-action"]').closest("label").removeClass( "checkbox_action_disabled" );
} else {
modal_product_actions.find('input[name="archive-action"]').prop( "disabled", true );
modal_product_actions.find('input[name="archive-action"]').closest("label").addClass( "checkbox_action_disabled" );
}
openModal(
modal_product_actions.html(),
() => {
if (is_time_to('validate_product_actions')) {
save_products_npa_minimal_stock(product, modal.find('input'));
commit_actions_on_product(product, modal.find('input'));
}
},
'Valider',
......@@ -2292,7 +2356,7 @@ $(document).ready(function() {
return 0;
});
$(document).on("click",".fa-info-circle", display_average_consumption_explanation)
$(document).on("click",".average_consumption_explanation_icon", display_average_consumption_explanation)
$.datepicker.regional['fr'] = {
monthNames: [
......
......@@ -148,6 +148,10 @@ footer { position: fixed;
width: 230px !important;
}
.tooltip .tooltip-xl {
width: 320px !important;
}
.tooltip .tt_twolines {
top: -15px !important;
}
......
......@@ -254,13 +254,20 @@ class CagetteProduct(models.Model):
return res
@staticmethod
def update_npa_and_minimal_stock(data):
"""Update NPA (ne pas acheter) and minimal stock data"""
def commit_actions_on_product(data):
""" Update:
- NPA (ne pas acheter)
- Product is active
- Minimal stock
"""
res = {}
try:
api = OdooAPI()
# Minimal stock
f = {'minimal_stock': data['minimal_stock']}
# NPA
if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']:
......@@ -279,6 +286,10 @@ class CagetteProduct(models.Model):
f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0:
f['purchase_ok'] = 1
# Active
f["active"] = not data['to_archive']
res["update"] = api.update('product.template', data['id'], f)
except Exception as e:
res["error"] = str(e)
......@@ -612,7 +623,7 @@ class CagetteProducts(models.Model):
"product_variant_ids",
"minimal_stock"
]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]]
products_t = api.search_read('product.template', c, f)
filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
......
......@@ -11,7 +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'^commit_actions_on_product$', views.commit_actions_on_product),
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
......
......@@ -100,7 +100,7 @@ def update_product_stock(request):
'products': [p]
}
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(inventory_data)
return JsonResponse({"res": res})
......@@ -134,13 +134,44 @@ def update_product_internal_ref(request):
else:
return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request):
def commit_actions_on_product(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)
product_data = CagetteProducts.get_products_for_order_helper(None, [data["id"]])["products"][0]
# Don't allow to archive product if incomin qty > 0
if data["to_archive"] is True and product_data["incoming_qty"] > 0:
res["code"] = "archiving_with_incoming_qty"
return JsonResponse(res, status=500)
res = CagetteProduct.commit_actions_on_product(data)
# If stock > 0: do inventory to set stock to 0
if data["to_archive"] is True and product_data["qty_available"] != 0:
try:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': -product_data["qty_available"]
}
inventory_data = {
'name': 'Archivage - ' + product_data['name'],
'products': [p]
}
res_inventory = CagetteInventory.update_products_stock(inventory_data)
if res_inventory['errors'] or res_inventory['missed']:
res["code"] = "error_stock_update"
res["error"] = res_inventory['errors']
return JsonResponse(res, status=500)
except Exception as e:
res["code"] = "error_stock_update"
return JsonResponse(res, status=500)
except Exception as e:
res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error'])
......
......@@ -173,7 +173,7 @@ def do_shelf_inventory(request):
return JsonResponse(res, status=500)
# Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
full_inventory_data['inventory_id'] = res['inventory']['inv_id']
shelf_data['last_inventory_id'] = res['inventory']['inv_id']
......
......@@ -46,7 +46,7 @@ def do_movement(request):
'products': products
}
res = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data)
res = CagetteInventory.update_products_stock(inventory_data)
else:
res = CagetteStock.do_stock_movement(data)
......
......@@ -98,7 +98,7 @@
<input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %">
</div>
<div>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg'></i>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg average_consumption_explanation_icon'></i>
</div>
</form>
</div>
......@@ -277,19 +277,28 @@
<div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3>
<p>
<h4>NPA</h4>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">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>
<h4>Stock minimum</h4>
</div>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">Archiver le produit</h4>
<label class="checkbox_action_disabled"><input type="checkbox" name="archive-action" value="archive" disabled /> Archiver </label>
<div class="tooltip">
<i class='main fa fa-info-circle'></i>
<span class="tooltiptext tooltip-xl tt_twolines">
Un produit ne peut pas être archivé si une quantité entrante est prévue.
</span>
</div>
</div>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</p>
</div>
</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