Commit 973b1ecb by Félicie

Merge branch 'dev_cooperatic' of…

Merge branch 'dev_cooperatic' of gl.cooperatic.fr:cooperatic-foodcoops/third-party into 2207-type-of-members
parents 3c23bcc9 d378cd28
Pipeline #1757 failed with stage
in 1 minute 36 seconds
#cash, #ch {
margin-top: 15px;
}
.envelop_section {
margin-bottom: 10px;
}
......@@ -16,6 +20,70 @@
margin-top: 30px;
}
.update_envelop_button, .delete_envelop_button {
margin: 0 0 10px 15px;
}
.envelop_content_list {
margin: 20px 0 15px 0;
}
/* Update envelop modal */
.envelop_lines {
margin: 20px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.update_envelop_line {
margin: 5px 0;
width: 60%;
display: flex;
position: relative;
}
.deleted_line_through {
border-bottom: 1px solid #d9534f;
position: absolute;
content: "";
width: 95%;
height: 50%;
display: none;
}
.update_envelop_line div {
flex: 50% 1 0;
}
.line_partner_name_container {
display: flex;
justify-content: flex-start;
align-items: center;
}
.line_partner_name {
text-align: left;
padding: 0 5px;
}
.line_partner_input_container {
display: flex;
align-items: center;
}
.delete_envelop_line_icon {
margin-left: 10px;
color: #d9534f;
cursor: pointer;
}
.envelop_comments {
width: 60%;
margin-bottom: 20px;
}
/* Accordion style */
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
......@@ -23,7 +91,6 @@
color: #212529;
cursor: pointer;
padding: 18px;
/* width: 80%; */
text-align: left;
border: none;
outline: none;
......@@ -32,7 +99,6 @@
.archive_button {
padding: 18px;
/* width: 20%; */
}
hr {
......
var cash_envelops = [];
var ch_envelops = [];
var envelop_to_update = null;
function reset() {
$('#cash_envelops').empty();
......@@ -12,7 +13,8 @@ function toggle_error_alert() {
$('#envelop_cashing_error').toggle(250);
}
function toggle_success_alert() {
function toggle_success_alert(message) {
$('#envelop_cashing_success').find(".success_alert_content").text(message);
$('#envelop_cashing_success').toggle(250);
}
......@@ -20,7 +22,48 @@ function toggle_deleted_alert() {
$('#envelop_deletion_success').toggle(250);
}
// Set an envelop content on the document
/**
* Get an envelop from the cash or cheque lists dependings on the params
* @param {String} type
* @param {String} index
* @returns
*/
function get_envelop_from_type_index(type, index) {
if (type === "cash") {
return cash_envelops[index];
} else {
return ch_envelops[index];
}
}
/**
* Define a name for an envelop depending on its type, with or with its type
* @param {Object} envelop
* @param {String} name_type short | long
* @returns
*/
function get_envelop_name(envelop, name_type = 'short') {
let envelop_name = "";
if (envelop.type === "cash") {
let split_id = envelop._id.split('_');
let envelop_date = split_id[3].padStart(2, '0') + "/" + split_id[2].padStart(2, '0') + "/" + split_id[1];
envelop_name = `Enveloppe${(name_type === "short") ? "" : " de liquide"} du ${envelop_date}`;
} else if (envelop.type == "ch") {
envelop_name = `Enveloppe${(name_type === "short") ? "" : " de chèques"} #${envelop.display_id}`;
}
return envelop_name;
}
/**
* Set the envelops contents on the document (could use a little cleanup someday: don't generate html in js, etc...)
* @param {Object} envelop
* @param {String} envelop_name
* @param {Int} envelop_content_id
* @param {Int} envelop_index
*/
function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_index) {
var envelops_section = $('#' + envelop.type + '_envelops');
......@@ -37,14 +80,14 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
// Allow checking for all cash and first check envelops
if (envelop.type == 'cash' || envelop.type == 'ch' && envelop_index == 0) {
new_html += '<button class="accordion w80">' + envelop_name + ' - <i>' + total_amount + '€</i></button>'
+ '<button class="btn--success archive_button item-fluid" onClick="openModal(\'<h3>Êtes-vous sûr ?</h3>\', function() {archive_envelop(\'' + envelop.type + '\', ' + envelop_index + ');}, \'Encaisser\')">Encaisser</button>';
+ '<button class="btn--success archive_button item-fluid" onClick="openModal(\'<h3>Êtes-vous sûr ?</h3>\', function() {archive_envelop(\'' + envelop.type + '\', ' + envelop_index + ');}, \'Encaisser\', false)">Encaisser</button>';
} else {
new_html += '<button class="accordion w100">' + envelop_name + ' - <i>' + total_amount + '€</i></button>';
}
new_html += '</div>'
+ '<div class="panel panel_' + envelop_content_id + '"><ol id="' + envelop_content_id + '"></ol></div>'
+ '</div>';
+ '<div class="panel panel_' + envelop_content_id + '"><ol class="envelop_content_list" id="' + envelop_content_id + '"></ol></div>'
+ '</div>';
$(new_html).appendTo(envelops_section);
......@@ -64,10 +107,44 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
}
let envelop_panel = $(`.panel_${envelop_content_id}`);
envelop_panel.append('<button class="btn--danger delete_envelop_button item-fluid" onClick="openModal(\'<h3>Supprimer enveloppe ?</h3>\', function() {delete_envelop(\'' + envelop.type + '\', ' + envelop_index + ');}, \'Supprimer\')">Supprimer l\'enveloppe</button>');
envelop_panel.append(`<button class="btn--danger delete_envelop_button item-fluid" id="update_envelop_${envelop.type}_${envelop_index}">Supprimer l'enveloppe</button>`);
envelop_panel.append(`<button class="btn--primary update_envelop_button item-fluid" id="update_envelop_${envelop.type}_${envelop_index}">Modifier</button>`);
$(".update_envelop_button").off("click");
$(".update_envelop_button").on("click", function() {
let el_id = $(this).attr("id").split("_");
envelop_to_update = {
type: el_id[2],
index: el_id[3],
lines_to_delete: []
}
set_update_envelop_modal();
});
$(".delete_envelop_button").off("click");
$(".delete_envelop_button").on("click", function() {
let el_id = $(this).attr("id").split("_");
let type = el_id[2];
let index = el_id[3];
let envelop = get_envelop_from_type_index(type, index);
openModal(
"<h3>Supprimer l'enveloppe ?</h3>",
function() {
delete_envelop(envelop);
},
'Supprimer'
);
});
}
// Set the envelops data according to their type
/**
* Given the raw list of envelop documents, generate the cash and cheque lists
* @param {Array} envelops
*/
function set_envelops(envelops) {
var cash_index = 0;
var ch_index = 0;
......@@ -79,9 +156,7 @@ function set_envelops(envelops) {
if (envelop.type == "cash") {
cash_envelops.push(envelop);
let split_id = envelop._id.split('_');
let envelop_date = split_id[3] + "/" + split_id[2] + "/" + split_id[1];
let envelop_name = 'Enveloppe du ' + envelop_date;
let envelop_name = get_envelop_name(envelop);
let envelop_content_id = 'content_cash_list_' + cash_index;
set_envelop_dom(envelop, envelop_name, envelop_content_id, cash_index);
......@@ -90,7 +165,7 @@ function set_envelops(envelops) {
} else if (envelop.type == "ch") {
ch_envelops.push(envelop);
let envelop_name = 'Enveloppe #' + envelop.display_id;
let envelop_name = get_envelop_name(envelop);
let envelop_content_id = 'content_ch_list_' + ch_index;
set_envelop_dom(envelop, envelop_name, envelop_content_id, ch_index);
......@@ -125,18 +200,112 @@ function set_envelops(envelops) {
}
}
function delete_envelop(type, index) {
if (is_time_to('delete_envelop', 1000)) {
openModal();
/**
* Generate content & set listeners for the modal to update an envelop
*/
function set_update_envelop_modal() {
let envelop = get_envelop_from_type_index(envelop_to_update.type, envelop_to_update.index);
let envelop_name = get_envelop_name(envelop, 'long');
let modal_update_envelop = $('#templates #modal_update_envelop');
modal_update_envelop.find(".envelop_name").text(envelop_name);
modal_update_envelop.find(".envelop_lines").empty();
let update_line_template = $('#templates #update_envelop_line_template');
let cpt = 1;
for (let partner_id in envelop.envelop_content) {
let line = envelop.envelop_content[partner_id];
update_line_template.find(".update_envelop_line").attr('id', `update_line_${partner_id}`);
update_line_template.find(".line_number").html(`${cpt}.&nbsp;`);
update_line_template.find(".line_partner_name").text(line.partner_name);
modal_update_envelop.find(".envelop_lines").append(update_line_template.html());
cpt += 1;
}
openModal(
modal_update_envelop.html(),
() => {
update_envelop();
},
'Mettre à jour',
true,
true,
() => {
envelop_to_update = null;
}
);
// Elements needs to be on the document so value & listeners can be set
for (let partner_id in envelop.envelop_content) {
let line = envelop.envelop_content[partner_id];
$(`#update_line_${partner_id}`).find('.line_partner_amount').val(line.amount);
}
modal.find('.envelop_comments').val((envelop.comments !== undefined) ? envelop.comments : '');
$(".delete_envelop_line_icon").off("click");
$(".delete_envelop_line_icon").on("click", function() {
let line_id = $(this).closest(".update_envelop_line").attr("id").split("_");
let partner_id = line_id[line_id.length-1];
envelop_to_update.lines_to_delete.push(partner_id);
$(this).hide();
$(this).closest(".update_envelop_line").find(".deleted_line_through").show();
})
}
var envelop = null;
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
/**
* Update an envelop in couchdb
*/
function update_envelop() {
if (is_time_to('update_envelop', 1000)) {
let envelop = get_envelop_from_type_index(envelop_to_update.type, envelop_to_update.index);
// Update lines amounts
let amount_inputs = modal.find('.line_partner_amount');
amount_inputs.each(function (i,e) {
let line_id = $(e).closest(".update_envelop_line").attr("id").split("_");
let partner_id = line_id[line_id.length-1];
envelop.envelop_content[partner_id].amount = parseInt($(e).val());
});
// Delete lines
for (let partner_id of envelop_to_update.lines_to_delete) {
delete(envelop.envelop_content[partner_id])
}
// Envelop comments
envelop.comments = modal.find('.envelop_comments').val();
dbc.put(envelop, function callback(err, result) {
envelop_to_update = null;
if (!err && result !== undefined) {
get_envelops();
toggle_success_alert("Enveloppe modifiée !");
} else {
alert("Erreur lors de la mise à jour de l'enveloppe. Si l'erreur persiste contactez un administrateur svp.");
console.log(err);
}
});
}
}
/**
* Delete an envelop from couchdb.
* @param {Object} envelop
*/
function delete_envelop(envelop) {
if (is_time_to('delete_envelop', 1000)) {
envelop._deleted = true;
dbc.put(envelop, function callback(err, result) {
if (!err && result !== undefined) {
toggle_deleted_alert();
......@@ -150,6 +319,11 @@ function delete_envelop(type, index) {
}
}
/**
* Send the request to save an envelop payments in Odoo. The envelop will be deleted from couchdb.
* @param {String} type
* @param {String} index
*/
function archive_envelop(type, index) {
if (is_time_to('archive_envelop', 1000)) {
$('#envelop_cashing_error').hide();
......@@ -157,11 +331,7 @@ function archive_envelop(type, index) {
// Loading on
openModal();
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
let envelop = get_envelop_from_type_index(type, index);
// Proceed to envelop cashing
$.ajax({
......@@ -204,7 +374,7 @@ function archive_envelop(type, index) {
}
if (display_success_alert) {
toggle_success_alert();
toggle_success_alert("Enveloppe encaissée !");
}
},
error: function() {
......@@ -215,7 +385,9 @@ function archive_envelop(type, index) {
}
}
// Get all the envelops from couch db
/**
* Get all the envelops from couchdb
*/
function get_envelops() {
dbc.allDocs({
include_docs: true,
......@@ -223,26 +395,26 @@ function get_envelops() {
}).then(function (result) {
set_envelops(result.rows);
})
.catch(function (err) {
alert('Erreur lors de la récupération des enveloppes.');
console.log(err);
});
.catch(function (err) {
alert('Erreur lors de la récupération des enveloppes.');
console.log(err);
});
}
// Hande change in couc db
sync.on('change', function (info) {
// handle change
if (info.direction == 'pull') {
get_envelops();
}
}).on('error', function (err) {
// handle error
console.log('erreur sync');
console.log(err);
});
$(document).ready(function() {
if (typeof must_identify == "undefined" || coop_is_connected()) {
get_envelops();
// Hande change in couc db
sync.on('change', function (info) {
// handle change
if (info.direction == 'pull') {
get_envelops();
}
}).on('error', function (err) {
// handle error
console.log('erreur sync');
console.log(err);
});
}
});
......@@ -368,8 +368,8 @@ class CagetteInventory(models.Model):
return {'missed': missed, 'unchanged': unchanged, 'done': done}
@staticmethod
def update_stock_with_shelf_inventory_data(inventory_data):
"""Updates Odoo stock after a shelf inventory"""
def update_products_stock(inventory_data):
""" Updates Odoo stock after a shelf inventory or another action"""
TWOPLACES = Decimal(10) ** -2
api = OdooAPI()
......
......@@ -94,7 +94,7 @@ def do_custom_list_inventory(request):
full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data)
# Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
# remove file
CagetteInventory.remove_custom_inv_file(inventory_data['id'])
......
......@@ -84,7 +84,7 @@ class CagetteMember(models.Model):
'point_qty': pts
}
"""
try:
return self.o_api.create('shift.counter.event', data)
except Exception as e:
......@@ -152,7 +152,7 @@ class CagetteMember(models.Model):
if (password == d + m + y):
if coop_id is None:
coop_id = coop['id']
data['id'] = coop_id
data['id'] = coop_id
auth_token_seed = fp + coop['create_date']
data['auth_token'] = hashlib.sha256(auth_token_seed.encode('utf-8')).hexdigest()
data['token'] = hashlib.sha256(coop['create_date'].encode('utf-8')).hexdigest()
......@@ -508,7 +508,7 @@ class CagetteMember(models.Model):
stype = shift_template['data']['type']
res['shift'] = \
m.create_coop_shift_subscription(shift_t_id, stype)
m.add_first_point(stype)
# m.add_first_point(stype) # Not needed anymore
# Update couchdb do with new data
try:
......@@ -743,13 +743,14 @@ class CagetteMember(models.Model):
keep_it = False
if not shift_id is None and len(shift_id) > 0:
# Only member registred to shift_id will be returned
cond = [['id', '=', m['tmpl_reg_line_ids'][0]]]
fields = ['shift_template_id']
shift_templ_res = api.search_read('shift.template.registration.line', cond, fields)
if (len(shift_templ_res) > 0
and
int(shift_templ_res[0]['shift_template_id'][0]) == int(shift_id)):
keep_it = True
if len(m['tmpl_reg_line_ids']) > 0:
cond = [['id', '=', m['tmpl_reg_line_ids'][0]]]
fields = ['shift_template_id']
shift_templ_res = api.search_read('shift.template.registration.line', cond, fields)
if (len(shift_templ_res) > 0
and
int(shift_templ_res[0]['shift_template_id'][0]) == int(shift_id)):
keep_it = True
else:
keep_it = True
if keep_it is True:
......@@ -845,7 +846,7 @@ class CagetteMember(models.Model):
def update_member_makeups(self, member_data):
api = OdooAPI()
res = {}
f = { 'makeups_to_do': int(member_data["target_makeups_nb"]) }
res_item = api.update('res.partner', [self.id], f)
res = {
......@@ -1462,4 +1463,3 @@ class CagetteUser(models.Model):
pass
return answer
/* Comments : */
/* - Screens */
/* -- Sections */
/* - Common */
.page_body{
position: relative;
}
......@@ -9,8 +15,6 @@
right: 0;
}
/* - Common */
.pill {
border-radius: 30px;
min-width: 200px;
......@@ -198,7 +202,7 @@
}
#coverage_form > div {
display:inline-block;
display:block;
float:left;
}
......@@ -367,7 +371,7 @@
width: 90%;
}
/* product actions modal*/
/* -- Product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
......@@ -376,6 +380,27 @@
.npa-options label {
display: block;
}
.modal_product_actions_section {
margin: 1em 0;
}
.modal_product_actions_section .tooltip {
margin-left: 5px;
}
.modal_product_actions_title {
font-weight: bold;
font-size: 2.2rem;
margin-bottom: 10px;
}
.checkbox_action_disabled {
cursor: not-allowed;
opacity: .5;
}
/* - Orders created screen */
.order_created_header {
......
......@@ -29,7 +29,7 @@ var dbc = null,
var clicked_order_pill = null;
var timerId
/* - UTILS */
/**
......@@ -116,6 +116,11 @@ function _compute_stats_date_from() {
return val;
}
function debounceFunction(func, delay = 1000) {
clearTimeout(timerId)
timerId = setTimeout(func, delay)
}
/* - PRODUCTS */
/**
......@@ -165,7 +170,7 @@ function add_product() {
res.default_code = ' ';
products.unshift(res);
update_main_screen({'sort_order_dir':'desc'});
update_cdb_order();
debounceFunction(update_cdb_order);
} else {
alert("L'article n'a pas toutes les caractéristiques pour être ajouté.");
}
......@@ -261,8 +266,12 @@ function check_products_data() {
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
let loaded_products_ids = products.map(p => p.id);
// Going through products fetched from server
for (let product of data.res.products) {
const p_index = products.findIndex(p => p.id == product.id);
const p_id = product.id;
const p_index = products.findIndex(p => p.id == p_id);
if (p_index === -1) {
// Add product if it wasn't fetched before (made available since last access to order)
......@@ -283,9 +292,39 @@ function check_products_data() {
}
}
}
}
// Remove fetched product id from loaded products list
const loaded_p_index = loaded_products_ids.indexOf(p_id);
if (loaded_p_index > -1) {
loaded_products_ids.splice(loaded_p_index, 1);
}
}
$('.notifyjs-wrapper').trigger('notify-hide');
/**
* If loaded p_ids are remaining:
* these products were loaded but don't match the conditions to be fetched anymore.
* Remove them.
*/
if (loaded_products_ids.length > 0) {
for (pid of loaded_products_ids) {
const p_index = products.findIndex(p => p.id == pid);
const p_name = products[p_index].name;
products.splice(p_index, 1);
$.notify(
`Produit "${p_name}" retiré de la commande.\nIl a probablement été passé en archivé ou en NPA sur un autre poste.`,
{
globalPosition:"top left",
className: "info",
autoHideDelay: 12000,
clickToHide: false
}
);
}
}
resolve();
},
error: function(data) {
......@@ -346,7 +385,7 @@ function update_product_ref(input_el, p_id, p_index) {
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
success: () => {
update_cdb_order();
debounceFunction(update_cdb_order);
$(".actions_buttons_area .right_action_buttons").notify(
"Référence sauvegardée !",
......@@ -424,7 +463,7 @@ function add_supplier() {
save_supplier_products(supplier, data.res.products);
update_main_screen();
$("#supplier_input").val("");
update_cdb_order();
debounceFunction(update_cdb_order);
closeModal();
},
error: function(data) {
......@@ -460,7 +499,7 @@ function remove_supplier(supplier_id) {
products = products.filter(product => product.suppliersinfo.length > 0);
update_main_screen();
update_cdb_order();
debounceFunction(update_cdb_order);
}
......@@ -527,7 +566,7 @@ function save_supplier_product_association(product, supplier, cell) {
products_table.row(row).data(new_row_data)
.draw();
update_cdb_order();
debounceFunction(update_cdb_order);
closeModal();
},
error: function(data) {
......@@ -581,7 +620,7 @@ function end_supplier_product_association(product, supplier) {
// Update table
display_products();
update_cdb_order();
debounceFunction(update_cdb_order);
closeModal();
},
error: function(data) {
......@@ -688,21 +727,33 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */
function save_products_npa_minimal_stock(product, inputs) {
let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name};
function commit_actions_on_product(product, inputs) {
let actions = {
npa: [],
to_archive: false,
minimal_stock: 0,
id: product.id,
name: product.name
};
inputs.each(function (i,e) {
const input = $(e)
if (input.attr('type') == 'checkbox') {
if (input.attr('name') == 'npa-actions') {
if (input.prop('checked') == true) {
actions.npa.push(input.val())
actions.npa.push(input.val());
}
} else if (input.attr('name') == "minimal_stock") {
actions.minimal_stock = input.val()
actions.minimal_stock = input.val();
} else if (input.attr('name') == "archive-action") {
if (input.prop('checked') == true && product.incoming_qty === 0) {
actions.to_archive = true;
}
}
});
openModal();
$.ajax({
type: "POST",
url: "/products/update_npa_and_minimal_stock",
url: "/products/commit_actions_on_product",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
......@@ -710,43 +761,63 @@ function save_products_npa_minimal_stock(product, inputs) {
success: () => {
const index = products.findIndex(p => p.id == product.id);
// Give time for modal to fade
setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify(
"Actions enregistrées !",
{
elementPosition:"bottom right",
className: "success",
arrowShow: false
}
);
}, 500);
products[index].minimal_stock = actions.minimal_stock;
if (actions.npa.length > 0) {
// Remove NPA products
if (actions.npa.length > 0 || actions.to_archive === true) {
// Remove NPA & archived products
products.splice(index, 1);
update_main_screen();
update_cdb_order();
debounceFunction(update_cdb_order);
}
closeModal();
check_products_data()
.then(() => {
update_cdb_order();
update_main_screen();
closeModal();
// Give time for modal to fade
setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify(
"Actions enregistrées !",
{
elementPosition:"bottom right",
className: "success",
arrowShow: false
}
);
}, 500);
});
},
error: function(data) {
let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${product.id})`;
err = {msg: msg, ctx: 'save_products_npa_minimal_stock'};
err = {msg: msg, ctx: 'commit_actions_on_product'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
closeModal();
alert('Erreur lors de la sauvegarde de la donnée. Veuillez ré-essayer plus tard.');
update_main_screen();
try {
if (data.responseJSON.code === "archiving_with_incoming_qty") {
alert("Ce produit a des quantités entrantes, vous ne pouvez pas l'archiver.")
} else if (data.responseJSON.code === "error_stock_update") {
alert('Erreur lors de la mise à zéro du stock du produit archivé. Les actions ont bien été réalisées.');
} else {
alert('Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.');
}
} catch (error) {
alert('Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.');
}
check_products_data()
.then(() => {
update_cdb_order();
update_main_screen();
closeModal();
});
}
});
}
......@@ -1160,7 +1231,7 @@ function goto_main_screen(doc) {
check_products_data()
.then(() => {
update_cdb_order();
debounceFunction(update_cdb_order);
update_main_screen();
switch_screen();
});
......@@ -1606,7 +1677,7 @@ function display_products(params) {
products_table.row($(this).closest('tr')).data(new_row_data)
.draw();
update_cdb_order();
debounceFunction(update_cdb_order);
display_total_values();
} else {
$(this).val('');
......@@ -1655,20 +1726,25 @@ function display_products(params) {
.on('click', 'tbody td .product_actions', function(e){
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const product = products.find(p => p.id == p_id);
let modal_product_actions = $('#templates #modal_product_actions');
modal_product_actions.find(".product_name").text(product.name);
//modal_product_actions.find(".product_npa").text(null ? 'Ne Pas Acheter' : 'Peut Être Acheté');
const product_can_be_archived = product.incoming_qty === 0;
if (product_can_be_archived == true) {
modal_product_actions.find('input[name="archive-action"]').prop( "disabled", false );
modal_product_actions.find('input[name="archive-action"]').closest("label").removeClass( "checkbox_action_disabled" );
} else {
modal_product_actions.find('input[name="archive-action"]').prop( "disabled", true );
modal_product_actions.find('input[name="archive-action"]').closest("label").addClass( "checkbox_action_disabled" );
}
openModal(
modal_product_actions.html(),
() => {
if (is_time_to('validate_product_actions')) {
save_products_npa_minimal_stock(product, modal.find('input'));
commit_actions_on_product(product, modal.find('input'));
}
},
'Valider',
......@@ -2100,7 +2176,7 @@ $(document).ready(function() {
if (!isNaN(val)) {
order_doc.coverage_days = val;
compute_products_coverage_qties();
update_cdb_order();
debounceFunction(update_cdb_order);
update_main_screen();
} else {
$("#coverage_days_input").val(order_doc.coverage_days);
......@@ -2160,7 +2236,7 @@ $(document).ready(function() {
.then(() => {
compute_products_coverage_qties();
update_main_screen();
update_cdb_order();
debounceFunction(update_cdb_order);
closeModal();
});
}
......@@ -2177,7 +2253,7 @@ $(document).ready(function() {
openModal();
check_products_data()
.then(() => {
update_cdb_order();
debounceFunction(update_cdb_order);
update_main_screen();
$("#toggle_action_buttons").click();
closeModal();
......@@ -2279,7 +2355,7 @@ $(document).ready(function() {
return 0;
});
$(document).on("click",".fa-info-circle", display_average_consumption_explanation)
$(document).on("click",".average_consumption_explanation_icon", display_average_consumption_explanation)
$.datepicker.regional['fr'] = {
monthNames: [
......@@ -2416,6 +2492,21 @@ $(document).ready(function() {
alert('Erreur lors de la récupération des articles, rechargez la page plus tard');
}
});
$(document).on('click', '.accordion', function(){
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
} else {
$('#not_connected_content').show();
}
......
......@@ -148,6 +148,10 @@ footer { position: fixed;
width: 230px !important;
}
.tooltip .tooltip-xl {
width: 320px !important;
}
.tooltip .tt_twolines {
top: -15px !important;
}
......
......@@ -460,22 +460,6 @@ for (i = 0; i < acc.length; i++) {
});
}
$(document).on('click', '.accordion', function(){
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
console.log(panel)
});
function report_JS_error(e, m) {
try {
$.post('/log_js_error', {module: m, error: JSON.stringify(e)});
......
......@@ -189,34 +189,51 @@ class ExportPOS(View):
kept_sessions_id.append(s['id'])
key = y + '-' + m + '-' + d
if not (key in totals):
totals[key] = {'CB': 0, 'CSH': 0, 'CHQ': 0, 'TOTAL': 0}
totals[key] = {'CB': 0,
'CSH': 0,
'CHQ': 0,
'CB_DEJ': 0,
'CHQ_DEJ': 0,
'TOTAL': 0}
sub_total = 0
cb = chq = csh = 0
cb = chq = csh = cbd = chqd = 0
for p in s['payments']:
# p['name'] is a sequence generated string
# Test order is important as CHEQDEJ contains CHEQ for ex.
# p['journal'] could be used but easier to change in Odoo interface
sub_amount = round(p['total_amount'], 2)
if 'CSH' in p['name']:
csh = round(p['total_amount'], 2)
csh = sub_amount
elif 'CHEQDEJ' in p['name']:
chqd = sub_amount
elif 'CHEQ' in p['name']:
chq = round(p['total_amount'], 2)
chq = sub_amount
elif 'CBDEJ' in p['name']:
cbd = sub_amount
elif 'CB' in p['name']:
cb = round(p['total_amount'], 2)
sub_total += round(p['total_amount'], 2)
cb = sub_amount
sub_total += sub_amount
totals[key]['CB'] += cb
totals[key]['CSH'] += csh
totals[key]['CHQ'] += chq
totals[key]['CB_DEJ'] += cbd
totals[key]['CHQ_DEJ'] += chqd
totals[key]['TOTAL'] += round(sub_total, 2)
details_lines.append([mois, s['mm_dates']['min'], s['mm_dates']['min'], s['caisse'], s['name'],
cb, csh, chq, sub_total])
cb, csh, chq, cbd, chqd, sub_total])
wb = Workbook()
ws1 = wb.create_sheet("Totaux " + mois, 0)
ws2 = wb.create_sheet("Détails " + mois, 1)
ws1.append(['date', 'CB', 'CSH', 'CHQ', 'Total'])
ws1.append(['date', 'CB', 'CSH', 'CHQ', 'CB_DEJ', 'CHQ_DEJ', 'Total'])
for day in sorted(totals):
cb = totals[day]['CB']
csh = totals[day]['CSH']
chq = totals[day]['CHQ']
cbd = totals[day]['CB_DEJ']
chqd = totals[day]['CHQ_DEJ']
total = totals[day]['TOTAL']
ws1.append([day, cb, csh, chq, total])
ws2.append(['mois', 'min_date', 'max_date', 'Caisse', 'session', 'CB', 'CSH','CHQ', 'total'])
ws1.append([day, cb, csh, chq, cbd, chqd, total])
ws2.append(['mois', 'min_date', 'max_date', 'Caisse', 'session', 'CB', 'CSH','CHQ', 'CB_DEJ', 'CHQ_DEJ', 'total'])
for row in details_lines:
ws2.append(row)
wb_name = 'export_sessions__' + mois + '.xlsx'
......
......@@ -254,13 +254,20 @@ class CagetteProduct(models.Model):
return res
@staticmethod
def update_npa_and_minimal_stock(data):
"""Update NPA (ne pas acheter) and minimal stock data"""
def commit_actions_on_product(data):
""" Update:
- NPA (ne pas acheter)
- Product is active
- Minimal stock
"""
res = {}
try:
api = OdooAPI()
# Minimal stock
f = {'minimal_stock': data['minimal_stock']}
# NPA
if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']:
......@@ -279,6 +286,10 @@ class CagetteProduct(models.Model):
f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0:
f['purchase_ok'] = 1
# Active
f["active"] = not data['to_archive']
res["update"] = api.update('product.template', data['id'], f)
except Exception as e:
res["error"] = str(e)
......@@ -612,7 +623,7 @@ class CagetteProducts(models.Model):
"product_variant_ids",
"minimal_stock"
]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]]
products_t = api.search_read('product.template', c, f)
filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
......
......@@ -11,7 +11,7 @@ urlpatterns = [
url(r'^update_product_stock$', views.update_product_stock),
url(r'^update_product_purchase_ok$', views.update_product_purchase_ok),
url(r'^update_product_internal_ref$', views.update_product_internal_ref),
url(r'^update_npa_and_minimal_stock$', views.update_npa_and_minimal_stock),
url(r'^commit_actions_on_product$', views.commit_actions_on_product),
url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'),
url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print),
url(r'^shelf_labels$', views.shelf_labels), # massive print
......
......@@ -100,7 +100,7 @@ def update_product_stock(request):
'products': [p]
}
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(inventory_data)
return JsonResponse({"res": res})
......@@ -134,13 +134,44 @@ def update_product_internal_ref(request):
else:
return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request):
def commit_actions_on_product(request):
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
try:
data = json.loads(request.body.decode())
res = CagetteProduct.update_npa_and_minimal_stock(data)
product_data = CagetteProducts.get_products_for_order_helper(None, [data["id"]])["products"][0]
# Don't allow to archive product if incomin qty > 0
if data["to_archive"] is True and product_data["incoming_qty"] > 0:
res["code"] = "archiving_with_incoming_qty"
return JsonResponse(res, status=500)
res = CagetteProduct.commit_actions_on_product(data)
# If stock > 0: do inventory to set stock to 0
if data["to_archive"] is True and product_data["qty_available"] != 0:
try:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': -product_data["qty_available"]
}
inventory_data = {
'name': 'Archivage - ' + product_data['name'],
'products': [p]
}
res_inventory = CagetteInventory.update_products_stock(inventory_data)
if res_inventory['errors'] or res_inventory['missed']:
res["code"] = "error_stock_update"
res["error"] = res_inventory['errors']
return JsonResponse(res, status=500)
except Exception as e:
res["code"] = "error_stock_update"
return JsonResponse(res, status=500)
except Exception as e:
res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error'])
......
......@@ -14,49 +14,38 @@ class CagetteSales(models.Model):
def get_sales(self, date_from, date_to):
res = []
# Get pos sessions
cond = [['stop_at', '>=', date_from], ['stop_at', '<=', date_to], ['state', '=', "closed"]]
fields = []
sessions = self.o_api.search_read('pos.session', cond, fields)
# Get pos orders
cond = [['date_order', '>=', date_from], ['date_order', '<=', date_to]]
fields = ['partner_id', 'statement_ids', 'name']
orders = self.o_api.search_read('pos.order', cond, fields)
# Get bank statements of these sessions
statements = []
for s in sessions:
statements = statements + s["statement_ids"]
statements_partners = {}
statements_orders = {}
for o in orders:
statements = statements + o["statement_ids"]
for s in o["statement_ids"]:
statements_partners[s] = o["partner_id"][1]
statements_orders[s] = o["name"]
# Get payment lines
cond = [['statement_id', 'in', statements]]
fields = ["partner_id", "amount", "journal_id", "create_date", "date"]
cond = [['id', 'in', statements]]
fields = ["amount", "journal_id", "create_date"]
payments = self.o_api.search_read('account.bank.statement.line', cond, fields, order="create_date ASC", limit=50000)
item = None
try:
for payment in payments:
# POS session can contain payments from another day (closing session on next morning, ...)
if payment["date"] >= date_from and payment["date"] <= date_to:
# If the consecutive payment in the results is from the same partner on the same day, we consider it's the same basket
if item is not None and item["partner_id"][0] == payment["partner_id"][0] and item["date"] == payment["date"]:
res[len(res)-1]["total_amount"] += round(float(payment["amount"]), 2)
res[len(res)-1]["payments"].append({
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
})
else:
item = {
"partner_id": payment["partner_id"],
res.append({
"partner": statements_partners[payment["id"]],
"create_date": payment["create_date"],
"date": payment["date"],
"pos_order_name": statements_orders[payment["id"]],
"total_amount": round(float(payment["amount"]), 2),
"payments": [
{
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
}
}
]
}
res.append(item)
})
except Exception as e:
pass
coop_logger.error("get_sales %s", str(e))
return res
......@@ -38,20 +38,23 @@ function display_orders(orders) {
columns:[
{
data:"create_date",
title:"Date de vente",
title:"Date enregistrement",
width: "10%"
},
{
data:"partner_id",
data:"pos_order_name",
title:"Ref. Caisse",
width: "10%"
},
{
data:"partner",
title:"Membre",
width: "50%",
render: function (data) {
return data[1];
}
width: "40%"
},
{
data:"total_amount",
title: "Montant du panier",
title: "Montant dû",
className:"dt-body-center",
render: function (data) {
return parseFloat(data).toFixed(2) + ' €';
......
......@@ -173,7 +173,7 @@ def do_shelf_inventory(request):
return JsonResponse(res, status=500)
# Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
full_inventory_data['inventory_id'] = res['inventory']['inv_id']
shelf_data['last_inventory_id'] = res['inventory']['inv_id']
......
......@@ -46,7 +46,7 @@ def do_movement(request):
'products': products
}
res = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data)
res = CagetteInventory.update_products_stock(inventory_data)
else:
res = CagetteStock.do_stock_movement(data)
......
......@@ -17,7 +17,7 @@
<div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div>
</div>
<div id="envelop_cashing_success" class="alert--success clearfix custom_alert" onClick="toggle_success_alert()">
<div style="width: 90%" class="fl txtleft">Enveloppe encaissée !</div>
<div style="width: 90%" class="fl txtleft success_alert_content">Enveloppe encaissée !</div>
<div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div>
</div>
<div id="envelop_deletion_success" class="alert--success clearfix custom_alert" onClick="toggle_deleted_alert()">
......@@ -41,6 +41,33 @@
</section>
</section>
<div id="templates" style="display:none;">
<div id="modal_update_envelop">
<div class="modal_update_envelop_content">
<h3 class="envelop_name"></h3>
<div class="envelop_lines"></div>
<div class="envelop_comments_area">
<p>Commentaires</p>
<textarea class="envelop_comments"></textarea>
</div>
</div>
</div>
<div id="update_envelop_line_template">
<div class="update_envelop_line">
<div class="line_partner_name_container">
<span class="line_number"></span>
<span class="line_partner_name"></span>
</div>
<div class="line_partner_input_container">
<input type="text" class="line_partner_amount" placeholder="Montant">
<i class="fas fa-trash-alt fa-lg delete_envelop_line_icon"></i>
</div>
<div class="deleted_line_through"></div>
</div>
</div>
</div>
<script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript">
{%if must_identify %}
......
......@@ -98,7 +98,7 @@
<input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %">
</div>
<div>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg'></i>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg average_consumption_explanation_icon'></i>
</div>
</form>
</div>
......@@ -277,19 +277,28 @@
<div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3>
<p>
<h4>NPA</h4>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">NPA</h4>
<div class="npa-options">
<label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label>
<label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label>
</div>
</p>
<p>
<h4>Stock minimum</h4>
</div>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">Archiver le produit</h4>
<label class="checkbox_action_disabled"><input type="checkbox" name="archive-action" value="archive" disabled /> Archiver </label>
<div class="tooltip">
<i class='main fa fa-info-circle'></i>
<span class="tooltiptext tooltip-xl tt_twolines">
Un produit ne peut pas être archivé si une quantité entrante est prévue.
</span>
</div>
</div>
<div class="modal_product_actions_section">
<h4 class="modal_product_actions_title">Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</p>
</div>
</div>
<div id="modal_create_order">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment