Commit e2b7efce by Damien Moulard

AAC: calculate coverage needs

parent 8fd36ac7
Pipeline #1116 failed with stage
in 1 minute 23 seconds
...@@ -81,12 +81,11 @@ ...@@ -81,12 +81,11 @@
#supplier_input { #supplier_input {
width: 350px; width: 350px;
border-radius: 5px; border-radius: 8px;
} }
#date_planned_input { #date_planned_input, #coverage_days_input {
margin-left: 40px; border-radius: 8px;
border-radius: 5px;
} }
/* -- Table */ /* -- Table */
......
var suppliers_list = [], var suppliers_list = [],
selected_suppliers = [],
products_list = [], products_list = [],
products_table = null,
products = [], products = [],
selected_suppliers = [], products_table = null,
selected_rows = [], selected_rows = [],
dbc = null, product_orders = [],
date_format = "dd/mm/yy",
new_product_supplier_association = {
package_qty: null,
price: null
};
var dbc = null,
sync = null, sync = null,
order_doc = { order_doc = {
_id: null, _id: null,
date_planned: null, date_planned: null,
coverage_days: null,
last_update: { last_update: {
timestamp: null, timestamp: null,
fingerprint: null fingerprint: null
...@@ -17,13 +25,7 @@ var suppliers_list = [], ...@@ -17,13 +25,7 @@ var suppliers_list = [],
selected_suppliers: [], selected_suppliers: [],
selected_rows: [] selected_rows: []
}, },
fingerprint = null, fingerprint = null;
date_format = "dd/mm/yy"
product_orders = [],
new_product_supplier_association = {
package_qty: null,
price: null
};
/* - UTILS */ /* - UTILS */
...@@ -39,6 +41,7 @@ function reset_data() { ...@@ -39,6 +41,7 @@ function reset_data() {
order_doc = { order_doc = {
_id: null, _id: null,
date_planned: null, date_planned: null,
coverage_days: null,
last_update : { last_update : {
timestamp: null, timestamp: null,
fingerprint: null fingerprint: null
...@@ -137,6 +140,36 @@ function add_product() { ...@@ -137,6 +140,36 @@ function add_product() {
return 0; return 0;
} }
/**
* Compute the qty to buy for each product, depending the coverage days.
* Set the computed qty for the first supplier only.
*/
function compute_products_coverage_qties() {
for (const [key, product] of Object.entries(products)) {
let purchase_qty_for_coverage = null;
// Durée couverture produit = (stock + qté entrante + qté commandée ) / conso quotidienne
const stock = product.qty_available;
const incoming_qty = product.incoming_qty;
const daily_conso = product.daily_conso;
purchase_qty_for_coverage = order_doc.coverage_days * daily_conso - stock - incoming_qty;
purchase_qty_for_coverage = (purchase_qty_for_coverage < 0) ? 0 : purchase_qty_for_coverage;
// Reduce to nb of packages to purchase
purchase_package_qty_for_coverage = purchase_qty_for_coverage / product.suppliersinfo[0].package_qty;
// Round according to uom
if (product.uom_id[0] == 1 || product.uom_id[0] == 20) {
purchase_package_qty_for_coverage = parseFloat(purchase_package_qty_for_coverage).toFixed(0);
} else {
purchase_package_qty_for_coverage = parseFloat(purchase_package_qty_for_coverage).toFixed(2);
}
// Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
}
}
/* - SUPPLIERS */ /* - SUPPLIERS */
...@@ -737,6 +770,8 @@ function goto_main_screen(doc) { ...@@ -737,6 +770,8 @@ function goto_main_screen(doc) {
products = order_doc.products; products = order_doc.products;
selected_suppliers = order_doc.selected_suppliers; selected_suppliers = order_doc.selected_suppliers;
// TODO update products moving data (stock, qté entrante, conso moy/jour)
update_cdb_order(); update_cdb_order();
update_main_screen(); update_main_screen();
switch_screen(); switch_screen();
...@@ -794,6 +829,58 @@ function display_suppliers() { ...@@ -794,6 +829,58 @@ function display_suppliers() {
}); });
} }
function _compute_product_data(product) {
let item = {};
/* Supplier related data */
let purchase_qty = 0; // Calculate product's total purchase qty
let p_package_qties = []; // Look for differences in package qties
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)
let supplier_qty = ("qty" in p_supplierinfo) ? p_supplierinfo.qty : null;
item[supplier_column_name(p_supplierinfo)] = supplier_qty;
// Update product's total qty to buy if qty set for this supplier
if (supplier_qty !== null) {
purchase_qty += +parseFloat(supplier_qty * p_supplierinfo.package_qty).toFixed(2);
}
// Store temporarily product package qties
p_package_qties.push(p_supplierinfo.package_qty);
}
item.purchase_qty = purchase_qty;
// If product not related to supplier, set qty for this supplier to false (false -> don't display any input)
for (supplier of selected_suppliers) {
if (!is_product_related_to_supplier(product, supplier)) {
item[supplier_column_name(supplier)] = false;
}
}
if (p_package_qties.every( (val, i, arr) => val === arr[0] )) {
// If all package qties are equals, display it
item.package_qty = p_package_qties[0];
} else {
// Else display an X
item.package_qty = 'X';
}
/* Coverage related data */
if (order_doc.coverage_days !== null) {
let unmet_needs = product.daily_conso * order_doc.coverage_days - product.qty_available - product.incoming_qty - purchase_qty;
unmet_needs = -Math.round(unmet_needs);
unmet_needs = (unmet_needs > 0) ? 0 : unmet_needs;
item.unmet_needs = unmet_needs;
} else {
item.unmet_needs = 'X';
}
return item
}
/** /**
* @param {array} product_ids if set, return formatted data for these products only * @param {array} product_ids if set, return formatted data for these products only
* @returns Array of formatted data for datatable data setup * @returns Array of formatted data for datatable data setup
...@@ -809,49 +896,22 @@ function prepare_datatable_data(product_ids = []) { ...@@ -809,49 +896,22 @@ function prepare_datatable_data(product_ids = []) {
} }
for (product of products_to_format) { for (product of products_to_format) {
let item = { const item = {
id: product.id, id: product.id,
name: product.name, name: product.name,
default_code: product.default_code, default_code: product.default_code,
incoming_qty: +parseFloat(product.incoming_qty).toFixed(3), // '+' removes unecessary zeroes at the end incoming_qty: +parseFloat(product.incoming_qty).toFixed(3), // '+' removes unecessary zeroes at the end
qty_available: +parseFloat(product.qty_available).toFixed(3), qty_available: +parseFloat(product.qty_available).toFixed(3),
daily_conso: product.daily_conso,
purchase_ok: product.purchase_ok, purchase_ok: product.purchase_ok,
uom: product.uom_id[1] uom: product.uom_id[1]
}; };
let qty_to_buy = 0; // Calculate product's total qty to buy const computed_data = _compute_product_data(product);
let p_package_qties = []; // Look for differences in package qties
for (product_supplierinfo of product.suppliersinfo) {
// Set qty for input if product related to supplier: qty or null (qty to be set)
let supplier_qty = ("qty" in product_supplierinfo) ? product_supplierinfo.qty : null;
item[supplier_column_name(product_supplierinfo)] = supplier_qty;
// Update product's total qty to buy if qty set for this supplier const full_item = { ...item, ...computed_data };
if (supplier_qty !== null) {
qty_to_buy += supplier_qty * product_supplierinfo.package_qty;
}
p_package_qties.push(product_supplierinfo.package_qty) data.push(full_item);
}
item.qty_to_buy = qty_to_buy;
// If product not related to supplier: false;
for (supplier of selected_suppliers) {
if (!is_product_related_to_supplier(product, supplier)) {
item[supplier_column_name(supplier)] = false;
}
}
if (p_package_qties.every( (val, i, arr) => val === arr[0] )) {
// If all package qties are all equals, ok
item.package_qty = p_package_qties[0];
} else {
// Else display an X
item.package_qty = 'X'
}
data.push(item);
} }
return data; return data;
...@@ -909,6 +969,12 @@ function prepare_datatable_columns() { ...@@ -909,6 +969,12 @@ function prepare_datatable_columns() {
title: "Quantité entrante", title: "Quantité entrante",
className: "dt-body-center", className: "dt-body-center",
width: "4%" width: "4%"
},
{
data: "daily_conso",
title: "Conso moy /jour",
className: "dt-body-center",
width: "6%"
} }
]; ];
...@@ -965,12 +1031,19 @@ function prepare_datatable_columns() { ...@@ -965,12 +1031,19 @@ function prepare_datatable_columns() {
}); });
columns.push({ columns.push({
data: "qty_to_buy", data: "purchase_qty",
title: "Qté Achat", title: "Qté Achat",
className: "dt-body-center", className: "dt-body-center",
width: "4%" width: "4%"
}); });
columns.push({
data: "unmet_needs",
title: "Besoin non couvert",
className: "dt-body-center",
width: "4%"
});
return columns; return columns;
} }
...@@ -1031,10 +1104,11 @@ function display_products(params) { ...@@ -1031,10 +1104,11 @@ function display_products(params) {
// On inputs change // On inputs change
$('#products_table').on('change', 'tbody td .product_qty_input', function () { $('#products_table').on('change', 'tbody td .product_qty_input', function () {
let val = parseFloat($(this).val()); let val = ($(this).val() == '') ? 0 : $(this).val();
val = parseFloat(val);
// If value is a number // If value is a number
if (!isNaN(val) && val >= 0) { if (!isNaN(val)) {
const id_split = $(this).attr('id') const id_split = $(this).attr('id')
.split('_'); .split('_');
const prod_id = id_split[1]; const prod_id = id_split[1];
...@@ -1229,6 +1303,12 @@ function update_main_screen(params) { ...@@ -1229,6 +1303,12 @@ function update_main_screen(params) {
} else { } else {
$("#date_planned_input").val(''); $("#date_planned_input").val('');
} }
if (order_doc.coverage_days !== null) {
$("#coverage_days_input").val(order_doc.coverage_days);
} else {
$("#coverage_days_input").val('');
}
} }
/** /**
...@@ -1354,6 +1434,22 @@ $(document).ready(function() { ...@@ -1354,6 +1434,22 @@ $(document).ready(function() {
}); });
// Main screen // Main screen
$("#coverage_form").on("submit", function(e) {
e.preventDefault();
let val = $("#coverage_days_input").val();
val = parseInt(val);
if (!isNaN(val)) {
order_doc.coverage_days = val;
compute_products_coverage_qties();
update_cdb_order();
update_main_screen();
} else {
$("#coverage_days_input").val(order_doc.coverage_days);
alert(`Valeur non valide pour le nombre de jours de couverture !`)
}
});
$("#supplier_form").on("submit", function(e) { $("#supplier_form").on("submit", function(e) {
e.preventDefault(); e.preventDefault();
add_supplier(); add_supplier();
......
...@@ -505,8 +505,9 @@ class CagetteProducts(models.Model): ...@@ -505,8 +505,9 @@ class CagetteProducts(models.Model):
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)
filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"] filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
# sales = CagetteProducts.get_template_products_sales_average({'ids': ptids, 'from': '2020-06-10', 'to': '2020-08-10'})
sales = CagetteProducts.get_template_products_sales_average({'ids': ptids}) sales = CagetteProducts.get_template_products_sales_average({'ids': ptids, 'from': '2019-06-10', 'to': '2020-08-10'})
# sales = CagetteProducts.get_template_products_sales_average({'ids': ptids})
if 'list' in sales and len(sales['list']) > 0: if 'list' in sales and len(sales['list']) > 0:
sales = sales['list'] sales = sales['list']
else: else:
...@@ -520,9 +521,13 @@ class CagetteProducts(models.Model): ...@@ -520,9 +521,13 @@ class CagetteProducts(models.Model):
'package_qty': psi_item["package_qty"], 'package_qty': psi_item["package_qty"],
'price': psi_item["price"] 'price': psi_item["price"]
}] }]
for s in sales: for s in sales:
if s["id"] == fp["id"]: if s["id"] == fp["id"]:
filtered_products_t[i]['per_day_qty'] = s["average_qty"] filtered_products_t[i]['daily_conso'] = s["average_qty"]
if not 'daily_conso' in filtered_products_t[i]:
filtered_products_t[i]['daily_conso'] = 0
res["products"] = filtered_products_t res["products"] = filtered_products_t
except Exception as e: except Exception as e:
......
...@@ -45,6 +45,10 @@ ...@@ -45,6 +45,10 @@
</div> </div>
<div class="txtcenter" id="order_data_container"> <div class="txtcenter" id="order_data_container">
<form action="javascript:;" id="coverage_form">
<input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1">
<button type="submit" class='btn--primary'>Calculer les besoins</button>
</form>
<form action="javascript:;" id="supplier_form"> <form action="javascript:;" id="supplier_form">
<input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom"> <input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom">
<button type="submit" class='btn--primary'>Ajouter le fournisseur</button> <button type="submit" class='btn--primary'>Ajouter le fournisseur</button>
......
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