Commit 857ba3b1 by Administrator

Merge branch 'aide_a_la_commande' into 'dev_cooperatic'

Aide a la commande

See merge request !35
parents ae3dbb0f e7f80fe7
Pipeline #1101 failed with stage
in 36 seconds
......@@ -52,7 +52,7 @@ class CagetteInventory(models.Model):
file_data = json.load(json_file)
date_time = datetime.fromtimestamp(int(filename))
d = date_time.strftime("%m/%d/%Y, %H:%M")
d = date_time.strftime("%d/%m/%Y, %H:%M")
file_data['id'] = int(filename)
file_data['datetime_created'] = d
......@@ -113,7 +113,7 @@ class CagetteInventory(models.Model):
return file_data['inventory_status']
@staticmethod
def create_custom_inv_file(line_ids, line_type):
def create_custom_inv_file(line_ids, line_type, default_partners_id=[]):
res = {}
try:
......@@ -127,36 +127,54 @@ class CagetteInventory(models.Model):
api = OdooAPI()
ids = []
order = ['', '']
user = partner = ''
fields = ['create_uid', 'product_id', 'partner_id']
cond = [['id', 'in', line_ids]]
if (line_type == 'cpo'):
model = 'computed.purchase.order.line'
fields += ['computed_purchase_order_id']
user = ''
partners = []
if len(default_partners_id) > 0:
f = ['name']
c = [['id', 'in', default_partners_id]]
partners_name = api.search_read('res.partner', c, f)
for p in partners_name:
partners.append(p['name'])
if line_type == 'product_templates':
fields = ['id']
cond = [['product_tmpl_id', 'in', line_ids]]
model = 'product.product'
user="api"
else:
model = 'purchase.order.line'
fields += ['order_id']
fields = ['create_uid', 'product_id', 'partner_id']
cond = [['id', 'in', line_ids]]
if (line_type == 'cpo'):
model = 'computed.purchase.order.line'
fields += ['computed_purchase_order_id']
else:
model = 'purchase.order.line'
fields += ['order_id']
lines = api.search_read(model, cond, fields)
if len(lines) == len(line_ids):
for l in lines:
ids.append(l['product_id'][0])
user = l['create_uid'][1]
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
if line_type == 'product_templates':
ids.append(l['id'])
else:
order = l['order_id']
partner = l['partner_id'][1]
ids.append(l['product_id'][0])
user = l['create_uid'][1]
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
else:
order = l['order_id']
partners.append(l['partner_id'][1])
if (line_type == 'cpo'):
# partner_id isn't defined
f = ['partner_id']
c = [['id', '=', int(order[0])]]
cpo = api.search_read('computed.purchase.order', c, f)
if len(cpo) > 0:
partner = cpo[0]['partner_id'][1]
partners.append(cpo[0]['partner_id'][1])
file_data = {
'order': order[1],
'user': user,
'partner': partner,
'partners': partners,
'inventory_status': '',
'products': ids
}
......
......@@ -4,6 +4,13 @@ var shelfs_table = null,
function init_datatable() {
// For a smooth migration...
for (const i in lists) {
if (('partners' in lists[i]) === false) {
lists[i]['partners'] = [lists[i]['partner']]
}
}
return $('#lists').DataTable({
data: lists, // data passed at page loading
rowId: 'id',
......@@ -17,8 +24,15 @@ function init_datatable() {
}
},
{
data:"partner",
title:"Fournisseur"
data:"partners",
title:"Fournisseur(s)",
render: function (data) {
res = "";
for (const i in data) {
res += `${data[i]}<br/>`;
}
return res;
}
},
{
data:"order",
......
......@@ -43,7 +43,6 @@ def custom_list_inventory(request, id):
products = CagetteInventory.get_custom_list_products(id)
if 'error' in products:
print(products)
products['data'] = []
context = {'title': 'Inventaire',
......@@ -112,10 +111,25 @@ def do_custom_list_inventory(request):
def generate_inventory_list(request):
"""Responding to Odoo ajax call (no csrf)."""
res = {}
default_partners_id = []
try:
lines = json.loads(request.POST.get('lines'))
ltype = request.POST.get('type')
res = CagetteInventory.create_custom_inv_file(lines, ltype)
except Exception as e:
try:
# POST.get() returns None when request from django
data = json.loads(request.body.decode())
lines = data["lines"]
ltype = data["type"]
if "partners_id" in data:
default_partners_id = data["partners_id"]
except Exception as ee:
res['error'] = str(ee)
coop_looger.error("generate_inventory_list : %s", str(e))
return JsonResponse(res, status=500)
try:
res = CagetteInventory.create_custom_inv_file(lines, ltype, default_partners_id)
except Exception as e:
res['error'] = str(e)
coop_looger.error("generate_inventory_list : %s", str(e))
......
......@@ -268,3 +268,15 @@ class Orders(models.Model):
coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e))
return labels_data
class CagetteSuppliers(models.Model):
@staticmethod
def get_suppliers():
api = OdooAPI()
f = ['id', 'name', 'display_name']
c = [['supplier', '=', 1], ['parent_id', '=', False]]
res = api.search_read('res.partner', c, f)
return res
.page_body{
position: relative;
}
.page_content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/* - Common */
.pill {
border-radius: 30px;
min-width: 200px;
min-height: 35px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px 15px 8px 15px;
margin: 0 10px 5px 0;
}
.disabled {
background-color: #c9cbce;
}
.disabled:hover {
background-color: #a1a2a3;
}
/* - Order selection screen */
#new_order_area {
margin-bottom: 40px;
}
#new_order_form {
margin-top: 20px;
}
#existing_orders {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
width: 80%;
margin: 0 auto;
padding-top: 15px;
}
.order_last_update {
font-weight: bold;
}
.order_modified_msg {
font-size: 2rem;
color: #e62720;
}
/* - Main screen */
/* -- Top action button(s) */
#back_to_order_selection {
position: absolute;
}
#actions_buttons_area {
position: absolute;
top: 0;
right: 0;
}
/* -- Supplier form */
#supplier_form_container {
margin-top: 30px;
}
#supplier_input {
width: 500px;
margin-right: 10px;
border-radius: 5px;
}
/* -- Table */
#products_table_filter{
text-align: right !important;
}
#products_table_filter input{
height: 40px;
}
#table_header_select_all{
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
}
#table_header_select_all input{
margin-left: 5px;
}
.product_qty_input {
width: 100px;
}
.product_not_from_supplier {
background-color: #e7e9ed;
cursor: pointer;
}
.product_name, .supplier_name {
font-weight: bold;
}
.select_product_cb {
cursor: pointer;
}
/* -- Suppliers list */
#suppliers_container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin: 20px 0 20px 0;
}
.supplier_pill {
background-color: #e7e9ed;
}
.remove_supplier_icon {
color: red;
margin-left: 5px;
cursor: pointer;
}
\ No newline at end of file
var suppliers_list = [],
products_table = null,
products = [],
selected_suppliers = [],
selected_rows = [],
dbc = null,
sync = null,
order_doc = {
_id: null,
last_update : {
timestamp: null,
fingerprint: null,
},
products: [],
selected_suppliers: [],
selected_rows: []
},
fingerprint = null;
/* - UTILS */
/**
* Reset data that changes between screens
*/
function reset_data() {
products = [],
selected_suppliers = [],
selected_rows = [],
order_doc = {
_id: null,
last_update : {
timestamp: null,
fingerprint: null,
},
products: [],
selected_suppliers: []
};
}
/**
* Difference between two dates
* @param {Date} date1
* @param {Date} date2
* @returns difference object
*/
function dates_diff(date1, date2) {
var diff = {}
var tmp = date2 - date1;
tmp = Math.floor(tmp/1000);
diff.sec = tmp % 60;
tmp = Math.floor((tmp-diff.sec)/60);
diff.min = tmp % 60;
tmp = Math.floor((tmp-diff.min)/60);
diff.hours = tmp % 24;
tmp = Math.floor((tmp-diff.hours)/24);
diff.days = tmp;
return diff;
}
/* - SUPPLIERS */
/**
* Add a supplier to the selected suppliers list.
*
* @returns -1 if validation failed, 0 otherwise
*/
function add_supplier() {
const user_input = $("#supplier_input").val();
// Check if user input is a valid supplier
const supplier = suppliers_list.find(s => s.display_name === user_input);
if (supplier === undefined) {
alert("Le fournisseur renseigné n'est pas valide.\n"
+ "Veuillez sélectionner un fournisseur dans la liste déroulante.");
return -1;
}
const supplier_selected = selected_suppliers.find(s => s.display_name === user_input);
if (supplier_selected !== undefined) {
alert("Ce fournisseur est déjà sélectionné.");
return -1;
}
openModal();
selected_suppliers.push(supplier);
let url = "/orders/get_supplier_products";
url += "?sid=" + encodeURIComponent(supplier.id);
// Fetch supplier products
$.ajax({
type: 'GET',
url: url,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
save_supplier_products(supplier, data.res.products);
update_main_screen();
$("#supplier_input").val("");
update_order();
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des produits", ctx: 'get_supplier_products'};
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 récupération des produits, réessayer plus tard.');
}
});
return 0;
}
/**
* Remove a supplier from the selected list & its associated products
*
* @param {int} supplier_id
*/
function remove_supplier(supplier_id) {
// Remove from suppliers list
selected_suppliers = selected_suppliers.filter(supplier => supplier.id != supplier_id);
// Remove the supplier from the products suppliers list
for (const i in products) {
products[i].suppliers = products[i].suppliers.filter(supplier => supplier.id != supplier_id);
}
// Remove products only associated to this product
products = products.filter(product => product.suppliers.length > 0);
update_main_screen();
update_order();
}
/**
* Send to server the association product-supplier
*
* @param {object} product
* @param {object} supplier
* @param {node} cell product's row in datatable
*/
function save_supplier_product_association(product, supplier, cell) {
openModal();
const data = {
product_tmpl_id: product.id,
supplier_id: supplier.id
};
// Fetch supplier products
$.ajax({
type: "POST",
url: "/orders/associate_supplier_to_product",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
success: () => {
// Save relation locally
save_supplier_products(supplier, [product]);
// Update table
$(cell).removeClass("product_not_from_supplier");
const row = $(cell).closest("tr");
const new_row_data = prepare_datatable_data([product.id])[0];
products_table.row(row).data(new_row_data)
.draw();
closeModal();
},
error: function(data) {
let msg = "erreur serveur lors de la sauvegarde de l'association product/supplier".
msg += ` (product_tmpl_id: ${product.id}; supplier_id: ${supplier.id})`;
err = {msg: msg, ctx: 'save_supplier_product_association'};
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 l\'association. Veuillez ré-essayer plus tard.');
}
});
}
/**
* When products are fetched, save them and the relation with the supplier.
* If product already saved, add the supplier to its suppliers list.
* Else, add product with supplier.
*
* @param {object} supplier
* @param {array} new_products
*/
function save_supplier_products(supplier, new_products) {
for (np of new_products) {
let index = products.findIndex(p => p.id === np.id);
if (index === -1) {
np.suppliers = [{ ...supplier }];
products.push(np);
} else {
products[index].suppliers.push({ ...supplier });
}
}
}
/**
* Save the quantity set for a product/supplier
*
* @param {int} prod_id
* @param {int} supplier_id
* @param {float} val
*/
function save_product_supplier_qty(prod_id, supplier_id, val) {
for (const i in products) {
if (products[i].id == prod_id) {
for (const j in products[i].suppliers) {
if (products[i].suppliers[j].id == supplier_id) {
products[i].suppliers[j].qty = val;
break;
}
}
}
}
}
/**
* Look in the 'suppliers' property of a product
*
* @param {object} product
* @param {object} supplier
* @returns boolean
*/
function is_product_related_to_supplier(product, supplier) {
return product.suppliers.find(s => s.id === supplier.id) !== undefined;
}
/* - INVENTORY */
/**
* Create an inventory with the selected lines in the table
*/
function generate_inventory() {
if (products_table !== null) {
const selected_data = products_table.rows('.selected').data();
if (selected_data.length == 0) {
alert("Veuillez sélectionner les produits à inventorier en cochant les cases sur la gauche du tableau.");
} else {
data = {
lines: [],
partners_id: [],
type: 'product_templates'
};
for (var i = 0; i < selected_data.length; i++) {
const product = products.find(p => p.id == selected_data[i].id);
data.lines.push(product.id);
for (const supplier of product.suppliers) {
if (data.partners_id.indexOf(supplier.id) === -1) {
data.partners_id.push(supplier.id);
}
}
}
let modal_create_inventory = $('#templates #modal_create_inventory');
modal_create_inventory.find(".inventory_products_count").text(data.lines.length);
openModal(
modal_create_inventory.html(),
() => {
$.ajax({
type: "POST",
url: "/inventory/generate_inventory_list",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
success: () => {
unselect_all_rows();
// Give time for modal to fade
setTimeout(function() {
$.notify(
"Inventaire créé !",
{
globalPosition:"top left",
className: "success"
}
);
}, 500);
},
error: function(data) {
let msg = "erreur serveur lors de la création de l'inventaire".
err = {msg: msg, ctx: 'generate_inventory'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
alert("Erreur lors de la création de l'inventaire. Réessayez plus tard.");
}
});
},
'Valider'
);
}
}
}
/* - DISPLAY */
/**
* Create a string to represent a supplier column in product data
* @returns String
*/
function supplier_column_name(supplier) {
return `qty_supplier_${supplier.id}`;
}
/**
* Display the selected suppliers
*/
function display_suppliers() {
let supplier_container = $("#suppliers_container");
$("#suppliers_container").empty();
$(".remove_supplier_icon").off();
for (supplier of selected_suppliers) {
let template = $("#templates #supplier_pill_template");
template.find(".pill_supplier_name").text(supplier.display_name);
template.find(".remove_supplier_icon").attr('id', `remove_supplier_${supplier.id}`);
supplier_container.append(template.html());
}
$(".remove_supplier_icon").on("click", function() {
const el_id = $(this).attr('id')
.split('_');
const supplier_id = el_id[el_id.length-1];
let modal_remove_supplier = $('#templates #modal_remove_supplier');
modal_remove_supplier.find(".supplier_name").text(supplier.display_name);
openModal(
modal_remove_supplier.html(),
() => {
remove_supplier(supplier_id);
},
'Valider'
);
});
}
/**
* @param {array} product_ids if set, return formatted data for these products only
* @returns Array of formatted data for datatable data setup
*/
function prepare_datatable_data(product_ids = []) {
let data = [];
let products_to_format = [];
if (product_ids.length > 0) {
products_to_format = products.filter(p => product_ids.includes(p.id));
} else {
products_to_format = products;
}
for (product of products_to_format) {
let item = {
id: product.id,
name: product.name,
default_code: product.default_code,
incoming_qty: +parseFloat(product.incoming_qty).toFixed(3), // + sign removes unecessary zeroes at the end
qty_available: +parseFloat(product.qty_available).toFixed(3),
uom: product.uom_id[1]
};
// If product not related to supplier : false ; else null (qty to be set) or qty
for (product_supplier of product.suppliers) {
item[supplier_column_name(product_supplier)] = ("qty" in product_supplier) ? product_supplier.qty : null;
}
for (supplier of selected_suppliers) {
if (!is_product_related_to_supplier(product, supplier)) {
item[supplier_column_name(supplier)] = false;
}
}
data.push(item);
}
return data;
}
/**
* @returns Array of formatted data for datatable columns setup
*/
function prepare_datatable_columns() {
columns = [
{
data: "id",
title:` <div id="table_header_select_all">
Tout
<input type="checkbox" class="select_product_cb" id="select_all_products_cb" value="all">
</div>`,
className:"dt-body-center",
orderable: false,
render: function (data) {
return `<input type="checkbox" class="select_product_cb" id="select_product_${data}" value="${data}">`;
},
width: "4%"
},
{
data: "default_code",
title: "Référence Produit",
width: "8%",
render: function (data) {
return (data === false) ? "" : data;
}
},
{
data: "name",
title: "Produit"
},
{
data: "qty_available",
title: "Stock",
width: "4%"
},
{
data: "incoming_qty",
title: "Quantité entrante",
width: "4%"
}
];
for (const supplier of selected_suppliers) {
columns.push({
data: supplier_column_name(supplier),
title: supplier.display_name,
width: "10%",
className: `dt-body-center supplier_input_cell`,
render: (data, type, full) => {
const base_id = `product_${full.id}_supplier_${supplier.id}`;
if (data === false) {
return `<div id="${base_id}_cell_content" class="cell_content">X</div>`;
} else {
return `<div id="${base_id}_cell_content" class="cell_content">
<input type="number" class="product_qty_input" id="${base_id}_qty_input" value=${data}>
</div>`;
}
}
});
}
columns.push({
data: "uom",
title: "UDM",
width: "4%"
});
return columns;
}
/**
* Display the Datatable containing the products
*/
function display_products() {
if (products.length == 0) {
$('.main').hide();
return -1;
}
// Empty datatable if it already exists
if (products_table) {
products_table.clear().destroy();
$('#products_table').empty();
}
const data = prepare_datatable_data();
const columns = prepare_datatable_columns();
products_table = $('#products_table').DataTable({
data: data,
columns: columns,
order: [
[
5, // Order by default by first supplier
"asc"
]
],
stripeClasses: [], // Remove datatable cells coloring
orderClasses: false,
iDisplayLength: 100,
scrollX: true,
language: {url : '/static/js/datatables/french.json'},
createdRow: function(row) {
for (const cell_node of row.cells) {
const cell = $(cell_node);
if (cell.hasClass("supplier_input_cell")) {
if (cell.text() == "X") {
cell.addClass('product_not_from_supplier');
}
}
}
}
});
$('.main').show();
// Save value on inputs change
$('#products_table').on('input', 'tbody td .product_qty_input', function () {
let val = parseFloat($(this).val());
// If value is a number
if (!isNaN(val)) {
const id_split = $(this).attr('id')
.split('_');
const prod_id = id_split[1];
const supplier_id = id_split[3];
save_product_supplier_qty(prod_id, supplier_id, val);
update_order();
}
});
// Associate product to supplier on click on 'X' in the table
$('#products_table').on('click', 'tbody .product_not_from_supplier', function () {
// Get supplier & product id
const el_id = $(this).children()
.first()
.attr('id')
.split('_');
const product_id = el_id[1];
const supplier_id = el_id[3];
const product = products.find(p => p.id == product_id);
const supplier = selected_suppliers.find(s => s.id == supplier_id);
let modal_attach_product_to_supplier = $('#templates #modal_attach_product_to_supplier');
modal_attach_product_to_supplier.find(".product_name").text(product.name);
modal_attach_product_to_supplier.find(".supplier_name").text(supplier.display_name);
openModal(
modal_attach_product_to_supplier.html(),
() => {
save_supplier_product_association(product, supplier, this);
},
'Valider',
false
);
});
// Select row(s) on checkbox change
$(products_table.table().header()).on('click', 'th #select_all_products_cb', function () {
if (this.checked) {
selected_rows = [];
products_table.rows().every(function() {
const node = $(this.node());
node.addClass('selected');
node.find(".select_product_cb").first()
.prop("checked", true);
// Save selected rows in case the table is updated
selected_rows.push(this.data().id);
return 0;
});
} else {
unselect_all_rows();
}
});
$('#products_table').on('click', 'tbody td .select_product_cb', function () {
$(this).closest('tr')
.toggleClass('selected');
// Save / unsave selected row
p_id = products_table.row($(this).closest('tr')).data().id;
if (this.checked) {
selected_rows.push(p_id);
} else {
const i = selected_rows.findIndex(id => id == p_id);
selected_rows.splice(i, 1);
}
});
return 0;
}
/**
* Unselect all rows from datatable.
*/
function unselect_all_rows() {
products_table.rows().every(function() {
const node = $(this.node());
node.removeClass('selected');
node.find(".select_product_cb").first()
.prop("checked", false);
return 0;
});
selected_rows = [];
}
/**
* Update DOM display on main screen
*/
function update_main_screen() {
// Remove listener before recreating them
$('#products_table').off('input', 'tbody td .product_qty_input');
$('#products_table').off('click', 'tbody .product_not_from_supplier');
$('#products_table').off('click', 'thead th #select_all_products_cb');
$('#products_table').off('click', 'tbody td .select_product_cb');
$(".remove_supplier_icon").off();
$(".order_name_container").text(order_doc._id);
display_suppliers();
display_products();
// Re-select previously selected rows
if (selected_rows.length > 0) {
products_table.rows().every(function() {
if (selected_rows.includes(this.data().id)) {
const node = $(this.node());
node.addClass('selected');
node.find(".select_product_cb").first()
.prop("checked", true);
}
return 0;
});
}
}
/**
* Update DOM display on the order selection screen
*/
function update_order_selection_screen() {
// Remove listener before recreating them
$(".order_pill").off();
let existing_orders_container = $("#existing_orders");
existing_orders_container.empty();
dbc.allDocs({
include_docs: true
}).then(function (result) {
if (result.rows.length === 0) {
existing_orders_container.append(`<i>Aucune commande en cours...</i>`);
} else {
for (let row of result.rows) {
let template = $("#templates #order_pill_template");
template.find(".pill_order_name").text(row.id);
existing_orders_container.append(template.html());
}
$(".order_pill").on("click", order_pill_on_click);
}
})
.catch(function (err) {
alert('Erreur lors de la synchronisation des commandes. Vous pouvez créer une nouvelle commande.');
console.log(err);
});
}
/**
* Switch screen between order selection & main screens
* @param {String} direction target screen
*/
function switch_screen(direction = 'main_screen') {
let oldBox = null;
let newBox = null;
let outerWidth = null;
if (direction == 'main_screen') {
oldBox = $("#select_order_content");
newBox = $("#main_content");
outerWidth = oldBox.outerWidth(true);
} else {
oldBox = $("#main_content");
newBox = $("#select_order_content");
outerWidth = - oldBox.outerWidth(true);
}
// Display the new box and place it on the right of the screen
newBox.css({ "left": outerWidth + "px", "right": -outerWidth + "px", "display": "" });
// Make the old content slide to the left
oldBox.animate({ "left": -outerWidth + "px", "right": outerWidth + "px" }, 800, function() {
// Hide old content after animation
oldBox.css({ "left": "", "right": "", "display": "none" });
});
// Slide new box to regular place
newBox.animate({ "left": "", "right": "" }, 800);
}
/* - ORDER */
function goto_main_screen(doc) {
order_doc = doc;
products = order_doc.products;
selected_suppliers = order_doc.selected_suppliers;
update_order()
update_main_screen();
switch_screen();
}
function back() {
reset_data();
update_order_selection_screen();
switch_screen('order_selection');
}
/**
* Event fct: on click on an order button
*/
function order_pill_on_click() {
let order_name_container = $(this).find('.pill_order_name');
let doc_id = $(order_name_container).text();
dbc.get(doc_id).then((doc) => {
if (doc.last_update.fingerprint !== fingerprint) {
time_diff = dates_diff(new Date(doc.last_update.timestamp), new Date())
diff_str = ``
if (time_diff.days !== 0) {
diff_str += `${time_diff.days} jour(s), `
}
if (time_diff.hours !== 0) {
diff_str += `${time_diff.hours} heure(s), `
}
if (time_diff.min !== 0) {
diff_str += `${time_diff.min} min, `
}
diff_str += `${time_diff.sec}s`
let modal_order_access = $('#templates #modal_order_access');
modal_order_access.find(".order_last_update").text(diff_str);
openModal(
modal_order_access.html(),
() => {
goto_main_screen(doc)
},
'Valider'
);
} else {
goto_main_screen(doc)
}
})
.catch(function (err) {
alert('Erreur lors de la récupération de la commande. Si l\'erreur persiste, contactez un administrateur svp.');
console.log(err);
});
}
/**
* Create an order in couchdb if the name doesn't exist
*/
function create_order() {
const order_name = $("#new_order_name").val();
order_doc._id = order_name;
dbc.put(order_doc, function callback(err, result) {
if (!err) {
order_doc._rev = result.rev;
switch_screen();
} else {
if (err.status == 409) {
alert("Une commande porte déjà ce nom !");
}
console.log(err);
}
});
}
/**
* Update order data of an existing order in couchdb
*/
function update_order() {
order_doc.products = products;
order_doc.selected_suppliers = selected_suppliers;
// Save that current user last updated the order
order_doc.last_update = {
timestamp: Date.now(),
fingerprint: fingerprint,
};
dbc.put(order_doc, function callback(err, result) {
if (!err && result !== undefined) {
order_doc._rev = result.rev;
} else {
alert("Erreur lors de la sauvegarde de la commande... Si l'erreur persiste contactez un administrateur svp.");
console.log(err);
}
});
}
$(document).ready(function() {
fingerprint = new Fingerprint({canvas: true}).get();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
openModal();
// Init CouchDB
dbc = new PouchDB(couchdb_dbname);
sync = PouchDB.sync(couchdb_dbname, couchdb_server, {
live: true,
retry: true,
auto_compaction: false
});
sync.on('change', function (info) {
if (info.direction === "pull") {
for (const doc of info.change.docs) {
// If current order was modified somewhere else
if (order_doc._id === doc._id && order_doc._rev !== doc._rev) {
$.notify(
"Un autre navigateur est en train de modifier cette commande !",
{
globalPosition:"top right",
className: "error"
}
);
back();
break;
}
}
}
}).on('error', function (err) {
if (err.status === 409) {
alert("Une erreur de synchronisation s'est produite, la commande a sûrement été modifiée sur un autre navigateur. Vous allez être redirigé.e.");
back();
}
console.log('erreur sync');
console.log(err);
});
// Main screen listeners
$("#supplier_form").on("submit", function(e) {
e.preventDefault();
add_supplier();
});
$("#do_inventory").on("click", function() {
generate_inventory();
});
$('#back_to_order_selection').on('click', function() {
back();
});
// Order selection screen
update_order_selection_screen();
$("#new_order_form").on("submit", function(e) {
e.preventDefault();
create_order();
});
// Get suppliers
$.ajax({
type: 'GET',
url: "/orders/get_suppliers",
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
suppliers_list = data.res;
// Set up autocomplete on supplier input
$("#supplier_input").autocomplete({
source: suppliers_list.map(a => a.display_name)
});
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des fournisseurs", ctx: 'get_suppliers'};
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 récupération des fournisseurs, rechargez la page plus tard');
}
});
});
......@@ -8,5 +8,9 @@ urlpatterns = [
url(r'^export/([0-9]+)', views.export_one),
url(r'^export/([a-z]+)', views.export_regex),
url(r'^get_pdf_labels$', views.get_pdf_labels),
url(r'^print_product_labels$', views.print_product_labels)
url(r'^print_product_labels$', views.print_product_labels),
url(r'^helper$', views.helper),
url(r'^get_suppliers$', views.get_suppliers),
url(r'^get_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
]
from outils.common_imports import *
from outils.for_view_imports import *
from orders.models import Order, Orders
from products.models import CagetteProduct
from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts
from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
......@@ -13,6 +13,52 @@ def as_text(value): return str(value) if value is not None else ""
def index(request):
return HttpResponse('Orders')
def helper(request):
context = {
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders']
}
template = loader.get_template('orders/helper.html')
return HttpResponse(template.render(context, request))
def get_suppliers(request):
""" Get suppliers list """
res = {}
try:
res = CagetteSuppliers.get_suppliers()
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def get_supplier_products(request):
""" Get supplier products """
sid = request.GET.get('sid', '')
res = CagetteProducts.get_products_by_supplier(sid)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def associate_supplier_to_product(request):
""" This product is now supplied by this supplier """
res = {}
try:
data = json.loads(request.body.decode())
res = CagetteProduct.associate_supplier_to_product(data["product_tmpl_id"], data["supplier_id"])
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def export_one(request, oid):
msg = ''
try:
......
......@@ -99,6 +99,7 @@ STATICFILES_DIRS = (
"website/static",
"shop/static",
"shelfs/static",
"orders/static",
# "tests/static"
)
......
......@@ -19,7 +19,8 @@ COUCHDB = {
'member': 'coops',
'inventory': 'inventory',
'envelops': 'envelop',
'shop': 'shopping_carts'
'shop': 'shopping_carts',
'orders': 'orders_test'
}
}
......
......@@ -6,6 +6,7 @@ from outils.common import OdooAPI
import csv
import tempfile
import pymysql.cursors
import datetime
vcats = []
......@@ -126,6 +127,27 @@ class CagetteProduct(models.Model):
res = api.create('product.supplier.shortage', f)
return res
@staticmethod
def associate_supplier_to_product(product_tmpl_id, partner_id):
api = OdooAPI()
f = ["id", "standard_price", "purchase_ok"]
c = [['product_tmpl_id', '=', product_tmpl_id]]
res_products = api.search_read('product.product', c, f)
product = res_products[0]
f = {
'product_tmpl_id' : product_tmpl_id,
'product_id' : product["id"],
'name' : partner_id,
'product_purchase_ok': product["purchase_ok"],
'price': product["standard_price"], # By default, use product price
'base_price': product["standard_price"],
}
res = api.create('product.supplierinfo', f)
return res
class CagetteProducts(models.Model):
"""Initially used to make massive barcode update."""
......@@ -387,6 +409,43 @@ class CagetteProducts(models.Model):
bc_map[bc] = bc
return bc_map
@staticmethod
def get_products_by_supplier(supplier_id):
api = OdooAPI()
res = {}
# todo : try with no result
try:
today = datetime.date.today().strftime("%Y-%m-%d")
# Get products/supplier relation
f = ["product_tmpl_id", 'date_start', 'date_end']
c = [['name', '=', int(supplier_id)]]
psi = api.search_read('product.supplierinfo', c, f)
# Filter valid data
ptids = []
for p in psi:
if (p["product_tmpl_id"] is not False
and (p["date_start"] is False or p["date_end"] is not False and p["date_start"] <= today)
and (p["date_end"] is False or p["date_end"] is not False and p["date_end"] >= today)):
ptids.append(p["product_tmpl_id"][0])
# Get products templates
f = ["id", "state", "name", "default_code", "qty_available", "incoming_qty", "uom_id"]
c = [['id', 'in', ptids], ['purchase_ok', '=', 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"]
# Note: if product.product is needed, get "product_variant_ids" from product template
res["products"] = filtered_products_t
except Exception as e:
print(str(e))
res["error"] = str(e)
return res
class OFF(models.Model):
"""OpenFoodFact restricted DB queries."""
......
......@@ -10,10 +10,6 @@ def index(request):
context = {'title': 'Export de ventes'}
template = loader.get_template('sales/index.html')
# m = CagetteSales()
# sales = m.get_sales()
# print(sales)
return HttpResponse(template.render(context, request))
def get_sales(request):
......
......@@ -34,7 +34,7 @@
</div>
<script type="text/javascript">
lists = {{lists|safe}}
var lists = {{lists|safe}}
</script>
<script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/common.js" %}?v="></script>
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
<link rel="stylesheet" href="{% static 'css/oders_helper_style.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="select_order_content" class="page_content txtcenter">
<div id="new_order_area">
<h2>Créer une nouvelle commande</h2>
<form id="new_order_form">
<input type="text" id="new_order_name" placeholder="Nom de la commande...">
<button type="submit" class="btn btn--primary">Valider</button>
</form>
</div>
<div id="existing_orders_area">
<h2>Ou, continuer une commande existante</h2>
<div id="existing_orders"></div>
</div>
</div>
<div id="main_content" class="page_content" style="display:none;">
<div id="back_to_order_selection">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div id="actions_buttons_area">
<button type="button" class='btn--primary' id="do_inventory">Faire un inventaire</button>
</div>
<div class="header txtcenter">
<h1>Aide à la commande</h1>
<i>Commande : <span class="order_name_container"></span></i>
</div>
<div class="txtcenter" id="supplier_form_container">
<form action="javascript:;" id="supplier_form">
<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>
</form>
</div>
<div class="txtcenter" id="suppliers_container"></div>
<div class="main" style="display:none;">
<div class="table_area">
<table id="products_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
</div>
<div id="templates" style="display:none;">
<div id="supplier_pill_template">
<div class="pill supplier_pill">
<div class="supplier_name_container">
<span class="pill_supplier_name"></span>
<i class="fas fa-times remove_supplier_icon"></i>
</div>
</div>
</div>
<div id="order_pill_template">
<div class="pill order_pill btn btn--primary">
<span class="pill_order_name"></span>
</div>
</div>
<div id="modal_order_access">
<h3>Attention !</h3>
<br/>
<p class="order_modified_msg">
Un autre navigateur a modifié cette commande il y a <span class="order_last_update"></span>.
</p><br/>
<p>
Si quelqu'un d'autre que vous est à l'origine de la modification et que celle-ci est récente,
nous conseillons fortement de ne pas accéder à la commande afin d'éviter les conflits.
</p><br/>
<p>Voulez-vous quand même y accéder ?</p>
<hr/>
</div>
<div id="modal_remove_supplier">
<h3>Attention !</h3>
<p>
Vous vous apprêtez à supprimer le fournisseur <span class="supplier_name"></span> de la sélection.<br/>
Les produits associés uniquement à ce fournisseur seront supprimés du tableau.<br/>
Les données renseignées dans la colonne de ce fournisseur seront perdues.
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_attach_product_to_supplier">
<h3>Attention !</h3>
<p>
Vous vous apprêtez à associer le produit <span class="product_name"></span> au fournisseur <span class="supplier_name"></span>.<br/>
L'association sera sauvegardée dès que vous aurez cliqué sur "Valider".<br/>
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_create_inventory">
<p>
Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits.
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
</div>
</div>
<script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript">
var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
</script>
<script src="{% static "js/all_common.js" %}?v="></script>
<script type="text/javascript" src="{% static 'js/orders_helper.js' %}?v="></script>
{% endblock %}
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