Commit 191dc696 by François C.

Merge branch 'adaptation_supercafoutch' into adaptation_supercoop_supercafoutch

parents c805631f c1c80f59
Pipeline #2360 passed with stage
in 1 minute 22 seconds
...@@ -1339,7 +1339,7 @@ class CagetteServices(models.Model): ...@@ -1339,7 +1339,7 @@ class CagetteServices(models.Model):
@staticmethod @staticmethod
def get_services_at_time(time, tz_offset, with_members=True): def get_services_at_time(time, tz_offset, with_members=True):
"""Retrieve present services with member linked.""" """Retrieve present services with members linked."""
default_acceptable_minutes_after_shift_begins = getattr(settings, 'ACCEPTABLE_ENTRANCE_MINUTES_AFTER_SHIFT_BEGINS', 15) default_acceptable_minutes_after_shift_begins = getattr(settings, 'ACCEPTABLE_ENTRANCE_MINUTES_AFTER_SHIFT_BEGINS', 15)
minutes_before_shift_starts_delay = getattr(settings, 'ACCEPTABLE_ENTRANCE_MINUTES_BEFORE_SHIFT', 15) minutes_before_shift_starts_delay = getattr(settings, 'ACCEPTABLE_ENTRANCE_MINUTES_BEFORE_SHIFT', 15)
...@@ -1360,7 +1360,7 @@ class CagetteServices(models.Model): ...@@ -1360,7 +1360,7 @@ class CagetteServices(models.Model):
fields = ['name', 'week_number', 'registration_ids', fields = ['name', 'week_number', 'registration_ids',
'standard_registration_ids', 'standard_registration_ids',
'shift_template_id', 'shift_ticket_ids', 'shift_template_id', 'shift_ticket_ids',
'date_begin_tz', 'date_end_tz'] 'date_begin_tz', 'date_end_tz', 'state']
services = api.search_read('shift.shift', cond, fields,order ="date_begin_tz ASC") services = api.search_read('shift.shift', cond, fields,order ="date_begin_tz ASC")
for s in services: for s in services:
if (len(s['registration_ids']) > 0): if (len(s['registration_ids']) > 0):
...@@ -1510,13 +1510,18 @@ class CagetteServices(models.Model): ...@@ -1510,13 +1510,18 @@ class CagetteServices(models.Model):
cond = [['date_begin', '>=', date_24h_before.isoformat()], cond = [['date_begin', '>=', date_24h_before.isoformat()],
['date_begin', '<=', end_date.isoformat()], ['date_begin', '<=', end_date.isoformat()],
['state', '=', 'open']] ['state', '=', 'open']]
fields = ['state', 'partner_id', 'date_begin'] fields = ['state', 'partner_id', 'date_begin', 'shift_id']
res = api.search_read('shift.registration', cond, fields) res = api.search_read('shift.registration', cond, fields)
ids = [] ids = []
partner_ids = [] partner_ids = []
excluded_partner = [] excluded_partner = []
canceled_reg_ids = [] # for exempted people
shift_ids = []
for r in res: for r in res:
partner_ids.append(int(r['partner_id'][0])) partner_ids.append(int(r['partner_id'][0]))
shift_id = int(r['shift_id'][0])
if shift_id not in shift_ids:
shift_ids.append(shift_id)
cond = [['id', 'in', partner_ids], cond = [['id', 'in', partner_ids],
['cooperative_state', 'in', ['exempted']]] ['cooperative_state', 'in', ['exempted']]]
fields = ['id'] fields = ['id']
...@@ -1530,13 +1535,28 @@ class CagetteServices(models.Model): ...@@ -1530,13 +1535,28 @@ class CagetteServices(models.Model):
(_h, _m, _s) = h.split(':') (_h, _m, _s) = h.split(':')
if int(_h) < 21: if int(_h) < 21:
ids.append(int(r['id'])) ids.append(int(r['id']))
else:
canceled_reg_ids.append(int(r['id']))
# coop_logger.info("Traitement absences shift_registration ids %s", ids) # coop_logger.info("Traitement absences shift_registration ids %s", ids)
f = {'state': absence_status, 'date_closed': now.isoformat()} f = {'state': absence_status, 'date_closed': now.isoformat()}
update_shift_reg_result = {'update': api.update('shift.registration', ids, f), 'reg_shift': res} update_shift_reg_result = {'update': api.update('shift.registration', ids, f), 'reg_shift': res, 'errors': []}
if update_shift_reg_result['update'] is True: if update_shift_reg_result['update'] is True:
update_shift_reg_result['process_status_res'] = api.execute('res.partner','run_process_target_status', []) update_shift_reg_result['process_status_res'] = api.execute('res.partner','run_process_target_status', [])
# change shift state by triggering button_done method for all related shifts
if len(canceled_reg_ids) > 0:
f = {'state': 'cancel', 'date_closed': now.isoformat()}
api.update('shift.registration', canceled_reg_ids, f)
for sid in shift_ids:
try:
api.execute('shift.shift', 'button_done', sid)
except Exception as e:
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
if not (marshal_none_error in str(e)):
update_shift_reg_result['errors'].append({'shift_id': sid, 'msg' :str(e)})
return update_shift_reg_result return update_shift_reg_result
@staticmethod @staticmethod
def close_ftop_service(): def close_ftop_service():
"""Called by cron script""" """Called by cron script"""
...@@ -1670,6 +1690,104 @@ class CagetteServices(models.Model): ...@@ -1670,6 +1690,104 @@ class CagetteServices(models.Model):
coop_logger.error("easy_validate_shift_presence : %s %s", str(coop_id), str(e)) coop_logger.error("easy_validate_shift_presence : %s %s", str(coop_id), str(e))
return res return res
class CagetteService(models.Model):
"""Class to handle cagette Odoo service."""
def __init__(self, id):
"""Init with odoo id."""
self.id = int(id)
self.o_api = OdooAPI()
def _process_associated_people_extra_shift_done(self):
cond = [['shift_id', '=', self.id],
['state', '=', 'done'],
['associate_registered', '=', 'both'],
['should_increment_extra_shift_done', '=', True]]
fields = ['id', 'state', 'partner_id', 'date_begin']
res = self.o_api.search_read('shift.registration', cond, fields)
extra_shift_done_incremented_srids = [] # shift registration ids
for r in res:
cond = [['id', '=', r['partner_id'][0]]]
fields = ['id','extra_shift_done']
res_partner = self.o_api.search_read('res.partner', cond, fields)
f = {'extra_shift_done': res_partner[0]['extra_shift_done'] + 1 }
self.o_api.update('res.partner', [r['partner_id'][0]], f)
extra_shift_done_incremented_srids.append(int(r['id']))
# Make sure the counter isn't incremented twice
f = {'should_increment_extra_shift_done': False}
self.o_api.update('shift.registration', extra_shift_done_incremented_srids, f)
def _process_related_shift_registrations(self):
now = datetime.datetime.now()
absence_status = 'excused'
res_c = self.o_api.search_read('ir.config_parameter',
[['key', '=', 'lacagette_membership.absence_status']],
['value'])
if len(res_c) == 1:
absence_status = res_c[0]['value']
cond = [['shift_id', '=', self.id],
['state', '=', 'open']]
fields = ['state', 'partner_id', 'date_begin']
res = self.o_api.search_read('shift.registration', cond, fields)
ids = []
partner_ids = []
excluded_partner = []
canceled_reg_ids = [] # for exempted people
for r in res:
partner_ids.append(int(r['partner_id'][0]))
cond = [['id', 'in', partner_ids],
['cooperative_state', 'in', ['exempted']]]
fields = ['id']
res_exempted = self.o_api.search_read('res.partner', cond, fields)
for r in res_exempted:
excluded_partner.append(int(r['id']))
for r in res:
if not (int(r['partner_id'][0]) in excluded_partner):
d_begin = r['date_begin']
(d, h) = d_begin.split(' ')
(_h, _m, _s) = h.split(':')
if int(_h) < 21:
ids.append(int(r['id']))
else:
canceled_reg_ids.append(int(r['id']))
# coop_logger.info("Traitement absences shift_registration ids %s", ids)
f = {'state': absence_status, 'date_closed': now.isoformat()}
update_shift_reg_result = {'update': self.o_api.update('shift.registration', ids, f), 'reg_shift': res, 'errors': []}
if update_shift_reg_result['update'] is True:
update_shift_reg_result['process_status_res'] = self.o_api.execute('res.partner','run_process_target_status', [])
# change shift state by triggering button_done method for all related shifts
if len(canceled_reg_ids) > 0:
f = {'state': 'cancel', 'date_closed': now.isoformat()}
self.o_api.update('shift.registration', canceled_reg_ids, f)
try:
self.o_api.execute('shift.shift', 'button_done', self.id)
except Exception as e:
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
if not (marshal_none_error in str(e)):
update_shift_reg_result['errors'].append({'shift_id': self.id, 'msg' :str(e)})
return update_shift_reg_result
def record_absences(self, request):
"""Can only been executed if an Odoo user is beeing connected."""
res = {}
try:
if CagetteUser.are_credentials_ok(request) is True:
self._process_associated_people_extra_shift_done()
res = self._process_related_shift_registrations()
else:
res['error'] = 'Forbidden'
except Exception as e:
coop_logger.error("CagetteService.record_absences : %s %s", str(self.id), str(e))
res['error'] = str(e)
return res
class CagetteUser(models.Model): class CagetteUser(models.Model):
@staticmethod @staticmethod
......
...@@ -49,6 +49,7 @@ h1 .member_name {font-weight: bold;} ...@@ -49,6 +49,7 @@ h1 .member_name {font-weight: bold;}
.members_list li.btn--inverse.late {background-color: #de9b00; color: white} .members_list li.btn--inverse.late {background-color: #de9b00; color: white}
.members_list li.btn--inverse.both {background-color: #0275d8 ; color: white} .members_list li.btn--inverse.both {background-color: #0275d8 ; color: white}
.members_list.done li.btn {pointer-events: none;}
#service_entry_success {font-size: x-large;} #service_entry_success {font-size: x-large;}
#service_entry_success .explanations {margin: 25px 0; font-size: 18px;} #service_entry_success .explanations {margin: 25px 0; font-size: 18px;}
#service_entry_success .points, #service_entry_success .points,
......
...@@ -41,6 +41,7 @@ var coop_info = $('.coop-info'); ...@@ -41,6 +41,7 @@ var coop_info = $('.coop-info');
var service_data = null; var service_data = null;
const missed_begin_msg = $('#missed_begin_msg').html(); const missed_begin_msg = $('#missed_begin_msg').html();
const current_shift_process_data_actions = $('#current_shift_process_data_actions');
let no_pict_msg = $('#no-picture-msg'); let no_pict_msg = $('#no-picture-msg');
...@@ -70,6 +71,12 @@ var html_elts = { ...@@ -70,6 +71,12 @@ var html_elts = {
var chars = []; //input chars buffer var chars = []; //input chars buffer
var reset_shift_process_actions_zone = function() {
current_shift_process_data_actions.off('click', 'a');
current_shift_process_data_actions.hide();
current_shift_process_data_actions.empty();
}
function fill_member_slide(member) { function fill_member_slide(member) {
no_pict_msg.hide(); no_pict_msg.hide();
current_displayed_member = member; current_displayed_member = member;
...@@ -282,6 +289,9 @@ function fill_service_entry(s) { ...@@ -282,6 +289,9 @@ function fill_service_entry(s) {
// if (typeof s.late != "undefined" && s.late == true) { // if (typeof s.late != "undefined" && s.late == true) {
// m_list = '<ul class="members_list late">'; // m_list = '<ul class="members_list late">';
// } // }
if (s.state == 'done') {
m_list = '<ul class="members_list done">';
}
$.each(s.members, function(i, e) { $.each(s.members, function(i, e) {
var li_class = "btn"; var li_class = "btn";
var li_data = ""; var li_data = "";
...@@ -304,6 +314,9 @@ function fill_service_entry(s) { ...@@ -304,6 +314,9 @@ function fill_service_entry(s) {
} else { } else {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"'; li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
} }
if (s.state == 'done') {
li_data += ' disabled ';
}
m_list += '<li class="'+li_class+'" '+li_data+'>'; m_list += '<li class="'+li_class+'" '+li_data+'>';
m_list += e.partner_id[1]; m_list += e.partner_id[1];
m_list += '</li>'; m_list += '</li>';
...@@ -311,6 +324,45 @@ function fill_service_entry(s) { ...@@ -311,6 +324,45 @@ function fill_service_entry(s) {
m_list += '</ul>'; m_list += '</ul>';
} }
if (coop_is_connected()) {
// Add shift process data
reset_shift_process_actions_zone();
if (s.state == 'draft' || s.state == 'confirm') {
let btn = $('<a>').addClass('btn btn--primary txtcenter')
.text('Enregistrer les absences / présences')
.attr('id','record_shift_absences');
current_shift_process_data_actions.append(btn);
current_shift_process_data_actions.on('click', '#record_shift_absences', function(){
msg = "<p>Lancer le traitement des présences et absences de ce service</p>";
openModal(msg, function() {
btn.attr('disabled', 'true')
try {
$.ajax({
url: '/members/record_shift_absences/' + s.id,
dataType : 'json'
})
.done(function(rData) {
if (typeof rData.res.update !== "undefined" && rData.res.update == true) {
enqueue_message_for_next_loading("Données de présences traitées.");
location.reload();
} else {
closeModal();
btn.removeAttr('disabled');
alert(JSON.stringify(rData.res));
}
});
} catch (e) {
console.log(e);
}
}, 'Confirmer');
});
} else {
current_shift_process_data_actions.append("<em>Traitement des présences : " + s.state + "</em>");
}
current_shift_process_data_actions.show();
}
rattrapage_ou_volant = null; rattrapage_ou_volant = null;
shift_members.html(m_list); shift_members.html(m_list);
rattrapage_wanted.show(); rattrapage_wanted.show();
...@@ -401,6 +453,8 @@ function get_service_entry_data() { ...@@ -401,6 +453,8 @@ function get_service_entry_data() {
}) })
.done(function(rData) { .done(function(rData) {
info_place.text(''); info_place.text('');
reset_shift_process_actions_zone();
var page_title = pages.service_entry.find('h1'); var page_title = pages.service_entry.find('h1');
page_title.text('Qui es-tu ?'); page_title.text('Qui es-tu ?');
......
...@@ -43,6 +43,7 @@ urlpatterns = [ ...@@ -43,6 +43,7 @@ urlpatterns = [
url(r'^services_at_time/([0-9TZ\-\: \.]+)/([0-9\-]+)$', views.services_at_time), url(r'^services_at_time/([0-9TZ\-\: \.]+)/([0-9\-]+)$', views.services_at_time),
url(r'^service_presence/$', views.record_service_presence), url(r'^service_presence/$', views.record_service_presence),
url(r'^record_absences/?([0-9\-\ \:]*)$', views.record_absences), url(r'^record_absences/?([0-9\-\ \:]*)$', views.record_absences),
url(r'^record_shift_absences/?([0-9]+)$', views.record_shift_absences),
url(r'^close_ftop_service$', views.close_ftop_service), url(r'^close_ftop_service$', views.close_ftop_service),
url(r'^get_credentials$', views.get_credentials), url(r'^get_credentials$', views.get_credentials),
url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB), url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB),
......
...@@ -5,7 +5,7 @@ from outils.for_view_imports import * ...@@ -5,7 +5,7 @@ from outils.for_view_imports import *
from members.models import CagetteMember from members.models import CagetteMember
from members.models import CagetteUser from members.models import CagetteUser
from members.models import CagetteMembers from members.models import CagetteMembers
from members.models import CagetteServices from members.models import CagetteServices, CagetteService
from outils.forms import GenericExportMonthForm from outils.forms import GenericExportMonthForm
import datetime import datetime
...@@ -105,6 +105,7 @@ def inscriptions(request, type=1): ...@@ -105,6 +105,7 @@ def inscriptions(request, type=1):
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True), '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', ''), 'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', ''),
'can_create_binome': getattr(settings, 'CAN_CREATE_BINOME', True),
'prepa_odoo_url' : getattr(settings, 'PREPA_ODOO_URL', '/members/prepa-odoo'), 'prepa_odoo_url' : getattr(settings, 'PREPA_ODOO_URL', '/members/prepa-odoo'),
'committees_shift_id': committees_shift_id, 'committees_shift_id': committees_shift_id,
} }
...@@ -347,6 +348,10 @@ def easy_validate_shift_presence(request): ...@@ -347,6 +348,10 @@ def easy_validate_shift_presence(request):
def record_absences(request, date): def record_absences(request, date):
return JsonResponse({'res': CagetteServices.record_absences(date)}) return JsonResponse({'res': CagetteServices.record_absences(date)})
def record_shift_absences(request, id):
shift = CagetteService(id)
return JsonResponse({'res': shift.record_absences(request)})
def close_ftop_service(request): def close_ftop_service(request):
"""Close the closest past FTOP service""" """Close the closest past FTOP service"""
return JsonResponse({'res': CagetteServices.close_ftop_service()}) return JsonResponse({'res': CagetteServices.close_ftop_service()})
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
justify-content: space-between; justify-content: space-between;
} }
@media screen and (max-width:992px) { @media screen and (max-width:992px) {
#calendar_top_info { #calendar_top_info {
display: flex; display: flex;
......
var calendar = null, var calendar = null,
selected_shift = null, selected_shift = null,
vw = null; vw = null,
adding_mode = false;
/* - Logic */ /* - Logic */
...@@ -51,6 +52,9 @@ function add_or_change_shift(new_shift_id) { ...@@ -51,6 +52,9 @@ function add_or_change_shift(new_shift_id) {
if (selected_shift === null) { if (selected_shift === null) {
tUrl = '/shifts/add_shift'; tUrl = '/shifts/add_shift';
if (partner_data.makeups_to_do > 0) {
tData += '&is_makeup=1';
}
} else { } else {
tUrl = '/shifts/change_shift'; tUrl = '/shifts/change_shift';
tData = tData + '&idOldShift='+ selected_shift.shift_id[0] +'&idRegister=' + selected_shift.id; tData = tData + '&idOldShift='+ selected_shift.shift_id[0] +'&idRegister=' + selected_shift.id;
...@@ -143,6 +147,8 @@ function add_or_change_shift(new_shift_id) { ...@@ -143,6 +147,8 @@ function add_or_change_shift(new_shift_id) {
}, 300); }, 300);
} }
}); });
adding_mode = false;
$('#start_adding_shift').prop('disabled', false);
} }
return null; return null;
...@@ -315,6 +321,7 @@ function init_shifts_list() { ...@@ -315,6 +321,7 @@ function init_shifts_list() {
if (!can_exchange_shifts()) { if (!can_exchange_shifts()) {
shift_line_template.find(".selectable_shift_line").addClass("btn"); shift_line_template.find(".selectable_shift_line").addClass("btn");
shift_line_template.find(".checkbox").prop("disabled", "disabled"); shift_line_template.find(".checkbox").prop("disabled", "disabled");
$('#start_adding_shift').prop('disabled', true);
} else { } else {
if (shift.is_makeup==true) { if (shift.is_makeup==true) {
shift_line_template.find(".selectable_shift_line").addClass("btn--warning"); shift_line_template.find(".selectable_shift_line").addClass("btn--warning");
...@@ -577,12 +584,27 @@ function init_calendar_page() { ...@@ -577,12 +584,27 @@ function init_calendar_page() {
"Valider" "Valider"
); );
} else if (selected_shift === null && can_exchange_shifts()) { } else if (selected_shift === null && can_exchange_shifts()) {
if (adding_mode === false) {
/* could exchange shift but no old shift selected */ /* could exchange shift but no old shift selected */
openModal( openModal(
"Je dois sélectionner un service à échanger.", "Je dois sélectionner un service à échanger.",
closeModal, closeModal,
"J'ai compris" "J'ai compris"
); );
} else {
// Display modal
let modal_template = $("#modal_add_shift_template");
modal_template.find(".date_new_shift").text(new_shift_date);
modal_template.find(".time_new_shift").text(new_shift_time);
openModal(
modal_template.html(),
() => {
add_or_change_shift(new_shift_id);
},
"Valider"
);
}
} else if (should_select_makeup()) { } else if (should_select_makeup()) {
/* choose a makeup service */ /* choose a makeup service */
// Check if selected new shift is in less than 6 months // Check if selected new shift is in less than 6 months
...@@ -811,6 +833,19 @@ function init_shifts_exchange() { ...@@ -811,6 +833,19 @@ function init_shifts_exchange() {
init_calendar_page(); init_calendar_page();
} }
$('#start_adding_shift').click((c) => {
openModal(
"<p>Je souhaite sélectionner un service supplémentaire.</p>",
() => {
$(c.target).prop('disabled', true);
adding_mode = true;
closeModal();
},
"Confirmer",
false
);
});
$(window).smartresize(function() { $(window).smartresize(function() {
// only apply if a width threshold is passed // only apply if a width threshold is passed
if ( if (
......
...@@ -130,6 +130,8 @@ def index(request, exception=None): ...@@ -130,6 +130,8 @@ def index(request, exception=None):
if hasattr(settings, 'SHIFT_EXCHANGE_DAYS_TO_HIDE'): if hasattr(settings, 'SHIFT_EXCHANGE_DAYS_TO_HIDE'):
days_to_hide = settings.SHIFT_EXCHANGE_DAYS_TO_HIDE days_to_hide = settings.SHIFT_EXCHANGE_DAYS_TO_HIDE
context['daysToHide'] = days_to_hide context['daysToHide'] = days_to_hide
can_add_shift = getattr(settings, 'CAN_ADD_SHIFT', False)
context['canAddShift'] = "true" if can_add_shift is True else "false"
msettings = MConfig.get_settings('members') msettings = MConfig.get_settings('members')
context['forms_link'] = msettings['forms_link']['value'] if 'forms_link' in msettings else '' context['forms_link'] = msettings['forms_link']['value'] if 'forms_link' in msettings else ''
...@@ -169,7 +171,7 @@ def home(request): ...@@ -169,7 +171,7 @@ def home(request):
Consequently, the front-end url should be unknown from the server so the user is redirected to the index, Consequently, the front-end url should be unknown from the server so the user is redirected to the index,
then the front-end index will call this endpoint to load the home page then the front-end index will call this endpoint to load the home page
""" """
template = loader.get_template('members_space/home.html') template = loader.get_template(getattr(settings, 'MEMBERS_SPACE_HOME_TEMPLATE', 'members_space/home.html'))
context = { context = {
'title': 'Espace Membres', 'title': 'Espace Membres',
} }
...@@ -203,6 +205,7 @@ def shifts_exchange(request): ...@@ -203,6 +205,7 @@ def shifts_exchange(request):
template = loader.get_template('members_space/shifts_exchange.html') template = loader.get_template('members_space/shifts_exchange.html')
context = { context = {
'title': 'Échange de Services', 'title': 'Échange de Services',
'canAddShift': getattr(settings, 'CAN_ADD_SHIFT', False)
} }
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
......
...@@ -131,6 +131,10 @@ ...@@ -131,6 +131,10 @@
La Cagette use False to implement custom rules La Cagette use False to implement custom rules
- CAN_CREATE_BINOME = True (by default)
If set to False, in new member creation form, a member can be selected to be associated with.
- ASSOCIATE_MEMBER_SHIFT = '' - ASSOCIATE_MEMBER_SHIFT = ''
Id number of the associate shift template Id number of the associate shift template
...@@ -326,6 +330,11 @@ ...@@ -326,6 +330,11 @@
- MEMBERS_SPACE_FAQ_TEMPLATE = None - MEMBERS_SPACE_FAQ_TEMPLATE = None
If set to None, "FAQ menu" will not be shown. To use a custom content add a template and set it's relative path If set to None, "FAQ menu" will not be shown. To use a custom content add a template and set it's relative path
- MEMBERS_SPACE_HOME_TEMPLATE = 'members_space/supercafoutch/home.html'
If not set, 'members_space/home.html' (la Cagette)
- MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS = False - MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS = False
By default, is True. If False, tile showing explanations is not shown By default, is True. If False, tile showing explanations is not shown
...@@ -333,6 +342,10 @@ ...@@ -333,6 +342,10 @@
- BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE = False - BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE = False
Attached people can or not change his services Attached people can or not change his services
- CAN_ADD_SHIFT = True
By default, False. Set if coop can or not add shifts in their memberspace calendar
### Reception ### Reception
- RECEPTION_ADD_ADMIN_MODE = True - RECEPTION_ADD_ADMIN_MODE = True
......
...@@ -169,8 +169,8 @@ class CagetteShift(models.Model): ...@@ -169,8 +169,8 @@ class CagetteShift(models.Model):
"origin": 'memberspace', "origin": 'memberspace',
"is_makeup": data['is_makeup'], "is_makeup": data['is_makeup'],
"state": 'open'} "state": 'open'}
if shift_type == "standard" and data['is_makeup'] is not True: if (shift_type == "standard" and data['is_makeup'] is not True) or shift_type == "ftop":
fieldsDatas['template_created'] = 1 # It's not true but otherwise, presence add 1 standard point, which is not wanted fieldsDatas['template_created'] = 1 # It's not true but otherwise, presence add 1 standard point , which is not wanted
st_r_id = self.o_api.create('shift.registration', fieldsDatas) st_r_id = self.o_api.create('shift.registration', fieldsDatas)
except Exception as e: except Exception as e:
......
...@@ -281,9 +281,12 @@ def add_shift(request): ...@@ -281,9 +281,12 @@ def add_shift(request):
"idPartner": int(request.POST['idPartner']), "idPartner": int(request.POST['idPartner']),
"idShift":int(request.POST['idNewShift']), "idShift":int(request.POST['idNewShift']),
"shift_type":request.POST['shift_type'], "shift_type":request.POST['shift_type'],
"is_makeup":True "is_makeup": False
} }
if 'is_makeup' in request.POST and request.POST['is_makeup'] == "1":
data['is_makeup'] = True
#Insertion du nouveau shift #Insertion du nouveau shift
st_r_id = False st_r_id = False
try: try:
......
...@@ -365,7 +365,17 @@ class CagetteStock(models.Model): ...@@ -365,7 +365,17 @@ class CagetteStock(models.Model):
return res return res
@staticmethod
def get_valuable_stock():
articles = []
try:
api = OdooAPI()
cond = [['qty_available','>', 0], ['active', '=', True]]
fields = ["barcode", "display_name", "qty_available", "standard_price"]
articles = api.search_read('product.product', cond, fields, 1000000)
except Exception as e:
coop_logger.error("Erreur get_valuable_stock : %s", str(e))
return articles
def set_test(): def set_test():
o_api = OdooAPI() o_api = OdooAPI()
......
$(document).ready(function() {
table_article = $('#tableArticle').DataTable({
"ajax": {
"url": "get_valuable_stock",
"data": ""
},
"columns":[
{data:"barcode", "title":"Code-barre", "width": "50%"},
{data:"display_name", "title":"Article", "width": "50%"},
{data:"qty_available", "title":"Stock", "width":"15%"
},
{data:"standard_price", "title":"Prix achat", "width":"15%"
}
],
"searching": true,
"order": [
[
2,
"desc"
]
],
"iDisplayLength": 50,
"language": {
"emptyTable": "Pas de donnée",
"info": "Affiché : lignes _START_ à _END_ sur _TOTAL_",
"infoEmpty": "Affiché : 0 ligne",
"infoFiltered": "(filtré de _MAX_ lignes au total)",
"thousands": ",",
"lengthMenu": "Afficher _MENU_ lignes",
"loadingRecords": "Loading...",
"processing": "Processing...",
"search": "Rechercher un article :",
"searchPlaceholder": "Référence, code-barre",
"zeroRecords": "Aucun résultat",
"paginate": {
"first": "Premier",
"last": "Dernier",
"next": "Suivant",
"previous": "Precedant"
},
"aria": {
"sortAscending": ": activate to sort column ascending",
"sortDescending": ": activate to sort column descending"
}
},
buttons: [
{
extend: 'excelHtml5',
text: 'Export en Excel',
className: 'btn--primary btn_export'
},
],
dom: '<lr<t>ip><"clear"><B>',
});
});
\ No newline at end of file
...@@ -46,4 +46,8 @@ urlpatterns = [ ...@@ -46,4 +46,8 @@ urlpatterns = [
url(r'^get_saleWitheNotSale', views.get_saleWitheNotSale), url(r'^get_saleWitheNotSale', views.get_saleWitheNotSale),
url(r'^get_test', views.get_test), url(r'^get_test', views.get_test),
# Values
url(r'^stockValues', views.stockValues),
url(r'^get_valuable_stock', views.get_valuable_stock),
] ]
...@@ -550,8 +550,16 @@ def get_saleWitheNotSale(request): ...@@ -550,8 +550,16 @@ def get_saleWitheNotSale(request):
return JsonResponse({"data":lArticleSale}, safe=True) return JsonResponse({"data":lArticleSale}, safe=True)
def stockValues(request):
"""Page valeurs du stock (quantités positives)."""
context = {'title': 'Stock (quantités positives valorisées)'}
template = loader.get_template('stock/stock_values.html')
return HttpResponse(template.render(context, request))
def get_valuable_stock(request):
articles = CagetteStock.get_valuable_stock()
return JsonResponse({"data":articles}, safe=True)
def get_test(request): def get_test(request):
res = CagetteStock.get_sale_qty_by_from(1) res = CagetteStock.get_sale_qty_by_from(1)
return JsonResponse({"data":res}, safe=False) return JsonResponse({"data":res}, safe=False)
...@@ -141,7 +141,9 @@ ...@@ -141,7 +141,9 @@
<div class="col-2 row-2"> <div class="col-2 row-2">
<a class="btn btn--primary" data-next="first_page" >Retour accueil</a> <a class="btn btn--primary" data-next="first_page" >Retour accueil</a>
</div> </div>
<div class="col-2 row-2"></div> <div class="col-2 row-2 txtcenter">
<div id="current_shift_process_data_actions" style="display:none;"></div>
</div>
<div class="col-2 row-2 login_area"> <div class="col-2 row-2 login_area">
{% include "common/conn_admin.html" %} {% include "common/conn_admin.html" %}
</div> </div>
......
...@@ -79,16 +79,18 @@ ...@@ -79,16 +79,18 @@
</p> </p>
{% endif %} {% endif %}
{% if ASSOCIATE_MEMBER_SHIFT %} {% if can_create_binome %}
<p id="add_binome" >+ Binomes (facultatif)</p> <p id="add_binome" >+ Binomes (facultatif)</p>
<div id="associate_area" style="display:none;"> <div id="associate_area" style="display:none;">
<div class="choice_button_area d-flex" > <div class="choice_button_area d-flex" >
<div id="existing_member_choice" class="member_choice"> <div id="existing_member_choice" class="member_choice">
A mettre en binome avec un.e membre existant.e A mettre en binome avec un.e membre existant.e
</div> </div>
{% if ASSOCIATE_MEMBER_SHIFT %}
<div id="new_member_choice" class="member_choice"> <div id="new_member_choice" class="member_choice">
A mettre en binome avec un.e nouveau membre A mettre en binome avec un.e nouveau membre
</div> </div>
{% endif %}
</div> </div>
<div id="existing_member_choice_action" style="display:none;"> <div id="existing_member_choice_action" style="display:none;">
...@@ -110,6 +112,7 @@ ...@@ -110,6 +112,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if ASSOCIATE_MEMBER_SHIFT %}
<div id="new_member_choice_action" style="display:none;"> <div id="new_member_choice_action" style="display:none;">
<div > <div >
<div> <div>
...@@ -117,6 +120,7 @@ ...@@ -117,6 +120,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
{% endif %} {% endif %}
<div> <div>
......
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
var days_to_hide = "{{daysToHide}}" var days_to_hide = "{{daysToHide}}"
var partner_data = { var partner_data = {
"partner_id":"{{partnerData.id}}", "partner_id":"{{partnerData.id}}",
"name":"{{partnerData.display_name}}", "name":"{{partnerData.display_name|safe}}",
"shift_type":"{{partnerData.shift_type}}", "shift_type":"{{partnerData.shift_type}}",
"date_delay_stop":"{{partnerData.date_delay_stop}}", "date_delay_stop":"{{partnerData.date_delay_stop}}",
"cooperative_state":"{{partnerData.cooperative_state}}", "cooperative_state":"{{partnerData.cooperative_state}}",
...@@ -134,25 +134,26 @@ ...@@ -134,25 +134,26 @@
"can_have_delay" : "{{partnerData.can_have_delay}}", "can_have_delay" : "{{partnerData.can_have_delay}}",
"makeups_to_do" : "{{partnerData.makeups_to_do}}", "makeups_to_do" : "{{partnerData.makeups_to_do}}",
"barcode_base" : "{{partnerData.barcode_base}}", "barcode_base" : "{{partnerData.barcode_base}}",
"street" : "{{partnerData.street}}", "street" : "{{partnerData.street|safe}}",
"street2" : "{{partnerData.street2}}", "street2" : "{{partnerData.street2|safe}}",
"zip" : "{{partnerData.zip}}", "zip" : "{{partnerData.zip}}",
"city" : "{{partnerData.city}}", "city" : "{{partnerData.city|safe}}",
"mobile" : "{{partnerData.mobile}}", "mobile" : "{{partnerData.mobile}}",
"phone" : "{{partnerData.phone}}", "phone" : "{{partnerData.phone}}",
"email" : "{{partnerData.email}}", "email" : "{{partnerData.email}}",
"is_associated_people" : "{{partnerData.is_associated_people}}", "is_associated_people" : "{{partnerData.is_associated_people}}",
"parent_id" : "{{partnerData.parent_id}}", "parent_id" : "{{partnerData.parent_id}}",
"parent_name" : "{{partnerData.parent_name}}", "parent_name" : "{{partnerData.parent_name|safe}}",
"parent_verif_token" : "{{partnerData.parent_verif_token}}", "parent_verif_token" : "{{partnerData.parent_verif_token}}",
"associated_partner_id" : "{{partnerData.associated_partner_id}}", "associated_partner_id" : "{{partnerData.associated_partner_id}}",
"associated_partner_name" : "{{partnerData.associated_partner_name}}", "associated_partner_name" : "{{partnerData.associated_partner_name|safe}}",
"verif_token" : "{{partnerData.verif_token}}", "verif_token" : "{{partnerData.verif_token}}",
"leave_stop_date": "{{partnerData.leave_stop_date}}", "leave_stop_date": "{{partnerData.leave_stop_date}}",
"comite": "{{partnerData.comite}}", "comite": "{{partnerData.comite}}",
"extra_shift_done": parseInt("{{partnerData.extra_shift_done}}", 10) "extra_shift_done": parseInt("{{partnerData.extra_shift_done}}", 10)
}; };
var block_actions_for_attached_people = '{{block_actions_for_attached_people}}'; var block_actions_for_attached_people = '{{block_actions_for_attached_people}}';
const canAddShift = {{canAddShift}};
</script> </script>
<script src="{% static "js/all_common.js" %}?v=1651853225"></script> <script src="{% static "js/all_common.js" %}?v=1651853225"></script>
<script src="{% static "js/members-space-home.js" %}?v=1651853225"></script> <script src="{% static "js/members-space-home.js" %}?v=1651853225"></script>
......
...@@ -50,6 +50,9 @@ ...@@ -50,6 +50,9 @@
<i class="fas fa-spinner fa-spin fa-lg"></i> <i class="fas fa-spinner fa-spin fa-lg"></i>
</div> </div>
<div id="shifts_list"></div> <div id="shifts_list"></div>
{% if canAddShift %}
<button class="btn--primary selectable_shift_line" id="start_adding_shift"><strong>+ Ajouter un service</strong></button>
{% endif %}
</div> </div>
<div id="calendar_explaination_area"></div> <div id="calendar_explaination_area"></div>
<button id="calendar_explaination_button" class="btn--success">Légende du calendrier</button> <button id="calendar_explaination_button" class="btn--success">Légende du calendrier</button>
......
<div id="home">
<div class="page_title txtcenter">
<h1>Espace Membre</h1>
</div>
<div class="tiles_container">
<div class="tile high_tile" id="home_tile_my_info">
<div class="tile_title">
<i class="fas fa-user tile_icon"></i>
<span class="member_info member_name"></span>
</div>
<div class="tile_content">
{# <p><span class="member_info member_name"></span></p> #}
<p class="member_status_text_container">Mon statut : <span class="member_info member_status"></span></p>
<div class="delay_date_stop_container">
( jusqu'au <span class="delay_date_stop"></span> )
</div>
<div id="member_status_action">
<a href="#" target="_blank" class="btn--warning unsuscribed_form_link">
J'accède au formulaire
</a>
<button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages
</button>
<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>
<span class="member_shift_name member_info"></span>
</div>
<div class="member_coop_number_area">
<span>Mon numéro de coop : </span>
<span class="member_coop_number member_info"></span>
</div>
<div class="member_associated_partner_area">
<span>Je suis en binôme avec : </span>
<span class="member_associated_partner member_info"></span>
</div>
<div id="see_more_info">
<button type="button", class="btn btn--primary home_link_button" id="see_more_info_link">
Accéder à mes infos et comprendre mon statut
</button>
</div>
</div>
</div>
<div class="tile high_tile" id="home_tile_my_services">
<div class="tile_title">
<i class="fas fa-clipboard tile_icon"></i>
Mes Services
</div>
<div class="tile_content">
<h3>Services à venir</h3>
<div id="home_incoming_services">
<i class="fas fa-spinner fa-spin fa-lg"></i>
</div>
<div id="go_to_shift_history_area">
<button type="button", class="btn btn--primary" id="home_go_to_shift_history">
Accéder à mon historique
</button>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_services_exchange">
<div class="tile_title">
<i class="fas fa-exchange-alt tile_icon"></i>
Échange de services
</div>
<div class="tile_content">
<div>
Un empêchement ? J'anticipe et déplace mes services jusqu'à 24h avant leur début !
</div>
<div class="home_link_button_area">
<button type="button" class="btn--primary home_link_button" id="go_to_shifts_calendar">
Accéder au calendrier d'échange de services
</button>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_help">
<div class="tile_title">
<i class="fas fa-question-circle tile_icon"></i>
J'ai une demande
</div>
<div class="tile_content">
<div class="home_link_button_area">
<a
href="javascript:void(0);"
class="btn--primary home_link_button"
id="go_to_forms"
>
Accéder à la FAQ
</a>
</div>
</div>
</div>
<div class="tile small_tile" id="home_tile_shop_info">
<div class="tile_title">
<i class="fas fa-newspaper tile_icon"></i>
Informations magasins
</div>
<div id="shop_info_content">
<div class="shop_info_item shop_opening_hours">
<div class="shop_info_item_content">
<div class="opening_hours_title">
Horaires du magasin :
</div>
<div class="opening_hours_content">
{{shop_opening_hours|safe}}
</div>
</div>
</div>
<div class="shop_info_item shop_message">
<div class="shop_info_item_content shop_message_content">
{{msg_accueil|safe}}
</div>
</div>
</div>
</div>
</div>
</div>
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
<a class="dropdown-item" href="breakingArticleSet">Mettre en rupture un article sur odoo</a> <a class="dropdown-item" href="breakingArticleSet">Mettre en rupture un article sur odoo</a>
<a class="dropdown-item" href="stockQuantLastSale">Date de la dernière vente des articles</a> <a class="dropdown-item" href="stockQuantLastSale">Date de la dernière vente des articles</a>
<a class="dropdown-item" href="saleWithNotSale">Vente avec jours sans vente</a> <a class="dropdown-item" href="saleWithNotSale">Vente avec jours sans vente</a>
<a class="dropdown-item" href="stockValues">Stock (qtés > 0 valorisées)</a>
</div> </div>
</li> </li>
</ul> </ul>
......
{% extends "stock/stock_menu.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.buttons.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/buttons.html5.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/jszip.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stock_values.js' %}"></script>
{% endblock %}
{% block content %}
<h1>Stock valorisé</h1>
<br>
<div class="main">
<table id="tableArticle" class="display" width="95%" cellspacing="0" ></table>
</div>
<br/>
<br/>
<script src="{% static "js/all_common.js" %}?v=1651853225"></script>
<script src="{% static "js/common.js" %}?v=1651853225"></script>
{% endblock %}
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