Commit 5a937781 by François C.

Merge branch '2470-alc-update-price-and-stock' into 'dev_cooperatic'

2470 alc update price and stock

See merge request !156
parents 1e561340 cb2d1c59
Pipeline #2119 passed with stage
in 1 minute 30 seconds
...@@ -368,10 +368,10 @@ class CagetteInventory(models.Model): ...@@ -368,10 +368,10 @@ class CagetteInventory(models.Model):
return {'missed': missed, 'unchanged': unchanged, 'done': done} return {'missed': missed, 'unchanged': unchanged, 'done': done}
@staticmethod @staticmethod
def update_products_stock(inventory_data): def update_products_stock(inventory_data, precision=2):
""" Updates Odoo stock after a shelf inventory or another action""" """ Updates Odoo stock after a shelf inventory or another action"""
TWOPLACES = Decimal(10) ** -2 TWOPLACES = Decimal(10) ** -precision
api = OdooAPI() api = OdooAPI()
missed = [] missed = []
unchanged = [] unchanged = []
......
...@@ -264,7 +264,8 @@ ...@@ -264,7 +264,8 @@
padding: .5rem .5rem; padding: .5rem .5rem;
} }
.supplier_package_qty { .supplier_package_qty,
.supplier_price {
font-style: italic; font-style: italic;
font-size: 1.3rem; font-size: 1.3rem;
} }
...@@ -381,14 +382,46 @@ ...@@ -381,14 +382,46 @@
display: block; display: block;
} }
.modal_product_actions_section { .product_actions_container {
display: flex;
flex-direction: column;
}
.product_actions_section {
width: 100%;
display: flex;
margin: 1em 0; margin: 1em 0;
} }
.modal_product_actions_section .tooltip { .product_actions_column {
width: 50%;
}
.product_actions_full_column {
width: 100%;
}
.product_actions_column .tooltip {
margin-left: 5px; margin-left: 5px;
} }
.product_prices_title {
margin-bottom: 0 !important;
}
.product_prices_area {
margin: 20px 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.product_price_action {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
.modal_product_actions_title { .modal_product_actions_title {
font-weight: bold; font-weight: bold;
font-size: 2.2rem; font-size: 2.2rem;
......
...@@ -30,12 +30,9 @@ var dbc = null, ...@@ -30,12 +30,9 @@ var dbc = null,
fingerprint = null; fingerprint = null;
var clicked_order_pill = null; var clicked_order_pill = null;
let userAgent = navigator.userAgent; let userAgent = navigator.userAgent;
var timerId = null;
var timerId;
/* - UTILS */ /* - UTILS */
/** /**
...@@ -275,8 +272,8 @@ function compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, ...@@ -275,8 +272,8 @@ function compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty,
purchase_package_qty_for_coverage *= coeff; purchase_package_qty_for_coverage *= coeff;
} }
} }
// return Round up to unit for all products
return Math.ceil(purchase_package_qty_for_coverage); return Math.ceil(purchase_package_qty_for_coverage); // return Round up to unit for all products
} }
function compute_and_affect_product_supplier_quantities(coeff, days) { function compute_and_affect_product_supplier_quantities(coeff, days) {
...@@ -285,22 +282,19 @@ function compute_and_affect_product_supplier_quantities(coeff, days) { ...@@ -285,22 +282,19 @@ function compute_and_affect_product_supplier_quantities(coeff, days) {
product product
] of Object.entries(products)) { ] of Object.entries(products)) {
if ('suppliersinfo' in product && product.suppliersinfo.length > 0) { if ('suppliersinfo' in product && product.suppliersinfo.length > 0) {
let purchase_qty_for_coverage = null;
// Durée couverture produit = (stock + qté entrante + qté commandée ) / conso quotidienne // Durée couverture produit = (stock + qté entrante + qté commandée ) / conso quotidienne
const stock = product.qty_available; const stock = product.qty_available;
const incoming_qty = product.incoming_qty; const incoming_qty = product.incoming_qty;
const daily_conso = product.daily_conso; const daily_conso = product.daily_conso;
purchase_package_qty_for_coverage = compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, daily_conso, days); let purchase_package_qty_for_coverage = compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, daily_conso, days);
// Set qty to purchase for first supplier only // Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage; products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
} }
} }
} }
/** /**
* Compute the qty to buy for each product, depending the coverage days. * Compute the qty to buy for each product, depending the coverage days.
* Set the computed qty for the first supplier only. * Set the computed qty for the first supplier only.
...@@ -669,13 +663,14 @@ function save_supplier_product_association(product, supplier, cell) { ...@@ -669,13 +663,14 @@ function save_supplier_product_association(product, supplier, cell) {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(data), data: JSON.stringify(data),
success: () => { success: (res_data) => {
// Save supplierinfo in product // Save supplierinfo in product
if (!('suppliersinfo' in product)) { if (!('suppliersinfo' in product)) {
product.suppliersinfo = []; product.suppliersinfo = [];
} }
product.suppliersinfo.push({ product.suppliersinfo.push({
id: res_data.res.psi_id,
supplier_id: supplier.id, supplier_id: supplier.id,
package_qty: package_qty, package_qty: package_qty,
product_code: false, product_code: false,
...@@ -859,8 +854,10 @@ function commit_actions_on_product(product, inputs) { ...@@ -859,8 +854,10 @@ function commit_actions_on_product(product, inputs) {
npa: [], npa: [],
to_archive: false, to_archive: false,
minimal_stock: 0, minimal_stock: 0,
qty_available: 0,
id: product.id, id: product.id,
name: product.name name: product.name,
suppliersinfo: []
}; };
inputs.each(function (i, e) { inputs.each(function (i, e) {
...@@ -876,6 +873,13 @@ function commit_actions_on_product(product, inputs) { ...@@ -876,6 +873,13 @@ function commit_actions_on_product(product, inputs) {
if (input.prop('checked') == true && product.incoming_qty === 0) { if (input.prop('checked') == true && product.incoming_qty === 0) {
actions.to_archive = true; actions.to_archive = true;
} }
} else if (input.attr('name') == "actual_stock") {
actions.qty_available = parseFloat(input.val());
} else if (input.attr('class') !== undefined && input.attr('class').includes("product_supplier_price")) {
actions.suppliersinfo.push({
supplierinfo_id: parseInt(input.attr('supplierinfo_id')),
price: parseFloat(input.val())
});
} }
}); });
...@@ -1424,12 +1428,23 @@ function display_suppliers() { ...@@ -1424,12 +1428,23 @@ function display_suppliers() {
}); });
} }
/**
* Compute data to display in products table
*
* Package qties & prices are related to suppliers,
* so 1 product can have multiple values for these.
* In case of different values, display value under input in supplier column
*
* @param {Object} product
* @returns Object of computed data to add to product object
*/
function _compute_product_data(product) { function _compute_product_data(product) {
let item = {}; let item = {};
/* Supplier related data */ /* Supplier related data */
let purchase_qty = 0; // Calculate product's total purchase qty let purchase_qty = 0; // Calculate product's total purchase qty
let p_package_qties = []; // Look for differences in package qties let p_package_qties = []; // Look for differences in package qties
let p_price = [];
for (let p_supplierinfo of product.suppliersinfo) { for (let p_supplierinfo of product.suppliersinfo) {
// Preset qty for input if product related to supplier: existing qty or null (null -> qty to be set, display an empty input) // Preset qty for input if product related to supplier: existing qty or null (null -> qty to be set, display an empty input)
...@@ -1444,6 +1459,7 @@ function _compute_product_data(product) { ...@@ -1444,6 +1459,7 @@ function _compute_product_data(product) {
// Store temporarily product package qties // Store temporarily product package qties
p_package_qties.push(p_supplierinfo.package_qty); p_package_qties.push(p_supplierinfo.package_qty);
p_price.push(p_supplierinfo.price);
} }
item.purchase_qty = purchase_qty; item.purchase_qty = purchase_qty;
...@@ -1456,13 +1472,19 @@ function _compute_product_data(product) { ...@@ -1456,13 +1472,19 @@ function _compute_product_data(product) {
} }
if (p_package_qties.length == 0 || !p_package_qties.every((val, i, arr) => val === arr[0])) { if (p_package_qties.length == 0 || !p_package_qties.every((val, i, arr) => val === arr[0])) {
// Don't display package qty if no supplierinf or if not all package qties are equals, // Don't display package qty if no supplierinfo or if not all package qties are equals
item.package_qty = 'X'; item.package_qty = 'X';
} else { } else {
// If all package qties are equals, display it // If all package qties are equals, display it
item.package_qty = p_package_qties[0]; item.package_qty = p_package_qties[0];
} }
if (p_price.length == 0 || !p_price.every((val, i, arr) => val === arr[0])) {
item.price = 'X';
} else {
item.price = p_price[0];
}
/* Coverage related data */ /* Coverage related data */
const coverage_days = (order_doc.coverage_days !== null) ? order_doc.coverage_days : 0; const coverage_days = (order_doc.coverage_days !== null) ? order_doc.coverage_days : 0;
let qty_not_covered = 0; let qty_not_covered = 0;
...@@ -1600,6 +1622,7 @@ function prepare_datatable_columns() { ...@@ -1600,6 +1622,7 @@ function prepare_datatable_columns() {
let content = `<div id="${base_id}_cell_content" class="custom_cell_content"> let content = `<div id="${base_id}_cell_content" class="custom_cell_content">
<input type="number" class="product_qty_input" id="${base_id}_qty_input" min="-1" value=${data}>`; <input type="number" class="product_qty_input" id="${base_id}_qty_input" min="-1" value=${data}>`;
// Add package qty & price data if they differ between suppliers
if (full.package_qty === 'X') { if (full.package_qty === 'X') {
let product_data = products.find(p => p.id == full.id); let product_data = products.find(p => p.id == full.id);
...@@ -1610,6 +1633,16 @@ function prepare_datatable_columns() { ...@@ -1610,6 +1633,16 @@ function prepare_datatable_columns() {
} }
} }
if (full.price === 'X') {
let product_data = products.find(p => p.id == full.id);
if (product_data !== undefined) {
let supplierinfo = product_data.suppliersinfo.find(psi => psi.supplier_id == supplier.id);
content += `<span class="supplier_price">Prix HT : ${supplierinfo.price} €</span>`;
}
}
content += `</div>`; content += `</div>`;
return content; return content;
...@@ -1633,19 +1666,30 @@ function prepare_datatable_columns() { ...@@ -1633,19 +1666,30 @@ function prepare_datatable_columns() {
}); });
columns.push({ columns.push({
data: "purchase_qty", data: "price",
title: "Qté Achat", title: "Prix HT",
className: "dt-body-center", className: "dt-body-center",
render: function (data) {
return (data === 'X') ? data : `${data} €`;
},
width: "4%" width: "4%"
}); });
columns.push({ columns.push({
data: "qty_not_covered", data: "purchase_qty",
title: "Besoin non couvert (qté)", title: "Qté Achat",
className: "dt-body-center", className: "dt-body-center",
width: "4%" width: "4%"
}); });
// Not in use for now
// columns.push({
// data: "qty_not_covered",
// title: "Besoin non couvert (qté)",
// className: "dt-body-center",
// width: "4%"
// });
columns.push({ columns.push({
data: "days_covered", data: "days_covered",
title: "Jours de couverture", title: "Jours de couverture",
...@@ -1657,7 +1701,7 @@ function prepare_datatable_columns() { ...@@ -1657,7 +1701,7 @@ function prepare_datatable_columns() {
title: ``, title: ``,
className: "dt-body-center", className: "dt-body-center",
orderable: false, orderable: false,
render: function (data) { render: function () {
return `<button type="button" class="btn--primary product_actions">Actions</button>`; return `<button type="button" class="btn--primary product_actions">Actions</button>`;
}, },
width: "4%" width: "4%"
...@@ -1804,7 +1848,7 @@ function display_products(params) { ...@@ -1804,7 +1848,7 @@ function display_products(params) {
.focus(); .focus();
} }
}) })
.on('click', 'tbody td .product_actions', function(e) { .on('click', 'tbody td .product_actions', function() {
// 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);
...@@ -1812,6 +1856,7 @@ function display_products(params) { ...@@ -1812,6 +1856,7 @@ function display_products(params) {
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(".actual_stock_input").val(product.qty_available);
const product_can_be_archived = product.incoming_qty === 0; const product_can_be_archived = product.incoming_qty === 0;
...@@ -1825,6 +1870,18 @@ function display_products(params) { ...@@ -1825,6 +1870,18 @@ function display_products(params) {
.addClass("checkbox_action_disabled"); .addClass("checkbox_action_disabled");
} }
let product_price_action_template = $('#templates #product_price_action_template');
modal_product_actions.find(".product_prices_area").empty();
for (let supplierinfo of product.suppliersinfo) {
let supplier = suppliers_list.find(s => s.id == supplierinfo.supplier_id);
product_price_action_template.find(".supplier_name").text(supplier.display_name);
product_price_action_template.find(".product_supplier_price").attr('supplierinfo_id', supplierinfo.id);
modal_product_actions.find(".product_prices_area").append(product_price_action_template.html());
}
openModal( openModal(
modal_product_actions.html(), modal_product_actions.html(),
() => { () => {
...@@ -1835,7 +1892,13 @@ function display_products(params) { ...@@ -1835,7 +1892,13 @@ function display_products(params) {
'Valider', 'Valider',
false false
); );
// Set inputs val after modal is displayed
modal.find('input[name="minimal_stock"]').val(product.minimal_stock); modal.find('input[name="minimal_stock"]').val(product.minimal_stock);
modal.find('input[name="actual_stock"]').val(product.qty_available);
for (let supplierinfo of product.suppliersinfo) {
modal.find(`input[supplierinfo_id="${supplierinfo.id}"]`).val(supplierinfo.price);
}
}); });
...@@ -2616,17 +2679,12 @@ $(document).ready(function() { ...@@ -2616,17 +2679,12 @@ $(document).ready(function() {
// Have to capture mousedown and mouseup events, instead of using only click event // Have to capture mousedown and mouseup events, instead of using only click event
// Indeed, capturing click only remove the ability to click to have focus on the input to type a number. // Indeed, capturing click only remove the ability to click to have focus on the input to type a number.
$(document).on("mousedown", '[type="number"]', function() { $(document).on("mousedown", '[type="number"]', function() {
const clicked = this; qties_values[$(this).attr('id')] = $(this).val();
qties_values[$(clicked).attr('id')] = $(clicked).val();
}); });
$(document).on("mouseup", '[type="number"]', function() { $(document).on("mouseup", '[type="number"]', function() {
const clicked = this;
try { try {
if ($(clicked).val() != qties_values[$(clicked).attr('id')]) { if ($(this).val() != qties_values[$(this).attr('id')]) {
process_new_product_qty(clicked); process_new_product_qty(this);
} }
} catch (err) { } catch (err) {
console.log(err); console.log(err);
......
...@@ -160,6 +160,7 @@ class CagetteProduct(models.Model): ...@@ -160,6 +160,7 @@ class CagetteProduct(models.Model):
try: try:
res["update"] = api.update('product.supplierinfo', psi_id, f) res["update"] = api.update('product.supplierinfo', psi_id, f)
res["psi_id"] = psi_id
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
else: else:
...@@ -185,6 +186,7 @@ class CagetteProduct(models.Model): ...@@ -185,6 +186,7 @@ class CagetteProduct(models.Model):
try: try:
res['create'] = api.create('product.supplierinfo', f) res['create'] = api.create('product.supplierinfo', f)
res['psi_id'] = res['create'] # consistency between update & create res
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
...@@ -259,13 +261,17 @@ class CagetteProduct(models.Model): ...@@ -259,13 +261,17 @@ class CagetteProduct(models.Model):
- NPA (ne pas acheter) - NPA (ne pas acheter)
- Product is active - Product is active
- Minimal stock - Minimal stock
- price /supplier
""" """
res = {} res = {}
try: try:
api = OdooAPI() api = OdooAPI()
# Minimal stock # Minimal & Actual stock, Active
f = {'minimal_stock': data['minimal_stock']} f = {
'minimal_stock': float(data['minimal_stock']),
'active': not data['to_archive']
}
# NPA # NPA
if 'simple-npa' in data['npa']: if 'simple-npa' in data['npa']:
...@@ -286,14 +292,20 @@ class CagetteProduct(models.Model): ...@@ -286,14 +292,20 @@ 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']
res["update"] = api.update('product.template', data['id'], f) res["update"] = api.update('product.template', int(data['id']), f)
# Update suppliers info
res["update_supplierinfo"] = []
for supplierinfo in data["suppliersinfo"]:
f= {'price': supplierinfo["price"]}
res_update_si = api.update('product.supplierinfo', int(supplierinfo['supplierinfo_id']), f)
res["update_supplierinfo"].append(res_update_si)
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 %s", str(e), str(data)) coop_logger.error("commit_actions_on_product : %s %s", str(e), str(data))
return res return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -593,7 +605,7 @@ class CagetteProducts(models.Model): ...@@ -593,7 +605,7 @@ class CagetteProducts(models.Model):
if supplier_ids is not None and len(supplier_ids) > 0: if supplier_ids is not None and len(supplier_ids) > 0:
# Get products/supplier relation # Get products/supplier relation
f = ["product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name', 'product_code'] f = ["id", "product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name', 'product_code']
c = [['name', 'in', [ int(x) for x in supplier_ids]]] c = [['name', 'in', [ int(x) for x in supplier_ids]]]
psi = api.search_read('product.supplierinfo', c, f) psi = api.search_read('product.supplierinfo', c, f)
...@@ -651,6 +663,7 @@ class CagetteProducts(models.Model): ...@@ -651,6 +663,7 @@ class CagetteProducts(models.Model):
for psi_item in valid_psi: for psi_item in valid_psi:
if psi_item["product_tmpl_id"] is not False and psi_item ["product_tmpl_id"][0] == fp["id"]: if psi_item["product_tmpl_id"] is not False and psi_item ["product_tmpl_id"][0] == fp["id"]:
filtered_products_t[i]['suppliersinfo'].append({ filtered_products_t[i]['suppliersinfo'].append({
'id': int(psi_item["id"]),
'supplier_id': int(psi_item["name"][0]), 'supplier_id': int(psi_item["name"][0]),
'package_qty': psi_item["package_qty"], 'package_qty': psi_item["package_qty"],
'price': psi_item["price"], 'price': psi_item["price"],
......
...@@ -149,29 +149,48 @@ def commit_actions_on_product(request): ...@@ -149,29 +149,48 @@ def commit_actions_on_product(request):
res = CagetteProduct.commit_actions_on_product(data) res = CagetteProduct.commit_actions_on_product(data)
# If stock > 0: do inventory to set stock to 0 do_stock_update = False
# If product to archive and stock > 0: do inventory to set stock to 0
if data["to_archive"] is True and product_data["qty_available"] != 0: if data["to_archive"] is True and product_data["qty_available"] != 0:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': 0
}
inventory_data = {
'name': 'Archivage - ' + product_data['name'],
'products': [p]
}
do_stock_update = True
# Else update actual stock if changed
elif data["qty_available"] != product_data["qty_available"]:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': data["qty_available"]
}
inventory_data = {
'name': 'MAJ stock depuis Aide à la Commande - ' + product_data['name'],
'products': [p]
}
do_stock_update = True
if do_stock_update is True:
try: try:
p = { res_inventory = CagetteInventory.update_products_stock(inventory_data, 3)
'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']: if res_inventory['errors'] or res_inventory['missed']:
res["code"] = "error_stock_update" res["code"] = "error_stock_update"
res["error"] = res_inventory['errors'] res["error"] = res_inventory['errors']
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
except Exception as e: except Exception as e:
res["code"] = "error_stock_update" res["code"] = "error_stock_update"
return JsonResponse(res, status=500) 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'])
......
...@@ -276,29 +276,53 @@ ...@@ -276,29 +276,53 @@
<hr/> <hr/>
</div> </div>
<div id="product_price_action_template">
<div class="product_price_action">
<span class="supplier_name"></span>
<input type="number" class="product_supplier_price" name="" value="" />
</div>
</div>
<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>
<div class="modal_product_actions_section"> <div class="product_actions_container">
<h4 class="modal_product_actions_title">NPA</h4> <div class="product_actions_section">
<div class="npa-options"> <div class="product_actions_column">
<label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label> <h4 class="modal_product_actions_title">NPA</h4>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label> <div class="npa-options">
<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="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>
</div>
<div class="product_actions_column">
<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> </div>
</div> <div class="product_actions_section">
<div class="modal_product_actions_section"> <div class="product_actions_column">
<h4 class="modal_product_actions_title">Archiver le produit</h4> <h4 class="modal_product_actions_title">Stock minimum</h4>
<label class="checkbox_action_disabled"><input type="checkbox" name="archive-action" value="archive" disabled /> Archiver </label> <input type="number" name="minimal_stock" value="" />
<div class="tooltip"> </div>
<i class='main fa fa-info-circle'></i> <div class="product_actions_column">
<span class="tooltiptext tooltip-xl tt_twolines"> <h4 class="modal_product_actions_title">Stock réel</h4>
Un produit ne peut pas être archivé si une quantité entrante est prévue. <input type="number" name="actual_stock" value="" />
</span> </div>
</div>
<div class="product_actions_section">
<div class="product_actions_full_column">
<h4 class="modal_product_actions_title product_prices_title">Prix</h4>
<i class="product_prices_title_label">(par fournisseur dans cette commande)</i>
<div class="product_prices_area"></div>
</div>
</div> </div>
</div>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</div> </div>
</div> </div>
......
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