Commit df42ebaf by Damien Moulard

Revert "merge dev_cooperatic into dev_principale"

This reverts commit b190df6e
parent b190df6e
Pipeline #1707 passed with stage
in 1 minute 34 seconds
......@@ -109,12 +109,3 @@ RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception,
# display or not column "Autres" in reception process
DISPLAY_COL_AUTRES = False
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True
# URL to the metabase dashboard for orders helper
ORDERS_HELPER_METABASE_URL = "url_meta_base"
# New members space
USE_NEW_MEMBERS_SPACE = True
START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
AMNISTIE_DATE= "2021-11-24 00:00:00"
......@@ -91,7 +91,7 @@ class CagetteEnvelops(models.Model):
if not ('error' in res):
try:
if int(float(data['amount']) * 100) == int(float(invoice['residual_signed']) * 100):
# This payment is what was left to pay, so change invoice state
# This payment is what it was left to pay, so change invoice state
self.o_api.update('account.invoice', [invoice['id']], {'state': 'paid'})
except Exception as e:
res['error'] = repr(e)
......
......@@ -16,10 +16,6 @@ function toggle_success_alert() {
$('#envelop_cashing_success').toggle(250);
}
function toggle_deleted_alert() {
$('#envelop_deletion_success').toggle(250);
}
// Set an envelop content on the document
function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_index) {
var envelops_section = $('#' + envelop.type + '_envelops');
......@@ -43,7 +39,7 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
}
new_html += '</div>'
+ '<div class="panel panel_' + envelop_content_id + '"><ol id="' + envelop_content_id + '"></ol></div>'
+ '<div class="panel"><ol id="' + envelop_content_id + '"></ol></div>'
+ '</div>';
$(new_html).appendTo(envelops_section);
......@@ -62,9 +58,6 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
li_node.appendChild(textnode); // Append the text to <li>
document.getElementById(envelop_content_id).appendChild(li_node);
}
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>');
}
// Set the envelops data according to their type
......@@ -125,94 +118,67 @@ function set_envelops(envelops) {
}
}
function delete_envelop(type, index) {
if (is_time_to('delete_envelop', 1000)) {
openModal();
var envelop = null;
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
envelop._deleted = true;
dbc.put(envelop, function callback(err, result) {
if (!err && result !== undefined) {
toggle_deleted_alert();
get_envelops();
} else {
alert("Erreur lors de la suppression de l'enveloppe... Essaye de recharger la page et réessaye.");
console.log(err);
}
});
function archive_envelop(type, index) {
$('#envelop_cashing_error').hide();
$('#envelop_cashing_success').hide();
// Loading on
openModal();
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
}
function archive_envelop(type, index) {
if (is_time_to('archive_envelop', 1000)) {
$('#envelop_cashing_error').hide();
$('#envelop_cashing_success').hide();
// Loading on
openModal();
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
// Proceed to envelop cashing
$.ajax({
type: "POST",
url: "/envelops/archive_envelop",
headers: { "X-CSRFToken": getCookie("csrftoken") },
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(envelop),
success: function(response) {
closeModal();
var display_success_alert = true;
// Handle errors when saving payments
var error_payments = response.error_payments;
var error_message = "";
for (var i = 0; i < error_payments.length; i++) {
if (error_payments[i].done == false) {
error_message += "<p>Erreur lors de l'enregistrement du paiement de <b>" + error_payments[i]['partner_name']
+ "</b> (id odoo : " + error_payments[i]['partner_id'] + ", valeur à encaisser : " + error_payments[i]['amount'] + "€).";
error_message += "<br/><b>L'opération est à reprendre manuellement dans Odoo pour ce paiement.</b></p>";
}
// Proceed to envelop cashing
$.ajax({
type: "POST",
url: "/envelops/archive_envelop",
headers: { "X-CSRFToken": getCookie("csrftoken") },
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(envelop),
success: function(response) {
closeModal();
var display_success_alert = true;
// Handle errors when saving payments
var error_payments = response.error_payments;
var error_message = "";
for (var i = 0; i < error_payments.length; i++) {
if (error_payments[i].done == false) {
error_message += "<p>Erreur lors de l'enregistrement du paiement de <b>" + error_payments[i]['partner_name']
+ "</b> (id odoo : " + error_payments[i]['partner_id'] + ", valeur à encaisser : " + error_payments[i]['amount'] + "€).";
error_message += "<br/><b>L'opération est à reprendre manuellement dans Odoo pour ce paiement.</b></p>";
}
}
// If error during envelop deletion
var response_envelop = response.envelop;
// If error during envelop deletion
var response_envelop = response.envelop;
if (response_envelop == "error") {
error_message += "<p>Erreur lors de la suppression de l'enveloppe.<br/>";
error_message += "<b>Sauf contre-indication explicite, les paiements ont bien été enregistrés.</b><br/>";
error_message += "Les paiements déjà comptabilisés ne le seront pas à nouveau, vous pouvez ré-essayer. Si l'erreur persiste, l'enveloppe devra être supprimée manuellement.</p>";
display_success_alert = false;
}
if (response_envelop == "error") {
error_message += "<p>Erreur lors de la suppression de l'enveloppe.<br/>";
error_message += "<b>Sauf contre-indication explicite, les paiements ont bien été enregistrés.</b><br/>";
error_message += "Les paiements déjà comptabilisés ne le seront pas à nouveau, vous pouvez ré-essayer. Si l'erreur persiste, l'enveloppe devra être supprimée manuellement.</p>";
display_success_alert = false;
}
if (error_message !== "") {
$('#error_alert_txt').html(error_message);
toggle_error_alert();
}
if (error_message !== "") {
$('#error_alert_txt').html(error_message);
toggle_error_alert();
}
if (display_success_alert) {
toggle_success_alert();
}
},
error: function() {
closeModal();
alert('Erreur serveur. Merci de ne pas ré-encaisser l\'enveloppe qui a causé l\'erreur.');
if (display_success_alert) {
toggle_success_alert();
}
});
}
},
error: function() {
closeModal();
alert('Erreur serveur. Merci de ne pas ré-encaisser l\'enveloppe qui a causé l\'erreur.');
}
});
}
// Get all the envelops from couch db
......
......@@ -15,113 +15,6 @@ default_msettings = {'msg_accueil': {'title': 'Message borne accueil',
'type': 'textarea',
'value': ''
},
'shop_opening_hours': {
'title': 'Horaires ouverture magasin',
'type': 'textarea',
'value': '',
'sort_order': 3
},
'abcd_calendar_link': {
'title': 'Lien vers le calendrier ABCD',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 4
},
'forms_link': {
'title': 'Lien vers la page des formulaires',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 5
},
'unsuscribe_form_link': {
'title': 'Lien vers le formulaire de ré-inscription',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 6
},
'request_form_link': {
'title': 'Faire une demande au Bureau Des Membres',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 7
},
'late_service_form_link': {
'title': 'Retard à mon service ou oubli validation',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 8
},
'change_template_form_link': {
'title': 'Demande de changement de créneau',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 9
},
#TODO vérifier le nom d'un "binome"
'associated_subscribe_form_link': {
'title': 'Demande de création de binôme',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 10
},
#TODO vérifier le nom d'un "binome"
'associated_unsubscribe_form_link': {
'title': 'Se désolidariser de son binôme',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 11
},
'template_unsubscribe_form_link': {
'title': 'Se désinscrire de son créneau',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 12
},
'change_email_form_link': {
'title': 'Changer d\'adresse mail',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 13
},
'coop_unsubscribe_form_link': {
'title': 'Demande de démission de la coopérative et/ou de remboursement de mes parts sociales',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 14
},
'sick_leave_form_link': {
'title': 'Demande de congé maladie ou parental',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 15
},
'underage_subscribe_form_link': {
'title': 'Demande de création d’un compte mineur rattaché',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 16
},
'member_cant_have_delay_form_link': {
'title': 'Lien vers le formulaire pour les membres n\'ayant pas rattrapé leur service après 6 mois',
'type': 'text',
'value': '',
'class': 'link',
'sort_order': 21
}
}
def config(request):
......
{"msg_accueil": {"title": "Message borne accueil", "type": "textarea", "value": "", "sort_order": 1}, "no_picture_member_advice": {"title": "Message avertissement membre sans photo", "type": "textarea", "value": "", "sort_order": 2}, "shop_opening_hours": {"title": "Horaires ouverture magasin", "type": "textarea", "value": "", "sort_order": 3}, "abcd_calendar_link": {"title": "Lien vers le calendrier ABCD", "type": "text", "value": "", "class": "link", "sort_order": 4}, "forms_link": {"title": "Lien vers la page des formulaires", "type": "text", "value": "", "class": "link", "sort_order": 5}, "unsuscribe_form_link": {"title": "Lien vers le formulaire de r\u00e9-inscription", "type": "text", "value": "https://redmine.cooperatic.fr", "class": "link", "sort_order": 6}, "request_form_link": {"title": "Faire une demande au Bureau Des Membres", "type": "text", "value": "g", "class": "link", "sort_order": 7}, "late_service_form_link": {"title": "Retard \u00e0 mon service ou oubli validation", "type": "text", "value": "f", "class": "link", "sort_order": 8}, "change_template_form_link": {"title": "Demande de changement de cr\u00e9neau", "type": "text", "value": "d", "class": "link", "sort_order": 9}, "associated_subscribe_form_link": {"title": "Demande de cr\u00e9ation de bin\u00f4me", "type": "text", "value": "s", "class": "link", "sort_order": 10}, "associated_unsubscribe_form_link": {"title": "Se d\u00e9solidariser de son bin\u00f4me", "type": "text", "value": "q", "class": "link", "sort_order": 11}, "template_unsubscribe_form_link": {"title": "Se d\u00e9sinscrire de son cr\u00e9neau", "type": "text", "value": "p", "class": "link", "sort_order": 12}, "change_email_form_link": {"title": "Changer d'adresse mail", "type": "text", "value": "o", "class": "link", "sort_order": 13}, "coop_unsubscribe_form_link": {"title": "Demande de d\u00e9mission de la coop\u00e9rative et/ou de remboursement de mes parts sociales", "type": "text", "value": "i", "class": "link", "sort_order": 14}, "sick_leave_form_link": {"title": "Demande de cong\u00e9 maladie ou parental", "type": "text", "value": "u", "class": "link", "sort_order": 15}, "underage_subscribe_form_link": {"title": "Demande de cr\u00e9ation d\u2019un compte mineur rattach\u00e9", "type": "text", "value": "y", "class": "link", "sort_order": 16}, "helper_subscribe_form_link": {"title": "Rejoindre la Brigade Solidaire", "type": "text", "value": "t", "class": "link", "sort_order": 17}, "helper_unsubscribe_form_link": {"title": "Se d\u00e9sinscrire de la brigade solidaire", "type": "text", "value": "r", "class": "link", "sort_order": 18}, "covid_form_link": {"title": "J'ai le Covid-19 !", "type": "text", "value": "e", "class": "link", "sort_order": 19}, "covid_end_form_link": {"title": "Fin d'exemption: reprise de mes services", "type": "text", "value": "z", "class": "link", "sort_order": 20}, "member_cant_have_delay_form_link": {"title": "Lien vers le formulaire pour les membres n'ayant pas rattrap\u00e9 leur service apr\u00e8s 6 mois", "type": "text", "value": "a", "class": "link", "sort_order": 21}}
\ No newline at end of file
......@@ -63,7 +63,6 @@ var html_elts = {
real_capture : $('#real_capture'),
multi_results : $('#multi_results_preview'),
cooperative_state : $('#cooperative_state'),
status_explanation: $('#status_explanation'),
next_shifts : $('#next_shifts')
};
......@@ -93,12 +92,8 @@ function fill_member_slide(member) {
}
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
html_elts.cooperative_state.html(member.cooperative_state);
if (member.cooperative_state == 'Rattrapage') {
var explanation = "Tu as dû manquer un service! Pour pouvoir faire tes courses aujourd'hui, tu dois d'abord sélectionner un rattrapage sur ton espace membre."
html_elts.status_explanation.html(explanation)
}
if (member.cooperative_state == 'Désinscrit(e)') coop_info.addClass('b_red');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_orange');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé') coop_info.addClass('b_orange');
if (member.shifts.length > 0) {
html_elts.next_shifts.append('Prochains services : ');
......
var makeups_members_table = null,
makeups_members = null,
members_search_results = [],
selected_rows = []; // Contain members id
function switch_active_tab() {
// Set tabs
$('.tab').removeClass('active');
$(this).addClass('active');
// Tabs content
$('.tab_content').hide();
let tab = $(this).attr('id');
if (tab == 'tab_makeups') {
$('#tab_makeups_content').show();
}
load_tab_data();
}
/**
* Load data for the current tab
*/
function load_tab_data() {
let current_tab = $('.tab .active').attr('id');
if (current_tab === 'tab_makeups' && makeups_members === null) {
load_makeups_members();
}
}
/**
* Load partners who have makeups to do
*/
function load_makeups_members() {
$.ajax({
type: 'GET',
url: "/members/get_makeups_members",
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
makeups_members = data.res;
display_makeups_members();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des membres avec rattrapage", ctx: 'load_makeups_members'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
closeModal();
alert('Erreur serveur lors de la récupération des membres avec rattrapage. Ré-essayez plus tard.');
}
});
}
/**
* (Re)Display table of makeup members
*/
function display_makeups_members() {
if (makeups_members_table) {
$('#makeups_members_table').off();
makeups_members_table.clear().destroy();
$('#makeups_members_table').empty();
}
// Remove members with 0 makeups to do
ids_to_remove = [];
for (member_item of makeups_members) {
if (member_item.makeups_to_do == 0) {
ids_to_remove.push(member_item.id);
}
}
makeups_members = makeups_members.filter(m => !ids_to_remove.includes(m.id));
// TODO : select multiple and grouped action
makeups_members_table = $('#makeups_members_table').DataTable({
data: makeups_members,
columns: [
{
data: "id",
title: '',
className: "dt-body-center",
orderable: false,
render: function (data) {
return `<input type="checkbox" class="select_member_cb" id="select_member_${data}" value="${data}">`;
},
width: "3%"
},
{
data: "name",
title: "Nom"
},
{
data: "makeups_to_do",
title: "Nb rattrapages",
className: "dt-body-center",
width: "10%",
render: function (data, type, full) {
return `<b>${data}</b>
<button class="decrement_makeup btn--primary" id="decrement_member_${full.id}">
<i class="fas fa-minus"></i>
</button>
<button class="increment_makeup btn--primary" id="increment_member_${full.id}">
<i class="fas fa-plus"></i>
</button>`;
}
}
],
aLengthMenu: [
[
25,
50,
-1
],
[
25,
50,
"Tout"
]
],
iDisplayLength: 25,
oLanguage: {
"sProcessing": "Traitement en cours...",
"sSearch": "Rechercher dans le tableau",
"sLengthMenu": "Afficher _MENU_ &eacute;l&eacute;ments",
"sInfo": "Affichage de l'&eacute;l&eacute;ment _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
"sInfoEmpty": "Affichage de l'&eacute;l&eacute;ment 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
"sInfoFiltered": "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
"sInfoPostFix": "",
"sLoadingRecords": "Chargement en cours...",
"sZeroRecords": "Aucun &eacute;l&eacute;ment &agrave; afficher",
"sEmptyTable": "Aucune donn&eacute;e disponible dans le tableau",
"oPaginate": {
"sFirst": "Premier",
"sPrevious": "Pr&eacute;c&eacute;dent",
"sNext": "Suivant",
"sLast": "Dernier"
},
"oAria": {
"sSortAscending": ": activer pour trier la colonne par ordre croissant",
"sSortDescending": ": activer pour trier la colonne par ordre d&eacute;croissant"
},
"select": {
"rows": {
"_": "%d lignes séléctionnées",
"0": "Aucune ligne séléctionnée",
"1": "1 ligne séléctionnée"
}
}
}
});
$('#makeups_members_table').on('click', 'tbody td .decrement_makeup', function () {
const button_id = $(this).prop('id')
.split('_');
const member_id = button_id[button_id.length - 1];
const member = makeups_members.find(m => m.id == member_id);
openModal(
`Enlever un rattrapage à ${member.name} ?`,
() => {
update_members_makeups([member_id], "decrement");
},
"Confirmer",
false
);
});
$('#makeups_members_table').on('click', 'tbody td .increment_makeup', function () {
const button_id = $(this).prop('id')
.split('_');
const member_id = button_id[button_id.length - 1];
const member = makeups_members.find(m => m.id == member_id);
openModal(
`Ajouter un rattrapage à ${member.name} ?`,
() => {
update_members_makeups([member_id], "increment");
},
"Confirmer",
false
);
});
$('#makeups_members_table').on('click', 'tbody td .select_member_cb', function () {
$(this).closest('tr')
.toggleClass('selected');
// Save / unsave selected row
const m_id = makeups_members_table.row($(this).closest('tr')).data().id;
const first_select = selected_rows.length === 0;
if (this.checked) {
selected_rows.push(m_id);
} else {
const i = selected_rows.findIndex(id => id == m_id);
selected_rows.splice(i, 1);
}
if (selected_rows.length > 0) {
$("#decrement_selected_members_makeups").show();
if (first_select) {
$("#decrement_selected_members_makeups").on("click", () => {
openModal(
`Enlever un rattrapage aux membres sélectionnés ?`,
() => {
update_members_makeups(selected_rows, "decrement");
},
"Confirmer",
false
);
});
}
} else {
$("#decrement_selected_members_makeups").off()
.hide();
}
});
}
/**
* Send request to update members nb of makeups to do
*
* @param {Array} member_ids
* @param {String} action increment | decrement
*/
function update_members_makeups(member_ids, action) {
openModal();
data = [];
for (mid of member_ids) {
member_index = makeups_members.findIndex(m => m.id == mid);
if (action === "increment") {
makeups_members[member_index].makeups_to_do += 1;
} else {
makeups_members[member_index].makeups_to_do -= 1;
}
data.push({
member_id: mid,
target_makeups_nb: makeups_members[member_index].makeups_to_do,
member_shift_type: makeups_members[member_index].shift_type
});
}
$.ajax({
type: 'POST',
url: "/members/update_members_makeups",
data: JSON.stringify(data),
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function() {
selected_rows = [];
display_makeups_members();
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur pour décrémenter les rattrapages", ctx: 'decrement_makeups'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_admin');
closeModal();
alert('Erreur serveur pour décrémenter les rattrapages. Ré-essayez plus tard.');
}
});
}
/**
* Display the members from the search result
*/
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
let no_result = true;
if (members_search_results.length > 0) {
for (member of members_search_results) {
// Don't display members already in the table
if (makeups_members.find(m => m.id == member.id) != null) {
continue;
}
$(".search_results_text").show();
no_result = false;
// Display results (possible members) as buttons
var member_button = '<button class="btn--success btn_possible_member" member_id="'
+ member.id + '">'
+ member.barcode_base + ' - ' + member.name
+ '</button>';
$('.search_member_results').append(member_button);
// Set action on member button click
$('.btn_possible_member').on('click', function() {
for (member of members_search_results) {
if (member.id == $(this).attr('member_id')) {
if (makeups_members === null) {
makeups_members = [];
}
makeups_members.unshift({
id: member.id,
name: member.name,
makeups_to_do: 0,
shift_type: member.shift_type
});
openModal(
`Ajouter un rattrapage à ${member.name} ?`,
() => {
update_members_makeups([member.id], "increment");
members_search_results = [];
$('#search_member_input').val('');
$('.search_member_results_area').hide();
$('.search_member_results').empty();
},
"Confirmer",
false
);
break;
}
}
});
}
}
if (no_result === true) {
$(".search_results_text").hide();
$('.search_member_results').html(`<p>
<i>Aucun résultat ! Vérifiez votre recherche, ou si le.la membre n'est pas déjà dans le tableau...</i>
</p>`);
}
}
$(document).ready(function() {
if (coop_is_connected()) {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$(".page_content").show();
load_makeups_members();
$(".tabs .tab").on('click', switch_active_tab);
} else {
$(".page_content").hide();
}
// Set action to search for the member
$('#search_member_form').submit(function() {
let search_str = $('#search_member_input').val();
$.ajax({
url: '/members/search/' + search_str,
dataType : 'json',
success: function(data) {
members_search_results = [];
for (member of data.res) {
if (member.is_member || member.is_associated_people) {
members_search_results.push(member);
}
}
display_possible_members();
},
error: function() {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'confirm_movement.search_members'
};
report_JS_error(err, 'stock');
$.notify("Erreur lors de la recherche de membre, il faut ré-essayer plus tard...", {
globalPosition:"top right",
className: "error"
});
}
});
});
});
......@@ -96,7 +96,14 @@ function process_form_submission(event) {
closeModal();
if (!err) {
window.location.href = em_url;
var msg = "Vous êtes maintenant enregistré ! ";
msg += "<a href='" + em_url + "'>Cliquez ici</a> ";
msg += "pour découvrir l'espace membre";
$('p.intro').remove();
vform.remove();
display_msg_box(msg);
}
}
......
from django.db import models
from outils.common_imports import *
from members.models import CagetteServices
from outils.common import OdooAPI
class CagetteMembersSpace(models.Model):
"""Class to manage othe members space"""
def __init__(self):
"""Init with odoo id."""
self.o_api = OdooAPI()
def is_comite(self, partner_id):
"""Check if partner is from comite."""
cond = [['partner_id.id', '=', partner_id]]
fields = ['shift_template_id', 'is_current']
shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields)
answer = False
if (shiftTemplate and len(shiftTemplate) > 0):
s_t_id = None
for s_t in shiftTemplate:
if s_t['is_current'] is True:
s_t_id = s_t['shift_template_id'][0]
if s_t_id == CagetteServices.get_committees_shift_id():
answer = True
return answer
def get_shifts_history(self, partner_id, limit, offset, date_from):
""" Get partner shifts history """
res = []
paginated_res = []
today = str(datetime.date.today())
try:
cond = [
['partner_id', '=', partner_id],
['date_begin', '>', date_from],
['date_begin', '<', today],
['state', '!=', 'draft'],
['state', '!=', 'open'],
['state', '!=', 'waiting'],
['state', '!=', 'replaced'],
['state', '!=', 'replacing'],
]
f = ['create_date', 'date_begin', 'shift_id', 'name', 'state', 'is_late', 'is_makeup']
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try:
res = self.o_api.search_read('shift.registration', cond, f, order='date_begin DESC')
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = []
# Get committees shifts
committees_shifts_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
cond = [
['partner_id', '=', partner_id],
['name', '=', committees_shifts_name]
]
f = ['create_date']
try:
res_committees_shifts = self.o_api.search_read('shift.counter.event', cond, f, order='create_date DESC')
for committee_shift in res_committees_shifts:
item = {
"create_date": committee_shift["create_date"],
"date_begin": committee_shift["create_date"],
"shift_id": False,
"name": "Services des comités",
"state": "done",
"is_late": False,
"is_makeup": False,
}
res.append(item)
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = res + []
# Add amnesty line
is_amnesty = getattr(settings, 'AMNISTIE_DATE', False)
company_code = getattr(settings, 'COMPANY_CODE', '')
if is_amnesty and company_code == "lacagette":
amnesty = {}
amnesty['is_amnesty'] = True
amnesty['create_date'] = is_amnesty
amnesty['date_begin'] = is_amnesty
amnesty['shift_name'] = 'Amnistie'
amnesty['state'] = ''
res.append(amnesty)
# Ordering here is necessary for pagination
res.sort(key = lambda x: datetime.datetime.strptime(x['date_begin'], '%Y-%m-%d %H:%M:%S'), reverse=True)
# Paginate
end_index = offset + limit
paginated_res = res[offset:end_index]
except Exception as e:
coop_logger.error("get_shifts_history : %s", str(e))
return paginated_res
\ No newline at end of file
/* Add a black background color to the top navigation */
.topnav {
background-color: white;
overflow: hidden;
border-bottom: 1px solid #e7e9ed;
}
/* Style the links inside the navigation bar */
.topnav a {
float: left;
display: block;
color: #333;
text-align: center;
padding: 2rem 1.5rem;
text-decoration: none;
font-size: 17px;
font-weight: bold;
}
/* Change the color of links on hover */
.topnav a:hover {
background-color: #e7e9ed;
color: #333;
}
/* Add an active class to highlight the current page */
.topnav a.active {
background-color: #00a573;
color: white;
}
/* Hide the link that should open and close the topnav on small screens */
.topnav .icon {
display: none;
}
.pairs_info {
background-color: #00a573;
color: white;
padding: 1.5rem 1.2rem;
display: none;
}
@media screen and (max-width: 1146px) {
/* When the screen is less than 1146px pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the topnav (.icon) */
.topnav a:not(:first-child) {display: none;}
.topnav a.icon {
float: right;
display: block;
}
/* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens (display the links vertically instead of horizontally) */
.topnav.responsive {position: relative;}
.topnav.responsive a.icon {
position: absolute;
right: 0;
top: 0;
}
.topnav.responsive a {
float: none;
display: block;
text-align: left;
}
#deconnect {
float: none;
}
}
/* We override some styles defined on the home page for the specific needs of the My Info page */
#my_info {
font-size: 2rem;
}
#my_info_content {
display: flex;
flex-direction: column;
margin: 2rem 0;
}
.my_info_line {
display: flex;
flex-wrap: wrap;
width: 100%;
padding: 1.5rem 0;
}
@media screen and (min-width: 351px) and (max-width: 435px) {
.my_info_line {
font-size: 90%;
padding: 2vw 0;
}
}
@media screen and (max-width: 350px) {
.my_info_line {
font-size: 3.5vw;
padding: 2vw 0;
}
}
.my_info_line_left {
width: 50%;
text-align: right;
padding-right: 2rem;
font-weight: bold;
}
.my_info_line_right {
width: 50%;
padding-left: 2rem;
max-width: 100%;
word-break: break-all;
}
.my_info_line_middle {
width: 70%;
max-width: 100%;
word-break: normal;
text-align: center;
margin: 3rem 0;
}
#my_info #member_status_action,
#my_info .member_shift_name_area,
#my_info .member_coop_number_area {
margin-bottom: 0;
}
#my_info .member_shift_name_area {
margin-top: 0;
}
#attached_info {
display: flex;
flex-direction: column;
}
.member_phone_area {
display: flex;
flex-direction: column;
gap: 10px;
}
.edit-btn{
cursor: pointer;
}
#edit_address_form {
display: none ;
}
#edit_address_form #zip_form {
margin: 5px 0;
}
#edit_phone_form {
display: none ;
}
#edit_phone_form #mobile_form {
margin: 5px 0;
}
@media screen and (max-width: 992px) {
#my_info {
font-size: 1.7rem;
}
.my_info_line_left {
width: 30%;
padding-right: 1rem;
}
.my_info_line_right {
width: 70%;
padding-left: 1rem;
}
#attached_info .my_info_line_left {
width: 100%;
text-align: left;
padding: 0;
}
#attached_info .my_info_line_right {
width: 100%;
padding: 0;
}
#my_info .choose_makeups,
#my_info .unsuscribed_form_link {
white-space: normal;
}
#my_info .delay_date_stop_container {
white-space: nowrap;
}
#my_info .member_coop_number_area,
#my_info .member_shift_name_area {
align-items: flex-start;
}
}
.status_info_image {
display: block;
margin: 5rem auto 5rem auto;
width: 50%;
}
#my_shifts {
font-size: 1.8rem;
}
#incoming_shifts {
height: 100%;
flex-direction: column;
display: none;
}
.loading-history, .loading-incoming-shifts {
margin: 2rem 0;
}
#history {
display: flex;
flex-direction: column;
display: none;
}
table.dataTable tbody td {
text-overflow: ellipsis;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
background : none;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before {
color: #0275d8;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
@media screen {
}
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before {
color: #d8534f;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
.loading-more-history {
display: none;
}
.more_history {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
font-size: 2rem;
}
.more_history_button {
height: 50px;
width: 50px;
border-radius: 50%;
}
table.dataTable.display tbody tr.row_partner_ok {
background-color: #8feb8b;
}
table.dataTable.display tbody tr.row_partner_late {
background-color: #ffdf7d;
}
table.dataTable.display tbody tr.row_partner_absent {
background-color: #ff847b;
}
table.dataTable.display tbody tr.row_partner_amnistie {
background-color: rgb(78, 78, 78);
color: white;
}
table.dataTable.display tbody tr td {
border-top: 1px solid rgb(119, 119, 119);
}
\ No newline at end of file
.shifts_exchange_page_content {
width: 95%;
margin: 3rem auto;
display: flex;
flex-direction: column;
position: relative;
}
/* -- Suspended screen */
#suspended_content, #unsuscribed_content {
align-items: center;
text-align: center;
}
#shifts_exchange .select_makeups, #shifts_exchange .unsuscribed_form_link, .cant_have_delay_form_link {
margin: 1.5rem 0;
}
/* -- Suspended can't have delay screen */
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 50%;
}
@media screen and (max-width:992px) {
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 90%;
}
}
/* -- Calendar screen, area on top of the calendar */
#calendar_top_info {
display: flex;
justify-content: space-between;
}
@media screen and (max-width:992px) {
#calendar_top_info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
/* -- Calendar screen, shifts list */
#shifts_list {
flex-direction: column;
display: none;
width: min-content;
max-width: 100%;
white-space: nowrap;
}
@media screen and (max-width:992px) {
#partner_shifts_list {
display: flex;
flex-direction: column;
align-items: center;
}
}
.selectable_shift_line {
display: flex;
align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px;
}
.selectable_shift_line .checkbox {
margin-right: 10px;
}
.selectable_shift_line.btn {
cursor: not-allowed;
}
/* -- Calendar screen, makeups message */
#need_to_select_makeups_message {
display: none;
align-self: center;
background-color: #d9534f;
color: white;
margin: 0 1rem 2rem 1rem;
padding: 1rem 1.25rem;
text-align: center;
}
.makeups_warning {
margin-right: 3px;
}
@media screen and (max-width:992px) {
.select_makeups_message_block {
display: block;
}
}
/* -- Calendar screen, calendar */
#calendar {
margin: 2rem 1rem;
}
.loading-calendar {
margin: 3rem auto;
display: none;
}
@media screen and (max-width:992px) {
#calendar {
display: none;
}
}
.fc .fc-event {
cursor: pointer;
margin: 1px 10px !important;
}
.fc-event {
background-color: #008AD9;
border-color: #008AD9;
color: white;
}
td{
--fc-list-event-hover-bg-color:#005BA6;
}
.fc-event.shift_booked {
background-color: #585858;
cursor: auto;
border-color: #585858;
}
.fc-event.shift_booked td {
--fc-list-event-hover-bg-color:#585858;
}
.fc-list-event.shift_booked {
color: white;
}
#calendar .fc-list-table {
table-layout: auto;
}
.resp-header-toolbar {
display: flex;
flex-direction: column;
}
.resp-header-toolbar .fc-toolbar-chunk {
text-align: center;
margin: 0.25rem;
}
.date_old_shift, .time_old_shift, .date_new_shift, .time_new_shift {
font-weight: bold;
}
/* -- Explainations */
#calendar_explaination_area {
max-width: 33%;
border: 2px solid #585858;
border-radius: 15px;
padding: 1rem;
}
.example-event {
max-width: 200px;
margin: 2rem 0 0.5rem 0;
font-size: 1.4rem !important;
padding: 0 !important;
}
@media screen and (max-width:992px) {
.example-event {
margin: 2rem auto 0.5rem auto;
}
}
.arrow_explanation_numbers {
margin: 0 3px;
}
#calendar_explaination_button {
max-width: 60%;
margin: 2rem auto 0.5rem auto;
}
\ No newline at end of file
body {
margin: 0;
}
.page_title {
margin: 35px 0 30px 0;
}
@media screen and (max-width: 435px) {
.page_title {
margin: 4vw 0 3vw 0;
}
}
/* -- Tiles */
.tiles_container {
display: flex;
flex-wrap: wrap;
}
@media screen and (max-width: 992px) {
.tiles_container {
flex-direction: column;
}
}
.tile {
flex: 1 0 45%;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 30px;
margin: 1rem 1rem;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.high_tile {
min-height: 350px;
}
.small_tile {
min-height: 250px;
}
.full_width_tile {
flex: 1 0 90%;
min-height: 100px;
}
.tile_title {
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid #e7e9ed;
font-size: 2.4rem;
padding: 2rem 0;
width: 80%;
}
.tile_content {
position: relative;
margin: 3rem 0;
width: 80%;
display: flex;
height: 100%;
}
#home_tile_services_exchange .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
text-align: center;
}
/* -- My Shifts tile */
#home_tile_my_services .tile_content {
height: 100%;
flex-direction: column;
margin: auto;
padding: 2rem 0;
}
@media screen and (min-width: 769px) {
#home_tile_my_services .tile_content {
width: 50%;
}
}
#home_incoming_services {
min-height: 80px;
display: flex;
flex-direction: column;
}
.shift_line {
margin-left: 15px;
line-height: 2;
}
.shift_line_chevron {
color: #D9534F;
margin-right: 5px;
}
#go_to_shift_history_area {
width: 100%;
display: flex;
justify-content: center;
}
#home_go_to_shift_history {
width: 100%;
margin-top: 30px;
}
/* -- My Info tile */
#home_tile_my_info {
position: relative;
}
#home_tile_my_info .tile_content {
margin: 2rem 0;
}
.tile_icon {
margin-right: 15px;
color: #00a573;
}
#home_tile_my_info .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
font-size: 1.6rem;
}
@media screen and (max-width: 576px) {
#home_tile_my_info .tile_content {
font-size: 1.4rem !important;
}
}
#home .member_info {
font-weight: bold;
}
.member_status_text_container {
margin-bottom: 5px;
}
#member_status_action {
display: flex;
margin-bottom: 20px;
}
@media screen and (max-width: 992px) {
#member_status_action {
margin-top: 5px;
margin-bottom: 10px;
}
}
.choose_makeups {
display: none;
font-size: 1.5rem;
}
.unsuscribed_form_link {
display: none;
text-decoration: none;
font-size: 1.7rem;
word-break: normal;
}
.unsuscribed_form_link:hover {
text-decoration: none;
}
@media (max-width: 435px) {
.unsuscribed_form_link {
font-size: 90%;
line-height: 7vw;
}
}
.member_status_up_to_date,
.member_status_exempted {
color: #5cb85c;
}
.member_status_alert,
.member_status_delay {
color: #f0ad4e;
}
.member_status_suspended,
.member_status_unsubscribed {
color: #d9534f;
}
.member_shift_name_area,
.member_coop_number_area {
margin-bottom: 10px;
}
.member_associated_partner_area {
line-height: 1.3;
}
@media screen and (max-width: 992px) {
.member_associated_partner_area {
display: flex;
flex-direction: column;
align-items: center;
}
}
.delay_date_stop_container {
color: #f0ad4e;
margin-top: -1rem;
margin-bottom: 1rem;
display: none;
}
#see_more_info {
white-space: normal;
}
#see_more_info_link {
width: 100%;
}
/* --Shifts exchange tile tile */
.home_link_button_area {
width: 100%;
display: flex;
justify-content: center;
height: 100%;
}
.home_link_button {
width: 80%;
margin: 30px auto auto auto;
white-space: normal;
}
/* -- I have a question tile */
#go_to_forms {
text-decoration: none;
}
#go_to_forms:hover {
color: white;
}
#go_to_forms.active {
color: white !important;
}
/* -- Shop info tile */
#shop_info_content {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 80%;
margin: auto;
}
.shop_info_item {
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 2rem;
flex: 1 0 50%;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 2rem;
}
.opening_hours_title {
margin-bottom: 10px;
font-size: 2.3rem;
font-weight: bold;
}
.shop_message_content {
text-align: center;
}
@media screen and (min-width: 769px) {
.shop_info_item {
padding: 0 4rem;
}
.shop_message {
border-left: 1px solid #e7e9ed;
margin: 3rem 0;
}
}
@media screen and (max-width: 992px) {
#shop_info_content {
flex-direction: column;
}
.shop_info_item {
flex: 1 0 50%;
width: 100%;
font-size: 1.6rem;
padding: 1.5rem 0;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 1.6rem;
}
.opening_hours_title {
font-size: 1.9rem;
}
.shop_message {
border-top: 1px solid #e7e9ed;
}
.shop_message_content {
width: 90%;
}
}
/* - No content page */
.message_error {
padding: 7vw;
font-size: 1.8rem;
}
.no_content_title {
margin-bottom: 1.5rem;
}
function init_faq() {
$("#unsuscribe_form_link_btn").prop("href", unsuscribe_form_link);
$("#unsuscribe_form_link_btn2").prop("href", unsuscribe_form_link);
$("#change_template_form_link_btn").prop("href", change_template_form_link);
$("#template_unsubscribe_form_link_btn").prop("href", template_unsubscribe_form_link);
$("#late_service_form_link_btn").prop("href", late_service_form_link);
$("#sick_leave_form_link_btn").prop("href", sick_leave_form_link);
$("#associated_subscribe_form_link_btn").prop("href", associated_subscribe_form_link);
$("#associated_unsubscribe_form_link_btn").prop("href", associated_unsubscribe_form_link);
$("#covid_form_link_btn").prop("href", covid_form_link);
$("#covid_end_form_link_btn").prop("href", covid_end_form_link);
$("#underage_subscribe_form_link_btn").prop("href", underage_subscribe_form_link);
$("#change_email_form_link_btn").prop("href", change_email_form_link);
$("#coop_unsubscribe_form_link_btn").prop("href", coop_unsubscribe_form_link);
$("#helper_subscribe_form_link_btn").prop("href", helper_subscribe_form_link);
$("#helper_unsubscribe_form_link_btn").prop("href", helper_unsubscribe_form_link);
$("#request_form_link_btn2").prop("href", request_form_link);
$("#request_form_link_btn").prop("href", request_form_link);
}
$(document).on('click', "#shift_exchange_btn", () => {
goto('echange-de-services');
});
\ No newline at end of file
/**
* Request a 6 month delay
*/
function request_delay() {
return new Promise((resolve) => {
let today = new Date();
const delay_start = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
let today_plus_six_month = new Date();
today_plus_six_month.setMonth(today_plus_six_month.getMonth()+6);
const diff_time = Math.abs(today_plus_six_month - today);
const diff_days = Math.ceil(diff_time / (1000 * 60 * 60 * 24));
$.ajax({
type: 'POST',
url: "/shifts/request_delay",
dataType:"json",
data: {
verif_token: partner_data.verif_token,
idPartner: partner_data.partner_id,
start_date: delay_start,
duration: diff_days
},
success: function() {
partner_data.cooperative_state = 'delay';
partner_data.date_delay_stop = today_plus_six_month.getFullYear()+'-'+(today_plus_six_month.getMonth()+1)+'-'+today_plus_six_month.getDate();
resolve();
},
error: function(data) {
if (data.status == 403
&& typeof data.responseJSON != 'undefined'
&& data.responseJSON.message === "delays limit reached") {
closeModal();
let msg_template = $("#cant_have_delay_msg_template");
openModal(
msg_template.html(),
() => {
window.location =member_cant_have_delay_form_link;
},
"J'accède au formulaire",
true,
false
);
} else {
err = {msg: "erreur serveur lors de la création du délai", ctx: 'request_delay'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.home');
closeModal();
alert('Erreur lors de la création du délai.');
}
}
});
});
}
function init_my_shifts_tile() {
if (incoming_shifts.length === 0) {
$("#home_tile_my_services #home_incoming_services").text("Aucun service à venir...");
} else {
$("#home_tile_my_services #home_incoming_services").empty();
let cpt = 0;
for (shift of incoming_shifts) {
if (cpt === 3) {
break;
} else {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#home_tile_my_services #home_incoming_services").append(shift_line_template.html());
cpt++;
}
}
}
}
function init_home() {
$("#go_to_shifts_calendar").on("click", () => {
goto('echange-de-services');
});
$("#home_go_to_shift_history").on("click", () => {
goto('mes-services');
});
$("#see_more_info_link").on('click', (e) => {
e.preventDefault();
goto('mes-infos');
});
// $("#go_to_forms").prop("href", "forms_link");
$("#go_to_forms").on('click', (e) => {
e.preventDefault();
goto('faq');
});
if (partner_data.is_in_association === false) {
$("#home .member_associated_partner_area").hide();
} else {
if (partner_data.is_associated_people === "True") {
$(".member_associated_partner").text(partner_data.parent_name);
} else if (partner_data.associated_partner_id !== "False") {
$(".member_associated_partner").text(partner_data.associated_partner_name);
}
}
// TODO vérif tile my info avec données binomes + rattrapage et délai
// Init my info tile
init_my_info_data();
if (incoming_shifts !== null) {
init_my_shifts_tile();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_my_shifts_tile);
}
}
\ No newline at end of file
var history_table = null;
const history_items_limit = 10;
/**
* Load the partner points history
*/
function load_partner_history(offset = 0) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/members_space/get_shifts_history",
data: {
partner_id: partner_data.concerned_partner_id,
verif_token: partner_data.verif_token,
limit: history_items_limit,
offset: offset
},
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
formatted_data = prepare_server_data(data.data);
resolve(formatted_data);
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération de l'historique", ctx: 'load_partner_history'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.my_shifts');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de votre historique.');
}
});
});
}
/**
* Format history data to insert in the table
*
* @param {Array} data
* @returns formated data array
*/
function prepare_server_data(data) {
res = [];
for (history_item of data) {
if (history_item.is_amnesty !== undefined) {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
history_item.shift_name = `${history_item.shift_name} du ${str_shift_datetime}`;
} else {
history_item.shift_name = (history_item.shift_id === false) ? '' : history_item.shift_id[1];
if (history_item.name === "Services des comités") {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
str_shift_datetime = str_shift_datetime + " " + shift_datetime.toLocaleTimeString("fr-fr", time_options);
history_item.shift_name = `Services des comités ${str_shift_datetime}`;
}
}
history_item.details = '';
if (history_item.state === 'excused' || history_item.state === 'absent') {
history_item.details = "Absent.e";
} else if (history_item.state === 'done' && history_item.is_late != false) {
history_item.details = "Présent.e (En Retard)";
} else if (history_item.state === 'done') {
history_item.details = "Présent.e";
} else if (history_item.state === 'cancel') {
history_item.details = "Annulé";
}
}
return data;
}
/**
* Init the History section: display the history table
*/
function init_history() {
$(".loading-history").hide();
$("#history").show();
if (partner_history.length === 0) {
$("#history").empty()
.text("Aucun historique... pour l'instant !");
} else {
history_table = $('#history_table').DataTable({
data: partner_history,
columns: [
{
data: "date_begin",
title: "",
visible: false
},
{
data: "shift_name",
title: "<spans class='dt-body-center'>Service</span>",
width: "60%",
orderable: false
},
{
data: "details",
title: "Détails",
className: "tablet-l desktop",
orderable: false
}
],
iDisplayLength: -1,
order: [
[
0,
"desc"
]
],
language: {url : '/static/js/datatables/french.json'},
dom: "t",
responsive: true,
createdRow: function(row) {
for (var i = 0; i < row.cells.length; i++) {
const cell = $(row.cells[i]);
if (cell.text() === "Présent.e") {
$(row).addClass('row_partner_ok');
} else if (cell.text() === "Retard") {
$(row).addClass('row_partner_late');
} else if (cell.text() === "Absent.e") {
$(row).addClass('row_partner_absent');
} else if (cell.text().includes("Amnistie")) {
$(row).addClass('row_partner_amnistie');
}
}
}
});
}
}
/**
* Init the Incoming shifts section: display them
*/
function init_incoming_shifts() {
$(".loading-incoming-shifts").hide();
$("#incoming_shifts").show();
if (incoming_shifts.length === 0) {
$("#incoming_shifts").text("Aucun service à venir...");
} else {
$("#incoming_shifts").empty();
for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#incoming_shifts").append(shift_line_template.html());
}
}
}
function init_my_shifts() {
if (incoming_shifts !== null) {
init_incoming_shifts();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_incoming_shifts);
}
if (partner_history !== null) {
init_history();
} else {
load_partner_history()
.then((data) => {
partner_history = data;
for (d of data) {
d.create_date = Date.parse(d.create_date);
}
// Sort by date desc
partner_history.sort((a, b) => b.create_date - a.create_date);
if (partner_history.length>0 && partner_history[partner_history.length-1].is_amnesty != undefined) {
partner_history.pop();
}
init_history();
});
}
$(".more_history_button").on("click", function() {
// Hide button & display loading
$('.more_history_button').hide();
$('.loading-more-history').show();
load_partner_history(partner_history.length)
.then((data) => {
partner_history = partner_history.concat(data);
if (history_table) {
history_table.rows.add(data).draw(false);
}
$('.loading-more-history').hide();
// Show "load more" if there is more to load
if (data.length === history_items_limit) {
$('.more_history_button').show();
}
});
});
}
var calendar = null,
selected_shift = null,
vw = null;
/**
* A partner can exchange shifts if:
* - s.he doesn't have to choose a makeup shift
* - s.he's not an associated partner
* @returns boolean
*/
function can_exchange_shifts() {
return partner_data.makeups_to_do == 0 && partner_data.is_associated_people === "False";
}
/**
* A partner should select a shift if:
* - s.he has makeups to do
* - s.he's not an associated partner
* @returns boolean
*/
function should_select_makeup() {
return partner_data.makeups_to_do > 0 && partner_data.is_associated_people === "False";
}
/**
* Proceed to shift exchange or registration
* @param {int} new_shift_id
*/
function add_or_change_shift(new_shift_id) {
if (is_time_to('change_shift')) {
setTimeout(openModal, 100); // loading on
tData = 'idNewShift=' + new_shift_id
+'&idPartner=' + partner_data.partner_id
+ '&shift_type=' + partner_data.shift_type
+ '&verif_token=' + partner_data.verif_token;
if (selected_shift === null) {
tUrl = '/shifts/add_shift';
} else {
tUrl = '/shifts/change_shift';
tData = tData + '&idOldShift='+ selected_shift.shift_id[0] +'&idRegister=' + selected_shift.id;
}
$.ajax({
type: 'POST',
url: tUrl,
dataType:"json",
data: tData,
timeout: 3000,
success: function(data) {
if (data.result) {
// Decrement makeups to do if needed
if (partner_data.makeups_to_do > 0) {
partner_data.makeups_to_do = parseInt(partner_data.makeups_to_do, 10) - 1;
if (partner_data.makeups_to_do === 0) {
$("#need_to_select_makeups_message").hide();
} else {
$(".makeups_nb").text(partner_data.makeups_to_do);
}
}
let msg = "Parfait! ";
msg += (selected_shift === null)
? "Le service choisi a été ajouté."
: "Le service a été échangé.";
selected_shift = null;
// Refetch partner shifts list & update DOM
load_partner_shifts(partner_data.concerned_partner_id)
.then(() => {
init_shifts_list();
closeModal();
setTimeout(() => {
alert(msg);
}, 100);
});
// Redraw calendar
calendar.refetchEvents();
}
},
error: function(error) {
closeModal();
selected_shift = null;
if (error.status === 400) {
alert(`Désolé ! Le service que tu souhaites échanger démarre dans moins de 24h. ` +
`Afin de faciliter la logistique des services, il n'est plus possible de l'échanger. ` +
`Si tu ne peux vraiment pas venir, tu seras noté.e absent.e à ton service. ` +
`Tu devras alors sélectionner un service de rattrapage sur ton espace membre.`);
} else {
alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` +
`veuillez patienter quelques secondes puis vérifier vos services enregistrés.`);
}
// Refectch shifts anyway, if registration/exchange was still succesful
setTimeout(() => {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_shifts_list);
}, 300);
}
});
}
}
function init_shifts_list() {
$(".loading-incoming-shifts").hide();
$("#shifts_list").show();
if (incoming_shifts.length === 0) {
$("#shifts_list").text("Aucun service à venir...");
} else {
$("#shifts_list").empty();
for (shift of incoming_shifts) {
let shift_line_template = $("#selectable_shift_line_template");
let datetime_shift_start = new Date(shift.date_begin.replace(/\s/, 'T'));
let f_date_shift_start = datetime_shift_start.toLocaleDateString("fr-fr", date_options);
f_date_shift_start = f_date_shift_start.charAt(0).toUpperCase() + f_date_shift_start.slice(1);
shift_line_template.find(".shift_line_date").text(f_date_shift_start);
shift_line_template.find(".shift_line_time").text(datetime_shift_start.toLocaleTimeString("fr-fr", time_options));
if (!can_exchange_shifts()) {
shift_line_template.find(".selectable_shift_line").removeClass("btn--primary");
shift_line_template.find(".selectable_shift_line").addClass("btn");
shift_line_template.find(".checkbox").prop("disabled", "disabled");
} else {
shift_line_template.find(".selectable_shift_line").removeClass("btn");
shift_line_template.find(".selectable_shift_line").addClass("btn--primary");
shift_line_template.find(".checkbox").prop("disabled", false);
shift_line_template.find(".checkbox").prop("value", shift.id);
}
$("#shifts_list").append(shift_line_template.html());
}
$(".selectable_shift_line").on("click", function(e) {
if (can_exchange_shifts()) {
let cb = $(this).find(".checkbox");
// Select checkbox on click on button
if (!$(e.target).hasClass("checkbox")) {
cb.prop("checked", !cb.prop("checked"));
}
if (cb.prop("checked")) {
selected_shift = incoming_shifts.find(s => s.id == cb.prop("value"));
} else {
selected_shift = null;
}
// Unselect other checkboxes
if ($(this).find(".checkbox")
.prop("checked")) {
for (let cb_item of $("#shifts_list").find(".checkbox")) {
if (cb.prop("value") !== $(cb_item).prop("value")) {
$(cb_item).prop("checked", false);
}
}
}
}
});
}
}
/**
* Inits the page when the calendar is displayed
*/
function init_calendar_page() {
let template_explanations = $("#calendar_explaination_template");
if (vw <= 992) {
$(".loading-calendar").show();
$("#calendar_explaination_area").hide();
$("#calendar_explaination_button").on("click", () => {
openModal(
template_explanations.html(),
closeModal,
"J'ai compris"
);
})
.show();
} else {
$("#calendar_explaination_button").hide();
$("#calendar_explaination_area").html(template_explanations.html())
.show();
}
if (incoming_shifts !== null) {
init_shifts_list();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_shifts_list);
}
if (should_select_makeup()) {
$(".makeups_nb").text(partner_data.makeups_to_do);
$("#need_to_select_makeups_message").show();
}
let default_initial_view = "";
let header_toolbar = {};
if (vw <= 768) {
default_initial_view = 'listWeek';
header_toolbar = {
left: 'title',
center: 'listWeek,timeGridDay',
right: 'prev,next today'
};
} else if (vw <=992) {
default_initial_view = 'listWeek';
header_toolbar = {
left: 'title',
center: 'dayGridMonth,listWeek,timeGridDay',
right: 'prev,next today'
};
} else {
default_initial_view = 'dayGridMonth';
header_toolbar = {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listWeek,timeGridDay'
};
}
const hidden_days = days_to_hide.length > 0 ? $.map(days_to_hide.split(", "), Number) : [];
const calendarEl = document.getElementById('calendar');
calendar = new FullCalendar.Calendar(calendarEl, {
locale: 'fr',
initialView: default_initial_view,
headerToolbar: header_toolbar,
buttonText: {
list: "Semaine"
},
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit'
},
allDaySlot: false,
contentHeight: "auto",
eventDisplay: "block",
hiddenDays: hidden_days,
events: '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id,
eventClick: function(info) {
if (!$(info.el).hasClass("shift_booked")) {
const new_shift_id = info.event.id;
// Set new shift
const datetime_new_shift = info.event.start;
let new_shift_date = datetime_new_shift.toLocaleDateString("fr-fr", date_options);
let new_shift_time = datetime_new_shift.toLocaleTimeString("fr-fr", time_options);
if (selected_shift !== null && can_exchange_shifts()) {
/* shift exchange */
// Set old shift
let datetime_old_shift = new Date(selected_shift.date_begin);
let old_shift_date = datetime_old_shift.toLocaleDateString("fr-fr", date_options);
let old_shift_time = datetime_old_shift.toLocaleTimeString("fr-fr", time_options);
// Display modal
let modal_template = $("#modal_shift_exchange_template");
modal_template.find(".date_old_shift").text(old_shift_date);
modal_template.find(".time_old_shift").text(old_shift_time);
modal_template.find(".date_new_shift").text(new_shift_date);
modal_template.find(".time_new_shift").text(new_shift_time);
openModal(
modal_template.html(),
() => {
add_or_change_shift(new_shift_id);
},
"Valider"
);
} else if (selected_shift === null && can_exchange_shifts()) {
/* could exchange shift but no old shift selected */
openModal(
"Je dois sélectionner un service à échanger.",
closeModal,
"J'ai compris"
);
} else if (should_select_makeup()) {
/* choose a makeup service */
// Check if selected new shift is in less than 6 months
if (partner_data.date_delay_stop !== 'False') {
date_partner_delay_stop = new Date(partner_data.date_delay_stop);
if (datetime_new_shift > date_partner_delay_stop) {
let msg = `Vous avez jusqu'au ${date_partner_delay_stop.toLocaleDateString("fr-fr", date_options)} ` +
`pour sélectionner un rattrapage (soit une période de 6 mois depuis votre absence).`;
alert(msg);
return;
}
}
let modal_template = $("#modal_add_shift_template");
modal_template.find(".date_new_shift").text(new_shift_date);
modal_template.find(".time_new_shift").text(new_shift_time);
openModal(
modal_template.html(),
() => {
add_or_change_shift(new_shift_id);
},
"Valider"
);
}
}
},
eventDidMount: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide();
$("#calendar").show();
if (vw <= 992) {
$(".fc .fc-header-toolbar").addClass("resp-header-toolbar");
} else {
$(".fc .fc-header-toolbar").removeClass("resp-header-toolbar");
}
}
});
calendar.render();
}
function init_read_only_calendar_page() {
let template_explanations = $("#calendar_explaination_template");
if (vw <= 992) {
$(".loading-calendar").show();
$("#calendar_explaination_area").hide();
$("#calendar_explaination_button").on("click", () => {
openModal(
template_explanations.html(),
closeModal,
"J'ai compris"
);
})
.show();
} else {
$("#calendar_explaination_button").hide();
$("#calendar_explaination_area").html(template_explanations.html())
.show();
}
if (incoming_shifts !== null) {
init_shifts_list();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_shifts_list);
}
if (should_select_makeup()) {
$(".makeups_nb").text(partner_data.makeups_to_do);
$("#need_to_select_makeups_message").show();
}
let default_initial_view = "";
let header_toolbar = {};
if (vw <= 768) {
default_initial_view = 'listWeek';
header_toolbar = {
left: 'title',
center: 'listWeek,timeGridDay',
right: 'prev,next today'
};
} else if (vw <=992) {
default_initial_view = 'listWeek';
header_toolbar = {
left: 'title',
center: 'dayGridMonth,listWeek,timeGridDay',
right: 'prev,next today'
};
} else {
default_initial_view = 'dayGridMonth';
header_toolbar = {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listWeek,timeGridDay'
};
}
const hidden_days = days_to_hide.length > 0 ? $.map(days_to_hide.split(", "), Number) : [];
const calendarEl = document.getElementById('read_only_calendar');
console.log(calendarEl)
calendar = new FullCalendar.Calendar(calendarEl, {
locale: 'fr',
initialView: default_initial_view,
headerToolbar: header_toolbar,
buttonText: {
list: "Semaine"
},
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit'
},
allDaySlot: false,
contentHeight: "auto",
eventDisplay: "block",
hiddenDays: hidden_days,
events: '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id,
eventDidMount: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide();
$("#calendar").show();
if (vw <= 992) {
$(".fc .fc-header-toolbar").addClass("resp-header-toolbar");
} else {
$(".fc .fc-header-toolbar").removeClass("resp-header-toolbar");
}
}
});
calendar.render();
}
function init_shifts_exchange() {
$(".shifts_exchange_page_content").hide();
vw = window.innerWidth;
if (partner_data.cooperative_state === 'unsubscribed' || partner_data.cooperative_state === 'gone') {
$("#unsuscribed_content").show();
$(".unsuscribed_form_link")
.show()
.attr('href', unsuscribe_form_link)
.on('click', function() {
setTimeout(500, () => {
$(this).removeClass('active');
});
});
} else if (
partner_data.cooperative_state === 'suspended'
&& partner_data.can_have_delay === 'False') {
let msg_template = $("#cant_have_delay_msg_template");
$(".suspended_cant_have_delay_msg").html(msg_template.html());
$("#suspended_cant_have_delay_content").show();
$(".cant_have_delay_form_link")
.show()
.attr('href', member_cant_have_delay_form_link)
.on('click', function() {
setTimeout(500, () => {
$(this).removeClass('active');
});
});
} else if (
partner_data.comite === "True") {
let msg_template = $("#comite_template");
$(".comite_content_msg").html(msg_template.html());
$("#comite_content").show();
init_read_only_calendar_page();
} else if (partner_data.cooperative_state === 'suspended'
&& partner_data.date_delay_stop === 'False') {
$("#suspended_content .makeups_nb").text(partner_data.makeups_to_do);
$("#suspended_content").show();
$(".select_makeups").on('click', () => {
openModal();
// Create 6 month delay
request_delay()
.then(() => {
$("#suspended_content").hide();
$("#shifts_exchange_content").show();
closeModal();
init_calendar_page();
});
});
} else {
$("#shifts_exchange_content").show();
init_calendar_page();
}
$(window).smartresize(function() {
vw = window.innerWidth;
init_calendar_page();
});
}
/**
* Common logic between pages
*/
var base_location = null,
current_location = null,
incoming_shifts = null,
partner_history = null;
var date_options = {weekday: "long", year: "numeric", month: "long", day: "numeric"};
var time_options = {hour: '2-digit', minute:'2-digit'};
const possible_cooperative_state = {
suspended: "Rattrapage",
exempted: "Exempté.e",
alert: "En alerte",
up_to_date: "À jour",
unsubscribed: "Désinscrit.e des créneaux",
delay: "En délai",
gone: "Parti.e"
};
/* - Data */
/**
* Load the shifts the member is registered to
* @param {int} partner_id either the members id, or its parent's if s.he's attached
*/
function load_partner_shifts(partner_id) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/shifts/get_list_shift_partner/" + partner_id,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
incoming_shifts = data;
resolve();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des services", ctx: 'load_partner_shifts'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.index');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de vos services.');
}
});
});
}
/* - Navigation */
/**
* @param {String} page home | mes-infos | mes-services | echange-de-services | faq
*/
function goto(page) {
if (window.location.pathname === base_location) {
history.pushState({}, '', page);
} else {
history.replaceState({}, '', page);
}
update_dom();
}
/**
* Define which html content to load from server depending on the window location
*
* WARNING: For the routing system to work,
* public urls (those the users will see & navigate to) must be different than the server urls used to fetch resources
* (ex: public url: /members_space/mes-info ; server url: /members_space/my_info)
*/
function update_dom() {
$(".nav_item").removeClass('active');
if (window.location.pathname === base_location || window.location.pathname === base_location + "home") {
current_location = "home";
$("#main_content").load("/members_space/homepage", update_content);
$("#nav_home").addClass("active");
} else if (window.location.pathname === base_location + "mes-infos") {
current_location = "my_info";
$("#main_content").load("/members_space/my_info", update_content);
$("#nav_my_info").addClass("active");
} else if (window.location.pathname === base_location + "mes-services") {
current_location = "my_shifts";
$("#main_content").load("/members_space/my_shifts", update_content);
$("#nav_my_shifts").addClass("active");
} else if (window.location.pathname === base_location + "faq") {
current_location = "faq";
$("#main_content").load("/members_space/faqBDM", update_content);
$("#nav_faq").addClass("active");
} else if (window.location.pathname === base_location + "echange-de-services") {
current_location = "shifts_exchange";
$("#main_content").load("/members_space/shifts_exchange", update_content);
$("#nav_shifts_exchange").addClass("active");
} else {
$("#main_content").load("/members_space/no_content");
}
}
/**
* Update the data displayed depending on the current location
* (ex: insert personal data in the DOM when on the 'My Info' page)
*/
function update_content() {
switch (current_location) {
case 'home':
init_home();
break;
case 'my_info':
init_my_info();
break;
case 'my_shifts':
init_my_shifts();
break;
case 'faq':
init_faq();
break;
case 'shifts_exchange':
init_shifts_exchange();
break;
default:
console.log(`Bad input`);
}
}
/* - Shifts */
/**
* Prepare a shift line to insert into the DOM.
* Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section
*
* @param {String} date_begin beginning datetime of the shift
* @returns JQuery node object of the formatted template
*/
function prepare_shift_line_template(date_begin) {
let shift_line_template = $("#shift_line_template");
let datetime_shift_start = new Date(date_begin.replace(/\s/, 'T'));
let f_date_shift_start = datetime_shift_start.toLocaleDateString("fr-fr", date_options);
f_date_shift_start = f_date_shift_start.charAt(0).toUpperCase() + f_date_shift_start.slice(1);
shift_line_template.find(".shift_line_date").text(f_date_shift_start);
shift_line_template.find(".shift_line_time").text(datetime_shift_start.toLocaleTimeString("fr-fr", time_options));
return shift_line_template;
}
/* - Member info */
/**
* Init common personal data between screens
*/
function init_my_info_data() {
$(".choose_makeups").off();
$(".unsuscribed_form_link").off();
$(".member_shift_name").text(partner_data.regular_shift_name);
let pns = partner_data.name.split(" - ");
let name = pns.length > 1 ? pns[1] : pns[0];
$(".member_name").text(name);
// Status related
$(".member_status")
.text(possible_cooperative_state[partner_data.cooperative_state])
.addClass("member_status_" + partner_data.cooperative_state);
if (partner_data.cooperative_state === 'delay' && partner_data.date_delay_stop !== 'False') {
const d = new Date(Date.parse(partner_data.date_delay_stop));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
} else if (partner_data.cooperative_state === 'unsubscribed' || partner_data.cooperative_state === 'gone') {
$(".member_shift_name").text('X');
$(".unsuscribed_form_link")
.show()
.attr('href', unsuscribe_form_link)
.on('click', function() {
setTimeout(500, () => {
$(this).removeClass('active');
});
});
} else if (partner_data.cooperative_state === 'exempted') {
const d = new Date(Date.parse(partner_data.leave_stop_date));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
}
if (
partner_data.makeups_to_do > 0
&& partner_data.is_associated_people === "False"
&& partner_data.cooperative_state !== 'unsubscribed'
) {
$(".choose_makeups").show();
if (
partner_data.cooperative_state === 'suspended'
&& partner_data.date_delay_stop === 'False') {
// If the member is suspended & doesn't have a delay
$(".choose_makeups").on('click', () => {
// Create 6 month delay
request_delay()
.then(() => {
// Then redirect to calendar
goto('echange-de-services');
});
});
} else {
$(".choose_makeups").on('click', () => {
goto('echange-de-services');
});
}
}
$(".member_coop_number").text(partner_data.barcode_base);
}
$(document).ready(function() {
// TODO essayer de ne charger les js que au besoin
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// If partner is associated (attached), display the pair's main partner shift data
partner_data.concerned_partner_id =
(partner_data.is_associated_people === "True")
? partner_data.parent_id
: partner_data.partner_id;
partner_data.is_in_association =
partner_data.is_associated_people === "True" || partner_data.associated_partner_id !== "False";
// For associated people, their parent name is attached in their display name
let partner_name_split = partner_data.name.split(', ');
partner_data.name = partner_name_split[partner_name_split.length - 1];
base_location = (app_env === 'dev') ? '/members_space/' : '/';
update_dom();
window.onpopstate = function() {
update_dom();
};
});
(function($, sr) {
// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
};
// smartresize
jQuery.fn[sr] = function(fn) {
return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr);
};
})(jQuery, 'smartresize');
\ No newline at end of file
"""."""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index),
url(r'^homepage$', views.home), # These endpoints must be different than in-app url
url(r'^my_info$', views.my_info),
url(r'^my_shifts$', views.my_shifts),
url(r'^shifts_exchange$', views.shifts_exchange),
url(r'^faqBDM$', views.faqBDM),
url(r'^no_content$', views.no_content),
url(r'^get_shifts_history$', views.get_shifts_history),
url(r'^.*', views.index) # Urls unknown from the server will redirect to index
]
from outils.common_imports import *
from outils.for_view_imports import *
from django.urls import reverse
from outils.common import Verification
from outils.common import MConfig
from members.models import CagetteMember
from shifts.models import CagetteShift
from members_space.models import CagetteMembersSpace
import hashlib
def _get_response_according_to_credentials(request, credentials, context, template):
response = HttpResponse(template.render(context, request))
if ('token' in credentials and 'auth_token' in credentials):
response.set_cookie('id', credentials['id'])
response.set_cookie('token', credentials['token'])
response.set_cookie('auth_token', credentials['auth_token'])
response.set_cookie('deconnect_option', 'true')
return response
def index(request, exception=None):
"""Display main screen for the members space"""
credentials = CagetteMember.get_credentials(request)
context = {
'title': 'Espace Membre',
}
template = loader.get_template('members_space/index.html')
if ('failure' in credentials):
# Bad credentials (or none)
template = loader.get_template('website/connect.html')
context['msg'] = ''
if 'msg' in credentials:
context['msg'] = credentials['msg']
context['password_placeholder'] = 'Naissance (jjmmaaaa)'
context['is_member_space'] = True
elif ('validation_state' in credentials) and credentials['validation_state'] == 'waiting_validation_member':
# First connection, until the member validated his account
template = loader.get_template('members/validation_coop.html')
referer = request.META.get('HTTP_REFERER')
doc = CagetteMember.get_couchdb_data(credentials['login'])
if len(doc) > 1:
context = {'title': 'Validation inscription',
'coop': json.dumps(doc),
'coop_msg': doc.get('coop_msg'),
'warning_placeholder':
"""Signaler ici une anomalie du formulaire,
un problème lié à votre souscription""",
'referer': referer,
'mag_place_string': settings.MAG_NAME,
'office_place_string': settings.OFFICE_NAME,
'max_begin_hour': settings.MAX_BEGIN_HOUR,
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'em_url': settings.EM_URL,
'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG,
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
if hasattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX'):
context['ask_for_sex'] = settings.SUBSCRIPTION_ASK_FOR_SEX
if hasattr(settings, 'SUBSCRIPTION_ADD_STREET2'):
context['ask_for_street2'] = settings.SUBSCRIPTION_ADD_STREET2
if hasattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE'):
context['ask_for_second_phone'] = settings.SUBSCRIPTION_ADD_SECOND_PHONE
else:
# Members space
if 'id' in request.COOKIES:
partner_id = request.COOKIES['id']
else:
partner_id = credentials['id']
cs = CagetteShift()
partnerData = cs.get_data_partner(partner_id)
if 'create_date' in partnerData:
md5_calc = hashlib.md5(partnerData['create_date'].encode('utf-8')).hexdigest()
partnerData['verif_token'] = md5_calc
# Error case encountered from Odoo: member in delay state and last extension is over -> member is suspended
try:
if partnerData['cooperative_state'] == "delay" and datetime.datetime.strptime(partnerData['date_delay_stop'], '%Y-%m-%d') < datetime.datetime.now():
partnerData['cooperative_state'] = "suspended"
except:
pass
# look for parent for associated partner
if partnerData["parent_id"] is not False:
partnerData["parent_name"] = partnerData["parent_id"][1]
partnerData["parent_id"] = partnerData["parent_id"][0]
else:
partnerData["parent_name"] = False
# look for associated partner for parents
cm = CagetteMember(partner_id)
associated_partner = cm.search_associated_people()
partnerData["associated_partner_id"] = False if associated_partner is None else associated_partner["id"]
partnerData["associated_partner_name"] = False if associated_partner is None else associated_partner["name"]
if (associated_partner is not None and partnerData["associated_partner_name"].find(str(associated_partner["barcode_base"])) == -1):
partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"]
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partner_id))
m = CagetteMembersSpace()
partnerData["comite"] = m.is_comite(partner_id)
context['partnerData'] = partnerData
# Days to hide in the calendar
days_to_hide = "0"
context['ADDITIONAL_INFO_SHIFT_PAGE'] = getattr(settings, 'ADDITIONAL_INFO_SHIFT_PAGE', '')
if hasattr(settings, 'SHIFT_EXCHANGE_DAYS_TO_HIDE'):
days_to_hide = settings.SHIFT_EXCHANGE_DAYS_TO_HIDE
context['daysToHide'] = days_to_hide
msettings = MConfig.get_settings('members')
context['forms_link'] = msettings['forms_link']['value'] if 'forms_link' in msettings else ''
context['unsuscribe_form_link'] = ( msettings['unsuscribe_form_link']['value']
if 'unsuscribe_form_link' in msettings
else '')
context['member_cant_have_delay_form_link'] = ( msettings['member_cant_have_delay_form_link']['value']
if 'member_cant_have_delay_form_link' in msettings
else '')
context['abcd_calendar_link'] = ( msettings['abcd_calendar_link']['value']
if 'abcd_calendar_link' in msettings
else '')
context['request_form_link'] = msettings['request_form_link']['value'] if 'request_form_link' in msettings else ''
context['late_service_form_link'] = msettings['late_service_form_link']['value'] if 'late_service_form_link' in msettings else ''
context['change_template_form_link'] = msettings['change_template_form_link']['value'] if 'change_template_form_link' in msettings else ''
context['associated_subscribe_form_link'] = msettings['associated_subscribe_form_link']['value'] if 'associated_subscribe_form_link' in msettings else ''
context['associated_unsubscribe_form_link'] = msettings['associated_unsubscribe_form_link']['value'] if 'associated_unsubscribe_form_link' in msettings else ''
context['template_unsubscribe_form_link'] = msettings['template_unsubscribe_form_link']['value'] if 'template_unsubscribe_form_link' in msettings else ''
context['change_email_form_link'] = msettings['change_email_form_link']['value'] if 'change_email_form_link' in msettings else ''
context['coop_unsubscribe_form_link'] = msettings['coop_unsubscribe_form_link']['value'] if 'coop_unsubscribe_form_link' in msettings else ''
context['sick_leave_form_link'] = msettings['sick_leave_form_link']['value'] if 'sick_leave_form_link' in msettings else ''
context['underage_subscribe_form_link'] = msettings['underage_subscribe_form_link']['value'] if 'underage_subscribe_form_link' in msettings else ''
context['helper_subscribe_form_link'] = msettings['helper_subscribe_form_link']['value'] if 'helper_subscribe_form_link' in msettings else ''
context['helper_unsubscribe_form_link'] = msettings['helper_unsubscribe_form_link']['value'] if 'helper_unsubscribe_form_link' in msettings else ''
context['covid_form_link'] = msettings['covid_form_link']['value'] if 'covid_form_link' in msettings else ''
context['covid_end_form_link'] = msettings['covid_end_form_link']['value'] if 'covid_end_form_link' in msettings else ''
else:
# may arrive when switching database without cleaning cookie
return redirect('/website/deconnect')
return _get_response_according_to_credentials(request, credentials, context, template)
def home(request):
"""
Endpoint the front-end will call to load the "home" page.
Consequently, the front-end url should be unknown from the server so the user is redirected to the index,
then the front-end index will call this endpoint to load the home page
"""
template = loader.get_template('members_space/home.html')
context = {
'title': 'Espace Membres',
}
# Get messages to display
msettings = MConfig.get_settings('members')
if 'msg_accueil' in msettings:
context['msg_accueil'] = msettings['msg_accueil']['value']
if 'shop_opening_hours' in msettings:
context['shop_opening_hours'] = msettings['shop_opening_hours']['value']
return HttpResponse(template.render(context, request))
def my_info(request):
""" Endpoint the front-end will call to load the "My info" page. """
template = loader.get_template('members_space/my_info.html')
context = {
'title': 'Mes Infos',
}
return HttpResponse(template.render(context, request))
def my_shifts(request):
""" Endpoint the front-end will call to load the "My shifts" page. """
template = loader.get_template('members_space/my_shifts.html')
context = {
'title': 'Mes Services',
}
return HttpResponse(template.render(context, request))
def shifts_exchange(request):
""" Endpoint the front-end will call to load the "Shifts exchange" page. """
template = loader.get_template('members_space/shifts_exchange.html')
context = {
'title': 'Échange de Services',
}
return HttpResponse(template.render(context, request))
def faqBDM(request):
template = loader.get_template('members_space/faq.html')
context = {
'title': 'foire aux questions',
}
msettings = MConfig.get_settings('members')
return HttpResponse(template.render(context, request))
def no_content(request):
""" Endpoint the front-end will call to load the "No content" page. """
template = loader.get_template('members_space/no_content.html')
context = {
'title': 'Contenu non trouvé',
}
return HttpResponse(template.render(context, request))
def get_shifts_history(request):
res = {}
partner_id = int(request.GET.get('partner_id'))
m = CagetteMembersSpace()
limit = int(request.GET.get('limit'))
offset = int(request.GET.get('offset'))
date_from = getattr(settings, 'START_DATE_FOR_SHIFTS_HISTORY', '2018-01-01')
res["data"] = m.get_shifts_history(partner_id, limit, offset, date_from)
return JsonResponse(res)
\ No newline at end of file
......@@ -82,48 +82,6 @@
justify-content: flex-start;
}
.right_action_buttons {
display: flex;
}
#actions_buttons_wrapper {
position: relative;
margin-right: 5px;
}
#toggle_action_buttons {
width: 250px;
position: relative;
}
.toggle_action_buttons_icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 15px;
}
#actions_buttons_container {
position: absolute;
display: flex;
flex-direction: column;
width: 250px;
display: none;
}
.action_button {
width: 100%;
min-height: 45px;
}
#do_inventory {
border-top: 1px solid #004aa6;
}
#refresh_order {
border-top: 1px solid #004aa6;
border-bottom: 1px solid #004aa6;
}
/* -- Order data */
#order_data_container {
font-size: 1.8rem;
......@@ -148,23 +106,6 @@
border-radius: 3px;
}
#coverage_form > div {
display:inline-block;
float:left;
}
#coverage_form .input-wrapper {
margin-right: 3px;
}
#coverage_days_input, #percent_adjust_input {
display: block;
}
#coverage_days_input {
margin-bottom: 3px;
}
/* -- Table */
#products_table_filter{
text-align: right !important;
......@@ -192,10 +133,6 @@
margin-left: 5px;
}
.main.fa-info-circle {
color: #0275d8;
cursor: pointer;
}
.custom_cell_content {
display: flex;
flex-direction: column;
......@@ -292,15 +229,6 @@
width: 90%;
}
/* product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
margin: auto;
}
.npa-options label {
display: block;
}
/* - Orders created screen */
.order_created_header {
......@@ -358,12 +286,4 @@
background-color: #e7e9ed;
width: 100%;
padding: 15px;
}
/* - Miscellaneous */
footer {
display: none;
}
\ No newline at end of file
......@@ -149,42 +149,29 @@ function add_product() {
* Set the computed qty for the first supplier only.
*/
function compute_products_coverage_qties() {
const pc_adjust = $('#percent_adjust_input').val();
let coeff = 1;
if (!isNaN(parseFloat(pc_adjust))) {
coeff = (1 + parseFloat(pc_adjust) /100);
}
if (order_doc.coverage_days != null) {
order_doc.coeff = coeff;
for (const [
key,
product
] of Object.entries(products)) {
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
const stock = product.qty_available;
const incoming_qty = product.incoming_qty;
const daily_conso = product.daily_conso;
for (const [
key,
product
] of Object.entries(products)) {
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
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 + product.minimal_stock;
purchase_qty_for_coverage = (purchase_qty_for_coverage < 0) ? 0 : purchase_qty_for_coverage;
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;
// Reduce to nb of packages to purchase
purchase_package_qty_for_coverage = purchase_qty_for_coverage / product.suppliersinfo[0].package_qty;
if (order_doc.coeff != 1) {
purchase_package_qty_for_coverage *= order_doc.coeff;
}
// Round up to unit for all products
purchase_package_qty_for_coverage = Math.ceil(purchase_package_qty_for_coverage);
// Round up to unit for all products
purchase_package_qty_for_coverage = Math.ceil(purchase_package_qty_for_coverage);
// Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
}
// Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
}
}
}
......@@ -198,7 +185,7 @@ function check_products_data() {
if (suppliers_id.length > 0) {
$.notify(
"Vérification des informations produits...",
"Vérfication des informations produits...",
{
globalPosition:"top left",
className: "info"
......@@ -512,53 +499,54 @@ 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};
inputs.each(function (i,e) {
const input = $(e)
if (input.attr('type') == 'checkbox') {
if (input.prop('checked') == true) {
actions.npa.push(input.val())
}
} else if (input.attr('name') == "minimal_stock") {
actions.minimal_stock = input.val()
}
});
/**
* Update 'purchase_ok' of a product
*
* @param {int} p_id product id
* @param {Boolean} npa value to set purchase_ok to
*/
function set_product_npa(p_id, npa) {
openModal();
const data = {
product_tmpl_id: p_id,
purchase_ok: !npa
};
// Fetch supplier products
$.ajax({
type: "POST",
url: "/products/update_npa_and_minimal_stock",
url: "/products/update_product_purchase_ok",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(actions),
data: JSON.stringify(data),
success: () => {
const index = products.findIndex(p => p.id == product.id);
const index = products.findIndex(p => p.id == p_id);
// Give time for modal to fade
setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify(
"Actions enregistrées !",
$.notify(
"Produit passé en NPA !",
{
globalPosition:"top right",
className: "success"
}
);
}, 500);
products[index].minimal_stock = actions.minimal_stock;
if (actions.npa.length > 0) {
// Remove NPA products
products.splice(index, 1);
update_main_screen();
update_cdb_order();
}
// Remove NPA products
products.splice(index, 1);
update_main_screen();
update_cdb_order();
closeModal();
},
error: function(data) {
let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${product.id})`;
let msg = "erreur serveur lors de la sauvegarde du NPA".
msg += ` (product_tmpl_id: ${p_id})`;
err = {msg: msg, ctx: 'save_products_npa_minimal_stock'};
err = {msg: msg, ctx: 'set_product_npa'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
......@@ -569,10 +557,8 @@ function save_products_npa_minimal_stock(product, inputs) {
update_main_screen();
}
});
}
/* - INVENTORY */
/**
......@@ -1128,8 +1114,8 @@ function prepare_datatable_columns() {
{
data: "id",
title: `<div id="table_header_select_all" class="txtcenter">
<!--<span class="select_all_text">Sélectionner</span>-->
<label for="select_all_products_cb">Tout</label>
<span class="select_all_text">Sélectionner</span>
<label for="select_all_products_cb">- Tout</label>
<input type="checkbox" class="select_product_cb" id="select_all_products_cb" name="select_all_products_cb" value="all">
</div>`,
className: "dt-body-center",
......@@ -1243,11 +1229,12 @@ function prepare_datatable_columns() {
});
columns.push({
title: ``,
data: "purchase_ok",
title: `NPA`,
className: "dt-body-center",
orderable: false,
render: function (data) {
return `<button type="button" class="btn--primary product_actions">Actions</button>`;
return `<input type="checkbox" class="product_npa_cb" value="purchase_ok" ${data ? '' : 'checked'}>`;
},
width: "4%"
});
......@@ -1275,7 +1262,7 @@ function display_products(params) {
const data = prepare_datatable_data();
const columns = prepare_datatable_columns();
let sort_order_dir = "desc";
let sort_order_dir = "asc";
if (params != undefined && typeof params.sort_order_dir != "undefined") {
sort_order_dir = params.sort_order_dir;
......@@ -1285,11 +1272,11 @@ function display_products(params) {
columns: columns,
order: [
[
5, // Order by default by first supplier
6, // Order by default by first supplier
sort_order_dir
]
],
stateSave: true,
stripeClasses: [], // Remove datatable cells coloring
orderClasses: false,
aLengthMenu: [
[
......@@ -1355,71 +1342,7 @@ function display_products(params) {
} else {
$(this).val('');
}
})
.on('keypress', 'tbody td .product_qty_input', function(e) {
// Validate on Enter pressed
if (e.which == 13) {
$(this).blur();
}
})
.on('keydown', 'tbody td .product_qty_input', function(e) {
if (e.which == 38) {
e.preventDefault();
// On arrow up pressed, focus next row input
let next_input = $(this).closest("tr").prev().find(".product_qty_input");
next_input.focus();
// Scroll to a position where the target input is not hidden by the sticky suppliers container
const suppliers_container_top_offset =
$("#suppliers_container").offset().top
- $(window).scrollTop()
+ $("#suppliers_container").outerHeight();
const next_input_top_offset = next_input.offset().top - $(window).scrollTop();
if (next_input_top_offset < suppliers_container_top_offset) {
window.scrollTo({
top: $(window).scrollTop() - $("#suppliers_container").outerHeight()
});
}
} else if (e.which == 40) {
e.preventDefault();
// On arrow down pressed, focus previous row input
$(this).closest("tr").next().find(".product_qty_input").focus();
} else if (e.which == 13) {
e.preventDefault();
// On enter pressed, focus previous row input
$(this).closest("tr").next().find(".product_qty_input").focus();
}
})
.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é');
openModal(
modal_product_actions.html(),
() => {
if (is_time_to('validate_product_actions')) {
save_products_npa_minimal_stock(product, modal.find('input'));
}
},
'Valider',
false
);
modal.find('input[name="minimal_stock"]').val(product.minimal_stock)
});
});
// Associate product to supplier on click on 'X' in the table
$('#products_table').on('click', 'tbody .product_not_from_supplier', function () {
......@@ -1515,7 +1438,34 @@ function display_products(params) {
}
});
// Set product is NPA (Ne Pas Acheter)
$('#products_table').on('click', 'tbody td .product_npa_cb', function () {
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const npa = this.checked;
const product = products.find(p => p.id == p_id);
let modal_product_npa = $('#templates #modal_product_npa');
modal_product_npa.find(".product_name").text(product.name);
modal_product_npa.find(".product_npa").text(npa ? 'Ne Pas Acheter' : 'Peut Être Acheté');
openModal(
modal_product_npa.html(),
() => {
if (is_time_to('validate_set_product_npa')) {
set_product_npa(p_id, npa);
}
},
'Valider',
false,
true,
() => {
this.checked = !this.checked;
}
);
});
return 0;
}
......@@ -1592,21 +1542,8 @@ function update_main_screen(params) {
} else {
$("#coverage_days_input").val('');
}
if (order_doc.coeff && order_doc.coeff != 1) {
$("#percent_adjust_input").val(-Math.ceil((1 - order_doc.coeff) * 100));
}
if (order_doc.stats_date_period !== undefined && order_doc.stats_date_period !== null) {
$("#stats_date_period_select").val(order_doc.stats_date_period);
} else {
$("#stats_date_period_select").val('');
}
}
function display_average_consumption_explanation() {
openModal($('#explanations').html())
}
/**
* Update DOM display on the order selection screen
*/
......@@ -1782,55 +1719,6 @@ $(document).ready(function() {
}
});
$("#refresh_order").on("click", function() {
if (is_time_to('refresh_order', 1000)) {
openModal();
check_products_data()
.then(() => {
update_cdb_order();
update_main_screen();
$("#toggle_action_buttons").click();
closeModal();
});
}
});
$("#delete_order_button").on("click", function() {
if (is_time_to('press_delete_order_button', 1000)) {
let modal_remove_order = $('#templates #modal_remove_order');
modal_remove_order.find(".remove_order_name").text(order_doc._id);
openModal(
modal_remove_order.html(),
() => {
if (is_time_to('validate_remove_order')) {
delete_cdb_order().then(() => {
update_order_selection_screen().then(() => {
reset_data();
switch_screen('order_selection');
setTimeout(function() {
$.notify(
"Commande supprimée !",
{
globalPosition:"top left",
className: "success"
}
);
}, 500);
});
})
.catch(() => {
console.log("error deleting order");
});
}
},
'Valider'
);
}
});
$('#back_to_order_selection_from_main').on('click', function() {
if (is_time_to('back_to_order_selection_from_main', 1000)) {
back();
......@@ -1889,8 +1777,6 @@ $(document).ready(function() {
return 0;
});
$(document).on("click",".fa-info-circle", display_average_consumption_explanation)
$.datepicker.regional['fr'] = {
monthNames: [
'Janvier',
......
from outils.common_imports import *
from outils.for_view_imports import *
from outils.common import OdooAPI
from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts
......@@ -20,10 +19,7 @@ def helper(request):
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': settings.ODOO['url'],
'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''),
'nb_past_days_to_compute_sales_average': OdooAPI().get_system_param('lacagette_products.nb_past_days_to_compute_sales_average'),
'nb_of_consecutive_non_sale_days_considered_as_break': OdooAPI().get_system_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break')
'odoo_server': settings.ODOO['url']
}
template = loader.get_template('orders/helper.html')
......
......@@ -47,7 +47,7 @@ class OdooAPI:
order='id ASC'):
"""Main api request, retrieving data according search conditions."""
fields_and_context = {'fields': fields,
'context': {'lang': 'fr_FR', 'tz': 'Europe/Paris'},
'context': {'lang': 'fr_FR','tz':'Europe/Paris'},
'limit': limit,
'offset': offset,
'order': order
......@@ -59,11 +59,8 @@ class OdooAPI:
def update(self, entity, ids, fields):
"""Update entities which have ids, with new fields values."""
context = {
'context': {'lang': 'fr_FR', 'tz': 'Europe/Paris'}
}
return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, 'write', [ids, fields], context)
entity, 'write', [ids, fields])
def create(self, entity, fields):
"""Create entity instance with given fields values."""
......@@ -77,18 +74,6 @@ class OdooAPI:
def authenticate(self, login, password):
return self.common.authenticate(self.db, login, password, {})
def get_system_param(self, key):
value = ''
try:
res = self.search_read('ir.config_parameter',
[['key', '=', key]],
['value'])
if res:
value = res[0]['value']
except Exception as e:
coop_logger.error('get_system_param: (%s) %s', key, str(e))
return value
class CouchDB:
"""Class to handle interactions with CouchDB"""
......
......@@ -325,16 +325,7 @@
- MEALS_PICKING_TYPE_ID = 10
### New members space
- USE_NEW_MEMBERS_SPACE = True
Should be set to False by default if parameter not set
- START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
- AMNISTIE_DATE = "2021-11-24 00:00:00"
In members_space history display a special activity about amnistie
### Miscellious
......
......@@ -187,17 +187,4 @@ footer { position: fixed;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=);
}
.notifyjs-cancelable-base .buttons {width: 190px; margin: 5px auto;}
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
button.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
button.accordion.active::after {
content: "\2212";
}
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
\ No newline at end of file
#main_content {text-align: center;}
.param {margin-bottom: 15px;}
.param label {font-weight: bold;}
input.link {min-width: 50em;}
.submit_button {margin-bottom: 10px;}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
background-color: #ccc;
}
/* Style the accordion panel. Note: hidden by default */
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
.param label {font-weight: bold;}
\ No newline at end of file
......@@ -455,22 +455,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)});
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -83,4 +83,4 @@ function get_module_settings() {
});
}
get_module_settings();
get_module_settings();
\ No newline at end of file
......@@ -7,7 +7,6 @@ import csv
import tempfile
import pymysql.cursors
import datetime
import re
vcats = []
......@@ -173,58 +172,10 @@ class CagetteProduct(models.Model):
return res
@staticmethod
def update_product_internal_ref(product_tmpl_id, default_code):
api = OdooAPI()
res = {}
f = {
'default_code': default_code
}
try:
res["update"] = api.update('product.template', product_tmpl_id, f)
except Exception as e:
res["error"] = str(e)
print(str(e))
return res
@staticmethod
def update_npa_and_minimal_stock(data):
"""Update NPA (ne pas acheter) and minimal stock data"""
res = {}
try:
api = OdooAPI()
f = {'minimal_stock': data['minimal_stock']}
if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']:
# Add [NPA] in product name if needed
f['name'] = data['name'] if ('[NPA]' in data['name']) else data['name'] + " [NPA]"
f['purchase_ok'] = 0
elif '[NPA]' in data['name']:
# Remove [NPA] from name
f['name'] = re.sub(r'( \[NPA\])', '', data['name'])
current_name = data['name'] if ('name' not in f) else f['name']
if 'fds-in-name' in data['npa']:
f['name'] = current_name if '[FDS]' in data['name'] else current_name + " [FDS]"
f['purchase_ok'] = 0
elif '[FDS]' in current_name:
f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0:
f['purchase_ok'] = 1
res["update"] = api.update('product.template', data['id'], f)
except Exception as e:
res["error"] = str(e)
coop_logger.error("update_npa_and_minimal_stock : %s %s", str(e), str(data))
return res
class CagetteProducts(models.Model):
"""Initially used to make massive barcode update."""
@staticmethod
def get_simple_list():
res = []
......@@ -545,8 +496,7 @@ class CagetteProducts(models.Model):
"uom_id",
"purchase_ok",
"supplier_taxes_id",
"product_variant_ids",
"minimal_stock"
"product_variant_ids"
]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
products_t = api.search_read('product.template', c, f)
......
......@@ -10,8 +10,6 @@ urlpatterns = [
url(r'^get_products_stdprices$', views.get_products_stdprices),
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'^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
......
......@@ -3,7 +3,6 @@
from outils.common_imports import *
from outils.for_view_imports import *
from members.models import CagetteUser
from products.models import CagetteProduct
from products.models import CagetteProducts
from inventory.models import CagetteInventory
......@@ -104,51 +103,14 @@ def update_product_stock(request):
def update_product_purchase_ok(request):
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"])
res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else:
return JsonResponse(res, status=403)
def update_product_internal_ref(request):
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else:
return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(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)
except Exception as e:
res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error'])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse(res, status=403)
return JsonResponse({"res": res})
def labels_appli_csv(request, params):
"""Generate files to put in DAV directory to be retrieved by scales app."""
......
......@@ -996,8 +996,6 @@ function editProductInfo (productToEdit, value = null, batch = false) {
}
firstUpdate = true;
} else if (productToEdit.old_price_unit == newValue) {
productToEdit.new_shelf_price = null;
}
productToEdit.price_unit = newValue;
......
......@@ -125,8 +125,6 @@ class CagetteShift(models.Model):
"shift_type": "standard", # ftop
"related_shift_state": 'confirm',
"state": 'open'}
if shift_type == "standard" and data['is_makeup'] is not True:
fieldsDatas['template_created'] = 1 # It's not true but otherwise, presence add 1 standard point, which is not wanted
st_r_id = self.o_api.create('shift.registration', fieldsDatas)
except Exception as e:
......@@ -181,7 +179,7 @@ class CagetteShift(models.Model):
action = 'create'
# Get partner extension ids
cond = [['id', '=', data['idPartner']]]
cond = [['id','=',data['idPartner']]]
fields = ['extension_ids']
partner_extensions = self.o_api.search_read('res.partner', cond, fields)
response = False
......
.main {
.main, .barcode_search_area {
margin-top: 40px;
}
.top_content {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.top_content_item {
flex: 1 0 33%;
}
#sm_barcode_selector {
border: 1px solid #555;
border-radius:10px;
......@@ -23,6 +11,10 @@
max-width: 60px;
}
#movement_validation_button {
margin-top: 20px;
}
.notifyjs-test1-base {
background: #5CB85C !important;
}
......
......@@ -203,7 +203,7 @@ function init_datatable() {
});
// Show validation button
$('.movement_validation_area').show();
$('.footer').show();
}
}
......@@ -351,7 +351,7 @@ var add_product = function(product) {
}
update_total_value();
$('.movement_validation_area').show(); // if is a second or more access, movement_validation_area is hidden (init_datatable is not fired)
$('.footer').show(); // if is a second or more access, footer is hidden (init_datatable is not fired)
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'add_product'};
console.error(err);
......@@ -706,7 +706,7 @@ function do_stock_movement() {
products = [];
products_table.clear().draw();
$('.movement_validation_area').hide();
$('.footer').hide();
},
......
......@@ -20,10 +20,6 @@
<div style="width: 90%" class="fl txtleft">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()">
<div style="width: 90%" class="fl txtleft">Enveloppe supprimée !</div>
<div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div>
</div>
<section class="grid-2 has-gutter">
<section id="cash">
......
......@@ -102,8 +102,6 @@
</div>
<div id="cooperative_state">
</div>
<div id="status_explanation" style="font-weight:bold;">
</div>
<div id="next_shifts">
</div>
</div>
......
<div id="faqBDM" class=" mt-3">
<div class="page_title txtcenter">
<h1>
Problèmes et Demandes
</h1>
</div>
<div class="tiles_container">
<div class="tile full_width_tile">
<div class="tile_content">
<div class="block">
<div class="faq_intro_texts">
<p>Je rencontre un problème ou j'ai une demande concernant mon statut de membre :</p>
<ul>
<li>
Je clique sur l'onglet qui correspond à ma demande puis je remplis le formulaire adéquat.
</li>
<li>
Un·e coopérateur·rice du Bureau Des Membres répondra à ma demande dès que possible.
</li>
</ul>
<div class="info_slots_shifts">
<p><i class="fas fa-exclamation-circle"></i> Créneau/service c'est quoi la différence?</p>
<p>Un créneau, c'est une plage récurrente, par exemple, tous les jeudi de semaine A de 13h30 à 16h30.</p>
<p>Un service, c'est une plage horaire en particulier, par exemple le jeudi 28 novembre de 13h30 à 16h30.</p>
</div>
</div>
<div class="param">
<button type="button" class="accordion btn_faq">
<span class="full_width">000 Gestion de mon créneau: réinscription, changement, désinscription temporaire (absence de moyenne ou longue durée)</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Créneau/service c'est quoi la différence?</b></h3>
<p>
Un créneau, c'est une plage récurrente, par exemple, tous les jeudi de semaine A de 13h30 à 16h30.<br/>
Un service, c'est une plage horaire en particulier, par exemple le jeudi 28 novembre de 13h30 à 16h30.
</p>
</div>
<div class="grp_text">
<h3><b>Se réinscrire à un créneau:</b></h3>
<p>
Si tu es désinscrit.e parce que tu as manqué des services sans faire tes rattrapages il faut te réinscrire à un créneau. Il te faudra tout de même faire tes rattrapages avant d'être à jour de tes services.<br/>
<b>ATTENTION:</b> si tu es en binôme c'est avec le nom du.de la titulaire qu'il faut remplir le formulaire.
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="unsuscribe_form_link_btn"
>
Réinscription à un créneau
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Changer de créneau:</b></h3>
<p>
Si ton créneau ne te convient plus tu peux demander à le changer.<br/>
<b>ATTENTION:</b> si tu es en binôme c'est avec le nom du.de la titulaire qu'il faut remplir le formulaire.
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="change_template_form_link_btn"
>
Changement de créneau
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Se désinscrire de son créneau:</b></h3>
<p>
- Si tu as prévu de t'absenter de Montpellier ou que tu vas rencontrer une période durant laquelle tu ne pourras pas faire tes services tu peux te désinscrire de ton créneau. A ton retour il te faudra remplir le formulaire "réinscription à un créneau".<br/>
- Si tu ne souhaites plus participer à la coopérative mais que tu ne souhaites pas démissionner tout de suite, notamment car tu souhaites attendre que la part sociale prenne de la valeur pour te faire rembourser, tu dois te désinscrire de ton créneau. Au moment où tu souhaiteras démissionner il faudra remplir le formulaire du même nom. Tu ne pourras plus faire tes courses à la Cagette.<br/>
<b>ATTENTION:</b> si tu es en binôme et que l'autre personne souhaite continuer à participer il faut remplir le formulaire "se désolidariser de son binôme". Si tu es la personne titulaire du binôme l'autre personne devra remplir le formulaire "se réinscrire à un créneau".
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="template_unsubscribe_form_link_btn"
>
Se désinscrire de son créneau
</a>
</div>
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >001 Gestion de mes services: échanger son service, arrivé.e en retard à son service, demande de congés maladie ou parental</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Créneau/service c'est quoi la différence ?</b></h3>
<p>
Un créneau, c'est une plage récurrente, par exemple, tous les jeudi de semaine A de 13h30 à 16h30.<br/>
Un service, c'est une plage horaire en particulier, par exemple le jeudi 28 novembre de 13h30 à 16h30.
</p>
</div>
<div class="grp_text">
<h3><b>Échanger son service</b></h3>
<p>
Si tu ne peux pas venir effectuer ton service à la date prévue il te faut l'échanger sur ton espace membre.<br/>
<b>ATTENTION:</b> si tu es en binôme c'est via l'espace membre du titulaire du binôme qu'il faut le faire.
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
type="button"
class="btn--primary faq_link_button"
id="shift_exchange_btn"
>
Échange de services
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Oubli de validation de service</b></h3>
<p>
Si tu as effectué ton service mais que tu as oublié de le valider à l'entrée du magasin, il te faut remplir le formulaire suivant en indiquant la date de ton service.
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="request_form_link_btn2"
>
Oubli validation service
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Demande de congé maladie ou parental</b></h3>
<p><b>
- Si tu t'absentes sur une période de 4 semaines autre que pour une raison de santé ou parentale il t'es demandé de déplacer ton service.<br/>
- Si tu t'absentes plus il te faut se désinscrire de ton créneau puis, à ton retour te réinscrire à un créneau . Pendant cette période de désinscription tu ne dois pas faire de service et tu ne peux pas pas faire tes courses.
</p></b>
<p></b></p>
</div>
<div class="grp_text"><h3><b>Les congés maladie</b></h3>
Les congés maladie peuvent être pris en cas d’impossibilité physique de réaliser son service pendant une longue durée qui prend en compte<b> minimum 2 services</b>. Si ton impossibilité est d'un mois il t'est demandé de déplacer ton service.<br />
En cas de rhume nous encourageons les coops à simplement déplacer leur service depuis l’espace membre. La coop a besoin de la participation de chacun.e !<br />
<b>ATTENTION:</b> Compte tenu de la facilité offerte par le statut de binôme, les personnes formant un binôme ne pourront pas<br />
bénéficier de congés maladie. Il sera alors possible de se désolidariser de son binôme en remplissant le formulaire adéquat sur l’espace membre afin d’en bénéficier.<br />
</div>
<div class="grp_text"><h3><b>Les congés parentaux</b></h3>
Lors de la naissance d’un enfant, les coops peuvent continuer de faire leur courses sans faire de services pendant 12 mois.<br />
Si les deux parents font partie de la coopérative, ils peuvent se partager leurs 12 mois comme il l’entendent. Il peuvent prendre par exemple 6 mois chacun.e en même temps ou 8 mois pour l'un.e puis 4 mois pour l'autre.<br />
<br />
Particularités: si les deux parents forment un binôme, i.elles ont alors accès à 6 mois de congé parental simultanément. S’ielles souhaitent répartir le congé différement il faut alors qu’ielles se débinomisent. Si l’un.e des deux parents est binôme avec un.e personne qui n’est pas l’autre parent, ielle doit se débinomiser pour bénéficier du congé parental.<br />
<br />
</div>
<div class="grp_text"><h3><b>L'exemption</b></h3>
Si ton état de santé ne te permet pas ou plus de faire tes services et que cette situation ne va pas évoluer ou si tu as 80 ans il est possible de demander une exemption. Cela te permet de ne pas être dans l'obligation de faire tes services tout en pouvant faire tes courses. Cette exemption n'a pas de date de fin, c'est pour cela qu'elle est a demandée dans des cas précis. Aucun justificatif médical n'est demandé, nous nous basons sur la confiance.<br /><br/>
Quelque soit ta demande de congés merci de remplir le formulaire ci dessous:<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="sick_leave_form_link_btn"
>
Demande de congés
</a>
</div>
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width">002 Mon Statut : Je suis en statut "Rattrapage" ou désinscrit.e et je ne peux pas faire mes courses</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h2><b>Dans quel cas ?</b></h2>
Tu es en statut "Rattrapage" si tu as raté un service.<br />
Tu es désinscrit.e après avoir raté trois services d’affilée sans les rattraper. Tu es désinscrit.e de ton créneau afin d'y libérer une place.<br />
Attention, le binôme est un cas particulier où une des deux personnes est la titulaire et l'autre la suppléante dont le statut est "désinscrit.e".<br />
</div>
<div class="grp_text">
<img src="/static/img/diagramme_etat_statut_cooperateurs.png" />
<h3><b>Que faire ?
</b></h3>
<h4>Si tu es en statut "Rattrapage" :</h4>
Tu as raté un service et que tu ne t'es pas inscrit à un rattrapage. Tu n'as plus le droit de faire tes courses. Il faut t'inscrire au rattrapage sur ton espace membre, tu as 6 mois l'effectué. Quand tu seras inscrit à ton rattrapage tu seras en statut "délais" et tu pourras de nouveau faire tes courses.<br />
Pour choisir tes rattrapages, clique sur le bouton ci-dessous.<br />
<div class="faq_link_button_area">
<a
href="echange-de-services"
type="button"
class="btn--primary faq_link_button"
>
Je sélectionne mes rattrapages
</a>
</div>
</div>
<div class="grp_text">
<h4>Si tu es désinscrit.e :</h4>
Tu as raté trois services d’affilée sans les rattraper ou n'a pas effectué tes rattrapages auxquels tu était inscrit.<br/>
Tu es désinscrit.e de ton créneau afin d'y libérer une place.<br/><br/>
Dans ce cas là, tu ne peux plus rien faire, ni t’inscrire à un rattrapage ni faire tes courses. il faut te réinscrire sur un créneau.<br/><br/>
Pour te réinscrire, il faut remplir le formulaire "Se réinscrire sur un créneau" et attendre qu'il soit traité. Dès que tu es réinscrit sur un créneau, n'oublie pas de t'inscrire à tes deux rattrapages.<br/>
</div>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="unsuscribe_form_link_btn2"
>
Réinscription à un créneau
</a>
</div>
<div class="grp_text">
Attention, le binôme est un cas particulier où une des deux personnes est la titulaire et l'autre la suppléante dont le statut est "désinscrit.e".<br/><br/>
Si tu ne comprends pas pourquoi tu es désinscrit.e ou suspendu.e, tu peux le signaler au bureau des membres pour qu'il règle ton problème.<br/>
N'hésite pas à renseigner un maximum d'information sur ta situation pour nous aider à régler ton problème : <br />
</div>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="request_form_link_btn2"
>
Faire une demande au BDM
</a>
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >003 Mon binôme: créer un binôme, se désolidariser de son binôme, changer de binôme</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Créer un binôme</b></h3>
Afin de faciliter l’intégration de personnes qui ont des difficultés à rejoindre la Cagette et par mesure de solidarité : Chaque coopérateur·rice peut rattacher un·e autre coopérateur·rice. <br />
Cependant, La Cagette a besoin de forces vives pour pouvoir fonctionner correctement et il est nécessaire qu'un maximum de personnes soient présentes sur les créneaux. <h4><b>C'est pourquoi ce statut de binôme, qui doit rester exceptionnel, est réservé à des personnes qui rencontrent de grandes difficultés organisationnelles dans leur quotidien.</b></h4> Ces difficultés sont à évaluer par la personne concernée. Par exemple : les mères ou les pères célibataires avec enfants, les personnes ayant une charge de travail et /ou des conditions particulières de travail, les aidants, les personnes rencontrant des problèmes de santé... Ce statut est transitoire selon l’évolution des conditions de vie de la personne concernée.
<b>Comprendre le contexte du binôme : Titulaire du créneau et suppléant</b><br />
La procédure de création de binôme concerne deux membres, le titulaire et le suppléant.<br />
<b>Le titulaire </b>est le coopérateur qui va devenir responsable du binôme. Il est responsable de la réalisation des services. Par exemple, si le titulaire n’est pas à jour de ses services, les deux membres du binôme ne pourront plus faire leurs courses. C’est le titulaire qui doit faire la demande de binôme. C'est à partir de son espace membre que les services sont gérés.<br />
<b>Le suppléant</b> est la personne qui va se rattacher au compte du titulaire. Elle n’aura pas d’obligation de faire un service et pourra faire ses courses. Son statut est &quot;désinscrit&quot;, c'est normal. Lorsqu'ielle vient faire un service c'est au nom du de la titulaire du binôme. En revanche, lors du passage en caisse chaque membre du binôme doit donner son propre nom.<br />
Le titulaire et le suppléant peuvent s’organiser comme ils l’entendent pour remplir les obligations du titulaire. Ils peuvent faire un service sur deux ou se répartir l’année en deux semestres, ou bien encore, le titulaire peut faire tous les services et le suppléant aucun. Peu importe, ça les regarde.<br />
<b>Il existe 3 conditions pour créer un binôme :</b><br />
Les deux coopérateur.trice.s doivent justifier que leur situation nécessite un binôme.<br />
Les deux coopérateur.trice.s doivent avoir fait au moins 4 créneaux avant de pouvoir former un binôme,<br />
Les deux coopérateur.trice.s doivent être “à jour” pour former un binôme. Si l’un des membres n’est pas à jour, ses points négatifs peuvent être transférés à l’autre membre. Ainsi, une personne qui a 2 points d’avance peut rattacher à son compte une personne qui a -2 points à son compteur.<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="associated_subscribe_form_link_btn"
>
Création de binôme
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Se désolidariser de son binôme</b></h3>
Si pour quelconque raison l&quot;une des deux personnes composant le binôme souhaite le désolidarisé il faut remplir le formulaire qui suit. Par défaut nous inscrirons la personne suppléante sur le même créneau que la personne titulaire. Si cela ne lui convient pas elle devra demander à changer de créneau.<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="associated_unsubscribe_form_link_btn"
>
Ce désolidariser de binôme
</a>
</div>
</div>
<div class="grp_text">
<h3><b>Changer de binôme</b></h3>
Si l'un.e des deux personne composant le binôme souhaite changer de binôme il faut faire les deux demandes l'une après l'autre.
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >005 Je veux que mes enfants puissent faire les courses à ma place</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Dans quel cas ?</b></h3>
Les coops peuvent créer un compte de &quot;mineur rattaché&quot; à leurs enfants pour leur permettre d’aller faire les commissions.<br />
<h3></b>Comment faire ?</b></h3>
Rien de plus simple ! <br />
Il suffit de remplir ce formulaire<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="underage_subscribe_form_link_btn"
>
Cr&eacute;er un compte mineur rattach&eacute;
</a>
</div>
Le Bureau des Membres traitera la demande dès que possible ! <br />
Merci et bonne journée !
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >006 Je veux changer d'adresse mail</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Dans quel cas ?</b></h3>
Il arrive qu’au moment de l’inscription, une adresse mail erronée soit saisie. <br />
Il se peut aussi qu’un membre change d’adresse mail.<br />
Or l'adresse mail est utilisée comme identifiant pour se connecter à notre espace membre. <br />
<h3><b>Comment faire ?</b></h2>
C’est très simple ! <br />
Il suffit de remplir ce formulaire<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="change_email_form_link_btn"
>
Changer d'adresse mail
</a>
</div>
Le Bureau des Membres traitera la demande dès que possible ! <br />
Merci et bonne journée !
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >007 Je veux quitter la coopérative</span>
</button>
<div class="input-container panel">
<div class="grp_text">
<h3><b>Dans quel cas ?</b></h3>
Les membres de La Cagette peuvent quitter la coopérative à tout moment, quel que soit le motif.<br />
En ce qui concerne tes parts sociales, tu peux en demander le remboursement. Par défaut, si tu démissionnes et que tu ne fais pas de demande de remboursement, le montant de tes parts sociales sera considéré comme un don à la Cagette. <br />
Cas particulier : <br />
Si tu as acheté plus de 10 parts sociales de la Cagette, tu peux demander le remboursement de tes parts sociales au-delà de 10, tout en restant coopérateur.rice. Pour cela, tu n’as qu’à remplir le formulaire de demande de remboursement ci-dessous.<br />
<br/><br>
<h3><b>Modalités de remboursement</b></h3>
<br>
<h3>Délais de remboursement</h3>
Ta demande de remboursement sera traitée lors de l’Assemblée Générale Ordinaire (AGO) qui statuera sur les comptes de l’exercice comptable au cours duquel ta demande a été faite (soit 4 à 5 mois après le 30 juin qui suit la demande). Après l'assemblée générale ordinaire annuelle, La Cagette pourra procéder au remboursement des anciens associés par virement. Toutefois certaines conditions particulières peuvent différer ce remboursement :<br />
Si le capital de la coopérative a baissé de manière significative au point de mettre en danger la trésorerie de l’entreprise et son fonctionnement dans les mois à venir, l’AGO peut différer le remboursement à l’année suivante en maintenant la valeur de remboursement qu’elle vient de définir. Ce report pourrait se répéter 4 fois selon la situation financière de l’entreprise. Le remboursement doit quoi qu’il en soit intervenir dans un délai de maximum 5 ans à compter de la date de la demande.<br />
<br>
<h3>Valeur de remboursement des parts sociales</h3>
La valeur nominale (d’émission) des parts sociales est fixe dans le temps (10€ la part), mais la valeur de remboursement des parts sociales, elle, varie en fonction des résultats économiques de l’entreprise. La Cagette étant une coopérative à but non lucratif, la valeur de la part ne pourra jamais excéder 10€, mais elle peut être moindre :<br />
La valeur de la part dépend du résultat cumulé de la coopérative depuis sa création. Pour ouvrir le supermarché, nous avons réalisé de gros investissements. Aujourd'hui encore, bien que nos résultats aient été positifs en 2019 et 2020, le résultat net cumulé de la coopérative est négatif. En 2018, nous avons remboursé à une valeur de 5,02€ la part. Puis en 2019, à 7,58€ et en 2020, à 7,82€. Nous espérons pouvoir bientôt pouvoir atteindre une valeur de remboursement de 10€ la part, mais nous ne pouvons pas nous engager sur une année ou une date précise. La valeur de remboursement ne peut pas être connue avant la clôture de l’exercice le 30 juin.<br />
La procédure de calcul de la valeur de remboursement de la part sociale à la Cagette résulte de l’application stricte de l’article 8 de la Loi de 1947 sur les sociétés coopératives. <br />
Si tu souhaites attendre que la part sociale prenne de la valeur avant de te faire rembourser, il faut geler ton compte et demander ta démission et ton remboursement plus tard. Pour cela il suffit de te désinscrire de ton créneau en remplissant le formulaire &quot;Se désinscrire de son créneau&quot; dans la rubrique “Gestion de mon créneau”. Tu resteras membre de la cagette, ton statut sera “désinscrit”, tu n’auras pas de service à faire, et ne pourras plus faire tes courses).<br />
<br><br>
<h3><b>Comment faire ?</b></h3>
C'est très simple : remplis ce formulaire pour obtenir par mail les documents de démission et/ou de demande de remboursement à nous renvoyer.<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="coop_unsubscribe_form_link_btn"
>
Se D&eacute;sinscrire de la coop&eacute;rative
</a>
</div>
</div>
</div>
<button type="button" class="accordion btn_faq">
<span class="full_width" >009 Faire une autre demande au BDM: seulement si les autres formulaires ne correspondent pas à ma demande </span>
</button>
<div class="input-container panel">
<div class="grp_text">
<br>
Nous avons créé des formulaires spécifiques pour la plupart des problèmes rencontrés par les membres. Changer de créneau, créer un binôme, ajouter un produit à la gamme, partir en vacances... <br />
Cela dit, nous en découvrons de nouveaux tous les jours.<br />
Si tu n'as pas su quel formulaire remplir, tu es au bon endroit. <br />
Vas-y dit nous tout !<br /><br />
Attention: si tu souhaites contacter le BDM pour prévenir que tu seras absent-e à ton service cela ne sert à rien! Il te faut déplacer ton service via ton espace membre. Il n'est cependant pas possible d'échanger un service qui commence dans moins de 24h pour des raisons de logistiques. Si tu ne peux pas venir tu seras donc comptabilisé-e absent-e. Tu basculeras en statut "Rattrapage" et ne pourras plus faire tes courses. Il te faudra sélectionner dans ton espace membre un rattrapage à faire dans les 6 prochains mois pour basculer en statut "Délai" et pouvoir faire de nouveau tes courses.<br />
Merci de ne pas contacter le Bureau des membres pour cela, il te donnera exactement la même réponse.<br />
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="request_form_link_btn"
>
Faire une demande au BDM
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/members-space-header.css' %}">
{% endblock %}
{% block content %}
<div class="topnav" id="topnav">
<a href="javascript:void(0);" class="nav_item active" id="nav_home">Espace Membre</a>
<a href="javascript:void(0);" class="nav_item" id="nav_my_info">Mes Infos</a>
<a href="javascript:void(0);" class="nav_item" id="nav_my_shifts">Mes Services</a>
<a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Échange de services</a>
<a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a>
<a
href="javascript:void(0);"
target="_blank"
class="nav_item"
id="nav_calendar"
>
Calendrier ABCD
</a>
{# Disconnection button must have this id (logic in all_common.js) #}
<a href="javascript:void(0);" id="deconnect">Déconnexion</a>
<a href="javascript:void(0);" class="icon" onclick="toggleHeader()">
<i class="fa fa-bars"></i>
</a>
</div>
<div class="pairs_info">
<span>
<i class="fas fa-exclamation-circle"></i> Je suis en binôme.
Toutes les actions (changement de service, choix d'un rattrapage...)
ne sont faisables que sur l'espace membre du <b>binôme principal</b>.
Dans mon espace membre, les infos ne sont visibles qu'en lecture seule.
</span>
</div>
<script type="text/javascript" src="{% static 'js/members-space-header.js' %}"></script>
{% endblock %}
<div id="home">
<div class="page_title txtcenter">
<h1>Espace Membre</h1>
</div>
<div class="tiles_container">
<div class="tile high_tile" id="home_tile_my_info">
<div class="tile_title">
<i class="fas fa-user tile_icon"></i>
<span class="member_info member_name"></span>
</div>
<div class="tile_content">
{# <p><span class="member_info member_name"></span></p> #}
<p class="member_status_text_container">Mon statut : <span class="member_info member_status"></span></p>
<div class="delay_date_stop_container">
( jusqu'au <span class="delay_date_stop"></span> )
</div>
<div id="member_status_action">
<a href="#" target="_blank" class="btn--warning unsuscribed_form_link">
J'accède au formulaire
</a>
<button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages
</button>
</div>
<div class="member_shift_name_area">
<span>Mon créneau : </span>
<span class="member_shift_name member_info"></span>
</div>
<div class="member_coop_number_area">
<span>Mon numéro de coop : </span>
<span class="member_coop_number member_info"></span>
</div>
<div class="member_associated_partner_area">
<span>Je suis en binôme avec : </span>
<span class="member_associated_partner member_info"></span>
</div>
<div id="see_more_info">
<button type="button", class="btn btn--primary home_link_button" id="see_more_info_link">
Accéder à mes infos et comprendre mon statut
</button>
</div>
</div>
</div>
<div class="tile high_tile" id="home_tile_my_services">
<div class="tile_title">
<i class="fas fa-clipboard tile_icon"></i>
Mes Services
</div>
<div class="tile_content">
<h3>Services à venir</h3>
<div id="home_incoming_services">
<i class="fas fa-spinner fa-spin fa-lg"></i>
</div>
<div id="go_to_shift_history_area">
<button type="button", class="btn btn--primary" id="home_go_to_shift_history">
Accéder à mon historique
</button>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_services_exchange">
<div class="tile_title">
<i class="fas fa-exchange-alt tile_icon"></i>
Échange de services
</div>
<div class="tile_content">
<div>
Un empêchement ? J'anticipe et déplace mes services jusqu'à 24h avant leur début !
</div>
<div class="home_link_button_area">
<button type="button" class="btn--primary home_link_button" id="go_to_shifts_calendar">
Accéder au calendrier d'échange de services
</button>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_help">
<div class="tile_title">
<i class="fas fa-question-circle tile_icon"></i>
J'ai une demande
</div>
<div class="tile_content">
<div class="home_link_button_area">
<a
href="javascript:void(0);"
class="btn--primary home_link_button"
id="go_to_forms"
>
Accéder aux formulaires
</a>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_shop_info">
<div class="tile_title">
<i class="fas fa-newspaper tile_icon"></i>
Informations magasins
</div>
<div id="shop_info_content">
<div class="shop_info_item shop_opening_hours">
<div class="shop_info_item_content">
<div class="opening_hours_title">
Horaires du magasin :
</div>
<div class="opening_hours_content">
{{shop_opening_hours|safe}}
</div>
</div>
</div>
<div class="shop_info_item shop_message">
<div class="shop_info_item_content shop_message_content">
{{msg_accueil|safe}}
</div>
</div>
</div>
</div>
</div>
</div>
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/datatables/responsive.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'fullcalendar-5.9.0/lib/main.min.css' %}">
<link rel="stylesheet" href="{% static "css/members-space.css" %}?v=">
<link rel="stylesheet" href="{% static "css/members-space-my-shifts.css" %}?v=">
<link rel="stylesheet" href="{% static "css/members-space-faq.css" %}?v=">
<link rel="stylesheet" href="{% static "css/members-space-my-info.css" %}?v=">
<link rel="stylesheet" href="{% static "css/members-space-shifts-exchange.css" %}?v=">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.responsive.min.js' %}"></script>
<script type="text/javascript" src="{% static 'fullcalendar-5.9.0/lib/main.min.js' %}"></script>
<script type="text/javascript" src="{% static 'fullcalendar-5.9.0/lib/locales/fr.js' %}"></script>
{% endblock %}
{% block content %}
<div class="page_body">
{% include "members_space/header.html" %}
<div id="main_content" class="page_content">
</div>
<div id="templates" style="display:none;">
<div id="shift_line_template">
<div class="shift_line">
<i class="fas fa-chevron-right shift_line_chevron"></i>
<span class="shift_line_date"></span> - <span class="shift_line_time"></span>
</div>
</div>
<div id="selectable_shift_line_template">
<div class="selectable_shift_line btn--primary">
<input type="checkbox" class="checkbox">
<div class="selectable_shift_line_text">
<span class="shift_line_date"></span> - <span class="shift_line_time"></span>
</div>
</div>
</div>
<div id="modal_shift_exchange_template">
<div>Je suis sur le point d'échanger le service du : </div>
<div><span class="date_old_shift"></span> à <span class="time_old_shift"></span></div>
<div>par celui du : </div>
<div><span class="date_new_shift"></span> à <span class="time_new_shift"></span></div>
</div>
<div id="modal_add_shift_template">
<div>Je suis sur le point de m'inscrire au service du : <span class="date_new_shift"></span> à <span class="time_new_shift"></span></div>
</div>
<div id="calendar_explaination_template">
<h4>Légende du calendrier</h4>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_booked"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">06:00</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 9/12</div></div></div></div></a>
<p>Un service colorié en noir : je suis déjà inscrit.e à ce service.</p>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_less_alf"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">10:45</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 3/12</div></div></div></div></a>
<p>Un service colorié en bleu : je peux m'inscrire à ce service.</p>
<p>3/12 <i class="arrow_explanation_numbers fas fa-arrow-right"></i> il y a déjà 3 places réservées à ce service sur 12 disponibles.
<b>Plus le chiffre de gauche est petit, plus on a besoin de coopérateurs.rices à ce service !</b></p>
</div>
<div id="cant_have_delay_msg_template">
<h3>Bonjour, tu avais 6 mois pour rattraper tes services manqués et il semblerait que tu ne l'aies pas fait.</h3>
<h3>Tu ne peux plus sélectionner de rattrapages sur ton espace membre pour le moment,
merci de contacter le bureau des membres pour résoudre ce problème en remplissant ce formulaire : </h3>
</div>
<div id="comite_template">
<h3>Vous êtes inscrit.e dans le service des comités, vous n'avez pas accès au calendrier d'échange des services car vous vous organisez directement avec le responsable du comité. Si vous avez des rattrapages à réaliser, merci de contacter le responsable du comité qui vous aidera à planifier les rattrapages ou trouver une solution.</h3>
<h3>Le calendrier ci-dessous est en lecture seule</h3>
</div>
</div>
</div>
<script>
var app_env = '{{app_env}}';
var forms_link = '{{forms_link}}';
var unsuscribe_form_link = '{{unsuscribe_form_link}}';
var request_form_link = '{{request_form_link}}';
var late_service_form_link = '{{late_service_form_link}}';
var change_template_form_link = '{{change_template_form_link}}';
var associated_subscribe_form_link = '{{associated_subscribe_form_link}}';
var associated_unsubscribe_form_link = '{{associated_unsubscribe_form_link}}';
var template_unsubscribe_form_link = '{{template_unsubscribe_form_link}}';
var change_email_form_link = '{{change_email_form_link}}';
var coop_unsubscribe_form_link = '{{coop_unsubscribe_form_link}}';
var sick_leave_form_link = '{{sick_leave_form_link}}';
var underage_subscribe_form_link = '{{underage_subscribe_form_link}}';
var helper_subscribe_form_link = '{{helper_subscribe_form_link}}';
var helper_unsubscribe_form_link = '{{helper_unsubscribe_form_link}}';
var covid_form_link = '{{covid_form_link}}';
var covid_end_form_link = '{{covid_end_form_link}}';
var member_cant_have_delay_form_link = '{{member_cant_have_delay_form_link}}';
var abcd_calendar_link = "{{abcd_calendar_link}}"
var days_to_hide = "{{daysToHide}}"
var partner_data = {
"partner_id":"{{partnerData.id}}",
"name":"{{partnerData.display_name}}",
"shift_type":"{{partnerData.shift_type}}",
"date_delay_stop":"{{partnerData.date_delay_stop}}",
"cooperative_state":"{{partnerData.cooperative_state}}",
"regular_shift_name":"{{partnerData.regular_shift_name}}",
"can_have_delay" : "{{partnerData.can_have_delay}}",
"makeups_to_do" : "{{partnerData.makeups_to_do}}",
"barcode_base" : "{{partnerData.barcode_base}}",
"street" : "{{partnerData.street}}",
"street2" : "{{partnerData.street2}}",
"zip" : "{{partnerData.zip}}",
"city" : "{{partnerData.city}}",
"mobile" : "{{partnerData.mobile}}",
"phone" : "{{partnerData.phone}}",
"email" : "{{partnerData.email}}",
"is_associated_people" : "{{partnerData.is_associated_people}}",
"parent_id" : "{{partnerData.parent_id}}",
"parent_name" : "{{partnerData.parent_name}}",
"associated_partner_id" : "{{partnerData.associated_partner_id}}",
"associated_partner_name" : "{{partnerData.associated_partner_name}}",
"verif_token" : "{{partnerData.verif_token}}",
"leave_stop_date": "{{partnerData.leave_stop_date}}",
"comite": "{{partnerData.comite}}"
}
</script>
<script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/members-space-home.js" %}?v="></script>
<script src="{% static "js/members-space-my-info.js" %}?v="></script>
<script src="{% static "js/members-space-my-shifts.js" %}?v="></script>
<script src="{% static "js/members-space-faq.js" %}?v="></script>
<script src="{% static "js/members-space-shifts-exchange.js" %}?v="></script>
<script src="{% static "js/members-space.js" %}?v="></script>
{% endblock %}
<div id="my_info">
<div class="page_title txtcenter">
<h1>Mes Infos</h1>
</div>
<div class="tiles_container">
<div class="tile full_width_tile" id="my_info_area">
<div class="tile_title">
<i class="fas fa-user tile_icon"></i>
<span class="member_info member_name"></span>
</div>
<div class="tile_content" id="my_info_content">
<div class="my_info_line">
<div class="my_info_line_left">
Statut
</div>
<div class="my_info_line_right">
<p class="member_info member_status"></p>
<div class="delay_date_stop_container">
( jusqu'au <span class="delay_date_stop"></span> )
</div>
<div id="member_status_action">
<a href="#" target="_blank" class="btn--warning unsuscribed_form_link">
J'accède au formulaire
</a>
<button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages
</button>
</div>
</div>
</div>
<div class="my_info_line">
<div class="my_info_line_left">
Créneau
</div>
<div class="my_info_line_right member_shift_name_area">
<span class="member_shift_name member_info"></span>
</div>
</div>
<div class="my_info_line">
<div class="my_info_line_left">
Numéro de coop
</div>
<div class="my_info_line_right member_coop_number_area">
<span class="member_coop_number member_info"></span>
</div>
</div>
<div class="my_info_line member_email_line">
<div class="my_info_line_left">
Email
</div>
<div class="my_info_line_right member_email_area">
<span class="member_email member_info"></span>
</div>
</div>
<div class="my_info_line member_address_line">
<div class="my_info_line_left">
Adresse
</div>
<div class="my_info_line_right member_address_area" id="edit_address_value">
<span class="member_address member_info"></span><br>
<span id="edit_address"><i class="fas fa-edit tile_icon edit-btn" ></i></span>
</div>
<div class="my_info_line_right member_address_area1" id="edit_address_form">
<input type="text" name="street_form" id="street_form" placeholder="Rue"> <br>
<!-- <input type="text" name="street2_form" id="street2_form" placeholder="Complément"> -->
<input type="text" name="zip_form" id="zip_form" placeholder="Code postal"> <br>
<input type="text" name="city_form" id="city_form" placeholder="Ville"> <br>
<span id="cancel_edit_address"><i class="fas fa-times tile_icon edit-btn"></i></span>
<span id="save_edit_address"><i class="fas fa-check tile_icon edit-btn"></i></span>
</div>
</div>
<div class="my_info_line member_phone_line">
<div class="my_info_line_left">
Téléphone
</div>
<div class="my_info_line_right member_phone_area" id="edit_phone_value">
<span class="member_phone member_info"></span>
<span class="member_mobile member_info"></span>
<span id="edit_phone"><i class="fas fa-edit tile_icon edit-btn" ></i></span>
</div>
<div class="my_info_line_right member_phone_area1" id="edit_phone_form">
<input type="text" name="phone" id="phone_form" placeholder="Tel fixe"> <br>
<input type="text" name="mobile" id="mobile_form" placeholder="Tel mobile"><br>
<span id="cancel_edit_phone"><i class="fas fa-times tile_icon edit-btn"></i></span>
<span id="save_edit_phone"><i class="fas fa-check tile_icon edit-btn"></i></span>
</div>
</div>
</div>
</div>
<div class="tile full_width_tile" id="attached_info_area">
<div class="tile_title">
Mon Binôme
</div>
<div class="tile_content" id="attached_info">
<div class="my_info_line attached_partner_name_line">
<div class="my_info_line_left">
Je suis en binôme avec :
</div>
<div class="my_info_line_right attached_partner_name_area">
<span class="attached_partner_name member_info"></span>
</div>
</div>
</div>
</div>
<div class="tile full_width_tile">
<div class="tile_title">
Comprendre mon statut
</div>
<div class="my_info_line_middle">
Il existe différents statuts à La Cagette donnant ou non le droit de faire ses courses. Voici un schéma explicatif expliquant le passage d'un statut à un autre. Pour toute question relative aux statuts, rendez&#x2011;vous dans la rubrique <a href='faq'>Problèmes&nbsp;&&nbsp;Demandes</a>.
</div>
<a href="/static/img/diagramme_etat_statut_cooperateurs.png" target=”_blank”>
<img class="status_info_image" src="/static/img/diagramme_etat_statut_cooperateurs.png" alt="diagramme_etat_statut_cooperateurs"/>
</a>
</div>
</div>
</div>
<div id="shifts_exchange">
<div id="unsuscribed_content" class="shifts_exchange_page_content">
<h3>Je suis désinscrit.e, je dois remplir un formulaire pour qu'on me réinscrive à un créneau.</h3>
<a href="#" target="_blank" class="btn--warning unsuscribed_form_link">
J'accède au formulaire
</a>
</div>
<div id="suspended_cant_have_delay_content" class="shifts_exchange_page_content">
<div class="suspended_cant_have_delay_msg"></div>
<a href="#" target="_blank" class="btn--warning cant_have_delay_form_link">
J'accède au formulaire
</a>
</div>
<div id="comite_content" class="shifts_exchange_page_content">
<div class="comite_content_msg"></div>
<div id="read_only_calendar"></div>
</div>
<div id="suspended_content" class="shifts_exchange_page_content">
<h3>
J'ai <span class="makeups_nb"></span> rattrapage(s) à effectuer, je dois le(s) sélectionner pour pouvoir refaire mes courses.
J'ai 6 mois de délai pour le(s) rattraper.
</h3>
<h3>
Si besoin, je peux contacter le Bureau des membres via la rubrique "J'ai une demande" pour expliquer ma situation.
</h3>
<button type="button" class="btn--danger select_makeups">
Je sélectionne mes rattrapages
</button>
</div>
<div id="shifts_exchange_content" class="shifts_exchange_page_content">
<div id="need_to_select_makeups_message">
<span class="select_makeups_message_block">
<i class="fas fa-exclamation-triangle makeups_warning"></i>
J'ai <span class="makeups_nb"></span> rattrapage(s) à faire. </span>
<span class="select_makeups_message_block">Je dois les sélectionner dans le calendrier. </span>
<span class="select_makeups_message_block">Je ne peux pas échanger de service tant que je n'ai pas choisi mes rattrapages. </span>
</div>
<div id="calendar_top_info">
<div id="partner_shifts_list">
<h4>Liste de mes services :</h4>
<div class="loading-incoming-shifts">
<i class="fas fa-spinner fa-spin fa-lg"></i>
</div>
<div id="shifts_list"></div>
</div>
<div id="calendar_explaination_area"></div>
<button id="calendar_explaination_button" class="btn--success">Légende du calendrier</button>
</div>
<div class="loading-calendar">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div id="calendar"></div>
</div>
</div>
<div>
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculées les conso. moyennes / jour ?</label></button>
<div class="txtleft" style="display: none;">
<p>
La fonction qui calcule les consommations moyennes prend en paramètre une date de départ. <br/>
Si elle n'est pas indiquée, la date prise en compte sera "<em>aujourd'hui - nb de jours paramétré dans Odoo</em>".<br/>
Ce nombre de jours paramétrable se trouve en suivant les menus suivants :<br>
Configuration > Technique > Paramètres > Paramètres systèmes. <br/>
La valeur est définie avec la clef "<em>lacagette_products.nb_past_days_to_compute_sales_average</em>".<br/>
Ce paramètre vaut <strong>{{nb_past_days_to_compute_sales_average}}</strong> au moment du chargement de cette page.<br/>
Les ventes du dimanche sont exclues du calcul.<br/>
</p>
<p>
Une requête est faite sur l'ensemble des passages en caisse, de la date de départ à hier, récupérant pour tous les jours de la période (dimanches exclus) les quantités vendues des articles achetés chez le fournisseur.<br/>
Pour chaque article, le nombre de jours considérés pour faire la moyenne est défini commme suit :<br/>
<em>Nb de jours ouvrés de la période - Nb de jours des périodes de rupture</em><br/>
Les périodes de ruptures sont caractérisées par un nombre de jours consécutifs assez important de jours sans vente de l'article.<br/>
Le nombre de jours consécutifs sans vente pour considérer l'article en rupture est actuellement de <strong>{{nb_of_consecutive_non_sale_days_considered_as_break}}</strong><br/>
(c'est le paramètre système avec la clef "<em>lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break</em>").<br/>
</p>
<p>
Pour chaque article, la consommation moyenne par jour est obtenue en divisant la quantité totale vendue sur la période par le nombre de jours significatifs.
</p>
</div>
</div>
<div>
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculés les besoins ?</label></button>
<div class="txtleft" style="display: none;">
La quantité à commander pour couvrir les besoins (en jours) est le résultat de :
<p>
(<em>nb_jours</em> <strong>x</strong> <em>conso_moyenne</em>) <strong>-</strong> <em>stock_existant</em> <strong>-</strong> <em>quantités_entrantes</em> <strong>+</strong> <em>stock_minimum</em>
</p>
</div>
</div>
......@@ -40,32 +40,10 @@
<button type="button" class="btn--danger" id="back_to_order_selection_from_main">
<i class="fas fa-arrow-left"></i>&nbsp; Retour
</button>
<div class="right_action_buttons">
<div id="actions_buttons_wrapper">
<button type="button" class='btn--primary' id="toggle_action_buttons">
<span class="button_content">
Actions
</span>
<span class="toggle_action_buttons_icon">
<i class="fas fa-chevron-down"></i>
</span>
</button>
<div id="actions_buttons_container">
<button type="button" class='btn--primary action_button' id="do_inventory" style="display:none;">
Faire un inventaire
</button>
<button type="button" class='btn--primary action_button' id="refresh_order">
Rafraîchir la commande
</button>
<button type="button" class='btn--danger action_button' id="delete_order_button">
Supprimer la commande
</button>
</div>
</div>
<a class='btn--warning link_as_button' id="access_metabase" style="display:none;" href="{{metabase_url}}" target="_blank">
Stats Métabase
</a>
<div class="rights_buttons">
<button type="button" class='btn--primary' id="do_inventory" style="display:none;">
Faire un inventaire
</button>
</div>
</div>
......@@ -79,27 +57,9 @@
</div>
<div class="txtcenter" id="order_forms_container">
<form action="javascript:;" id="supplier_form" class="order_form_item">
<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>
<form action="javascript:;" id="stats_date_from_form" class="order_form_item">
<label for="stats_date_period_select">Période de calcul de la conso moyenne </label>
<select name="stats_date_period_select" id="stats_date_period_select">
<option value="">Par défaut</option>
<option value="1week">1 semaine</option>
<option value="2weeks">2 semaines</option>
</select>
</form>
<form action="javascript:;" id="coverage_form" class="order_form_item">
<div class="input-wrapper">
<input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1">
<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>
</div>
<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">
<input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom">
......@@ -250,21 +210,14 @@
<hr/>
</div>
<div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3>
<p>
<h4>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>
<div id="modal_product_npa">
<h3>Attention !</h3>
<p>
<h4>Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
Vous vous apprêtez à passer le produit <span class="product_name"></span> en <span class="product_npa"></span>.<br/>
Dès que vous aurez cliqué sur "Valider", le produit sera retiré du tableau et l'information sera enregistrée dans Odoo.
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_create_order">
......@@ -281,9 +234,7 @@
<br/>
<hr/>
</div>
<div id="explanations">
{% include "orders/explanations.html" %}
</div>
<div id="modal_create_order__supplier_date_planned">
<div class="modal_input_area">
<span class="modal_input_label supplier_name"></span>
......
......@@ -48,28 +48,22 @@
<p><i>Vous pouvez également taper le codebarre, puis appuyer sur la touche Entrée.</i></p>
</div>
<div class="top_content">
<div class="top_content_item top_content_left">
<div class="movement_validation_area txtcenter" style="display:none">
<h4>Total (H.T.) : <span class="total_value">0</span></h4>
<button type="button" class="btn--primary" id="movement_validation_button" name="button">Validation du total des produits saisis</button>
<div class="barcode_search_area txtcenter">
<input type="text" id="sm_barcode_selector" placeholder="Codebarre">
<div id="icon_product_not_found" class="tooltip" style="display:none;">
<span class="tooltiptext tooltip-lg tt_twolines">Aucun produit trouvé avec ce code-barre.</span>
<span style="color: #d9534f;"> <i class="fas fa-ban"></i>
</div>
</div>
<div class="barcode_search_area txtcenter top_content_item top_content_left">
<input type="text" id="sm_barcode_selector" placeholder="Codebarre">
<div id="icon_product_not_found" class="tooltip" style="display:none;">
<span class="tooltiptext tooltip-lg tt_twolines">Aucun produit trouvé avec ce code-barre.</span>
<span style="color: #d9534f;"> <i class="fas fa-ban"></i>
</div>
</div>
<div class="top_content_item top_content_left"></div>
</div>
<div class="main">
<table id="products_table" class="display" cellspacing="0" ></table>
</div>
<div class="footer txtcenter" style="display:none">
<h4>Total (H.T.) : <span class="total_value">0</span></h4>
<button type="button" class="btn--primary" id="movement_validation_button" name="button">Validation du total des produits saisis</button>
</div>
<input type="hidden" name="barcode"/>
</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