Commit c9bb2a20 by Félicie

Merge branch 'dev_cooperatic' into 2337-binome-access

parents 503beaef 260e86a9
Pipeline #1840 passed with stage
in 1 minute 26 seconds
......@@ -4,6 +4,7 @@ from outils.for_view_imports import *
from members.models import CagetteUser
from members.models import CagetteMembers
from members.models import CagetteMember
from shifts.models import CagetteShift
from outils.common import MConfig
......@@ -318,6 +319,13 @@ def manage_makeups(request):
'module': 'Membres'}
return HttpResponse(template.render(context, request))
def manage_shift_registrations(request):
""" Administration des services des membres """
template = loader.get_template('members/admin/manage_shift_registrations.html')
context = {'title': 'BDM - Services',
'module': 'Membres'}
return HttpResponse(template.render(context, request))
def get_makeups_members(request):
""" Récupération des membres qui doivent faire des rattrapages """
res = CagetteMembers.get_makeups_members()
......@@ -370,3 +378,24 @@ def update_members_makeups(request):
res["message"] = "Unauthorized"
response = JsonResponse(res, status=403)
return response
def delete_shift_registration(request):
""" From BDM admin, delete (cancel) a member shift registration """
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
shift_registration_id = int(data["shift_registration_id"])
member_id = int(data["member_id"])
m = CagetteShift()
res["res"] = m.cancel_shift([shift_registration_id])
# Note: 'upcoming_registration_count' in res.partner won't change because the _compute method
# in odoo counts canceled shift registrations.
response = JsonResponse(res, safe=False)
else:
res["message"] = "Unauthorized"
response = JsonResponse(res, status=403)
return response
\ No newline at end of file
.header {
margin: 1rem 0;
}
.login_area {
position: absolute;
display: block;
top: 5px;
right: 5px;
}
#back_to_admin_index {
position: absolute;
top: 5px;
left: 5px;
}
#table_top_area {
display: none;
width: 90%;
margin: 35px auto 0 auto;
text-align: center;
}
.table_area {
margin: 0 auto;
width: 90%;
display: flex;
justify-content: center;
}
.delete_shift_registration {
color: #d9534f;
cursor: pointer;
}
#member_shifts_table_filter {
padding-top: 0.755em;
}
/* Search membres area */
#search_member_area {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
#search_member_form_area {
display:flex;
align-items: center;
}
#search_member_form {
margin-left: 10px;
}
.search_member_results_area {
margin-top: 15px;
display: flex;
align-items: center;
}
.search_results_text {
min-width: 150px;
}
.search_member_results {
display: flex;
flex-wrap: wrap;
}
.btn_possible_member {
margin: 0.5rem 1rem;
}
\ No newline at end of file
......@@ -11,8 +11,8 @@ $(document).ready(function() {
window.location.assign(location + "/manage_makeups");
} else if (this.id == 'manage_attached_button') {
console.log('coming soon...');
} else if (this.id == 'manage_shifts_button') {
console.log('coming soon...');
} else if (this.id == 'manage_shift_registrations_button') {
window.location.assign(location + "/manage_shift_registrations");
} else if (this.id == 'manage_leaves_button') {
console.log('coming soon...');
}
......
......@@ -255,6 +255,7 @@ function update_members_makeups(member_ids, action) {
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
$('.btn_possible_member').off();
let no_result = true;
......@@ -276,39 +277,40 @@ function display_possible_members() {
$('.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;
}
// 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) {
......
var member_shifts_table = null,
members_search_results = [],
selected_member = null,
incoming_shifts = null;
/**
* Load partners who have makeups to do
*/
function load_member_future_shifts() {
$.ajax({
type: 'GET',
url: "/shifts/get_list_shift_partner/" + selected_member.id,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
incoming_shifts = data;
display_member_shifts();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des services du membre", ctx: 'load_member_future_shifts'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members.admin');
closeModal();
alert('Erreur lors de la récupération des services du membre.');
}
});
}
/**
* Display table of member future shifts
*/
function display_member_shifts() {
if (member_shifts_table) {
$('#member_shifts_table').off();
member_shifts_table.clear().destroy();
$('#member_shifts_table').empty();
}
$('#table_top_area #member_name').text(selected_member.name);
$('#table_top_area').show();
member_shifts_table = $('#member_shifts_table').DataTable({
data: incoming_shifts,
columns: [
{
data: "date_begin",
title: "",
visible: false
},
{
data: "shift_id",
title: "Service",
orderable: false,
render: function (data) {
return data[1];
}
},
{
data: null,
title: "",
className: "dt-body-center",
orderable: false,
width: "5%",
render: function () {
return `<i class="fa fa-lg fa-times delete_shift_registration"></i>`;
}
}
],
order: [
[
0,
"asc"
]
],
paging: false,
dom: 'tif',
oLanguage: {
"sProcessing": "Traitement en cours...",
"sSearch": "Rechercher dans le tableau",
"sInfo": "Total de _TOTAL_ &eacute;l&eacute;ments",
"sInfoEmpty": "",
"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": "Aucun futur service pour ce.tte membre"
}
});
$('#member_shifts_table').on('click', 'tbody td .delete_shift_registration', function () {
const row_data = member_shifts_table.row($(this).parents('tr')).data();
const shift_reg_id = row_data.id;
openModal(
`Enlever la présence de ${member.name} au service du ${row_data.shift_id[1]} ?`,
() => {
delete_shift_registration(shift_reg_id);
},
"Confirmer",
false
);
});
}
/**
* Send request to delete shift registration
* @param {Int} shift_reg_id Id of the shift_registration to delete
*/
function delete_shift_registration(shift_reg_id) {
openModal();
data = {
member_id: selected_member.id,
shift_registration_id: shift_reg_id
};
$.ajax({
type: 'POST',
url: "/members/delete_shift_registration",
data: JSON.stringify(data),
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function() {
closeModal();
alert("La présence a bien été annulée.");
const i = incoming_shifts.findIndex(is => is.id === shift_reg_id);
incoming_shifts.splice(i, 1);
display_member_shifts();
},
error: function(data) {
err = {msg: "erreur serveur pour supprimer la présence au service", ctx: 'delete_shift_registration'};
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 supprimer la présence au service. Ré-essayez plus tard.');
}
});
}
/**
* Display the members from the search result
*/
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
$('.btn_possible_member').off();
let no_result = true;
if (members_search_results.length > 0) {
for (member of members_search_results) {
$(".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')) {
selected_member = member;
load_member_future_shifts();
$('.search_member_results').empty();
$('.search_member_results_area').hide();
$('#search_member_input').val('');
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();
} else {
$(".page_content").hide();
}
$('#back_to_admin_index').on('click', function() {
let base_location = window.location.href.split("manage_shift_registrations")[0].slice(0, -1);
window.location.assign(base_location);
});
// 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: 'search_member_form.search_members'
};
report_JS_error(err, 'members.admin');
$.notify("Erreur lors de la recherche de membre, il faut ré-essayer plus tard...", {
globalPosition:"top right",
className: "error"
});
}
});
});
});
......@@ -56,6 +56,8 @@ urlpatterns = [
# BDM - members admin
url(r'^admin$', admin.admin),
url(r'^admin/manage_makeups$', admin.manage_makeups),
url(r'^admin/manage_shift_registrations$', admin.manage_shift_registrations),
url(r'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups),
url(r'^delete_shift_registration$', admin.delete_shift_registration),
]
......@@ -237,7 +237,7 @@ def update_couchdb_barcodes(request):
def search(request, needle, shift_id):
"""Search member has been requested."""
search_type = request.GET.get('search_type', '')
search_type = request.GET.get('search_type', "full")
try:
key = int(needle)
......
......@@ -25,17 +25,16 @@
Gestion des rattragapes
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_attached_button">
Gestion des binômes
<span class="management_type_button_icons"><i class="fas fa-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
<button type="button" class="btn--primary management_type_button" id="manage_shift_registrations_button">
Gestion des présences
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_shifts_button">
Gestion des créneaux
<button type="button" class="btn--primary management_type_button" id="manage_attached_button" disabled>
Gestion des binômes
<span class="management_type_button_icons"><i class="fas fa-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_leaves_button">
<button type="button" class="btn--primary management_type_button" id="manage_leaves_button" disabled>
Gestion des congés
<span class="management_type_button_icons"><i class="fas fa-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_shift_registrations.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Présences</h1>
</div>
<div class="page_content">
<div id="search_member_area">
<div id="search_member_form_area">
<h4>Rechercher un.e membre</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
</div>
<div id="table_top_area">
<h3>Liste des futurs services de <span id="member_name"></span></h3>
</div>
<div class="table_area">
<table id="member_shifts_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
<div id="templates" style="display:none;"></div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_shift_registrations.js" %}?v='></script>
{% endblock %}
......@@ -2,34 +2,62 @@
<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/>
La fonction qui calcule les consommations moyennes prend en paramètre soit :
</p>
<ul>
<li>un nombre de jours de couverture</li>
<li>un montant en €</li>
</ul>
<p>
qu'il sera ensuite possible d'ajuster en pourcentage.<br/>
</p>
<p>
Si rien n’est indiqué, le paramètre de calcul pris en compte sera : "<em>aujourd'hui - nb de jours paramétré dans Odoo</em>".<br/>
Le nombre de jours paramétré est actuellement de <strong>{{nb_past_days_to_compute_sales_average}}</strong> jours.<br/>
</p>
<blockquote>
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/>
</blockquote>
<br>
<p>
<strong>Dans le cas d'un nombre de jours de couverture :</strong>
</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/>
Une requête est faite sur l'ensemble des passages en caisse, du Nb jours de couverture à 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 comme suit :<br/>
"<em>Nb de jours ouvrés de la période - Nb de jours des périodes de rupture</em>"<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> jours.<br/>
</p>
<blockquote>
Cette période de rupture est paramétrable 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_of_consecutive_non_sale_days_considered_as_break</em>".<br/>
</blockquote>
<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>
<br>
<p>
<strong>Dans le cas d'un montant en € :</strong>
</p>
<p>
En entrant un montant, la fonction calculera et remplira les quantités d'articles à commander pour être au plus près de ce montant (en étant supérieur ou égal), tout en tenant compte des consommations moyennes des articles.
</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 :
La quantité à commander pour couvrir les besoins (en jours ou à partir d'un montant en €) 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>
<p>
Pour plus de précisions, ce résultat peut-être ensuite ajusté en pourcentage.
</p>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment