Commit a2101668 by Thibault Grandjean

Merge branch 'dev_cooperatic' of…

Merge branch 'dev_cooperatic' of https://gl.cooperatic.fr/cooperatic-foodcoops/third-party into 2170-create-delete-binome
parents 6182eb4d c71e8d67
......@@ -59,7 +59,7 @@ class CagetteEnvelops(models.Model):
# Get first invoice for which amount being paid <= amount left to pay in invoice
for invoice_item in invoice_res:
if int(float(data['amount']) * 100) <= int(float(invoice_item['residual_signed']) * 100):
invoice = invoice_ite
invoice = invoice_item
if invoice is None:
res['error'] = 'The amount is too high for the invoices found for this partner.'
......
......@@ -334,6 +334,13 @@ def manage_attached(request):
'module': 'Membres'}
return HttpResponse(template.render(context, request))
def manage_regular_shifts(request):
""" Administration des créneaux des membres """
template = loader.get_template('members/admin/manage_regular_shifts.html')
context = {'title': 'BDM - Créneaux',
'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()
......@@ -398,14 +405,63 @@ def delete_shift_registration(request):
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])
shift_registration_id = int(data["shift_registration_id"])
shift_is_makeup = data["shift_is_makeup"]
# Note: 'upcoming_registration_count' in res.partner won't change because the _compute method
# in odoo counts canceled shift registrations.
m = CagetteShift()
res["cancel_shift"] = m.cancel_shift([shift_registration_id])
if shift_is_makeup is True:
fields = {
'name': "Admin BDM - Suppression d'un rattrapage",
'shift_id': False,
'type': data["member_shift_type"],
'partner_id': member_id,
'point_qty': 1
}
res["update_counter"] = m.update_counter_event(fields)
response = JsonResponse(res, safe=False)
else:
res["message"] = "Unauthorized"
response = JsonResponse(res, status=403)
return response
def delete_shift_template_registration(request):
""" From BDM admin, delete a member shift template registration """
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
try:
data = json.loads(request.body.decode())
partner_id = int(data["partner_id"])
shift_template_id = int(data["shift_template_id"])
makeups_to_do = int(data["makeups_to_do"])
permanent_unsuscribe = data["permanent_unsuscribe"]
cm = CagetteMember(partner_id)
# Get partner nb of future makeup shifts
partner_makeups = cm.get_member_selected_makeups()
target_makeup = makeups_to_do + len(partner_makeups)
if target_makeup > 2:
target_makeup = 2
# Update partner makeups to do
res["update_makeups"] = cm.update_member_makeups({'target_makeups_nb': target_makeup})
# Delete all shift registrations & shift template registration
res["unsuscribe_member"] = cm.unsuscribe_member()
if permanent_unsuscribe is True:
res["set_done"] = cm.set_cooperative_state("gone")
except Exception as e:
res["error"] = str(e)
response = JsonResponse(res, safe=False)
else:
......
......@@ -13,6 +13,7 @@ import pytz
import locale
import re
import dateutil.parser
from datetime import date
......@@ -26,8 +27,7 @@ class CagetteMember(models.Model):
'display_ftop_points', 'display_std_points',
'is_exempted', 'cooperative_state', 'date_alert_stop']
m_shoft_default_fields = ['name', 'barcode_base', 'total_partner_owned_share',
'amount_subscription']
m_short_default_fields = ['name', 'barcode_base']
def __init__(self, id):
"""Init with odoo id."""
......@@ -96,7 +96,6 @@ class CagetteMember(models.Model):
# # # BDM
def save_partner_info(self, partner_id, fieldsDatas):
print(fieldsDatas)
return self.o_api.update('res.partner', partner_id, fieldsDatas)
......@@ -132,6 +131,7 @@ class CagetteMember(models.Model):
fp = request.POST.get('fp') # fingerprint (prevent using stolen cookies)
if login and password:
api = OdooAPI()
login = login.strip()
cond = [['email', '=', login]]
if getattr(settings, 'ALLOW_NON_MEMBER_TO_CONNECT', False) is False:
cond.append('|')
......@@ -350,6 +350,23 @@ class CagetteMember(models.Model):
return answer
@staticmethod
def is_associated(id_parent):
api = OdooAPI()
cond = [['parent_id', '=', int(id_parent)]]
fields = ['id','name','parent_id','birthdate']
res = api.search_read('res.partner', cond, fields, 10, 0, 'id DESC')
already_have_adult_associated = False
for partner in res:
birthdate = partner['birthdate']
if(birthdate):
today = date.today()
date1 = datetime.datetime.strptime(birthdate, "%Y-%m-%d")
age = today.year - date1.year - ((today.month, today.day) < (date1.month, date1.day))
if age > 17 :
already_have_adult_associated = True
return already_have_adult_associated
@staticmethod
def finalize_coop_creation(post_data):
""" Update coop data. """
res = {}
......@@ -505,13 +522,38 @@ class CagetteMember(models.Model):
m.create_capital_subscription_invoice(post_data['shares_euros'], today)
res['bc'] = m.generate_base_and_barcode(post_data)
# Create shift suscription
shift_template = json.loads(post_data['shift_template'])
shift_t_id = shift_template['data']['id']
stype = shift_template['data']['type']
res['shift'] = \
m.create_coop_shift_subscription(shift_t_id, stype)
# m.add_first_point(stype) # Not needed anymore
# if the new member is associated with an already existing member
# then we put the state in "associated" and we create the "associated" member
if 'is_associated_people' in post_data and 'parent_id' in post_data :
fields = {}
fields['cooperative_state'] = 'associated'
api.update('res.partner', [partner_id], fields)
associated_member = {
'email': post_data['_id'],
'name': name,
'birthdate': birthdate,
'sex': sex,
'street': post_data['address'],
'zip': post_data['zip'],
'city': post_data['city'],
'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
'barcode_rule_id': settings.ASSOCIATE_BARCODE_RULE_ID,
'parent_id' : post_data['parent_id'],
'is_associated_people': True
}
associated_member_id = api.create('res.partner', associated_member)
am = CagetteMember(associated_member_id)
res['bca'] = am.generate_base_and_barcode(post_data)
# If it's an new associated member with a new partner. Link will be made by the user in BDM/admin
# We add the associated member to the "associate" shift template so we can find them in Odoo
elif 'is_associated_people' not in post_data or 'is_associated_people' in post_data and 'parent_id' not in post_data:
# Create shift suscription if is not associated
shift_template = json.loads(post_data['shift_template'])
shift_t_id = shift_template['data']['id']
stype = shift_template['data']['type']
res['shift'] = \
m.create_coop_shift_subscription(shift_t_id, stype)
# m.add_first_point(stype) # Not needed anymore
# Update couchdb do with new data
try:
......@@ -734,9 +776,12 @@ class CagetteMember(models.Model):
cond = [['name', 'ilike', str(key)]]
cond.append('|')
cond.append(['is_member', '=', True])
cond.append(['is_associated_people', '=', True])
if search_type != 'members':
cond.append(['is_associated_people', '=', True])
else:
cond.append(['is_associated_people', '=', False])
# cond.append(['cooperative_state', '!=', 'unsubscribed'])
if search_type == "full":
if search_type == "full" or search_type == 'members':
fields = CagetteMember.m_default_fields
if not shift_id is None:
CagetteMember.m_default_fields.append('tmpl_reg_line_ids')
......@@ -775,8 +820,27 @@ class CagetteMember(models.Model):
members.append(m)
return CagetteMember.add_next_shifts_to_members(members)
elif search_type == "shift_template_data":
fields = CagetteMember.m_short_default_fields
fields = fields + ['id', 'makeups_to_do', 'cooperative_state']
res = api.search_read('res.partner', cond, fields)
if res:
for partner in res:
c = [['partner_id', '=', int(partner['id'])], ['state', 'in', ('draft', 'open')]]
f = ['shift_template_id']
shift_template_reg = api.search_read('shift.template.registration', c, f)
if shift_template_reg:
partner['shift_template_id'] = shift_template_reg[0]['shift_template_id']
else:
partner['shift_template_id'] = None
return res
else:
fields = CagetteMember.m_shoft_default_fields
# TODO differentiate short & subscription_data searches
fields = CagetteMember.m_short_default_fields
fields = fields + ['total_partner_owned_share','amount_subscription']
res = api.search_read('res.partner', cond, fields)
return res
......@@ -864,6 +928,66 @@ class CagetteMember(models.Model):
return res
def get_member_selected_makeups(self):
res = {}
c = [["partner_id", "=", self.id], ["is_makeup", "=", True], ["state", "=", "open"]]
f=['id']
res = self.o_api.search_read("shift.registration", c, f)
return res
def unsuscribe_member(self):
res = {}
now = datetime.datetime.now().isoformat()
# Get and then delete shift template registration
c = [['partner_id', '=', self.id]]
f = ['id']
res_ids = self.o_api.search_read("shift.template.registration", c, f)
ids = [d['id'] for d in res_ids]
if ids:
res["delete_shift_template_reg"] = self.o_api.execute('shift.template.registration', 'unlink', ids)
# Get and then delete shift registrations
c = [['partner_id', '=', self.id], ['date_begin', '>', now]]
f = ['id']
res_ids = self.o_api.search_read("shift.registration", c, f)
ids = [d['id'] for d in res_ids]
if ids:
res["delete_shifts_reg"] = self.o_api.execute('shift.registration', 'unlink', ids)
# Close extensions
c = [['partner_id', '=', self.id], ['date_start', '<=', now], ['date_stop', '>=', now]]
f = ['id']
res_ids = self.o_api.search_read("shift.extension", c, f)
ids = [d['id'] for d in res_ids]
if ids:
f = {'date_stop': now}
res["close_extensions"] = self.o_api.update('shift.extension', ids, f)
return res
def set_cooperative_state(self, state):
f = {'cooperative_state': state}
return self.o_api.update('res.partner', [self.id], f)
def update_extra_shift_done(self, value):
api = OdooAPI()
res = {}
f = { 'extra_shift_done': value }
res_item = api.update('res.partner', [self.id], f)
res = {
'mid': self.id,
'update': res_item
}
return res
class CagetteMembers(models.Model):
"""Class to manage operations on all members or part of them."""
......@@ -1089,7 +1213,7 @@ class CagetteMembers(models.Model):
def get_makeups_members():
api = OdooAPI()
cond = [['makeups_to_do','>', 0]]
fields = ['id', 'name', 'makeups_to_do','shift_type']
fields = ['id', 'name', 'display_std_points', 'display_ftop_points', 'shift_type', 'makeups_to_do']
res = api.search_read('res.partner', cond, fields)
return res
......@@ -1206,7 +1330,7 @@ class CagetteServices(models.Model):
).total_seconds() / 60 > default_acceptable_minutes_after_shift_begins
if with_members is True:
cond = [['id', 'in', s['registration_ids']], ['state', 'not in', ['cancel', 'waiting', 'draft']]]
fields = ['partner_id', 'shift_type', 'state', 'is_late']
fields = ['partner_id', 'shift_type', 'state', 'is_late', 'associate_registered']
members = api.search_read('shift.registration', cond, fields)
s['members'] = sorted(members, key=lambda x: x['partner_id'][0])
if len(s['members']) > 0:
......@@ -1215,22 +1339,27 @@ class CagetteServices(models.Model):
for m in s['members']:
mids.append(m['partner_id'][0])
cond = [['parent_id', 'in', mids]]
fields = ['parent_id', 'name']
fields = ['id', 'parent_id', 'name','barcode_base']
associated = api.search_read('res.partner', cond, fields)
if len(associated) > 0:
for m in s['members']:
for a in associated:
if int(a['parent_id'][0]) == int(m['partner_id'][0]):
m['partner_name'] = m['partner_id'][1]
m['partner_id'][1] += ' en binôme avec ' + a['name']
m['associate_name'] = str(a['barcode_base']) + ' - ' + a['name']
return services
@staticmethod
def registration_done(registration_id, overrided_date=""):
def registration_done(registration_id, overrided_date="", typeAction=""):
"""Equivalent to click present in presence form."""
api = OdooAPI()
f = {'state': 'done'}
if(typeAction != "normal" and typeAction != ""):
f['associate_registered'] = typeAction
late_mode = getattr(settings, 'ENTRANCE_WITH_LATE_MODE', False)
if late_mode is True:
# services = CagetteServices.get_services_at_time('14:28',0, with_members=False)
......@@ -1252,7 +1381,14 @@ class CagetteServices(models.Model):
return api.update('shift.registration', [int(registration_id)], f)
@staticmethod
def record_rattrapage(mid, sid, stid):
def reopen_registration(registration_id, overrided_date=""):
api = OdooAPI()
f = {'state': 'open'}
return api.update('shift.registration', [int(registration_id)], f)
@staticmethod
def record_rattrapage(mid, sid, stid, typeAction):
"""Add a shift registration for member mid.
(shift sid, shift ticket stid)
......@@ -1268,6 +1404,8 @@ class CagetteServices(models.Model):
"state": 'open'}
reg_id = api.create('shift.registration', fields)
f = {'state': 'done'}
if(typeAction != "normal" and typeAction != ""):
f['associate_registered'] = typeAction
return api.update('shift.registration', [int(reg_id)], f)
@staticmethod
......@@ -1283,6 +1421,20 @@ class CagetteServices(models.Model):
# let authorized people time to set presence for those who came in late
end_date = now - datetime.timedelta(hours=2)
api = OdooAPI()
# Let's start by adding an extra shift to associated member who came together
cond = [['date_begin', '>=', date_24h_before.isoformat()],
['date_begin', '<=', end_date.isoformat()],
['state', '=', 'done'], ['associate_registered', '=', 'both']]
fields = ['state', 'partner_id', 'date_begin']
res = api.search_read('shift.registration', cond, fields)
for r in res:
cond = [['id', '=', r['partner_id'][0]]]
fields = ['id','extra_shift_done']
res = api.search_read('res.partner', cond, fields)
f = {'extra_shift_done': res[0]['extra_shift_done'] + 1 }
api.update('res.partner', [r['partner_id'][0]], f)
absence_status = 'excused'
res_c = api.search_read('ir.config_parameter',
[['key', '=', 'lacagette_membership.absence_status']],
......
.header {
margin: 1rem 0;
}
.login_area {
position: absolute;
display: block;
top: 5px;
right: 5px;
}
#back_to_admin_index {
position: absolute;
top: 5px;
left: 5px;
}
/* Search members 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;
}
/* Member info area */
#partner_data_area {
width: 80%;
display: none;
flex-direction: column;
align-items: center;
border-radius: 30px;
margin: 30px auto;
padding: 15px;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.member_name_container {
margin: 1rem 0 2rem 0;
}
.member_name_icon {
color: #00a573;
margin-right: 5px;
}
.member_info {
font-weight: bold;
}
/* Actions */
#remove_shift_template_button {
display: none;
margin: 15px;
}
.checkbox_area {
margin: 10px 0;
}
#permanent_unsuscribe {
margin-right: 5px;
}
\ No newline at end of file
......@@ -38,7 +38,11 @@
padding-top: 0.755em;
}
/* Search membres area */
/* Search members area */
.makeup_row {
background-color: #ffc854 !important;
}
#search_member_area {
margin-top: 30px;
display: flex;
......
......@@ -23,6 +23,57 @@
[data-week="4"] {border: 2px #eed000 solid;}
#new_coop [name="email"] {width:25em;}
#new_coop{
max-width: 75%;
}
#mail_generation {position:absolute; bottom:30px;}
#sex {padding: 0;}
#existing_partner{
padding-right: 15px;
width: 45%;
}
#new_partner{
padding-left: 15px;
width: 45%;
}
#add_binome{
cursor: pointer;
text-decoration: underline;
font-weight: bold;
}
#existing_member_choice, #new_member_choice{
border: 1px solid black;
width: 40%;
cursor: pointer;
text-align: center;
max-width: 400px;
background-color: #e7e9ed;
color: black;
padding: 1rem 1.5rem;
}
#existing_member_choice:hover, #new_member_choice:hover{
background-color: #ccc;
}
.choice_active{
background-color: #0275d8 !important;
color: white !important;
}
#existing_member_choice{
margin-right: 15px;
}
.choice_button_area{
margin-bottom: 10px;
}
#associate_area{
margin-bottom: 15px;
}
\ No newline at end of file
......@@ -44,8 +44,10 @@ h1 .member_name {font-weight: bold;}
.members_list {list-style: none;}
.members_list li {display:block;margin-bottom:5px;}
.members_list li.btn--inverse {background: #449d44; cursor:not-allowed; color: #FFF; }
.members_list li.btn--inverse {background: #449d44; color: #FFF; }
.members_list li.btn--inverse.not_connected {cursor:not-allowed}
.members_list li.btn--inverse.late {background-color: #de9b00; color: white}
.members_list li.btn--inverse.both {background-color: #0275d8 ; color: white}
#service_entry_success {font-size: x-large;}
#service_entry_success .explanations {margin: 25px 0; font-size: 18px;}
......@@ -64,7 +66,7 @@ h1 .member_name {font-weight: bold;}
#service_en_cours .info a {line-height: 24px; font-size:14px; margin-top:15px;}
.outside_list a {margin-left:15px;}
#rattrapage_1 .advice {margin-top:15px;}
.btn.present {background:#50C878;}
.btn.present {background:#50C878; color: white !important; font-weight: bold;}
.btn.return {background: #ff3333; color:#FFF !important; margin-top:25px;}
.msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;}
......
......@@ -4,7 +4,7 @@ $(document).ready(function() {
$(".page_content").show();
let location = window.location.href;
let location = window.location.href.replace(/\/$/, '');
$('.management_type_button').on('click', function() {
if (this.id == 'manage_makeups_button') {
......@@ -19,6 +19,8 @@ $(document).ready(function() {
window.location.assign(location + "/create_pair");
} else if (this.id == 'manage_leaves_button') {
console.log('coming soon...');
} else if (this.id == 'manage_regular_shifts_button') {
window.location.assign(location + "/manage_regular_shifts");
}
});
} else {
......
......@@ -69,6 +69,19 @@ function display_makeups_members() {
title: "Nom"
},
{
data: "shift_type",
title: "Nb de points",
className: "dt-body-center",
width: "10%",
render: function (data, type, row) {
if (data == 'ftop') {
return row.display_ftop_points
} else if (data == 'standard') {
return row.display_std_points
}
}
},
{
data: "makeups_to_do",
title: "Nb rattrapages",
className: "dt-body-center",
......@@ -358,7 +371,7 @@ $(document).ready(function() {
error: function() {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'confirm_movement.search_members'
ctx: 'members.admin.manage_makeups.search_members'
};
report_JS_error(err, 'stock');
......
var selected_member = null,
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"
};
/**
* Send request to remove partner from shift template
*/
function remove_from_shift_template() {
let permanent_unsuscribe = modal.find("#permanent_unsuscribe").prop('checked');
openModal();
let data = {
partner_id: selected_member.id,
shift_template_id: selected_member.shift_template_id[0],
permanent_unsuscribe: permanent_unsuscribe,
makeups_to_do: selected_member.makeups_to_do,
};
$.ajax({
type: 'POST',
url: '/members/delete_shift_template_registration',
data: JSON.stringify(data),
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function() {
selected_member.shift_template_id = null;
selected_member.cooperative_state = (permanent_unsuscribe === true) ? "gone" : "unsubscribed";
display_member_info();
closeModal();
},
error: function() {
err = {
msg: "erreur serveur lors de la suppression du membre du créneau",
ctx: 'members.admin.manage_regular_shifts.remove_from_shift_template'
};
report_JS_error(err, 'members.admin');
closeModal();
$.notify("Une erreur est survenue lors du processus de suppression du membre du créneau.", {
globalPosition:"top right",
className: "error"
});
}
});
}
/**
* When a member is selected, display the selected member relevant info
*/
function display_member_info() {
$('.member_name').text(`${selected_member.barcode_base} - ${selected_member.name}`);
$('.member_status').text(possible_cooperative_state[selected_member.cooperative_state]);
$('.member_makeups').text(selected_member.makeups_to_do);
if (selected_member.shift_template_id === undefined || selected_member.shift_template_id === null) {
$('.member_shift').text("");
$("#remove_shift_template_button").hide();
$("#remove_shift_template_button").off();
} else {
$('.member_shift').text(selected_member.shift_template_id[1]);
$("#remove_shift_template_button").show();
$("#remove_shift_template_button").off();
$("#remove_shift_template_button").on("click", () => {
let modal_template = $("#modal_remove_shift_template");
modal_template.find(".shift_template_name").text(selected_member.shift_template_id[1]);
openModal(
modal_template.html(),
remove_from_shift_template,
"Valider",
false
);
});
}
$('#search_member_input').val();
$('#partner_data_area').css('display', 'flex');
}
/**
* 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;
display_member_info();
$('.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...</i>
</p>`);
}
}
$(document).ready(function() {
if (coop_is_connected()) {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$(".page_content").show();
// 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}?search_type=shift_template_data`,
dataType : 'json',
success: function(data) {
$('#partner_data_area').hide();
if (data.res.length === 1) {
selected_member = data.res[0];
display_member_info();
} else {
members_search_results = data.res;
display_possible_members();
}
},
error: function() {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'members.admin.manage_regular_shifts.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"
});
}
});
});
} else {
$(".page_content").hide();
}
$('#back_to_admin_index').on('click', function() {
let base_location = window.location.href.split("manage_regular_shifts")[0].slice(0, -1);
window.location.assign(base_location);
});
});
......@@ -88,17 +88,29 @@ function display_member_shifts() {
"sLoadingRecords": "Chargement en cours...",
"sZeroRecords": "Aucun &eacute;l&eacute;ment &agrave; afficher",
"sEmptyTable": "Aucun futur service pour ce.tte membre"
}
},
createdRow: function(row, rdata) {
if (rdata.is_makeup === true) {
$(row).addClass("makeup_row");
$(row).prop('title', 'Ce service est un rattrapage');
}
},
});
$('#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;
const shift_is_makeup = row_data.is_makeup;
let msg = `<p>Enlever la présence de <b>${member.name}</b> au service du <b>${row_data.shift_id[1]}</b> ?</p>`;
if (shift_is_makeup === true) {
msg += `<p><i class="fas fa-exclamation-triangle"></i> Ce service est un rattrapage. Le supprimer ajoutera un point au compteur de ce.tte membre.</p>`;
}
openModal(
`Enlever la présence de ${member.name} au service du ${row_data.shift_id[1]} ?`,
msg,
() => {
delete_shift_registration(shift_reg_id);
delete_shift_registration(shift_reg_id, shift_is_makeup);
},
"Confirmer",
false
......@@ -109,13 +121,16 @@ function display_member_shifts() {
/**
* Send request to delete shift registration
* @param {Int} shift_reg_id Id of the shift_registration to delete
* @param {Boolean} shift_is_makeup Is the shift a makeup?
*/
function delete_shift_registration(shift_reg_id) {
function delete_shift_registration(shift_reg_id, shift_is_makeup) {
openModal();
data = {
member_id: selected_member.id,
shift_registration_id: shift_reg_id
member_shift_type: selected_member.shift_type,
shift_registration_id: shift_reg_id,
shift_is_makeup: shift_is_makeup
};
$.ajax({
......
......@@ -17,8 +17,9 @@ var latest_odoo_coop_bb = null,
subs_cap = $('#subs_cap'),
m_barcode = $('#m_barcode'),
sex = $('#sex'),
self_records = [],
selected_associate=null,
associated_old_choice= null,
choose_shift_msg = "Il est nécessaire de choisir un créneau (ABCD ou Volant) avant de pouvoir faire quoique ce soit d'autre.\nUne personne qui souhaite être rattachée au compte d'un autre membre dans le cadre d'un binôme doit choisir le créneau volant.";
......@@ -91,12 +92,18 @@ function new_coop_validation() {
coop_registration_details.find('.shift_template').text(st);
process_state.html(current_coop.firstname + ' ' +current_coop.lastname);
get_next_shift(current_coop.shift_template.data.id, function(data) {
if (data != null)
coop_registration_details.find('.next_shift').text(data.date_begin);
if (current_coop.shift_template.data && current_coop.shift_template.data.id != ASSOCIATE_MEMBER_SHIFT) {
get_next_shift(current_coop.shift_template.data.id, function(data) {
if (data != null) {
coop_registration_details.find('#next_shift_registration_detail').show();
coop_registration_details.find('.next_shift').text(data.date_begin);
}
coop_registration_details.show();
});
} else {
coop_registration_details.find('#next_shift_registration_detail').hide();
coop_registration_details.show();
});
}
}
function reset_sex_radios() {
......@@ -106,6 +113,11 @@ function reset_sex_radios() {
}
function create_new_coop() {
selected_associate= null;
$('#associate_area').hide();
$('.chosen_associate').html("");
$('.chosen_associate_area').hide();
$('.member_choice').removeClass('choice_active');
local_in_process = getLocalInProcess();
if (getLocalInProcess().length > 0) {
empty_waiting_local_processes();
......@@ -129,7 +141,6 @@ function create_new_coop() {
alert(choose_shift_msg);
}
}
}
function swipe_to_shift_choice() {
ncoop_view.hide();
......@@ -167,6 +178,30 @@ function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
coop.payment_meaning = pm;
coop.checks_nb = cn;
coop.fingerprint = fingerprint;
if (associated_old_choice == 'existing_member_choice') {
if (selected_associate!=null) {
coop.is_associated_people = true;
coop.parent_id=selected_associate.id;
coop.parent_name=selected_associate.barcode_base + ' - '+ selected_associate.name;
coop.shift_template = shift_templates[ASSOCIATE_MEMBER_SHIFT];
}
} else if (associated_old_choice == 'new_member_choice' && $('#new_member_input').val()!='') {
coop.is_associated_people = true;
coop.parent_name=$('#new_member_input').val();
delete coop.parent_id;
coop.shift_template = shift_templates[ASSOCIATE_MEMBER_SHIFT];
} else {
delete coop.is_associated_people;
delete coop.parent_id;
delete coop.parent_name;
}
selected_associate=null;
$('#new_member_input').val('');
$('#associate_area').hide();
$('.chosen_associate_area').hide();
$('.chosen_associate').html("");
associated_old_choice= null;
if (m_barcode.length > 0) coop.m_barcode = bc;
if (sex.length > 0) coop.sex = msex;
coop.validation_state = "to_fill";
......@@ -174,11 +209,14 @@ function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
if (!err) {
coop._rev = result.rev;
current_coop = coop;
if (typeof coop.shift_template != "undefined") {
if (typeof coop.shift_template != "undefined" && coop.shift_template.data.id != ASSOCIATE_MEMBER_SHIFT) {
openModal(
'Voulez-vous modifier le créneau choisi ?', swipe_to_shift_choice, 'oui',
false, true, show_coop_list
);
} else if (coop.is_associated_people && typeof coop.shift_template != "undefined" && coop.shift_template.data.id == ASSOCIATE_MEMBER_SHIFT) {
ncoop_view.hide();
new_coop_validation();
} else {
swipe_to_shift_choice();
}
......@@ -226,17 +264,36 @@ function store_new_coop(event) {
if (typeof(rData.answer) == 'boolean' && rData.answer == true) {
errors.push("Il y a déjà un enregistrement Odoo avec cette adresse mail !");
}
});
if (errors.length == 0) {
_really_save_new_coop(
email, fname, lname,
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex
);
if (selected_associate!=null) {
$.ajax({url : '/members/is_associated/' + selected_associate.id,
dataType :'json'
}).done(function(rData) {
if (typeof(rData.answer) == 'boolean' && rData.answer == true) {
errors.push("Ce membre à déjà un Binôme majeur");
}
if (errors.length == 0) {
_really_save_new_coop(
email, fname, lname,
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex
);
} else {
alert(errors.join("\n"));
}
});
} else {
alert(errors.join("\n"));
}
} else {
if (errors.length == 0) {
_really_save_new_coop(
email, fname, lname,
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex
);
} else {
alert(errors.join("\n"));
}
}
});
}
......@@ -273,6 +330,29 @@ function modify_current_coop() {
} else {
ch_qty.hide();
}
if (current_coop.is_associated_people) {
$('.member_choice').removeClass('choice_active');
$('#associate_area').show();
if (current_coop.parent_id) {
$('#existing_member_choice_action').show();
$('#new_member_choice_action').hide();
$('#existing_member_choice').addClass('choice_active');
var member_button = '<div>' + current_coop.parent_name + '</div>';
$('.chosen_associate').html(member_button);
$('.chosen_associate_area').show();
associated_old_choice = 'existing_member_choice';
} else {
$('#new_member_choice_action').show();
$('#existing_member_choice_action').hide();
$('#new_member_choice').addClass('choice_active');
$('#new_member_input').val(current_coop.parent_name);
$('.chosen_associate').html('');
$('.chosen_associate_area').hide();
associated_old_choice = 'new_member_choice';
}
}
subs_cap.val(current_coop.shares_euros);
if (m_barcode.length > 0) m_barcode.val(current_coop.m_barcode);
if (sex.length > 0) {
......@@ -497,8 +577,48 @@ $('#coop_create').submit(store_new_coop);
$('#generate_email').click(generate_email);
$('#odoo_user_connect').click();
$('#add_binome').click(function() {
if ($('#associate_area').is(':visible')) {
$('#associate_area').hide();
associated_old_choice = null;
$('#new_member_input').val('');
if (current_coop !=null) {
delete current_coop.parent_name;
delete current_coop.parent_id;
delete current_coop.is_associated_people;
delete current_coop.shift_template;
}
} else {
$('#associate_area').show();
$('.member_choice').removeClass('choice_active');
$('#existing_member_choice_action').hide();
$('#new_member_choice_action').hide();
associated_old_choice = null;
}
});
$('.member_choice').on('click', function() {
if (associated_old_choice !=null && associated_old_choice!=$(this).attr('id')) {
$('#'+$(this).attr('id')+'_action').show();
$('#'+associated_old_choice+'_action').hide();
$('#'+associated_old_choice).removeClass('choice_active');
} else if (associated_old_choice ==null) {
$('#'+$(this).attr('id')+'_action').show();
}
associated_old_choice=$(this).attr('id');
$(this).addClass('choice_active');
});
$('#shift_calendar').click(show_shift_calendar);
$('#search_member_input').keypress((event) => {
if (event.keyCode==13) {
event.preventDefault();
searchMembersForAssociate();
}
});
//get_latest_odoo_coop_bb();
update_self_records();
......@@ -516,3 +636,109 @@ payment_meaning.change(function() {
window.addEventListener("beforeunload", keep_in_process_work);
empty_waiting_local_processes();
/**
* Display the members from the search result
*/
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
$('.btn_possible_member').off();
$('.chosen_associate').html("");
$('.chosen_associate_area').hide();
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_associate = member;
var member_button = '<div member_id="' + member.id + '">' + member.barcode_base + ' - ' + member.name + '</button>';
$('.chosen_associate').html(member_button);
$('.chosen_associate_area').show();
$('.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>`);
}
}
/**
* Search for members to associate a new member with an old one.
*/
function searchMembersForAssociate() {
let search_str = $('#search_member_input').val();
if (search_str) {
$.ajax({
url: '/members/search/' + search_str+ "?search_type=members",
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"
});
}
});
} else {
members_search_results = [];
display_possible_members();
}
}
$(document).ready(function() {
retrieve_and_draw_shift_tempates();
// Set action to search for the member
$('#search_member_button').on('click', function() {
searchMembersForAssociate();
});
});
......@@ -31,12 +31,14 @@ var search_field = $('input[name="search_string"]');
var shift_title = $('#current_shift_title');
var shift_members = $('#current_shift_members');
var service_validation = $('#service_validation');
var associated_service_validation = $('#associated_service_validation');
var validation_last_call = 0;
var rattrapage_wanted = $('[data-next="rattrapage_1"]');
var webcam_is_attached = false;
var photo_advice = $('#photo_advice');
var photo_studio = $('#photo_studio');
var coop_info = $('.coop-info');
var service_data = null;
const missed_begin_msg = $('#missed_begin_msg').html();
......@@ -264,7 +266,7 @@ function get_simple_service_name(s) {
}
function move_service_validation_to(page) {
service_validation.find('.btn').data('stid', '0');
service_data.stid=0;
page.find('.validation_wrapper')
.append(service_validation.detach());
}
......@@ -284,11 +286,21 @@ function fill_service_entry(s) {
var li_class = "btn";
var li_data = "";
if (e.state == "done") {
if (e.state == "done" && coop_is_connected()) {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
li_class += "--inverse";
if (e.is_late == true) {
li_class += " late";
}
if (e.associate_registered=='both') {
li_class += " both";
}
} else if (e.state == "done" && !coop_is_connected()) {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
li_class += "--inverse not_connected";
if (e.is_late == true) {
li_class += " late";
}
} else {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
}
......@@ -321,13 +333,29 @@ function clean_service_entry() {
function fill_service_validation(rid, coop_num_name, coop_id) {
var coop_name_elts = coop_num_name.split(' - ');
for (member of loaded_services[0].members) {
if (member.id ==rid) {
if (member.associate_name) {
pages.service_entry_validation.find('#service_validation').hide();
pages.service_entry_validation.find('#associated_service_validation').show();
pages.service_entry_validation.find('#associated_btn').text(member.associate_name);
pages.service_entry_validation.find('#partner_btn').text(member.partner_name);
} else {
pages.service_entry_validation.find('#associated_service_validation').hide();
pages.service_entry_validation.find('#service_validation').show();
}
}
}
service_data={
rid: rid,
sid: selected_service.id,
mid: coop_id};
pages.service_entry_validation.find('span.member_name').text(coop_name_elts[1]);
move_service_validation_to(pages.service_entry_validation);
service_validation.find('.btn')
.data('rid', rid)
.data('sid', selected_service.id)
.data('mid', coop_id);
}
function select_possible_service() {
......@@ -372,7 +400,6 @@ function get_service_entry_data() {
dataType : 'json'
})
.done(function(rData) {
//console.log(rData);
info_place.text('');
var page_title = pages.service_entry.find('h1');
......@@ -397,6 +424,7 @@ function get_service_entry_data() {
page_title.text('Quel est ton service ?');
} else {
loaded_services = rData.res;
fill_service_entry(rData.res[0]);
}
}
......@@ -443,22 +471,21 @@ function fill_service_entry_sucess(member) {
}
function record_service_presence() {
function record_service_presence(e) {
var d = new Date();
var elapsed_since_last_call = d.getTime() - validation_last_call;
if (elapsed_since_last_call > 10000) {
if (elapsed_since_last_call > 1000) {
loading2.show();
validation_last_call = d.getTime();
var clicked = service_validation.find('.btn');
var rid = clicked.data('rid');
var mid = clicked.data('mid');
var sid = clicked.data('sid');
var stid = clicked.data('stid');
var rid = service_data.rid;
var mid = service_data.mid;
var sid = service_data.sid;
var stid = service_data.stid;
post_form(
'/members/service_presence/',
{'mid': mid, 'rid': rid, 'sid': sid, 'stid' : stid},
{'mid': mid, 'rid': rid, 'sid': sid, 'stid' : stid, 'cancel': false, 'type': e.data.type},
function(err, rData) {
if (!err) {
var res = rData.res;
......@@ -480,6 +507,28 @@ function record_service_presence() {
}
}
function cancel_service_presence(mid, rid) {
var d = new Date();
var elapsed_since_last_call = d.getTime() - validation_last_call;
if (elapsed_since_last_call > 1000) {
loading2.show();
validation_last_call = d.getTime();
var sid = selected_service.id;
post_form(
'/members/service_presence/',
{'mid': mid, 'rid': rid, 'sid': sid, 'stid' : 0, 'cancel': true},
function(err) {
if (!err) {
get_service_entry_data();
}
loading2.hide();
}
);
}
}
function fill_rattrapage_2() {
pages.rattrapage_2.find('span.member_name').text(current_displayed_member.name);
var msg = "Bienvenue pour ton rattrapage !";
......@@ -494,13 +543,11 @@ function fill_rattrapage_2() {
msg = "Tu es en désincrit.e ... La situation doit être réglée avez le Bureau des Membres";
} else {
move_service_validation_to(pages.rattrapage_2);
service_validation.find('.btn')
.data('rid', 0)
.data('sid', selected_service.id)
.data('stid', shift_ticket_id)
.data('mid', current_displayed_member.id);
service_data = {
rid : 0,
sid : selected_service.id,
stid : shift_ticket_id,
mid : current_displayed_member.id};
}
pages.rattrapage_2.find('h2').text(msg);
......@@ -641,7 +688,10 @@ $('.btn[data-next]').click(function() {
});
service_validation.on("click", ".btn", record_service_presence);
service_validation.on("click", ".btn", {type:'normal'}, record_service_presence);
associated_service_validation.on("click", "#associated_btn", {type:'associate'}, record_service_presence);
associated_service_validation.on("click", "#partner_btn", {type:'partner'}, record_service_presence);
associated_service_validation.on("click", "#both_btn", {type:'both'}, record_service_presence);
shift_members.on("click", '.btn[data-rid]', function() {
var clicked = $(this);
......@@ -653,6 +703,16 @@ shift_members.on("click", '.btn[data-rid]', function() {
});
shift_members.on("click", '.btn--inverse', function() {
if (coop_is_connected()) {
var clicked = $(this);
var rid = clicked.data('rid');
var mid = clicked.data('mid');
cancel_service_presence(mid, rid);
}
});
pages.shopping_entry.on('css', function() {
photo_advice.hide();
photo_studio.hide();
......
......@@ -32,6 +32,15 @@ function display_current_coop_form() {
let street2_input = form.find('[name="street2"]'),
phone_input = form.find('[name="phone"]');
if (current_coop.parent_name) {
$('#associated_member').show();
if (current_coop.parent_id)
$('#associated_member_name').text(current_coop.parent_name);
else $('#associated_member_name').text(current_coop.parent_name + " ATTENTION à faire manuellement");
} else {
$('#associated_member').hide();
}
chgt_shift_btn.hide();
chgt_shift_btn.off('click', open_shift_choice);
form.find('[name="firstname"]').val(current_coop.firstname);
......
......@@ -31,6 +31,7 @@ urlpatterns = [
url(r'^latest_coop_id/$', views.latest_coop_id),
url(r'^get/([0-9]+)$', views.get),
url(r'^exists/([a-zA-Z0-9_\-\.\+@]+)$', views.exists),
url(r'^is_associated/([0-9]+)$', views.is_associated),
url(r'^get_couchdb_odoo_markers/(.+)$', views.get_couchdb_odoo_markers),
url(r'^menu/$', views.menu),
url(r'^verify_final_state$', views.verify_final_state),
......@@ -50,13 +51,18 @@ urlpatterns = [
url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence),
# conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases),
# BDM
# BDM
url(r'^save_partner_info$', views.save_partner_info),
# 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_shift_registrations$', admin.manage_shift_registrations),
url(r'^admin/manage_regular_shifts$', admin.manage_regular_shifts),
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),
url(r'^delete_shift_template_registration$', admin.delete_shift_template_registration),
url(r'^admin/manage_attached$', admin.manage_attached),
url(r'^admin/manage_attached/create_pair$', admin.create_pair),
url(r'^admin/manage_attached/delete_pair$', admin.delete_pair),
......
......@@ -63,6 +63,10 @@ def exists(request, mail):
answer = CagetteMember.exists(mail)
return JsonResponse({'answer': answer})
def is_associated(request, id_parent):
answer = CagetteMember.is_associated(id_parent)
return JsonResponse({'answer': answer})
def getmemberimage(request, id):
m = CagetteMember(id)
call_res = m.get_image()
......@@ -97,7 +101,9 @@ def inscriptions(request, type=1):
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
'db': settings.COUCHDB['dbs']['member'],
'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', '')
}
response = HttpResponse(template.render(context, request))
return response
......@@ -277,6 +283,9 @@ def record_service_presence(request):
mid = int(request.POST.get("mid", 0)) # member id
sid = int(request.POST.get("sid", 0)) # shift id
stid = int(request.POST.get("stid", 0)) # shift_ticket_id
cancel = request.POST.get("cancel") == 'true'
typeAction = str(request.POST.get("type"))
app_env = getattr(settings, 'APP_ENV', "prod")
if (rid > -1 and mid > 0):
overrided_date = ""
......@@ -286,28 +295,31 @@ def record_service_presence(request):
if o_date:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
# rid = 0 => C'est un rattrapage, sur le service
if sid > 0 and stid > 0:
# Add member to service and take presence into account
res['rattrapage'] = CagetteServices.record_rattrapage(mid, sid, stid)
if res['rattrapage'] is True:
res['update'] = 'ok'
else:
if (CagetteServices.registration_done(rid, overrided_date) is True):
res['update'] = 'ok'
if(not cancel):
# rid = 0 => C'est un rattrapage, sur le service
if sid > 0 and stid > 0:
# Add member to service and take presence into account
res['rattrapage'] = CagetteServices.record_rattrapage(mid, sid, stid, typeAction)
if res['rattrapage'] is True:
res['update'] = 'ok'
else:
res['update'] = 'ko'
if res['update'] == 'ok':
members = CagetteMember.search('id', mid)
m = members[0]
for k in ['image_medium', 'barcode', 'barcode_base']:
del m[k]
next_shift = {}
if len(m['shifts']) > 0:
next_shift = m['shifts'][0]
del m['shifts']
m['next_shift'] = next_shift
res['member'] = m
if (CagetteServices.registration_done(rid, overrided_date, typeAction) is True):
res['update'] = 'ok'
else:
res['update'] = 'ko'
if res['update'] == 'ok':
members = CagetteMember.search('id', mid)
m = members[0]
for k in ['image_medium', 'barcode', 'barcode_base']:
del m[k]
next_shift = {}
if len(m['shifts']) > 0:
next_shift = m['shifts'][0]
del m['shifts']
m['next_shift'] = next_shift
res['member'] = m
else: CagetteServices.reopen_registration(rid, overrided_date)
except Exception as e:
res['error'] = str(e)
return JsonResponse({'res': res})
......
......@@ -54,6 +54,13 @@
margin: 3rem 0;
}
#my_info .choose_makeups,
#my_info .unsuscribed_form_link,
#my_info .remove_future_registration {
font-size: 1.8rem;
word-break: normal;
}
#my_info #member_status_action,
#my_info .member_shift_name_area,
#my_info .member_coop_number_area {
......
......@@ -64,14 +64,39 @@
}
}
.shift_line_container {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
.shift_line_extra_actions {
display: flex;
justify-content: flex-start;
flex-wrap: nowrap;
}
@media screen and (max-width:768px) {
.shift_line_container {
flex-direction: column;
}
.shift_line_extra_actions {
width: 100%;
}
.affect_associate_registered {
margin: 0.5rem 0;
}
}
.selectable_shift_line {
min-width: 325px;
display: flex;
align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px;
margin-right: 15px;
}
.selectable_shift_line .checkbox {
......@@ -85,11 +110,31 @@
.affect_associate_registered {
display: flex;
align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px;
}
@media screen and (min-width:768px) {
.selectable_shift_line {
margin: 0 15px;
}
.affect_associate_registered {
margin-left: 15px;
}
}
.selectable_shift{
margin: 1rem 0;
}
.delete_registration_button {
justify-content: center;
align-items: center;
margin: 0.75rem 15px;
color: #d9534f;
cursor: pointer;
display: none;
}
/* -- Calendar screen, makeups message */
......@@ -113,6 +158,21 @@
}
}
/* -- Calendar screen, can delete registrations message */
#can_delete_future_registrations_area {
display: none;
justify-content: center;
align-items: center;
margin: 0 1rem 1rem 1rem;
}
#can_delete_future_registrations_area button {
white-space: normal;
word-break: normal;
margin: 1rem;
}
/* -- Calendar screen, calendar */
#calendar {
......@@ -157,6 +217,19 @@ td{
color: white;
}
.fc-event.shift_booked_makeup {
background-color: #f0ad4e;
cursor: auto;
border-color: #f0ad4e;
}
.fc-event.shift_booked_makeup td {
--fc-list-event-hover-bg-color:#f0ad4e;
}
.fc-list-event.shift_booked_makeup {
color: white;
}
#calendar .fc-list-table {
table-layout: auto;
}
......@@ -204,4 +277,14 @@ td{
#calendar_explaination_button {
max-width: 60%;
margin: 2rem auto 0.5rem auto;
}
/* -- Assign shift modal */
.modal_affect_shift_buttons {
margin: 1rem 0;
}
.assign_shift_button {
margin: 0.25rem;
}
\ No newline at end of file
......@@ -166,6 +166,11 @@ body {
font-size: 1.5rem;
}
.remove_future_registration {
display: none;
white-space: normal;
}
.unsuscribed_form_link {
display: none;
text-decoration: none;
......
......@@ -20,7 +20,7 @@ function init_faq() {
}
$(document).on('click', "#shift_exchange_btn", () => {
goto('echange-de-services');
goto('echange-de-services');
});
$(document).on('click', '.accordion', function() {
......
/**
* 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...");
......
......@@ -68,8 +68,7 @@ function prepare_server_data(data) {
if (history_item.associate_registered == false || history_item.associate_registered == undefined) {
history_item.associate_registered = "";
}
else {
} else {
if (partner_data.associated_partner_id != "False") {
if (history_item.associate_registered==="partner") {
history_item.associate_registered = partner_data.name;
......
......@@ -2,6 +2,8 @@ var calendar = null,
selected_shift = null,
vw = null;
/* - Logic */
/**
* A partner can exchange shifts if:
* - s.he doesn't have to choose a makeup shift
......@@ -20,9 +22,11 @@ function can_exchange_shifts() {
* @returns boolean
*/
function should_select_makeup() {
return partner_data.makeups_to_do == 0 && (partner_data.is_associated_people === "False" || (partner_data.is_associated_people === "True" && block_actions_for_attached_people === "False"));
return partner_data.makeups_to_do > 0 || (partner_data.makeups_to_do > 0 && partner_data.is_associated_people === "True" && block_actions_for_attached_people === "False");
}
/* - Server requests */
/**
* Proceed to shift exchange or registration
* @param {int} new_shift_id
......@@ -36,7 +40,7 @@ function add_or_change_shift(new_shift_id) {
+'&idPartner=' + partner_data.partner_id
+ '&shift_type=' + partner_data.shift_type
+ '&verif_token=' + partner_data.verif_token;
} else if(partner_data.is_associated_people === "True" && block_actions_for_attached_people === "False") {
} else if (partner_data.is_associated_people === "True" && block_actions_for_attached_people === "False") {
tData = 'idNewShift=' + new_shift_id
+'&idPartner=' + partner_data.parent_id
+ '&shift_type=' + partner_data.shift_type
......@@ -101,10 +105,10 @@ function add_or_change_shift(new_shift_id) {
`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
// Refectch shifts anyway, if registration/exchange was still succesful
setTimeout(() => {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_shifts_list);
.then(init_shifts_list);
}, 300);
}
},
......@@ -112,16 +116,17 @@ function add_or_change_shift(new_shift_id) {
closeModal();
selected_shift = null;
if (error.status === 400 && error.msg === "Old service in less than 24hours.") {
if (error.status === 400 && 'msg' in error.responseJSON && error.responseJSON.msg === "Old service in less than 24hours.") {
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 if (error.status === 500 && error.msg === "Fail to create shift") {
} else if (error.status === 500 && 'msg' in error.responseJSON && error.responseJSON.msg === "Fail to create shift") {
// TODO differentiate error cases!
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.`);
} else if (error.status === 400 && error.msg === "Bad arguments") {
} else if (error.status === 400 && 'msg' in error.responseJSON && error.responseJSON.msg === "Bad arguments") {
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.`);
......@@ -139,8 +144,147 @@ function add_or_change_shift(new_shift_id) {
}
});
}
return null;
}
/**
* Send request to delete (cancel) a shift registration.
* @param {Int} shift_registration_id shift registration to cancel
*/
function delete_shift_registration(shift_registration_id) {
if (is_time_to('delete_shift_registration')) {
openModal();
tData = 'idPartner=' + partner_data.concerned_partner_id
+ '&idRegister=' + shift_registration_id
+ '&extra_shift_done=' + partner_data.extra_shift_done;
if (partner_data.is_associated_people === "False") {
tData += '&verif_token=' + partner_data.verif_token;
} else if (partner_data.is_associated_people === "True" && block_actions_for_attached_people === "False") {
tData += '&verif_token=' + partner_data.parent_verif_token;
} else {
return false;
}
$.ajax({
type: 'POST',
url: "/shifts/cancel_shift",
dataType:"json",
data: tData,
timeout: 3000,
success: function() {
partner_data.extra_shift_done -= 1;
// Refetch partner shifts list & update DOM
load_partner_shifts(partner_data.concerned_partner_id)
.then(() => {
init_shifts_list();
if (partner_data.extra_shift_done > 0) {
$(".extra_shift_done").text(partner_data.extra_shift_done);
init_delete_registration_buttons();
} else {
$("#can_delete_future_registrations_area").hide();
$(".delete_registration_button").off();
$(".delete_registration_button").hide();
}
closeModal();
setTimeout(() => {
alert("La présence a bien été annulée !");
}, 100);
});
// Redraw calendar
calendar.refetchEvents();
},
error: function() {
closeModal();
alert("Une erreur est survenue.");
}
});
}
return null;
}
/**
* Proceed affecting a shift registration to a/both member(s) of a pair
* @param {string} partner
* @param {string} shift_id
*/
function affect_shift(partner, shift_id) {
if (is_time_to('affect_shift', 1000)) {
tData = 'idShiftRegistration=' + shift_id
+'&idPartner=' + partner_data.partner_id
+ '&affected_partner=' + partner
+ '&verif_token=' + partner_data.verif_token;
tUrl = '/shifts/affect_shift';
$.ajax({
type: 'POST',
url: tUrl,
dataType:"json",
data: tData,
timeout: 3000,
success: function() {
load_partner_shifts(partner_data.concerned_partner_id)
.then(() => {
init_shifts_list();
modal.find(".btn-modal-ok").show();
closeModal();
});
},
error: function() {
init_shifts_list();
modal.find(".btn-modal-ok").show();
closeModal();
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.`);
}
});
}
}
/**
* Reset a member extra_shift_done to 0
*/
function offer_extra_shift() {
if (is_time_to('offer_extra_shift')) {
openModal();
$.ajax({
type: 'POST',
url: "/members_space/offer_extra_shift",
dataType:"json",
data: {
partner_id: partner_data.concerned_partner_id
},
timeout: 3000,
success: function() {
$("#can_delete_future_registrations_area").hide();
$(".delete_registration_button").off();
$(".delete_registration_button").hide();
closeModal();
alert("Don de service effectué");
},
error: function() {
closeModal();
alert("Une erreur est survenue");
}
});
}
}
/* - DOM */
function init_shifts_list() {
$(".loading-incoming-shifts").hide();
$("#shifts_list").show();
......@@ -150,7 +294,7 @@ function init_shifts_list() {
} else {
$("#shifts_list").empty();
for (shift of incoming_shifts) {
for (let shift of incoming_shifts) {
let shift_line_template = $("#selectable_shift_line_template");
let datetime_shift_start = new Date(shift.date_begin.replace(/\s/, 'T'));
......@@ -161,24 +305,42 @@ function init_shifts_list() {
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() && block_actions_for_attached_people === "True") {
shift_line_template.find(".selectable_shift_line").removeClass("btn--primary");
// Disable or not
shift_line_template.find(".selectable_shift_line").removeClass("btn--primary");
shift_line_template.find(".selectable_shift_line").removeClass("btn");
shift_line_template.find(".selectable_shift_line").removeClass("btn--warning");
if (!can_exchange_shifts()) {
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);
if (shift.is_makeup==true) {
shift_line_template.find(".selectable_shift_line").addClass("btn--warning");
shift_line_template.find(".checkbox").prop("disabled", false);
shift_line_template.find(".checkbox").prop("value", shift.id);
} else {
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);
}
}
// Set assign shift button
if (partner_data.associated_partner_id === "False" && partner_data.parent_id === "False") {
shift_line_template.find('.affect_associate_reistered').hide();
shift_line_template.find('.affect_associate_registered').hide();
} else {
shift_line_template.find('.affect_associate_registered').attr('id', 'shidt_id_'+shift.id);
if (!can_exchange_shifts()) {
shift_line_template.find('.affect_associate_registered').hide();
} else {
shift_line_template.find('.affect_associate_registered').show();
}
shift_line_template.find('.affect_associate_registered').closest(".shift_line_container")
.attr('id', 'shift_id_'+shift.id);
if (shift.associate_registered==="both") {
shift_line_template.find('.affect_associate_registered').text("Les deux");
shift_line_template.find('.affect_associate_registered').addClass('btn--success');
} else if (shift.associate_registered==="partner") {
shift_line_template.find('.affect_associate_registered').addClass('btn--success');
if (partner_data.associated_partner_id !== "False") {
shift_line_template.find('.affect_associate_registered').text(partner_data.name);
} else {
......@@ -186,6 +348,7 @@ function init_shifts_list() {
}
} else if (shift.associate_registered==="associate") {
shift_line_template.find('.affect_associate_registered').addClass('btn--success');
if (partner_data.associated_partner_id !== "False") {
shift_line_template.find('.affect_associate_registered').text(partner_data.associated_partner_name);
} else {
......@@ -193,10 +356,24 @@ function init_shifts_list() {
}
} else {
shift_line_template.find('.affect_associate_registered').text("A déterminer");
shift_line_template.find('.affect_associate_registered').addClass('btn--danger');
}
}
// Set delete registration button if shift isn't a makeup
if (partner_data.extra_shift_done > 0 && shift.is_makeup === false) {
if (shift_line_template.find(".delete_registration_button").length === 0) {
let delete_reg_button_template = $("#delete_registration_button_template");
shift_line_template.find(".shift_line_extra_actions").append(delete_reg_button_template.html());
}
} else {
shift_line_template.find(".delete_registration_button").remove();
}
$("#shifts_list").append(shift_line_template.html());
shift_line_template.find('.affect_associate_registered').removeClass('btn--danger');
shift_line_template.find('.affect_associate_registered').removeClass('btn--success');
}
$(".selectable_shift_line").on("click", function(e) {
......@@ -228,7 +405,8 @@ function init_shifts_list() {
$(".affect_associate_registered").on("click", function() {
// Display modal
id = $(this).attr('id')
let id = $(this).closest(".shift_line_container")
.attr('id')
.split('_')[2];
let modal_template = $("#modal_affect_shift");
......@@ -237,8 +415,8 @@ function init_shifts_list() {
modal_template.find("#shift_associate").text(partner_data.associated_partner_name);
} else {
modal_template.find("#shift_partner").text(partner_data.associated_partner_name);
modal_template.find("#shift_associate").text(partner_data.parent_name);
modal_template.find("#shift_partner").text(partner_data.parent_name);
modal_template.find("#shift_associate").text(partner_data.name);
}
openModal(
......@@ -267,48 +445,6 @@ function init_shifts_list() {
}
}
/**
* Proceed to shift modification
* @param {string} partner
* @param {string} shift_id
*/
function affect_shift(partner, shift_id) {
tData = 'idShiftRegistration=' + shift_id
+'&idPartner=' + partner_data.partner_id
+ '&affected_partner=' + partner
+ '&verif_token=' + partner_data.verif_token;
tUrl = '/shifts/affect_shift';
$.ajax({
type: 'POST',
url: tUrl,
dataType:"json",
data: tData,
timeout: 3000,
success: function() {
load_partner_shifts(partner_data.concerned_partner_id)
.then(() => {
init_shifts_list();
modal.find(".btn-modal-ok").show();
closeModal();
});
},
error: function() {
init_shifts_list();
modal.find(".btn-modal-ok").show();
closeModal();
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.`);
}
});
}
/**
* Inits the page when the calendar is displayed
*/
......@@ -345,6 +481,22 @@ function init_calendar_page() {
$("#need_to_select_makeups_message").show();
}
if (partner_data.extra_shift_done > 0) {
$(".extra_shift_done").text(partner_data.extra_shift_done);
$("#can_delete_future_registrations_area").css('display', 'flex');
$("#offer_extra_shift").on("click", () => {
openModal(
"<p>Je ne souhaite pas supprimer un service futur.</p>",
offer_extra_shift,
"Confirmer",
false
);
});
$("#delete_future_registration").on("click", init_delete_registration_buttons);
}
let default_initial_view = "";
let header_toolbar = {};
......@@ -392,7 +544,7 @@ function init_calendar_page() {
hiddenDays: hidden_days,
events: '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id,
eventClick: function(info) {
if (!$(info.el).hasClass("shift_booked")) {
if (!$(info.el).hasClass("shift_booked") && !$(info.el).hasClass("shift_booked_makeup")) {
const new_shift_id = info.event.id;
// Set new shift
......@@ -569,6 +721,31 @@ function init_read_only_calendar_page() {
calendar.render();
}
function init_delete_registration_buttons() {
$(".delete_registration_button").off();
$(".delete_registration_button").on("click", function() {
let shift_name = $(this).closest("div")
.parent().parent()
.find(".shift_line_date")
.text()
.trim();
let shift_id = $(this).closest(".shift_line_container")
.attr('id')
.split('_')[2];
openModal(
`<p>Je m'apprête à supprimer ma présence au service du <b>${shift_name}</b></p>`,
() => {
delete_shift_registration(shift_id);
},
"Confirmer",
false
);
});
$(".delete_registration_button").css('display', 'flex');
}
function init_shifts_exchange() {
$(".shifts_exchange_page_content").hide();
vw = window.innerWidth;
......@@ -600,8 +777,7 @@ function init_shifts_exchange() {
$(this).removeClass('active');
});
});
} else if (
partner_data.comite === "True") {
} else if (partner_data.comite === "True") {
let msg_template = $("#comite_template");
$(".comite_content_msg").html(msg_template.html());
......@@ -630,7 +806,17 @@ function init_shifts_exchange() {
}
$(window).smartresize(function() {
vw = window.innerWidth;
init_calendar_page();
// only apply if a width threshold is passed
if (
vw > 992 && window.innerWidth <= 992 ||
vw <= 992 && window.innerWidth > 992 ||
vw > 768 && window.innerWidth <= 768 ||
vw <= 768 && window.innerWidth > 768
) {
vw = window.innerWidth;
init_calendar_page();
} else {
vw = window.innerWidth;
}
});
}
......@@ -131,6 +131,70 @@ function update_content() {
/* - Shifts */
/**
* 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.is_associated_people === "True") ? partner_data.parent_verif_token : partner_data.verif_token,
idPartner: partner_data.concerned_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.');
}
}
});
});
}
/**
* Prepare a shift line to insert into the DOM.
* Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section
*
......@@ -147,7 +211,7 @@ function prepare_shift_line_template(date_begin) {
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;
}
......@@ -158,6 +222,7 @@ function prepare_shift_line_template(date_begin) {
*/
function init_my_info_data() {
$(".choose_makeups").off();
$(".remove_future_registration").off();
$(".unsuscribed_form_link").off();
$(".member_shift_name").text(partner_data.regular_shift_name);
......@@ -199,7 +264,6 @@ function init_my_info_data() {
if (
partner_data.makeups_to_do > 0
&& partner_data.is_associated_people === "False"
&& partner_data.cooperative_state !== 'unsubscribed'
) {
$(".choose_makeups").show();
......@@ -223,12 +287,17 @@ function init_my_info_data() {
}
}
if (partner_data.extra_shift_done > 0) {
$(".remove_future_registration").show();
$(".remove_future_registration").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
......@@ -242,7 +311,6 @@ $(document).ready(function() {
// 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/' : '/';
......@@ -257,7 +325,7 @@ $(document).ready(function() {
// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) {
var timeout;
var timeout = null;
return function debounced () {
var obj = this, args = arguments;
......
......@@ -12,5 +12,6 @@ urlpatterns = [
url(r'^faqBDM$', views.faqBDM),
url(r'^no_content$', views.no_content),
url(r'^get_shifts_history$', views.get_shifts_history),
url(r'^offer_extra_shift$', views.offer_extra_shift),
url(r'^.*', views.index) # Urls unknown from the server will redirect to index
]
......@@ -99,9 +99,14 @@ def index(request, exception=None):
partnerData["parent_id"] = partnerData["parent_id"][0]
md5_calc = hashlib.md5(partnerData['parent_create_date'].encode('utf-8')).hexdigest()
partnerData['parent_verif_token'] = md5_calc
partnerData['makeups_to_do'] = partnerData['parent_makeups_to_do']
partnerData['date_delay_stop'] = partnerData['parent_date_delay_stop']
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partnerData["parent_id"]))
partnerData['extra_shift_done'] = partnerData["parent_extra_shift_done"]
else:
partnerData["parent_name"] = False
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partner_id))
# look for associated partner for parents
cm = CagetteMember(partner_id)
......@@ -113,8 +118,6 @@ def index(request, exception=None):
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()
context['show_faq'] = getattr(settings, 'MEMBERS_SPACE_FAQ_TEMPLATE', 'members_space/faq.html')
partnerData["comite"] = m.is_comite(partner_id)
......@@ -234,4 +237,13 @@ def get_shifts_history(request):
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
return JsonResponse(res)
def offer_extra_shift(request):
res = {}
partner_id = int(request.POST['partner_id'])
m = CagetteMember(partner_id)
res = m.update_extra_shift_done(0)
return JsonResponse(res)
......@@ -9,7 +9,8 @@ var suppliers_list = [],
new_product_supplier_association = {
package_qty: null,
price: null
};
},
qties_values = {};
var dbc = null,
sync = null,
......@@ -30,6 +31,10 @@ var dbc = null,
var clicked_order_pill = null;
let userAgent = navigator.userAgent;
var timerId;
/* - UTILS */
......@@ -124,12 +129,74 @@ function debounceFunction(func, delay = 1000) {
timerId = setTimeout(func, delay);
}
/* - PRODUCTS */
var process_new_product_qty = function(input) {
// Remove line coloring on input blur
const row = $(input).closest('tr');
row.removeClass('focused_line');
let val = ($(input).val() == '') ? 0 : $(input).val();
const id_split = $(input).attr('id')
.split('_');
const prod_id = id_split[1];
const supplier_id = id_split[3];
if (val == -1) {
let modal_end_supplier_product_association = $('#templates #modal_end_supplier_product_association');
const product = products.find(p => p.id == prod_id);
modal_end_supplier_product_association.find(".product_name").text(product.name);
const supplier = selected_suppliers.find(s => s.id == supplier_id);
modal_end_supplier_product_association.find(".supplier_name").text(supplier.display_name);
openModal(
modal_end_supplier_product_association.html(),
() => {
if (is_time_to('validate_end_supplier_product_association')) {
end_supplier_product_association(product, supplier);
}
},
'Valider',
false,
true,
() => {
// Reset value in input on cancel
const psi = product.suppliersinfo.find(psi_item => psi_item.supplier_id == supplier_id);
$(input).val(psi.qty);
}
);
} else {
val = parseFloat(val);
// If value is a number
if (!isNaN(val)) {
// Save value
save_product_supplier_qty(prod_id, supplier_id, val);
// Update row
const product = products.find(p => p.id == prod_id);
const new_row_data = prepare_datatable_data([product.id])[0];
products_table.row($(input).closest('tr')).data(new_row_data)
.draw();
debounceFunction(update_cdb_order);
display_total_values();
} else {
$(input).val('');
}
}
};
/**
* Add a product.
*
* @returns -1 if validation failed, 0 otherwise
*/
function add_product() {
const user_input = $("#product_input").val();
......@@ -191,15 +258,22 @@ function add_product() {
return 0;
}
function compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, daily_conso, days) {
let purchase_qty_for_coverage = null;
purchase_qty_for_coverage = days * daily_conso - stock - incoming_qty + product.minimal_stock;
purchase_qty_for_coverage = (purchase_qty_for_coverage < 0) ? 0 : purchase_qty_for_coverage;
let purchase_qty_for_coverage = 0,
purchase_package_qty_for_coverage = 0;
if (stock == 0 && daily_conso == 0) {
purchase_package_qty_for_coverage = 1;
// Reduce to nb of packages to purchase
purchase_package_qty_for_coverage = purchase_qty_for_coverage / product.suppliersinfo[0].package_qty;
} else {
purchase_qty_for_coverage = days * daily_conso - stock - incoming_qty + product.minimal_stock;
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;
if (coeff != 1) {
purchase_package_qty_for_coverage *= coeff;
if (coeff != 1) {
purchase_package_qty_for_coverage *= coeff;
}
}
// return Round up to unit for all products
return Math.ceil(purchase_package_qty_for_coverage);
......@@ -207,20 +281,21 @@ function compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty,
function compute_and_affect_product_supplier_quantities(coeff, days) {
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_package_qty_for_coverage = compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, daily_conso, days);
// Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
}
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_package_qty_for_coverage = compute_purchase_qty_for_coverage(product, coeff, stock, incoming_qty, daily_conso, days);
// Set qty to purchase for first supplier only
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
}
}
}
......@@ -231,9 +306,10 @@ function compute_and_affect_product_supplier_quantities(coeff, days) {
* Set the computed qty for the first supplier only.
*/
function compute_products_coverage_qties() {
return new Promise((resolve) => {
return new Promise((resolve) => {
const pc_adjust = $('#percent_adjust_input').val();
let coeff = 1;
if (!isNaN(parseFloat(pc_adjust))) {
coeff = (1 + parseFloat(pc_adjust) /100);
}
......@@ -242,20 +318,20 @@ function compute_products_coverage_qties() {
compute_and_affect_product_supplier_quantities(coeff, order_doc.coverage_days);
} else if (order_doc.targeted_amount != null) {
const small_step = 0.1,
max_iter = 182; // Assume that no more than 1/2 year coverage is far enough
max_iter = 182; // Assume that no more than 1/2 year coverage is far enough
let go_on = true,
iter = 0,
days = 1,
step = 1;
step = 1;
//Let's compute the nearst amount, by changing days quantity
while(go_on == true && iter < max_iter) {
while (go_on == true && iter < max_iter) {
order_total_value = 0;
compute_and_affect_product_supplier_quantities(coeff, days);
_compute_total_values_by_supplier();
for (let supplier of selected_suppliers) {
order_total_value += supplier.total_value;
}
}
let order_total_value_f = parseFloat(order_total_value),
targeted_amount_f = parseFloat(order_doc.targeted_amount);
......@@ -277,7 +353,7 @@ function compute_products_coverage_qties() {
iter++;
}
}
}
resolve();
});
......@@ -1662,7 +1738,7 @@ function display_products(params) {
}
}
});
products_table.search('');
$('.main').show();
$('#main_content_footer').show();
$('#do_inventory').show();
......@@ -1674,71 +1750,14 @@ function display_products(params) {
row.addClass('focused_line');
});
// Manage data on inputs blur
$('#products_table').on('blur', 'tbody td .product_qty_input', function () {
// Remove line coloring on input blur
const row = $(this).closest('tr');
row.removeClass('focused_line');
let val = ($(this).val() == '') ? 0 : $(this).val();
const id_split = $(this).attr('id')
.split('_');
const prod_id = id_split[1];
const supplier_id = id_split[3];
if (val == -1) {
let modal_end_supplier_product_association = $('#templates #modal_end_supplier_product_association');
const product = products.find(p => p.id == prod_id);
modal_end_supplier_product_association.find(".product_name").text(product.name);
const supplier = selected_suppliers.find(s => s.id == supplier_id);
modal_end_supplier_product_association.find(".supplier_name").text(supplier.display_name);
openModal(
modal_end_supplier_product_association.html(),
() => {
if (is_time_to('validate_end_supplier_product_association')) {
end_supplier_product_association(product, supplier);
}
},
'Valider',
false,
true,
() => {
// Reset value in input on cancel
const psi = product.suppliersinfo.find(psi_item => psi_item.supplier_id == supplier_id);
$(this).val(psi.qty);
}
);
} else {
val = parseFloat(val);
// If value is a number
if (!isNaN(val)) {
// Save value
save_product_supplier_qty(prod_id, supplier_id, val);
// Update row
const product = products.find(p => p.id == prod_id);
const new_row_data = prepare_datatable_data([product.id])[0];
products_table.row($(this).closest('tr')).data(new_row_data)
.draw();
debounceFunction(update_cdb_order);
display_total_values();
} else {
$(this).val('');
}
}
})
// Manage data on inputs blur
$('#products_table')
.on('blur', 'tbody td .product_qty_input', function () {
process_new_product_qty(this);
})
.on('keypress', 'tbody td .product_qty_input', function(e) {
// Validate on Enter pressed
// Validate on Enter pressed
if (e.which == 13) {
$(this).blur();
}
......@@ -1756,9 +1775,9 @@ function display_products(params) {
// 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();
$("#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) {
......@@ -1786,7 +1805,7 @@ function display_products(params) {
}
})
.on('click', 'tbody td .product_actions', function(e) {
// Save / unsave selected row
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const product = products.find(p => p.id == p_id);
......@@ -2251,15 +2270,15 @@ $(document).ready(function() {
order_doc.coverage_days = days_val;
order_doc.targeted_amount = amount_val;
compute_products_coverage_qties()
.then(() => {
debounceFunction(update_cdb_order);
update_main_screen();
})
.then(() => {
debounceFunction(update_cdb_order);
update_main_screen();
});
} else {
$("#coverage_days_input").val(order_doc.coverage_days || '');
$('#targeted_amount_input').val(order_doc.targeted_amount || '');
alert("Ni le nombre de jours de couverture, ni le montant à atteindre sont correctement renseignés")
alert("Ni le nombre de jours de couverture, ni le montant à atteindre sont correctement renseignés");
}
}
......@@ -2315,11 +2334,11 @@ $(document).ready(function() {
check_products_data()
.then(() => {
compute_products_coverage_qties()
.then(() => {
update_main_screen();
debounceFunction(update_cdb_order);
closeModal();
})
.then(() => {
update_main_screen();
debounceFunction(update_cdb_order);
closeModal();
});
});
}
......@@ -2475,6 +2494,7 @@ $(document).ready(function() {
e.preventDefault();
if (is_time_to('submit_new_order_form', 1000)) {
create_cdb_order();
}
});
......@@ -2590,6 +2610,30 @@ $(document).ready(function() {
panel.style.display = "block";
}
});
if (/Firefox\//.exec(userAgent)) {
// needed to prevent bug using number input arrow to change quantity (https://bugzilla.mozilla.org/show_bug.cgi?id=1012818)
// Have to capture mousedown and mouseup events, instead of using only click event
// Indeed, capturing click only remove the ability to click to have focus on the input to type a number.
$(document).on("mousedown", '[type="number"]', function() {
const clicked = this;
qties_values[$(clicked).attr('id')] = $(clicked).val();
});
$(document).on("mouseup", '[type="number"]', function() {
const clicked = this;
try {
if ($(clicked).val() != qties_values[$(clicked).attr('id')]) {
process_new_product_qty(clicked);
}
} catch (err) {
console.log(err);
}
});
}
} else {
$('#not_connected_content').show();
}
......
......@@ -69,6 +69,8 @@
- COOP_BARCODE_RULE_ID = 11
- ASSOCIATE_BARCODE_RULE_ID = 12
- FUNDRAISING_CAT_ID = 1
- PARTS_PRICE_UNIT = 10.0
......@@ -129,6 +131,10 @@
La Cagette use False to implement custom rules
- ASSOCIATE_MEMBER_SHIFT = ''
Id number of the associate shift template
### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette'
......
......@@ -9,7 +9,7 @@ class OdooEntityFieldsForm(forms.Form):
class ExportComptaForm(forms.Form):
mois = forms.DateField(
required=True,
widget=MonthYearWidget()
widget=MonthYearWidget(years=range(datetime.date.today().year-2,datetime.date.today().year+1))
)
#fichier = forms.FileField()
# CHOICES = [('zip', '1 fichier par journal'),('compact', '1 seul fichier')]
......
......@@ -7,7 +7,8 @@
.red {color:#FF0000;}
.b_red, .b_less_than_25pc {background:#ff3333 !important;}
.loading {background-image: url("/static/img/ajax-loader.gif"); background-repeat:no-repeat;}
.loading2 {display: none;}
.loading2 {display: none; position:absolute; top:-20px;}
.loading2-container {position:relative;}
body {background: #fff; margin:5px;}
a, a:active, a:focus,
......
......@@ -35,14 +35,15 @@ function get_shift_name(s_data) {
if (s_data && s_data.week) {
shift_name = weeks_name[s_data.week];
if (s_data.type == 2 && typeof manage_ftop != "undefined" && manage_ftop == true) {
if (s_data.type == 2 && typeof manage_ftop != "undefined" && manage_ftop == true && s_data.id != ASSOCIATE_MEMBER_SHIFT) {
shift_name = 'Volant';
} else if(s_data.id == ASSOCIATE_MEMBER_SHIFT) {
shift_name = 'Binôme';
} else {
shift_name += s_data.day + ' - ' + s_data.begin;
shift_name += ' - ' + s_data.place;
}
}
return shift_name;
}
......
......@@ -38,22 +38,28 @@ class CagetteShift(models.Model):
'cooperative_state', 'final_standard_point', 'create_date',
'final_ftop_point', 'shift_type', 'leave_ids', 'makeups_to_do', 'barcode_base',
'street', 'street2', 'zip', 'city', 'mobile', 'phone', 'email',
'is_associated_people', 'parent_id']
'is_associated_people', 'parent_id', 'extra_shift_done']
partnerData = self.o_api.search_read('res.partner', cond, fields, 1)
if partnerData:
partnerData = partnerData[0]
if partnerData['is_associated_people']:
cond = [['id', '=', partnerData['parent_id'][0]]]
fields = ['create_date']
fields = ['create_date', 'makeups_to_do', 'date_delay_stop', 'extra_shift_done']
parentData = self.o_api.search_read('res.partner', cond, fields, 1)
if parentData:
partnerData['parent_create_date'] = parentData[0]['create_date']
partnerData['parent_makeups_to_do'] = parentData[0]['makeups_to_do']
partnerData['parent_date_delay_stop'] = parentData[0]['date_delay_stop']
partnerData['parent_extra_shift_done'] = parentData[0]['extra_shift_done']
if partnerData['shift_type'] == 'standard':
partnerData['in_ftop_team'] = False
# Because 'in_ftop_team' doesn't seem to be reset to False in Odoo
cond = [['partner_id.id', '=', id]]
if partnerData['is_associated_people']:
cond = [['partner_id.id', '=', partnerData['parent_id'][0]]]
else:
cond = [['partner_id.id', '=', id]]
fields = ['shift_template_id', 'is_current']
shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields)
if (shiftTemplate and len(shiftTemplate) > 0):
......@@ -91,7 +97,7 @@ class CagetteShift(models.Model):
def get_shift_partner(self, id):
"""Récupère les shift du membre"""
fields = ['date_begin', 'date_end','final_standard_point',
'shift_id', 'shift_type','partner_id', "id", "associate_registered"] # res.partner
'shift_id', 'shift_type','partner_id', "id", "associate_registered", "is_makeup"] # res.partner
cond = [['partner_id.id', '=', id],['state', '=', 'open'],
['date_begin', '>', datetime.datetime.now().isoformat()]]
shiftData = self.o_api.search_read('shift.registration', cond, fields, order ="date_begin ASC")
......@@ -178,20 +184,30 @@ class CagetteShift(models.Model):
def affect_shift(self, data):
"""Affect shift to partner, his associate or both"""
response = None
cond = [['partner_id', '=', int(data['idPartner'])],
['id', '=', int(data['idShiftRegistration'])]]
fields = ['id']
try:
print(cond)
shit_to_affect = self.o_api.search_read('shift.registration', cond, fields, 1)
print(shit_to_affect)
if (len(shit_to_affect) == 1):
shift_res = shit_to_affect[0]
print(shift_res)
fieldsDatas = { "associate_registered":data['affected_partner']}
response = self.o_api.update('shift.registration', [shift_res['id']], fieldsDatas)
except Exception as e:
coop_logger.error("Reopen shift : %s", str(e))
# partner_id can be 'associated_people' one, which is never use as shift partner_id reference
# So, let's first retrieved data about the res.partner involved
cond = [['id', '=', int(data['idPartner'])]]
fields = ['parent_id']
partner = self.o_api.search_read('res.partner', cond, fields, 1)
if partner:
if partner[0]['parent_id']:
partner_id = partner[0]['parent_id'][0]
else:
partner_id = int(data['idPartner'])
cond = [['partner_id', '=', partner_id],
['id', '=', int(data['idShiftRegistration'])]]
fields = ['id']
try:
# make sure there is coherence between shift.registration id and partner_id (to avoid forged request)
shit_to_affect = self.o_api.search_read('shift.registration', cond, fields, 1)
if (len(shit_to_affect) == 1):
shift_res = shit_to_affect[0]
fieldsDatas = { "associate_registered":data['affected_partner']}
response = self.o_api.update('shift.registration', [shift_res['id']], fieldsDatas)
except Exception as e:
coop_logger.error("Model affect shift : %s", str(e))
else:
coop_logger.error("Model affect shift nobody found : %s", str(cond))
return response
def cancel_shift(self, idsRegisteur):
......@@ -356,4 +372,8 @@ class CagetteShift(models.Model):
def member_can_have_delay(self, partner_id):
""" Can a member have a delay? """
return self.o_api.execute('res.partner', 'can_have_extension', [partner_id])
\ No newline at end of file
return self.o_api.execute('res.partner', 'can_have_extension', [partner_id])
def update_counter_event(self, fields):
""" Add/remove points """
return self.o_api.create('shift.counter.event', fields)
\ No newline at end of file
......@@ -15,6 +15,7 @@ urlpatterns = [
url(r'^change_shift', views.change_shift),
url(r'^affect_shift', views.affect_shift),
url(r'^add_shift', views.add_shift),
url(r'^cancel_shift', views.cancel_shift),
url(r'^request_delay', views.request_delay),
url(r'^reset_members_positive_points', views.reset_members_positive_points)
]
......@@ -101,8 +101,11 @@ def get_list_shift_calendar(request, partner_id):
use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False)
listRegisterPartner = []
listMakeUpShift = []
for v in registerPartner:
listRegisterPartner.append(v['id'])
if v['is_makeup']:
listMakeUpShift.append(v['id'])
start = request.GET.get('start')
end = request.GET.get('end')
......@@ -136,7 +139,10 @@ def get_list_shift_calendar(request, partner_id):
if len(l) > 0:
if use_new_members_space is True:
event["classNames"] = ["shift_booked"]
if set(value['registration_ids']) & set(listRegisterPartner) & set(listMakeUpShift):
event["classNames"] = ["shift_booked_makeup"]
else :
event["classNames"] = ["shift_booked"]
else:
event["className"] = "shift_booked"
event["changed"] = False
......@@ -239,11 +245,14 @@ def affect_shift(request):
if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True:
cs = CagetteShift()
if 'idShiftRegistration' in request.POST and 'affected_partner' in request.POST:
# if request is made by associated people, idPartner is it's id, not "master" res.partner
# it's will be handled in model's method (affect_shift)
data = {
"idPartner": int(request.POST['idPartner']),
"idShiftRegistration": int(request.POST['idShiftRegistration']),
"affected_partner": request.POST['affected_partner'],
}
st_r_id = None
try:
st_r_id = cs.affect_shift(data)
except Exception as e:
......@@ -307,6 +316,37 @@ def add_shift(request):
else:
return HttpResponseForbidden()
def cancel_shift(request):
""" Annule une présence à un shift """
if 'verif_token' in request.POST:
partner_id = int(request.POST.get('idPartner'))
if Verification.verif_token(request.POST.get('verif_token'), partner_id) is True:
cs = CagetteShift()
listRegister = [int(request.POST['idRegister'])]
try:
response = cs.cancel_shift(listRegister)
# decrement extra_shift_done if param exists
if 'extra_shift_done' in request.POST:
target = int(request.POST["extra_shift_done"]) - 1
# extra security
if target < 0:
target = 0
cm = CagetteMember(partner_id)
cm.update_extra_shift_done(target)
return JsonResponse({"res" : 'response'})
except Exception as e:
return JsonResponse({"error" : str(e)}, status=500)
else:
return HttpResponseForbidden()
else:
return HttpResponseForbidden()
def request_delay(request):
if 'verif_token' in request.POST:
if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True:
......
......@@ -22,7 +22,7 @@
<div class="management_type_buttons txtcenter">
<button type="button" class="btn--primary management_type_button" id="manage_makeups_button">
Gestion des rattragapes
Gestion des rattrapages
<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_shift_registrations_button">
......@@ -33,6 +33,10 @@
Gestion des binômes
<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_regular_shifts_button">
Gestion des créneaux
<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" disabled>
Gestion des congés
<span class="management_type_button_icons"><i class="fas fa-wrench"></i></span>
......
......@@ -11,6 +11,7 @@
{% 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>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
......
{% 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_regular_shifts.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>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></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 Créneaux</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="partner_data_area">
<h4 class="member_name_container">
<i class="fas fa-user member_name_icon"></i>
<span class="member_info member_name"></span>
</h4>
<p class="shift_name_container">Créneau : <span class="member_info member_shift"></span></p>
<p class="status_container">Statut : <span class="member_info member_status"></span></p>
<p class="makeups_container">Nb rattrapage(s) : <span class="member_info member_makeups"></span></p>
<div id="actions_on_member">
<button class="btn--primary" id="remove_shift_template_button">
Désinscrire du créneau
</button>
</div>
</div>
</div>
<div id="templates" style="display:none;">
<div id="modal_remove_shift_template">
<p>Voulez vraiment désinscrire ce membre du créneau <span class="shift_template_name"></span> ?</p>
<div class="checkbox_area">
<input type="checkbox" id="permanent_unsuscribe" name="permanent_unsuscribe">
<label for="permanent_unsuscribe">Désinscription définitive</label>
</div>
</div>
</div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_regular_shifts.js" %}?v='></script>
{% endblock %}
......@@ -11,6 +11,7 @@
{% 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>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
......
......@@ -138,9 +138,13 @@
</section>
</section>
<section class="grid-6 has-gutter" id="service_entry">
<div class="col-6 row-2">
<div class="col-2 row-2">
<a class="btn btn--primary" data-next="first_page" >Retour accueil</a>
</div>
<div class="col-2 row-2"></div>
<div class="col-2 row-2 login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="col-1"></div>
<div class="col-4">
<h1 class="col-4 txtcenter">Qui es-tu ?</h1>
......@@ -178,9 +182,23 @@
<section id="service_validation" class="col-6 grid-6 has-gutter">
<div class="col-2"></div>
<a class="col-2 btn present">{{CONFIRME_PRESENT_BTN|safe}}</a>
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
<div class="col-2"></div>
<div class="col-2 loading2-container">
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
</div>
</section>
<div id="associated_service_validation">
<p class="col-6 txtcenter">Qui est présent à ce service ?</p>
<section class="col-6 grid-5 has-gutter">
<div class="col-1"></div>
<a id="associated_btn" class=" btn present">Membre</a>
<a id="partner_btn" class=" btn present">Associé</a>
<a id="both_btn" class=" btn present">Les deux</a>
<div class="col-1 loading2-container">
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
</div>
</section>
</div>
</div>
<div class="col-2"></div>
<a class="btn col-2 return" data-next="service_entry">Retour</a>
......
......@@ -37,11 +37,11 @@
<section class="center" id="new_coop">
<div class="grid-1">
<div class="item-center">
<div class="item-center">
<h2 class="title">
NOUVEAU MEMBRE
</h2>
<form id="coop_create" lang="fr">
<form id="coop_create" lang="fr">
{% if ask_for_sex %}
<p>
{% include "members/sex_input.html" %}
......@@ -65,17 +65,58 @@
<input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/>
</p>
{% if input_barcode %}
<p>
<input type="text" name="m_barcode" id="m_barcode" maxlength="13" size="13" placeholder="Code barre" autocomplete="off" required/>
</p>
{% endif %}
<button class="btn--primary">Valider</button>
</form>
</div>
<div id="mail_generation">
(*) L'adresse mail étant obligatoire, si le nouveau membre n'en a pas, veuillez en créer une en cliquant sur le bouton suivant : <a class="btn--info" id="generate_email">+</a>
</div>
{% if ASSOCIATE_MEMBER_SHIFT %}
<p id="add_binome" >+ Binomes (facultatif)</p>
<div id="associate_area" style="display:none;">
<div class="choice_button_area d-flex" >
<div id="existing_member_choice" class="member_choice">
A mettre en binome avec un.e membre existant.e
</div>
<div id="new_member_choice" class="member_choice">
A mettre en binome avec un.e nouveau membre
</div>
</div>
<div id="existing_member_choice_action" style="display:none;">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." >
<div class="btn--primary" id="search_member_button">Recherche</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 class="chosen_associate_area" style="display:none;">
<div >
<p><i>Binôme choisit : </i></p>
</div>
<div class="chosen_associate"></div>
</div>
</div>
<div id="new_member_choice_action" style="display:none;">
<div >
<div>
<input type="text" id="new_member_input" value="" placeholder="Nom du membre" >
</div>
</div>
</div>
</div>
{% endif %}
<div>
<button class="btn--primary">Valider</button>
</div>
</form>
<div id="mail_generation">
(*) L'adresse mail étant obligatoire, si le nouveau membre n'en a pas, veuillez en créer une en cliquant sur le bouton suivant : <a class="btn--info" id="generate_email">+</a>
</div>
</div>
</section>
{% include "members/shift_template_choice.html" %}
......@@ -91,7 +132,7 @@
<p>Pensez à inscrire ce numéro temporaire de coopérateur au crayon de papier sur les deux formulaires papier.</p>
-->
<p>Créneau choisi : <span class="shift_template"></span></p>
<p>Prochain service : <span class="next_shift"></span></p>
<p id="next_shift_registration_detail">Prochain service : <span class="next_shift"></span></p>
<button class="btn--primary" id="next_coop">Coopérateur.rice suivant.e !</button>
</div>
......@@ -119,6 +160,7 @@
var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var dbc = new PouchDB(couchdb_dbname);
var ASSOCIATE_MEMBER_SHIFT = '{{ASSOCIATE_MEMBER_SHIFT}}';
var sync = PouchDB.sync(couchdb_dbname, couchdb_server, {
live: true,
retry: true,
......
......@@ -60,6 +60,9 @@
<input type="text" name="m_barcode" id="m_barcode" disabled/>
</p>
{% endif %}
<div id="associated_member">
En binôme avec : <span id ="associated_member_name"></span>
</div>
</div>
<p class="buttons">
<button class="btn--success" name="valider">Tout est bon</button>
......
......@@ -21,6 +21,9 @@
<button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages
</button>
<button type="button" class="btn--success remove_future_registration">
J'ai validé un service à deux, je peux supprimer une présence
</button>
</div>
<div class="member_shift_name_area">
<span>Mon créneau : </span>
......
......@@ -36,29 +36,36 @@
</div>
<div id="selectable_shift_line_template">
<div class="d-flex">
<div class="d-flex shift_line_container selectable_shift">
<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 class="affect_associate_registered button--warning">
<div class="shift_line_extra_actions">
<div class="affect_associate_registered">
</div>
</div>
</div>
</div>
<div id="delete_registration_button_template">
<div class="delete_registration_button"><i class="fas fa-lg fa-trash"></i></div>
</div>
<div id="modal_affect_shift">
<div>Qui sera présent.e ?</div>
<div id="shift_partner" class="btn--primary">
</div>
<div id="shift_associate" class=" btn--primary">
</div>
<div id="shift_both" class=" btn--primary">
Les deux
<div class="modal_affect_shift_buttons">
<div id="shift_partner" class="btn--primary assign_shift_button">
</div>
<div id="shift_associate" class=" btn--primary assign_shift_button">
</div>
<div id="shift_both" class=" btn--primary assign_shift_button">
Les deux
</div>
</div>
</div>
<div id="modal_shift_exchange_template">
......@@ -74,10 +81,12 @@
<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>
<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_booked_makeup"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">13:30</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 7/12</div></div></div></div></a>
<p>Un service colorié en orange : je suis inscrit.e à un rattrapage sur 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>
......@@ -140,7 +149,8 @@
"associated_partner_name" : "{{partnerData.associated_partner_name}}",
"verif_token" : "{{partnerData.verif_token}}",
"leave_stop_date": "{{partnerData.leave_stop_date}}",
"comite": "{{partnerData.comite}}"
"comite": "{{partnerData.comite}}",
"extra_shift_done": parseInt("{{partnerData.extra_shift_done}}", 10)
};
var block_actions_for_attached_people = '{{block_actions_for_attached_people}}';
</script>
......
......@@ -25,6 +25,9 @@
<button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages
</button>
<button type="button" class="btn--success remove_future_registration">
J'ai validé un service à deux, je peux supprimer une présence
</button>
</div>
</div>
</div>
......
......@@ -35,6 +35,14 @@
<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="can_delete_future_registrations_area">
<button class="btn--success can_delete_future_registrations_button" id="delete_future_registration">
J'ai validé <span class="extra_shift_done"></span> service(s) à deux, je supprime un service futur
</button>
<button class="btn--success can_delete_future_registrations_button" id="offer_extra_shift">
Je souhaite donner <span class="extra_shift_done"></span> service(s) d'avance à la communauté
</button>
</div>
<div id="calendar_top_info">
<div id="partner_shifts_list">
<h4>Liste de mes services :</h4>
......
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