Commit e1eb5891 by Damien Moulard

archive a product from orders helper: set active to false, set stock to 0 if needed

parent d53e7003
Pipeline #1740 passed with stage
in 1 minute 36 seconds
...@@ -368,8 +368,8 @@ class CagetteInventory(models.Model): ...@@ -368,8 +368,8 @@ class CagetteInventory(models.Model):
return {'missed': missed, 'unchanged': unchanged, 'done': done} return {'missed': missed, 'unchanged': unchanged, 'done': done}
@staticmethod @staticmethod
def update_stock_with_shelf_inventory_data(inventory_data): def update_products_stock(inventory_data):
"""Updates Odoo stock after a shelf inventory""" """ Updates Odoo stock after a shelf inventory or another action"""
TWOPLACES = Decimal(10) ** -2 TWOPLACES = Decimal(10) ** -2
api = OdooAPI() api = OdooAPI()
......
...@@ -94,7 +94,7 @@ def do_custom_list_inventory(request): ...@@ -94,7 +94,7 @@ def do_custom_list_inventory(request):
full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data) full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data)
# Proceed with inventory # 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 # remove file
CagetteInventory.remove_custom_inv_file(inventory_data['id']) CagetteInventory.remove_custom_inv_file(inventory_data['id'])
......
/* Comments : */
/* - Screens */
/* -- Sections */
/* - Common */
.page_body{ .page_body{
position: relative; position: relative;
} }
...@@ -9,8 +15,6 @@ ...@@ -9,8 +15,6 @@
right: 0; right: 0;
} }
/* - Common */
.pill { .pill {
border-radius: 30px; border-radius: 30px;
min-width: 200px; min-width: 200px;
...@@ -198,7 +202,7 @@ ...@@ -198,7 +202,7 @@
} }
#coverage_form > div { #coverage_form > div {
display:inline-block; display:block;
float:left; float:left;
} }
...@@ -367,7 +371,7 @@ ...@@ -367,7 +371,7 @@
width: 90%; width: 90%;
} }
/* product actions modal*/ /* -- Product actions modal*/
.npa-options { .npa-options {
width: fit-content; width: fit-content;
text-align: left; text-align: left;
...@@ -376,6 +380,27 @@ ...@@ -376,6 +380,27 @@
.npa-options label { .npa-options label {
display: block; 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 */ /* - Orders created screen */
.order_created_header { .order_created_header {
......
...@@ -688,23 +688,33 @@ function _compute_total_values_by_supplier() { ...@@ -688,23 +688,33 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */ /* - PRODUCT */
function save_products_npa_minimal_stock(product, inputs) { function commit_actions_on_product(product, inputs) {
let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name}; let actions = {
npa: [],
to_archive: false,
minimal_stock: 0,
id: product.id,
name: product.name
};
inputs.each(function (i,e) { inputs.each(function (i,e) {
const input = $(e) const input = $(e)
if (input.attr('type') == 'checkbox') { if (input.attr('name') == 'npa-actions') {
if (input.prop('checked') == true) { if (input.prop('checked') == true) {
actions.npa.push(input.val()) actions.npa.push(input.val());
} }
} else if (input.attr('name') == "minimal_stock") { } 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(); openModal();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/products/update_npa_and_minimal_stock", url: "/products/commit_actions_on_product",
dataType: "json", dataType: "json",
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
...@@ -741,18 +751,32 @@ function save_products_npa_minimal_stock(product, inputs) { ...@@ -741,18 +751,32 @@ function save_products_npa_minimal_stock(product, inputs) {
let msg = "erreur serveur lors de la sauvegarde". let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${product.id})`; 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') { if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error; err.msg += ' : ' + data.responseJSON.error;
} }
report_JS_error(err, 'orders'); report_JS_error(err, 'orders');
closeModal(); try {
alert('Erreur lors de la sauvegarde de la donnée. Veuillez ré-essayer plus tard.'); if (data.responseJSON.code === "archiving_with_incoming_qty") {
update_main_screen(); 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();
});
} }
}); });
} }
...@@ -1661,20 +1685,25 @@ function display_products(params) { ...@@ -1661,20 +1685,25 @@ function display_products(params) {
.on('click', 'tbody td .product_actions', function(e){ .on('click', 'tbody td .product_actions', function(e){
// Save / unsave selected row // Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id; const p_id = products_table.row($(this).closest('tr')).data().id;
const product = products.find(p => p.id == p_id); const product = products.find(p => p.id == p_id);
let modal_product_actions = $('#templates #modal_product_actions'); let modal_product_actions = $('#templates #modal_product_actions');
modal_product_actions.find(".product_name").text(product.name); 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( openModal(
modal_product_actions.html(), modal_product_actions.html(),
() => { () => {
if (is_time_to('validate_product_actions')) { 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', 'Valider',
...@@ -2285,7 +2314,7 @@ $(document).ready(function() { ...@@ -2285,7 +2314,7 @@ $(document).ready(function() {
return 0; 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'] = { $.datepicker.regional['fr'] = {
monthNames: [ monthNames: [
......
...@@ -148,6 +148,10 @@ footer { position: fixed; ...@@ -148,6 +148,10 @@ footer { position: fixed;
width: 230px !important; width: 230px !important;
} }
.tooltip .tooltip-xl {
width: 320px !important;
}
.tooltip .tt_twolines { .tooltip .tt_twolines {
top: -15px !important; top: -15px !important;
} }
......
...@@ -254,13 +254,20 @@ class CagetteProduct(models.Model): ...@@ -254,13 +254,20 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod @staticmethod
def update_npa_and_minimal_stock(data): def commit_actions_on_product(data):
"""Update NPA (ne pas acheter) and minimal stock data""" """ Update:
- NPA (ne pas acheter)
- Product is active
- Minimal stock
"""
res = {} res = {}
try: try:
api = OdooAPI() api = OdooAPI()
# Minimal stock
f = {'minimal_stock': data['minimal_stock']} f = {'minimal_stock': data['minimal_stock']}
# NPA
if 'simple-npa' in data['npa']: if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0 f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']: if 'npa-in-name' in data['npa']:
...@@ -279,6 +286,11 @@ class CagetteProduct(models.Model): ...@@ -279,6 +286,11 @@ class CagetteProduct(models.Model):
f['name'] = re.sub(r'( \[FDS\])', '', current_name) f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0: if len(data['npa']) == 0:
f['purchase_ok'] = 1 f['purchase_ok'] = 1
# Active
f["active"] = not data['to_archive']
# TODO if to archive, set stock to 0
res["update"] = api.update('product.template', data['id'], f) res["update"] = api.update('product.template', data['id'], f)
except Exception as e: except Exception as e:
res["error"] = str(e) res["error"] = str(e)
......
...@@ -11,7 +11,7 @@ urlpatterns = [ ...@@ -11,7 +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'^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'^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
......
...@@ -100,7 +100,7 @@ def update_product_stock(request): ...@@ -100,7 +100,7 @@ def update_product_stock(request):
'products': [p] '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}) return JsonResponse({"res": res})
...@@ -134,13 +134,44 @@ def update_product_internal_ref(request): ...@@ -134,13 +134,44 @@ def update_product_internal_ref(request):
else: else:
return JsonResponse(res, status=403) return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request): def commit_actions_on_product(request):
res = {} res = {}
is_connected_user = CagetteUser.are_credentials_ok(request) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True: if is_connected_user is True:
try: try:
data = json.loads(request.body.decode()) 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: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error']) coop_logger.error("Update npa and minimal stock : %s", res['error'])
......
...@@ -173,7 +173,7 @@ def do_shelf_inventory(request): ...@@ -173,7 +173,7 @@ def do_shelf_inventory(request):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
# Proceed with inventory # 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'] full_inventory_data['inventory_id'] = res['inventory']['inv_id']
shelf_data['last_inventory_id'] = res['inventory']['inv_id'] shelf_data['last_inventory_id'] = res['inventory']['inv_id']
......
...@@ -46,7 +46,7 @@ def do_movement(request): ...@@ -46,7 +46,7 @@ def do_movement(request):
'products': products 'products': products
} }
res = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data) res = CagetteInventory.update_products_stock(inventory_data)
else: else:
res = CagetteStock.do_stock_movement(data) res = CagetteStock.do_stock_movement(data)
......
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
<input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %"> <input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %">
</div> </div>
<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> </div>
</form> </form>
</div> </div>
...@@ -277,19 +277,28 @@ ...@@ -277,19 +277,28 @@
<div id="modal_product_actions"> <div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3> Actions sur <h3><span class="product_name"></span></h3>
<p> <div class="modal_product_actions_section">
<h4>NPA</h4> <h4 class="modal_product_actions_title">NPA</h4>
<div class="npa-options"> <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="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="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> <label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label>
</div> </div>
</p> </div>
<p> <div class="modal_product_actions_section">
<h4>Stock minimum</h4> <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="" /> <input type="number" name="minimal_stock" value="" />
</p> </div>
</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