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 * ...@@ -4,6 +4,7 @@ from outils.for_view_imports import *
from members.models import CagetteUser from members.models import CagetteUser
from members.models import CagetteMembers from members.models import CagetteMembers
from members.models import CagetteMember from members.models import CagetteMember
from shifts.models import CagetteShift
from outils.common import MConfig from outils.common import MConfig
...@@ -318,6 +319,13 @@ def manage_makeups(request): ...@@ -318,6 +319,13 @@ def manage_makeups(request):
'module': 'Membres'} 'module': 'Membres'}
return HttpResponse(template.render(context, request)) 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): def get_makeups_members(request):
""" Récupération des membres qui doivent faire des rattrapages """ """ Récupération des membres qui doivent faire des rattrapages """
res = CagetteMembers.get_makeups_members() res = CagetteMembers.get_makeups_members()
...@@ -370,3 +378,24 @@ def update_members_makeups(request): ...@@ -370,3 +378,24 @@ def update_members_makeups(request):
res["message"] = "Unauthorized" res["message"] = "Unauthorized"
response = JsonResponse(res, status=403) response = JsonResponse(res, status=403)
return response 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() { ...@@ -11,8 +11,8 @@ $(document).ready(function() {
window.location.assign(location + "/manage_makeups"); window.location.assign(location + "/manage_makeups");
} else if (this.id == 'manage_attached_button') { } else if (this.id == 'manage_attached_button') {
console.log('coming soon...'); console.log('coming soon...');
} else if (this.id == 'manage_shifts_button') { } else if (this.id == 'manage_shift_registrations_button') {
console.log('coming soon...'); window.location.assign(location + "/manage_shift_registrations");
} else if (this.id == 'manage_leaves_button') { } else if (this.id == 'manage_leaves_button') {
console.log('coming soon...'); console.log('coming soon...');
} }
......
...@@ -255,6 +255,7 @@ function update_members_makeups(member_ids, action) { ...@@ -255,6 +255,7 @@ function update_members_makeups(member_ids, action) {
function display_possible_members() { function display_possible_members() {
$('.search_member_results_area').show(); $('.search_member_results_area').show();
$('.search_member_results').empty(); $('.search_member_results').empty();
$('.btn_possible_member').off();
let no_result = true; let no_result = true;
...@@ -276,6 +277,8 @@ function display_possible_members() { ...@@ -276,6 +277,8 @@ function display_possible_members() {
$('.search_member_results').append(member_button); $('.search_member_results').append(member_button);
}
// Set action on member button click // Set action on member button click
$('.btn_possible_member').on('click', function() { $('.btn_possible_member').on('click', function() {
for (member of members_search_results) { for (member of members_search_results) {
...@@ -309,7 +312,6 @@ function display_possible_members() { ...@@ -309,7 +312,6 @@ function display_possible_members() {
} }
}); });
} }
}
if (no_result === true) { if (no_result === true) {
$(".search_results_text").hide(); $(".search_results_text").hide();
......
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 = [ ...@@ -56,6 +56,8 @@ urlpatterns = [
# BDM - members admin # BDM - members admin
url(r'^admin$', admin.admin), url(r'^admin$', admin.admin),
url(r'^admin/manage_makeups$', admin.manage_makeups), 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'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups), 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): ...@@ -237,7 +237,7 @@ def update_couchdb_barcodes(request):
def search(request, needle, shift_id): def search(request, needle, shift_id):
"""Search member has been requested.""" """Search member has been requested."""
search_type = request.GET.get('search_type', '') search_type = request.GET.get('search_type', "full")
try: try:
key = int(needle) key = int(needle)
......
...@@ -25,17 +25,16 @@ ...@@ -25,17 +25,16 @@
Gestion des rattragapes Gestion des rattragapes
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br> </button><br>
<button type="button" class="btn--primary management_type_button" id="manage_attached_button"> <button type="button" class="btn--primary management_type_button" id="manage_shift_registrations_button">
Gestion des binômes Gestion des présences
<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>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
</button><br> </button><br>
<button type="button" class="btn--primary management_type_button" id="manage_shifts_button"> <button type="button" class="btn--primary management_type_button" id="manage_attached_button" disabled>
Gestion des créneaux 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-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #} {# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
</button><br> </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 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-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></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 @@ ...@@ -2,34 +2,62 @@
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculées les conso. moyennes / jour ?</label></button> <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;"> <div class="txtleft" style="display: none;">
<p> <p>
La fonction qui calcule les consommations moyennes prend en paramètre une date de départ. <br/> La fonction qui calcule les consommations moyennes prend en paramètre soit :
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/> </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> Ce nombre de jours paramétrable se trouve en suivant les menus suivants :<br>
Configuration > Technique > Paramètres > Paramètres systèmes. <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/> 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/> </blockquote>
Les ventes du dimanche sont exclues du calcul.<br/> <br>
<p>
<strong>Dans le cas d'un nombre de jours de couverture :</strong>
</p> </p>
<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/> 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 commme suit :<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/> "<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> jours.<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>
<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> <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. 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> </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> </div>
<div> <div>
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculés les besoins ?</label></button> <button type="button" class="accordion" style="width:100%"><label>Comment sont calculés les besoins ?</label></button>
<div class="txtleft" style="display: none;"> <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> <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> (<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>
<p>
Pour plus de précisions, ce résultat peut-être ensuite ajusté en pourcentage.
</p>
</div> </div>
</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