Commit 0fedc22d by Damien Moulard

get modifications from third-party-v12 repo

parent 4e4725ff
Pipeline #3730 failed with stage
in 1 minute 31 seconds
...@@ -40,6 +40,8 @@ CASH_PAYMENT_ID = 18 ...@@ -40,6 +40,8 @@ CASH_PAYMENT_ID = 18
STOCK_LOC_ID = 12 STOCK_LOC_ID = 12
CATEG_FRUIT = 151 CATEG_FRUIT = 151
CATEG_LEGUME = 152 CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
COEFF_MAG_ID = 1 COEFF_MAG_ID = 1
......
...@@ -42,6 +42,8 @@ STOCK_LOC_ID = 12 ...@@ -42,6 +42,8 @@ STOCK_LOC_ID = 12
CATEG_FRUIT = 151 CATEG_FRUIT = 151
CATEG_LEGUME = 152 CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
VRAC_CATEGS = [166, 167, 174, 179] VRAC_CATEGS = [166, 167, 174, 179]
#EXPORT_POS_CAT_FOR_SCALES = True #EXPORT_POS_CAT_FOR_SCALES = True
FLV_CSV_NB = 2 FLV_CSV_NB = 2
......
...@@ -48,6 +48,8 @@ CASH_PAYMENT_ID = 18 ...@@ -48,6 +48,8 @@ CASH_PAYMENT_ID = 18
STOCK_LOC_ID = 12 STOCK_LOC_ID = 12
CATEG_FRUIT = 151 CATEG_FRUIT = 151
CATEG_LEGUME = 152 CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
COEFF_MAG_ID = 1 COEFF_MAG_ID = 1
......
...@@ -47,6 +47,8 @@ MEALS_PICKING_TYPE_ID = 17 ...@@ -47,6 +47,8 @@ MEALS_PICKING_TYPE_ID = 17
CATEG_FRUIT = 189 CATEG_FRUIT = 189
CATEG_LEGUME = 189 CATEG_LEGUME = 189
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
VRAC_CATEGS = [197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207] VRAC_CATEGS = [197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207]
EXPORT_POS_CAT_FOR_SCALES = True EXPORT_POS_CAT_FOR_SCALES = True
SHELF_LABELS_ADD_FIELDS = ['code', 'category_print_id', 'base_price', 'categ_id', 'country_id', 'label_ids', 'uom_id', 'suppliers'] SHELF_LABELS_ADD_FIELDS = ['code', 'category_print_id', 'base_price', 'categ_id', 'country_id', 'label_ids', 'uom_id', 'suppliers']
......
...@@ -49,6 +49,8 @@ STOCK_LOC_ID = 12 ...@@ -49,6 +49,8 @@ STOCK_LOC_ID = 12
CATEG_FRUIT = 151 CATEG_FRUIT = 151
CATEG_LEGUME = 152 CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
VRAC_CATEGS = [166, 167, 174, 179] VRAC_CATEGS = [166, 167, 174, 179]
FLV_CSV_NB = 2 FLV_CSV_NB = 2
......
...@@ -2,6 +2,7 @@ from django.db import models ...@@ -2,6 +2,7 @@ from django.db import models
from outils.common import OdooAPI from outils.common import OdooAPI
from outils.common import CouchDB from outils.common import CouchDB
from outils.common import MARSHALL_ERROR
from outils.common_imports import * from outils.common_imports import *
from decimal import * from decimal import *
import os import os
...@@ -418,8 +419,9 @@ class CagetteInventory(models.Model): ...@@ -418,8 +419,9 @@ class CagetteInventory(models.Model):
api.execute('stock.inventory', 'action_done', [inv]) api.execute('stock.inventory', 'action_done', [inv])
done.append('Closed inventory') done.append('Closed inventory')
except Exception as e: except Exception as e:
coop_logger.error(str(e)) if not (MARSHALL_ERROR in str(e)):
errors.append(str(e)) coop_logger.error("update_stock_with_shelf_inventory_data %s",str(e))
errors.append(str(e))
return {'errors': errors, return {'errors': errors,
'missed': missed, 'missed': missed,
......
...@@ -333,7 +333,7 @@ def manage_makeups(request): ...@@ -333,7 +333,7 @@ def manage_makeups(request):
context = {'title': 'BDM - Rattrapages', context = {'title': 'BDM - Rattrapages',
'module': 'Membres', 'module': 'Membres',
'has_committe_shift': committees_shift_id is not None, 'has_committe_shift': committees_shift_id is not None,
'extension_duration': m.get_extension_duration() 'extension_duration': m.get_months_extension_duration()
} }
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
...@@ -410,7 +410,7 @@ def update_members_makeups_core(members_data, res): ...@@ -410,7 +410,7 @@ def update_members_makeups_core(members_data, res):
if member_data["member_shift_type"] == "standard": if member_data["member_shift_type"] == "standard":
# Set points to minus the number of makeups to do + the makeups to come (limited to -2) # Set points to minus the number of makeups to do + the makeups to come (limited to -2)
cs = CagetteShift() cs = CagetteShift()
shift_data = cs.get_shift_partner(int(member_data["member_id"])) [shiftData, is_ftop] = cs.get_shift_partner(int(member_data["member_id"]))
target_points = - int(member_data["target_makeups_nb"]) - sum(1 for value in shift_data if value['is_makeup']) target_points = - int(member_data["target_makeups_nb"]) - sum(1 for value in shift_data if value['is_makeup'])
if (target_points < -2): if (target_points < -2):
target_points = -2 target_points = -2
......
...@@ -63,3 +63,22 @@ p.important {border: #ff0000 1px solid; padding: 15px; margin-top:15px;} ...@@ -63,3 +63,22 @@ p.important {border: #ff0000 1px solid; padding: 15px; margin-top:15px;}
#form_delete, #problem_delete {display:none;} #form_delete, #problem_delete {display:none;}
#dashboard {margin-bottom:25px;} #dashboard {margin-bottom:25px;}
.geo_suggestions {
border: 1px solid rgb(239, 239, 239);
background: rgb(255, 255, 255);
z-index: 1000;
padding: 0 10px;
}
.geo_suggestions ul {
list-style-type: none;
padding: 0;
}
.geo_suggestions li {
cursor: pointer;
border-bottom: 1px solid rgb(239, 239, 239);
margin-bottom: 2px;
}
.geo_suggestions li i, .geo_suggestions li svg{
margin-right: 8px;
}
...@@ -374,7 +374,7 @@ function modify_current_coop() { ...@@ -374,7 +374,7 @@ function modify_current_coop() {
ncoop_view.find('input[name="email"]').val(current_coop._id); ncoop_view.find('input[name="email"]').val(current_coop._id);
payment_meaning.find('option').removeAttr('selected'); payment_meaning.find('option').removeAttr('selected');
payment_meaning.find('option[value="'+current_coop.payment_meaning+'"]').attr('selected', 'selected'); payment_meaning.find('option[value="'+current_coop.payment_meaning+'"]').attr('selected', 'selected');
if (current_coop.checks_nb.length > 0) { if (current_coop.checks_nb && current_coop.checks_nb.length > 0) {
ch_qty.val(current_coop.checks_nb); ch_qty.val(current_coop.checks_nb);
ch_qty.show(); ch_qty.show();
} else { } else {
......
...@@ -737,5 +737,7 @@ $(document).ready(function() { ...@@ -737,5 +737,7 @@ $(document).ready(function() {
home(); home();
} }
}); });
$('[name="birthdate"]').helpFillDate();
$('[name="phone"]').helpFillTel();
$('[name="mobile"]').helpFillTel();
}); });
...@@ -37,6 +37,8 @@ urlpatterns = [ ...@@ -37,6 +37,8 @@ urlpatterns = [
url(r'^verify_final_state$', views.verify_final_state), url(r'^verify_final_state$', views.verify_final_state),
url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes), url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes),
url(r'^add_shares_to_member$', views.add_shares_to_member), url(r'^add_shares_to_member$', views.add_shares_to_member),
url(r'^ask_for_new_password$', views.ask_for_new_password),
url(r'^reset_password/([0-9-a-z\-]+)$', views.reset_new_password),
# Borne accueil # Borne accueil
url(r'^search/([^\/.]+)/?([0-9]*)', views.search), url(r'^search/([^\/.]+)/?([0-9]*)', views.search),
url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'), url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'),
......
...@@ -108,6 +108,7 @@ def inscriptions(request, type=1): ...@@ -108,6 +108,7 @@ def inscriptions(request, type=1):
'mag_place_string': settings.MAG_NAME, 'mag_place_string': settings.MAG_NAME,
'office_place_string': settings.OFFICE_NAME, 'office_place_string': settings.OFFICE_NAME,
'max_begin_hour': settings.MAX_BEGIN_HOUR, 'max_begin_hour': settings.MAX_BEGIN_HOUR,
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True),
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS, 'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'force_firstname_hyphen': getattr(settings, 'FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME', True), 'force_firstname_hyphen': getattr(settings, 'FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME', True),
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False), 'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
...@@ -118,6 +119,7 @@ def inscriptions(request, type=1): ...@@ -118,6 +119,7 @@ def inscriptions(request, type=1):
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''), 'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12), 'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True), 'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', 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), 'can_create_binome': getattr(settings, 'CAN_CREATE_BINOME', True),
...@@ -160,6 +162,7 @@ def prepa_odoo(request): ...@@ -160,6 +162,7 @@ def prepa_odoo(request):
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False), 'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False), 'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True), 'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True),
'db': settings.COUCHDB['dbs']['member'], 'db': settings.COUCHDB['dbs']['member'],
'committees_shift_id': committees_shift_id, 'committees_shift_id': committees_shift_id,
'exemptions_shift_id': exemptions_shift_id, 'exemptions_shift_id': exemptions_shift_id,
...@@ -198,6 +201,7 @@ def validation_inscription(request, email): ...@@ -198,6 +201,7 @@ def validation_inscription(request, email):
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False), 'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False), 'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True), 'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True),
'em_url': settings.EM_URL, 'em_url': settings.EM_URL,
'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG, 'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG,
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')} 'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
...@@ -324,7 +328,7 @@ def record_service_presence(request): ...@@ -324,7 +328,7 @@ def record_service_presence(request):
overrided_date = "" overrided_date = ""
if app_env != "prod": if app_env != "prod":
import re import re
o_date = re.search(r'/([^\/]+?)$', request.META.get('HTTP_REFERER')) o_date = re.search(r'/([^\/]+?)$', request.META.get('HTTP_REFERER').replace('%20', ' '))
if o_date: if o_date:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1)) overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
...@@ -336,7 +340,7 @@ def record_service_presence(request): ...@@ -336,7 +340,7 @@ def record_service_presence(request):
if res['rattrapage'] is True: if res['rattrapage'] is True:
res['update'] = 'ok' res['update'] = 'ok'
else: else:
if (CagetteServices.registration_done(rid, overrided_date, typeAction) is True): if (CagetteServices.registration_done(request, rid, overrided_date, typeAction) is True):
res['update'] = 'ok' res['update'] = 'ok'
else: else:
res['update'] = 'ko' res['update'] = 'ko'
...@@ -413,6 +417,52 @@ def create_from_csv(request): ...@@ -413,6 +417,52 @@ def create_from_csv(request):
res['error'] = "Forbidden" res['error'] = "Forbidden"
return JsonResponse(res, safe=False) return JsonResponse(res, safe=False)
def ask_for_new_password(request):
succeeded = False
res = {'succeeded': None}
try:
data = json.loads(request.body.decode())
CagetteMember.get_new_password_link(data)
succeeded = True
except Exception as e:
coop_logger.error("ask_for_new_password : %s", str(e))
res['error'] = str(e)
res['succeeded'] = succeeded
if succeeded is True:
return JsonResponse(res, safe=False)
else:
return JsonResponse(res, status=500)
def reset_new_password(request, token):
"""Return form or redirect to login is POST process succeeded"""
external_msg = ''
received_pwd = request.POST.get('password')
is_valid_pwd = received_pwd and len(received_pwd) >=10
if is_valid_pwd is True:
try:
# CagetteMember.lksfllkkl_generate_error('doesnt exist')
external_msg = CagetteMember.set_new_password(received_pwd, token)
except Exception as e:
is_valid_pwd = False
external_msg = "reset_password_failure"
coop_logger.error("reset_new_password : %s", str(e))
# Let's return content to visitor
if request.method == 'GET' or is_valid_pwd is False:
template = loader.get_template('website/change_pwd.html')
context = {'token': token,
'password_placeholder': 'Nouveau mot de passe',
'title': 'Changement du mot de passe',
'external_msg': external_msg}
response = HttpResponse(template.render(context, request))
else:
landing_url = getattr(settings, 'AFTER_NEW_PASS_SETTING_REDIRECT', '/')
if getattr(settings, 'APP_ENV', '') == 'dev':
landing_url = '/members_space/'
response = redirect(landing_url + '?msg=' + external_msg)
return response
def panel_get_purchases(request): def panel_get_purchases(request):
"""Return INRA panel purchases (possible filter : month (w/wo year))""" """Return INRA panel purchases (possible filter : month (w/wo year))"""
if request.method == 'GET': if request.method == 'GET':
......
...@@ -27,12 +27,18 @@ class CagetteMembersSpace(models.Model): ...@@ -27,12 +27,18 @@ class CagetteMembersSpace(models.Model):
answer = True answer = True
return answer return answer
def get_extension_duration(self): def get_months_extension_duration(self):
"""Return nb of months""" duration = 2
# TODO : add a unit parameter and convert if not month try:
extension_duration = OdooAPI().get_system_param('lacagette_membership.extension_duration') import re
nb, unit = extension_duration.split(' ') p = self.o_api.get_system_param('lacagette_membership.extension_duration')
return nb pattern = re.compile(r'([0-9]+) month')
m = pattern.search(p)
if m:
duration = m.group(1)
except Exception as e:
coop_logger.error('get_months_extension_duration : %s', str())
return duration
def get_shifts_history(self, partner_id, limit, offset, date_from): def get_shifts_history(self, partner_id, limit, offset, date_from):
""" Get partner shifts history """ """ Get partner shifts history """
......
...@@ -8,10 +8,15 @@ body { ...@@ -8,10 +8,15 @@ body {
@media screen and (max-width: 435px) { @media screen and (max-width: 435px) {
.page_title { .page_title {
margin: 4vw 0 3vw 0; margin: 4vw 0 3vw 0;
} }
} }
/* -- Calendar */
#shift_choice .shift {
padding-left: 5px;
}
/* -- Tiles */ /* -- Tiles */
.tiles_container { .tiles_container {
......
...@@ -25,21 +25,6 @@ $(document).on('click', "#shift_exchange_btn", () => { ...@@ -25,21 +25,6 @@ $(document).on('click', "#shift_exchange_btn", () => {
goto('echange-de-services'); goto('echange-de-services');
}); });
$(document).on('click', '.accordion', function() {
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
function display_messages_for_attached_people() { function display_messages_for_attached_people() {
if (block_actions_for_attached_people === "False") { if (block_actions_for_attached_people === "False") {
$(".attached-unblocked").show(); $(".attached-unblocked").show();
......
...@@ -91,6 +91,11 @@ function add_or_change_shift(new_shift_id) { ...@@ -91,6 +91,11 @@ function add_or_change_shift(new_shift_id) {
load_partner_shifts(partner_data.concerned_partner_id) load_partner_shifts(partner_data.concerned_partner_id)
.then(() => { .then(() => {
init_shifts_list(); init_shifts_list();
if (partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True") {
init_delete_registration_buttons();
}
closeModal(); closeModal();
setTimeout(() => { setTimeout(() => {
...@@ -125,10 +130,7 @@ function add_or_change_shift(new_shift_id) { ...@@ -125,10 +130,7 @@ function add_or_change_shift(new_shift_id) {
`Si tu ne peux vraiment pas venir, tu seras noté.e absent.e à ton service. ` + `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.`); `Tu devras alors sélectionner un service de rattrapage sur ton espace membre.`);
} else if (error.status === 400 && 'msg' in error.responseJSON && error.responseJSON.msg === "Not allowed to change shift") { } else if (error.status === 400 && 'msg' in error.responseJSON && error.responseJSON.msg === "Not allowed to change shift") {
alert(`Désolé ! Le service que tu souhaites échanger démarre dans trop peu de temps. ` + alert(not_allowed_shift_op);
`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 && 'msg' in error.responseJSON && error.responseJSON.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! // TODO differentiate error cases!
alert(`Une erreur est survenue. ` + alert(`Une erreur est survenue. ` +
...@@ -138,6 +140,8 @@ function add_or_change_shift(new_shift_id) { ...@@ -138,6 +140,8 @@ function add_or_change_shift(new_shift_id) {
alert(`Une erreur est survenue. ` + alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` + `Il est néanmoins possible que la requête ait abouti, ` +
`veuillez patienter quelques secondes puis vérifier vos services enregistrés.`); `veuillez patienter quelques secondes puis vérifier vos services enregistrés.`);
} else if (error.status === 422) {
alert('Désolé ! Ce service ne peut pas être ajouté en raison des règles établies.');
} else { } else {
alert(`Une erreur est survenue. ` + alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` + `Il est néanmoins possible que la requête ait abouti, ` +
...@@ -373,8 +377,12 @@ function init_shifts_list() { ...@@ -373,8 +377,12 @@ function init_shifts_list() {
} }
} }
// Set delete registration button if shift isn't a makeup // Set delete registration button if shift isn't a makeup or user is ftop and has right to delete shifts
if (partner_data.extra_shift_done > 0 && shift.is_makeup === false) { if (
partner_data.extra_shift_done > 0 && shift.is_makeup === false
|| partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True"
) {
if (shift_line_template.find(".delete_registration_button").length === 0) { if (shift_line_template.find(".delete_registration_button").length === 0) {
let delete_reg_button_template = $("#delete_registration_button_template"); let delete_reg_button_template = $("#delete_registration_button_template");
...@@ -458,6 +466,20 @@ function init_shifts_list() { ...@@ -458,6 +466,20 @@ function init_shifts_list() {
} }
} }
function add_week_letter_to_elt(elt) {
const date_string = $(elt.el).data('date'),
date = new Date(date_string);
if (date.getDay() == 1) {
const wl = date.getABCDWeekLetter();
let week_letter_div = document.createElement('div');
week_letter_div.innerHTML = '<span>' + wl + '</span>';
week_letter_div.classList.add('week-letter');
$('td[data-date="' + date_string + '"]').append(week_letter_div);
}
}
/** /**
* Inits the page when the calendar is displayed * Inits the page when the calendar is displayed
*/ */
...@@ -510,6 +532,10 @@ function init_calendar_page() { ...@@ -510,6 +532,10 @@ function init_calendar_page() {
$("#delete_future_registration").on("click", init_delete_registration_buttons); $("#delete_future_registration").on("click", init_delete_registration_buttons);
} }
if (partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True") {
init_delete_registration_buttons();
}
let default_initial_view = ""; let default_initial_view = "";
let header_toolbar = {}; let header_toolbar = {};
...@@ -600,7 +626,7 @@ function init_calendar_page() { ...@@ -600,7 +626,7 @@ function init_calendar_page() {
}, },
"Valider" "Valider"
); );
} else if (selected_shift === null && can_exchange_shifts()) { } else if (partner_data.shift_type !== 'ftop' && selected_shift === null && can_exchange_shifts()) {
if (adding_mode === false) { if (adding_mode === false) {
/* could exchange shift but no old shift selected */ /* could exchange shift but no old shift selected */
openModal( openModal(
...@@ -626,7 +652,7 @@ function init_calendar_page() { ...@@ -626,7 +652,7 @@ function init_calendar_page() {
); );
} }
} else if (should_select_makeup()) { } else if (should_select_makeup() || partner_data.shift_type == 'ftop') {
/* choose a makeup service */ /* choose a makeup service */
// Check if selected new shift is in less than extension end // Check if selected new shift is in less than extension end
if (partner_data.date_delay_stop !== 'False') { if (partner_data.date_delay_stop !== 'False') {
...@@ -657,7 +683,7 @@ function init_calendar_page() { ...@@ -657,7 +683,7 @@ function init_calendar_page() {
} }
} }
}, },
eventDidMount: function() { eventsSet: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded // Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide(); $(".loading-calendar").hide();
$("#calendar").show(); $("#calendar").show();
...@@ -667,6 +693,11 @@ function init_calendar_page() { ...@@ -667,6 +693,11 @@ function init_calendar_page() {
} else { } else {
$(".fc .fc-header-toolbar").removeClass("resp-header-toolbar"); $(".fc .fc-header-toolbar").removeClass("resp-header-toolbar");
} }
},
dayCellDidMount: function(dayRenderInfo) {
add_week_letter_to_elt(dayRenderInfo);
return dayRenderInfo.el;
} }
}); });
...@@ -761,7 +792,7 @@ async function init_read_only_calendar_page() { ...@@ -761,7 +792,7 @@ async function init_read_only_calendar_page() {
eventDisplay: "block", eventDisplay: "block",
hiddenDays: hidden_days, hiddenDays: hidden_days,
events: event_src, events: event_src,
eventDidMount: function() { eventsSet: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded // Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide(); $(".loading-calendar").hide();
$("#calendar").show(); $("#calendar").show();
...@@ -781,7 +812,7 @@ function init_delete_registration_buttons() { ...@@ -781,7 +812,7 @@ function init_delete_registration_buttons() {
$(".delete_registration_button").off(); $(".delete_registration_button").off();
$(".delete_registration_button").hide(); $(".delete_registration_button").hide();
if (partner_data.extra_shift_done > 0) { if (partner_data.extra_shift_done > 0 || partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True") {
$(".delete_registration_button").on("click", function() { $(".delete_registration_button").on("click", function() {
let shift_name = $(this).closest("div") let shift_name = $(this).closest("div")
.parent() .parent()
......
...@@ -10,6 +10,7 @@ from shifts.models import CagetteShift ...@@ -10,6 +10,7 @@ from shifts.models import CagetteShift
from members_space.models import CagetteMembersSpace from members_space.models import CagetteMembersSpace
import hashlib import hashlib
import re
def _get_response_according_to_credentials(request, credentials, context, template): def _get_response_according_to_credentials(request, credentials, context, template):
...@@ -41,9 +42,14 @@ def index(request, exception=None): ...@@ -41,9 +42,14 @@ def index(request, exception=None):
# Bad credentials (or none) # Bad credentials (or none)
template = loader.get_template('website/connect.html') template = loader.get_template('website/connect.html')
context['msg'] = '' context['msg'] = ''
context['external_msg'] = request.GET.get('msg')
if 'msg' in credentials: if 'msg' in credentials:
context['msg'] = credentials['msg'] context['msg'] = credentials['msg']
context['password_placeholder'] = 'Naissance (jjmmaaaa)' if getattr(settings, 'USE_MEMBERS_CUSTOM_PASSWORD', False) is True:
context['password_placeholder'] = "Mot de passe ou naissance"
context['reset_password_available'] = True
else:
context['password_placeholder'] = 'Naissance (jjmmaaaa)'
context['is_member_space'] = True context['is_member_space'] = True
elif ('validation_state' in credentials) and credentials['validation_state'] == 'waiting_validation_member': elif ('validation_state' in credentials) and credentials['validation_state'] == 'waiting_validation_member':
# First connection, until the member validated his account # First connection, until the member validated his account
...@@ -87,6 +93,11 @@ def index(request, exception=None): ...@@ -87,6 +93,11 @@ def index(request, exception=None):
partnerData = cs.get_data_partner(partner_id) partnerData = cs.get_data_partner(partner_id)
default_not_allowed_shift_op = """Désolé ! Le service que tu souhaites échanger démarre dans trop peu de temps.
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."""
if 'create_date' in partnerData: if 'create_date' in partnerData:
md5_calc = hashlib.md5(partnerData['create_date'].encode('utf-8')).hexdigest() md5_calc = hashlib.md5(partnerData['create_date'].encode('utf-8')).hexdigest()
partnerData['verif_token'] = md5_calc partnerData['verif_token'] = md5_calc
...@@ -124,19 +135,28 @@ def index(request, exception=None): ...@@ -124,19 +135,28 @@ def index(request, exception=None):
partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"] partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"]
m = CagetteMembersSpace() m = CagetteMembersSpace()
context['extension_duration'] = m.get_extension_duration() context['extension_duration'] = m.get_months_extension_duration()
context['show_faq'] = getattr(settings, 'MEMBERS_SPACE_FAQ_TEMPLATE', 'members_space/faq.html') context['show_faq'] = getattr(settings, 'MEMBERS_SPACE_FAQ_TEMPLATE', 'members_space/faq.html')
context['show_abcd_calendar'] = getattr(settings, 'SHOW_ABCD_CALENDAR_TAB', True) context['show_abcd_calendar'] = getattr(settings, 'SHOW_ABCD_CALENDAR_TAB', True)
partnerData["comite"] = m.is_comite(partner_id) partnerData["comite"] = m.is_comite(partner_id)
context['partnerData'] = partnerData context['partnerData'] = partnerData
context['mag_name'] = getattr(settings, 'MAG_NAME', '')
# Days to hide in the calendar # Days to hide in the calendar
days_to_hide = "0" days_to_hide = "0"
context['SHIFTS_MOVING_ALLOWED'] = getattr(settings, 'SHIFTS_MOVING_ALLOWED', '')
context['ALLOW_FTOP_TO_DELETE_SHIFT'] = getattr(settings, 'ALLOW_FTOP_TO_DELETE_SHIFT', False)
context['ADDITIONAL_INFO_SHIFT_PAGE'] = getattr(settings, 'ADDITIONAL_INFO_SHIFT_PAGE', '') context['ADDITIONAL_INFO_SHIFT_PAGE'] = getattr(settings, 'ADDITIONAL_INFO_SHIFT_PAGE', '')
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
# message shown when not allowed to move shift
context['not_allowed_shift_op'] = getattr(settings, 'NOT_ALLOWED_SHIFT_OP_MSG', default_not_allowed_shift_op)
context['not_allowed_shift_op'] = re.sub(r"\r\s( *)", "\n", context['not_allowed_shift_op'], flags=re.MULTILINE)
context['not_allowed_shift_op'] = re.sub(r"\n( *)", "\n", context['not_allowed_shift_op'], flags=re.MULTILINE)
can_add_shift = getattr(settings, 'CAN_ADD_SHIFT', False) can_add_shift = getattr(settings, 'CAN_ADD_SHIFT', False)
context['canAddShift'] = "true" if can_add_shift is True else "false" context['canAddShift'] = "true" if can_add_shift is True else "false"
...@@ -238,7 +258,7 @@ def shifts_exchange(request): ...@@ -238,7 +258,7 @@ def shifts_exchange(request):
context = { context = {
'title': 'Échange de Services', 'title': 'Échange de Services',
'canAddShift': getattr(settings, 'CAN_ADD_SHIFT', False), 'canAddShift': getattr(settings, 'CAN_ADD_SHIFT', False),
'extension_duration': m.get_extension_duration() 'extension_duration': m.get_months_extension_duration()
} }
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
...@@ -249,6 +269,7 @@ def faqBDM(request): ...@@ -249,6 +269,7 @@ def faqBDM(request):
template = loader.get_template(template_path) template = loader.get_template(template_path)
context = { context = {
'title': 'foire aux questions', 'title': 'foire aux questions',
'MEMBERS_GUIDE_URL': getattr(settings, 'MEMBERS_GUIDE_URL', None)
} }
content = template.render(context, request) content = template.render(context, request)
......
...@@ -165,7 +165,7 @@ class Order(models.Model): ...@@ -165,7 +165,7 @@ class Order(models.Model):
def export(self): def export(self):
res = {'success': True} res = {'success': True}
try: try:
f = ["id", "name", "date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "x_reception_status"] f = ["id", "name", "date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "reception_status"]
c = [['id', '=', self.id]] c = [['id', '=', self.id]]
order = self.o_api.search_read('purchase.order', c, f) order = self.o_api.search_read('purchase.order', c, f)
if order: if order:
...@@ -226,6 +226,7 @@ class Order(models.Model): ...@@ -226,6 +226,7 @@ class Order(models.Model):
def get_custom_barcode_labels_to_print(self): def get_custom_barcode_labels_to_print(self):
import re import re
forced_quantity = getattr(settings, 'RECEPTION_PDT_LABELS_NB_FORCE_TO_NB', None)
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490') fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
labels_data = {'total': 0, 'details': []} labels_data = {'total': 0, 'details': []}
lines_data = self.get_lines() lines_data = self.get_lines()
...@@ -233,6 +234,8 @@ class Order(models.Model): ...@@ -233,6 +234,8 @@ class Order(models.Model):
bc_pattern = re.compile('^' + fixed_prefix) bc_pattern = re.compile('^' + fixed_prefix)
for l in lines: for l in lines:
if ('barcode' in l) and not (bc_pattern.match(str(l['barcode'])) is None): if ('barcode' in l) and not (bc_pattern.match(str(l['barcode'])) is None):
if forced_quantity is not None:
l['product_qty'] = forced_quantity
labels_data['details'].append(l) labels_data['details'].append(l)
labels_data['total'] += l['product_qty'] labels_data['total'] += l['product_qty']
return labels_data return labels_data
...@@ -253,69 +256,75 @@ class Order(models.Model): ...@@ -253,69 +256,75 @@ class Order(models.Model):
@staticmethod @staticmethod
def create(supplier_id, date_planned, order_lines): def create(supplier_id, date_planned, order_lines):
order_data = { if len(list(order_lines)) > 0:
"partner_id": int(supplier_id), order_data = {
"partner_ref": False, "partner_id": int(supplier_id),
"currency_id": 1, "partner_ref": False,
"date_order": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "currency_id": 1,
"origin": "Aide à la commande", "date_order": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"company_id": 1, "origin": "Aide à la commande",
"order_line": [], "company_id": 1,
"notes": False, "order_line": [],
"date_planned": date_planned, "notes": False,
"picking_type_id": 1, "date_planned": date_planned,
"dest_address_id": False, "picking_type_id": 1,
"incoterm_id": False, "dest_address_id": False,
"payment_term_id": False, "incoterm_id": False,
"fiscal_position_id": False, "payment_term_id": False,
"message_follower_ids": False, "fiscal_position_id": False,
"message_ids": False "message_follower_ids": False,
} "message_ids": False
}
for line in order_lines:
product_line_name = line["name"] for line in order_lines:
if "product_code" in line and line["product_code"] is not False: product_line_name = line["name"]
product_code = str(line["product_code"]) if "product_code" in line and line["product_code"] is not False:
product_line_name = "[" + product_code + "] " + product_line_name product_code = str(line["product_code"])
product_line_name = "[" + product_code + "] " + product_line_name
order_data["order_line"].append(
[ order_data["order_line"].append(
0, [
False, 0,
{ False,
"package_qty": line["package_qty"], {
"price_policy": "uom", "package_qty": line["package_qty"],
"indicative_package": True, "price_policy": "uom",
"product_id": line["product_variant_ids"][0], "indicative_package": True,
"name": product_line_name, "product_id": line["product_variant_ids"][0],
"date_planned": date_planned, "name": product_line_name,
"account_analytic_id": False, "date_planned": date_planned,
"product_qty_package":line["product_qty_package"], "account_analytic_id": False,
"product_qty": line["product_qty"], "product_qty_package":line["product_qty_package"],
"product_uom": line["product_uom"], "product_qty": line["product_qty"],
"price_unit": line["price_unit"], "product_uom": line["product_uom"],
"discount": 0, "price_unit": line["price_unit"],
"taxes_id": [ "discount": 0,
[ "taxes_id": [
6, [
False, 6,
line["supplier_taxes_id"] False,
line["supplier_taxes_id"]
]
] ]
] }
} ]
] )
)
api = OdooAPI() api = OdooAPI()
id_po = api.create('purchase.order', order_data) id_po = api.create('purchase.order', order_data)
res_confirm = api.execute('purchase.order', 'button_confirm', [id_po]) res_confirm = api.execute('purchase.order', 'button_confirm', [id_po])
res = { res = {
'id_po': id_po, 'id_po': id_po,
'confirm_po': True, 'confirm_po': True,
'supplier_id': supplier_id, 'supplier_id': supplier_id,
'date_planned': date_planned 'date_planned': date_planned
} }
else:
res = {
'id_po': 0,
'supplier_id': supplier_id
}
return res return res
...@@ -354,6 +363,7 @@ class Orders(models.Model): ...@@ -354,6 +363,7 @@ class Orders(models.Model):
import re import re
labels_data = {} labels_data = {}
try: try:
forced_quantity = getattr(settings, 'RECEPTION_PDT_LABELS_NB_FORCE_TO_NB', None)
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490') fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
bc_pattern = re.compile('^' + fixed_prefix) bc_pattern = re.compile('^' + fixed_prefix)
lines_data = Orders.get_lines(oids) lines_data = Orders.get_lines(oids)
...@@ -361,7 +371,10 @@ class Orders(models.Model): ...@@ -361,7 +371,10 @@ class Orders(models.Model):
if not (bc_pattern.match(str(l['barcode'])) is None): if not (bc_pattern.match(str(l['barcode'])) is None):
if not (l['product_tmpl_id'] in labels_data): if not (l['product_tmpl_id'] in labels_data):
labels_data[l['product_tmpl_id']] = 0 labels_data[l['product_tmpl_id']] = 0
labels_data[l['product_tmpl_id']] += int(l['product_qty']) if forced_quantity is not None:
labels_data[l['product_tmpl_id']] = int(forced_quantity)
else:
labels_data[l['product_tmpl_id']] += int(l['product_qty'])
except Exception as e: except Exception as e:
coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e)) coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e))
......
...@@ -13,6 +13,21 @@ ...@@ -13,6 +13,21 @@
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 500;
}
.preferences_area {
display: block;
width: fit-content;
float: left;
position: relative;
z-index: 501;
}
.preferences_area button {cursor: pointer;}
.ui-autocomplete {
z-index: 9999 !important;
} }
.pill { .pill {
...@@ -495,6 +510,36 @@ ...@@ -495,6 +510,36 @@
/* - Miscellaneous */ /* - Miscellaneous */
.modal_input_area form {
width: 100%;
}
.modal_input_area form label {
display: block;
float: left;
clear: left;
width: 75%;
text-align: left;
}
.modal_input_area form .input-wrapper {
display: block;
float: left;
width: 23%;
}
.modal_input_area form .input-wrapper.checkboxes div {
float: left;
margin-right: 5px;
}
footer { footer {
display: none; display: none;
}
@media screen and (max-width: 1600px) {
#order_forms_container {
font-size: small;
}
#supplier_form {
flex: min-content;
text-align: left;
}
} }
\ No newline at end of file
...@@ -9,11 +9,12 @@ urlpatterns = [ ...@@ -9,11 +9,12 @@ urlpatterns = [
url(r'^export/([a-z]+)', views.export_regex), url(r'^export/([a-z]+)', views.export_regex),
url(r'^get_pdf_labels$', views.get_pdf_labels), url(r'^get_pdf_labels$', views.get_pdf_labels),
url(r'^print_product_labels$', views.print_product_labels), url(r'^print_product_labels$', views.print_product_labels),
url(r'^helper$', views.helper), url(r'^helper(/?[a-z]*)$', views.helper),
url(r'^get_suppliers$', views.get_suppliers), url(r'^get_suppliers$', views.get_suppliers),
url(r'^get_supplier_products$', views.get_supplier_products), url(r'^get_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product), url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
url(r'^end_supplier_product_association$', views.end_supplier_product_association), url(r'^end_supplier_product_association$', views.end_supplier_product_association),
url(r'^create_orders$', views.create_orders), url(r'^create_orders$', views.create_orders),
url(r'^get_orders_attachment$', views.get_orders_attachment), url(r'^get_orders_attachment$', views.get_orders_attachment),
url(r'^set_user_preferences$', views.set_user_preferences),
] ]
...@@ -4,6 +4,7 @@ from outils.common import OdooAPI ...@@ -4,6 +4,7 @@ from outils.common import OdooAPI
from orders.models import Order, Orders, CagetteSuppliers from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts from products.models import CagetteProduct, CagetteProducts
from members.models import CagetteUser
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook from openpyxl.writer.excel import save_virtual_workbook
...@@ -15,13 +16,25 @@ def as_text(value): return str(value) if value is not None else "" ...@@ -15,13 +16,25 @@ def as_text(value): return str(value) if value is not None else ""
def index(request): def index(request):
return HttpResponse('Orders') return HttpResponse('Orders')
def helper(request): def helper(request, params_query):
"""params_query is query string subpart, after /order in url."""
can_customize = getattr(settings, 'ORDERS_HELPER_CUSTOMIZE', False)
uoms = CagetteProducts.get_uoms()
preferences = {}
if can_customize is True:
preferences = CagetteUser.get_preferences(request, 'third_party_order_helper')
context = { context = {
'title': 'Aide à la commande', 'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'], 'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'], 'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': getattr(settings, 'ODOO_PUBLIC_URL', settings.ODOO['url']), 'odoo_server': getattr(settings, 'ODOO_PUBLIC_URL', settings.ODOO['url']),
'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''), 'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''),
'uoms': json.dumps(uoms),
'can_customize_parameters': can_customize,
'uoms': json.dumps(uoms),
'preferences': json.dumps(preferences),
'nb_past_days_to_compute_sales_average': OdooAPI().get_system_param('lacagette_products.nb_past_days_to_compute_sales_average'), 'nb_past_days_to_compute_sales_average': OdooAPI().get_system_param('lacagette_products.nb_past_days_to_compute_sales_average'),
'nb_of_consecutive_non_sale_days_considered_as_break': OdooAPI().get_system_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break') 'nb_of_consecutive_non_sale_days_considered_as_break': OdooAPI().get_system_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break')
} }
...@@ -30,6 +43,17 @@ def helper(request): ...@@ -30,6 +43,17 @@ def helper(request):
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def set_user_preferences(request):
res = {}
can_customize = getattr(settings, 'ORDERS_HELPER_CUSTOMIZE', False)
if can_customize is True:
data = json.loads(request.body.decode())
res = CagetteUser.set_preferences(request, data, 'third_party_order_helper')
else:
res['error'] = 'Customization not available'
return JsonResponse(res, status=403)
return JsonResponse({'res': res})
def get_suppliers(request): def get_suppliers(request):
""" Get suppliers list """ """ Get suppliers list """
res = {} res = {}
...@@ -44,10 +68,15 @@ def get_suppliers(request): ...@@ -44,10 +68,15 @@ def get_suppliers(request):
def get_supplier_products(request): def get_supplier_products(request):
""" Get supplier products """ """ Get supplier products """
with_fake_data = False
# Fake data can never be set to True in production env.
if getattr(settings, 'APP_ENV', '') == 'dev':
if '/fake' in request.META.get('HTTP_REFERER'):
with_fake_data = True
suppliers_id = request.GET.getlist('sids', '') suppliers_id = request.GET.getlist('sids', '')
stats_from = request.GET.get('stats_from') stats_from = request.GET.get('stats_from')
res = CagetteProducts.get_products_for_order_helper(suppliers_id, [], stats_from) res = CagetteProducts.get_products_for_order_helper(suppliers_id, [], stats_from, with_fake_data)
if 'error' in res: if 'error' in res:
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
......
...@@ -7,20 +7,26 @@ import logging ...@@ -7,20 +7,26 @@ import logging
coop_logger = logging.getLogger("coop.framework") coop_logger = logging.getLogger("coop.framework")
MARSHALL_ERROR = "cannot marshal None unless allow_none is enabled" # api user need to use english language
class OdooAPI: class OdooAPI:
"""Class to handle Odoo API requests.""" """Class to handle Odoo API requests."""
url = settings.ODOO['url'] url = None
user = settings.ODOO['user'] user = None
passwd = settings.ODOO['passwd'] passwd = None
db = settings.ODOO['db'] db = None
common = None common = None
uid = None uid = None
models = None models = None
def __init__(self): def __init__(self, odoo=settings.ODOO):
"""Initialize xmlrpc connection.""" """Initialize xmlrpc connection."""
try: try:
self.url = odoo['url']
self.user = odoo['user']
self.passwd = odoo['passwd']
self.db = odoo['db']
common_proxy_url = '{}/xmlrpc/2/common'.format(self.url) common_proxy_url = '{}/xmlrpc/2/common'.format(self.url)
object_proxy_url = '{}/xmlrpc/2/object'.format(self.url) object_proxy_url = '{}/xmlrpc/2/object'.format(self.url)
self.common = xmlrpc.client.ServerProxy(common_proxy_url) self.common = xmlrpc.client.ServerProxy(common_proxy_url)
...@@ -67,8 +73,11 @@ class OdooAPI: ...@@ -67,8 +73,11 @@ class OdooAPI:
def create(self, entity, fields): def create(self, entity, fields):
"""Create entity instance with given fields values.""" """Create entity instance with given fields values."""
context = {
'context': {'lang': 'fr_FR', 'tz': 'Europe/Paris'}
}
return self.models.execute_kw(self.db, self.uid, self.passwd, return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, 'create', [fields]) entity, 'create', [fields], context)
def delete(self, entity, ids): def delete(self, entity, ids):
"""Destroy entity instance by given ids.""" """Destroy entity instance by given ids."""
...@@ -76,8 +85,15 @@ class OdooAPI: ...@@ -76,8 +85,15 @@ class OdooAPI:
entity, 'unlink', [ids]) entity, 'unlink', [ids])
def execute(self, entity, method, ids, params={}): def execute(self, entity, method, ids, params={}):
return self.models.execute_kw(self.db, self.uid, self.passwd, res = []
entity, method, [ids], params) try:
res = self.models.execute_kw(self.db, self.uid, self.passwd,
entity, method, [ids], params)
except Exception as e:
if not (MARSHALL_ERROR in str(e)):
coop_logger.error("Error while api execute: %s", str(e))
raise RuntimeError('Failed api execute') from e
return res
def authenticate(self, login, password): def authenticate(self, login, password):
return self.common.authenticate(self.db, login, password, {}) return self.common.authenticate(self.db, login, password, {})
...@@ -262,3 +278,24 @@ class Verification: ...@@ -262,3 +278,24 @@ class Verification:
if token == md5_calc: if token == md5_calc:
match = True match = True
return match return match
@staticmethod
def has_right_to_overriden_entrance_date(request):
from members.models import CagetteUser # Error if declared on top....
from urllib.parse import unquote
answer = False
u_grants = getattr(settings, 'ODOO_USERS_GRANTS', [])
if len(u_grants) > 0 and CagetteUser.are_credentials_ok(request):
try:
login = unquote(request.COOKIES['login'])
for user in u_grants:
if user['login'] == login:
if 'bdm' in user:
if (('all' in user['bdm'] and user['bdm']['all'] is True)
or ('record_absence' in user['bdm'] and user['bdm']['record_absence'] is True)):
answer = True
except Exception as e:
coop_logger.error("has right to overriden date : %s", str(e))
return answer
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
"""Import which are used in most of modules files.""" """Import which are used in most of modules files."""
from django.conf import settings from django.conf import settings
from django.core import validators
from .common_functions import * from .common_functions import *
import json, time, datetime, pytz import json, time, datetime, pytz
import logging import logging
......
...@@ -152,6 +152,10 @@ ...@@ -152,6 +152,10 @@
- CATEG_LEGUME = 152 - CATEG_LEGUME = 152
- FR_CATEGS = [CATEG_FRUIT]
- FR_CATEGS = [CATEG_LEGUME]
- FIXED_BARCODE_PREFIX = '0491' - FIXED_BARCODE_PREFIX = '0491'
- FLV_CSV_NB = 4 - FLV_CSV_NB = 4
...@@ -299,6 +303,14 @@ ...@@ -299,6 +303,14 @@
If set to True, a "personnal data" menu is shown, permitting connected member to modify its data. If set to True, a "personnal data" menu is shown, permitting connected member to modify its data.
- SHIFTS_MOVING_ALLOWED = True
If set to False, ABCD members cannot move their shifts using calendar
- FTOP_SHIFTS_VIEW_LIMIT = 2
By default, no limit is set. Unit must be weeks
- CALENDAR_NO_MORE_LINK = True - CALENDAR_NO_MORE_LINK = True
If True, in shifts calendar view (to choose one or exchange one) If True, in shifts calendar view (to choose one or exchange one)
...@@ -327,6 +339,26 @@ ...@@ -327,6 +339,26 @@
Message shown to people when they connect to the Member Space Message shown to people when they connect to the Member Space
- MEMBERS_GUIDE_URL = 'https://.....'
URL to Members guide page
- MIN_SHIFT_DURATION = 3
Define minimum shift duration (in hours). 2 is not set.
- FTOP_BLOCK_SERVICE_EXCHANGE_DELAY = 1
Define duration, in hours, before shift starts within exchange is not more available, for ftop shift_type member
- STANDARD_BLOCK_SERVICE_EXCHANGE_DELAY = 48
Define duration, in hours, before shift starts within exchange is not more available, for standard shift_type member
- FTOP_SERVICES_RULES = {'successive_shifts_allowed': 0, 'max_shifts_per_cycle': 3}
Define ftop members constraints (Les amis de La Coopé ex.)
- 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
...@@ -391,6 +423,10 @@ ...@@ -391,6 +423,10 @@
- RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits' - RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits'
- RECEPTION_PDT_LABELS_NB_FORCE_TO_NB = 65
If set, force , by example, to 65 labels per product
- RECEPTION_SHELF_LABEL_PRINT = True - RECEPTION_SHELF_LABEL_PRINT = True
- DISPLAY_COL_AUTRES = True - DISPLAY_COL_AUTRES = True
...@@ -401,6 +437,12 @@ ...@@ -401,6 +437,12 @@
DB coeff id, needed to compute product shelf price DB coeff id, needed to compute product shelf price
### Orders helper
- ORDERS_HELPER_CUSTOMIZE = False
If lacagette_connection Odoo module is actived, True can be set, in order to allow user to record its preferences
### Stocks ### Stocks
- STOCK_LOC_ID = 12 - STOCK_LOC_ID = 12
...@@ -464,6 +506,10 @@ ...@@ -464,6 +506,10 @@
True by default. Remove 15 minutes to Odoo shift end (https://redmine.cooperatic.fr/issues/1680) True by default. Remove 15 minutes to Odoo shift end (https://redmine.cooperatic.fr/issues/1680)
- ALLOW_FTOP_TO_DELETE_SHIFT = False
If True, allow ftop member to delete futur shifts
### BDM Admin ### BDM Admin
- BDM_SHOW_FTOP_BUTTON = True (by default) - BDM_SHOW_FTOP_BUTTON = True (by default)
...@@ -474,6 +520,12 @@ ...@@ -474,6 +520,12 @@
By defaut, True. Show "Gestion des binômes" in bdm admin By defaut, True. Show "Gestion des binômes" in bdm admin
### Odoo users mapping
- ODOO_USERS_GRANTS = [ {'login': 'admin', 'bdm': {'all': True}}, {'login': gdm@lacoope.fr', 'bdm': {'record_absence': True}} ]
If not set, any authentified Odoo user can make any action
### Miscellious ### Miscellious
- EXPORT_COMPTA_FORMAT = 'Quadratus' - EXPORT_COMPTA_FORMAT = 'Quadratus'
......
...@@ -34,6 +34,9 @@ footer { position: fixed; ...@@ -34,6 +34,9 @@ footer { position: fixed;
font-style: italic; font-style: italic;
color: blue; color: blue;
} }
.change_passwd_info {background-color: blue;}
#deconnect, #password_change {float:right; margin-left: 5px;} #deconnect, #password_change {float:right; margin-left: 5px;}
/* The Overlay (background) */ /* The Overlay (background) */
......
...@@ -373,6 +373,7 @@ function show_admin_menu() { ...@@ -373,6 +373,7 @@ function show_admin_menu() {
function store_credentials(data) { function store_credentials(data) {
createCookie("authtoken", data.authtoken); createCookie("authtoken", data.authtoken);
createCookie("uid", data.uid); createCookie("uid", data.uid);
createCookie("login", data.login);
} }
function coop_authenticate(callback) { function coop_authenticate(callback) {
...@@ -499,26 +500,50 @@ Number.prototype.pad = function(size) { ...@@ -499,26 +500,50 @@ Number.prototype.pad = function(size) {
return s; return s;
}; };
// Accordions Date.prototype.getABCDWeekLetter = function(debug) {
var acc = document.getElementsByClassName("accordion"); //high level const week_a_date must be declared (done in base.html template)
var i; try {
const weeks = ['A', 'B', 'C', 'D'],
target_year = this.getFullYear(),
target_month = this.getMonth();
let letter = '';
this.setUTCFullYear(target_year)
this.setUTCMonth(target_month)
this.setUTCHours(0)
const difference = this.getTime() - new Date(week_a_date).getTime();
const w_index = Math.floor(difference / (1000 * 3600 * 24 * 7)) % 4;
letter = weeks[w_index];
if (debug) {
console.log(this)
console.log(letter + ' (' + (w_index) + ')')
}
return letter
} catch(e) {
// not critic
console.log(e);
return null;
}
}
for (i = 0; i < acc.length; i++) { // Accordions
acc[i].addEventListener("click", function() { $(document).on('click', '.accordion', function(){
/* Toggle between adding and removing the "active" class, /* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */ to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */ this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.maxHeight) { /* Toggle between hiding and showing the active panel */
panel.style.maxHeight = null; var panel = this.nextElementSibling;
} else {
panel.style.maxHeight = panel.scrollHeight + "px"; if (panel.style.display === "block") {
} panel.style.display = "none";
}); } else {
} panel.style.display = "block";
}
});
function report_JS_error(e, m) { function report_JS_error(e, m) {
try { try {
...@@ -543,4 +568,34 @@ function isMacUser() { ...@@ -543,4 +568,34 @@ function isMacUser() {
if (isMacUser() && isSafari()) $('.mac-msg').show(); if (isMacUser() && isSafari()) $('.mac-msg').show();
show_enqueued_messages(); show_enqueued_messages();
\ No newline at end of file
(function($){
$.fn.helpFillDate = function (sep='/') {
var elt = this
elt.on('keyup touchend', function(){
var text = elt.val();
var keyChar = text.substr(-1);
var reg = /^\d+$/;
if (reg.test(keyChar) == false || text.length == 11) text = text.slice(0, -1);
if (text.length == 2 || text.length == 5) text += sep
elt.val(text);
});
};
$.fn.helpFillTel = function (sep=' ') {
var elt = this
elt.on('keyup touchend', function(){
var text = elt.val();
var keyChar = text.substr(-1);
var reg = /^\d+$/;
if (reg.test(keyChar) == false || text.length == 15) text = text.slice(0, -1);
if (text.length == 2 || text.length == 5 || text.length == 8 || text.length == 11) text += sep
elt.val(text);
});
};
}(jQuery));
\ No newline at end of file
(function($){
// Suppose input name="zip" and name="city" are in form
// A div with class="geo_suggestions" has to be available too
$.fn.addSearchAutocomplete = function () {
var elt = this,
prev_key_evt_datetime = null,
prev_search_datetime = null,
prev_answer_datetime = null,
suggest_list = document.querySelector('.geo_suggestions');
const get_element_geom = function(el) {
var rect = el.getBoundingClientRect(),
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return { top: rect.top + scrollTop, left: rect.left + scrollLeft, height: el.offsetHeight, width: el.offsetWidth}
}
const clear_suggestion_list = function() {
if (suggest_list) suggest_list.innerHTML = '';
}
const fill_with_suggestion = function(item) {
$('[name="address"]').val(item.getAttribute('data-street'));
$('[name="zip"]').val(item.getAttribute('data-zip'));
$('[name="city"]').val(item.getAttribute('data-city'));
suggest_list.style.display = 'none';
}
const display_results = function(features) {
let geom = get_element_geom(elt.get(0)),
list = document.createElement("ul");
suggest_list.style.position = 'absolute';
suggest_list.style.display = 'block';
suggest_list.style.top = (geom.top + geom.height + 2) + 'px';
suggest_list.style.left = geom.left + 'px';
suggest_list.style.width = geom.width + 'px';
features.forEach(function(f){
let item = document.createElement("li"),
cat = street = '';
if (f.properties.type == 'municipality') {
cat = 'fa fa-building';
} else if (f.properties.type == 'street') {
cat = 'fa fa-road';
} else {
cat = 'fa fa-map-marker';
}
item.classList.add('geo-suggestion')
item.setAttribute('data-zip', f.properties.postcode);
item.setAttribute('data-city', f.properties.city);
if (f.properties.type == 'housenumber' || f.properties.type == "street") {
street = f.properties.name;
}
item.setAttribute('data-street',street);
item.innerHTML = '<i class="' + cat + '"></i>' + f.properties.label
list.appendChild(item)
});
suggest_list.innerHTML = list.outerHTML;
}
//fa fa-building fa fa-road fa fa-map-marker
const search_address = function(q) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
try {
const results = JSON.parse(this.responseText)
prev_answer_datetime = (new Date()).getTime();
if (results.features.length > 0) {
display_results(results.features)
}
} catch (err) {
console.log(err)
}
}
};
xhttp.open("GET", "https://api-adresse.data.gouv.fr/search/?q=" + q);
xhttp.send();
}
const launch_search_if_needed = function(text, now) {
// launch search, excepted if one has been launched few time ago
if (text.length > 0 && (prev_search_datetime == null || (now - prev_search_datetime >= 500))) {
prev_search_datetime = now;
clear_suggestion_list();
search_address(text);
}
}
elt.on('keyup touchend', function(){
suggest_list.style.display = 'none';
var text = elt.val().trim(),
now = (new Date()).getTime();
if (prev_key_evt_datetime == null) {
prev_key_evt_datetime = now;
} else {
if ((now - prev_key_evt_datetime) > 750 && text.length > 0) {
// elapsed time is enough to consider it as a possible end
launch_search_if_needed(text, now);
}
}
prev_key_evt_datetime = now
setTimeout(function(){
// needed to launch search if no more keyup
let now = (new Date()).getTime();
launch_search_if_needed(elt.val().trim(), now);
}, 500);
});
document.addEventListener('click', function (event) {
if (event.target.tagName == "LI" && event.target.matches('.geo-suggestion')) {
fill_with_suggestion(event.target);
} else {
suggest_list.style.display = 'none';
}
});
};
}(jQuery));
...@@ -8,6 +8,8 @@ import tempfile ...@@ -8,6 +8,8 @@ import tempfile
import pymysql.cursors import pymysql.cursors
import datetime import datetime
import re import re
import sys
import traceback
vcats = [] vcats = []
...@@ -55,8 +57,8 @@ class CagetteProduct(models.Model): ...@@ -55,8 +57,8 @@ class CagetteProduct(models.Model):
api = OdooAPI() api = OdooAPI()
cond = [['product_tmpl_id.id', '=', template_id]] cond = [['product_tmpl_id.id', '=', template_id]]
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos', fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price', 'price_weight', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight', 'meal_voucher_ok'] 'weight', 'volume', 'to_weight', 'meal_voucher_ok']
if not getattr(settings, 'SHOW_MEAL_VOUCHER_OK_LINE_IN_PRODUCT_INFO_FOR_LABEL', True): if not getattr(settings, 'SHOW_MEAL_VOUCHER_OK_LINE_IN_PRODUCT_INFO_FOR_LABEL', True):
fields.remove('meal_voucher_ok') fields.remove('meal_voucher_ok')
additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', []) additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
...@@ -78,24 +80,36 @@ class CagetteProduct(models.Model): ...@@ -78,24 +80,36 @@ class CagetteProduct(models.Model):
res = {} res = {}
try: try:
p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id) p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id)
coop_logger.info("Generate label info : %s ", str(p))
if (p and p[0]['product_tmpl_id'][0] == int(templ_id)): if (p and p[0]['product_tmpl_id'][0] == int(templ_id)):
product = p[0] product = p[0]
txt = '' txt = ''
meal_voucher_found = False
if price is not None and len(price) == 0:
price = None
for k, v in product.items(): for k, v in product.items():
if type(v) == list and len(v) > 0 : if type(v) == list and len(v) > 0 :
v = v[-1] v = v[-1]
if k == 'product_tmpl_id': if k == 'product_tmpl_id':
k = 'name' k = 'name'
if k == 'list_price' and len(price) > 0 and float(price) > 0: if k == 'price_weight':
k = 'price_weight_net'
if k == 'weight':
k = 'weight_net'
if k == 'meal_voucher_ok':
meal_voucher_found = True
if k == 'list_price' and price is not None and float(price) > 0:
v = price v = price
if k == 'price_weight_net' and len(v) > 0 and len(price) > 0 and float(price) > 0: if k == 'price_weight_net' and len(str(v)) > 0 and float(v) > 0 and price is not None and float(price) > 0:
v = round(float(price) / float(product['weight_net']), 2) v = round(float(price) / float(product['weight_net']), 2)
if k == 'price_volume' and len(v) > 0 and len(price) > 0 and float(price) > 0: if k == 'price_volume' and len(str(v)) > 0 and float(v) > 0 and price is not None and float(price) > 0:
v = round(float(price) / float(product['volume']), 2) v = round(float(price) / float(product['volume']), 2)
if directory != "/product_labels/" or (directory == "/product_labels/" and k != "meal_voucher_ok"): if directory != "/product_labels/" or (directory == "/product_labels/" and k != "meal_voucher_ok"):
# add parameter to text unless it's for a product label and parameter is meal_voucher_ok # add parameter to text unless it's for a product label and parameter is meal_voucher_ok
txt += k + '=' + str(v).strip() + "\r\n" txt += k + '=' + str(v).strip() + "\r\n"
if directory == '/labels/' and meal_voucher_found is False:
txt += 'meal_voucher_ok=' + "\r\n"
if not (nb is None) and len(nb) > 0: if not (nb is None) and len(nb) > 0:
txt += 'nb_impression=' + str(nb) + "\r\n" txt += 'nb_impression=' + str(nb) + "\r\n"
res['txt'] = txt res['txt'] = txt
...@@ -107,7 +121,8 @@ class CagetteProduct(models.Model): ...@@ -107,7 +121,8 @@ class CagetteProduct(models.Model):
file.close() file.close()
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
coop_logger.error("Generate label : %s %s", templ_id, str(e)) trace=traceback.extract_tb(sys.exc_info()[2])
coop_logger.error("Generate label trace : %s %s", templ_id, str(trace))
return res return res
@staticmethod @staticmethod
...@@ -334,13 +349,18 @@ class CagetteProducts(models.Model): ...@@ -334,13 +349,18 @@ class CagetteProducts(models.Model):
if ('image_medium' in p): if ('image_medium' in p):
p['image'] = p['image_medium'] p['image'] = p['image_medium']
p['image_medium'] = '' p['image_medium'] = ''
else:
p['image'] = ''
if 'image_small' in p:
p['image'] = p['image_small']
p['image_small'] = ''
# if ('image' in p): # if ('image' in p):
# p['image'] = __process_img_data(p, 'image') # p['image'] = __process_img_data(p, 'image')
if type(p['image']) is bool: if type(p['image']) is bool:
p['image'] = '' p['image'] = ''
if p['categ_id'][0] == settings.CATEG_FRUIT: if p['categ_id'][0] in settings.FR_CATEGS:
p['categ'] = 'F' p['categ'] = 'F'
elif p['categ_id'][0] == settings.CATEG_LEGUME: elif p['categ_id'][0] in settings.VEG_CATEGS:
p['categ'] = 'L' p['categ'] = 'L'
elif (p['name'].lower().find(' vrac') > -1) or (p['categ_id'][0] in vcats): elif (p['name'].lower().find(' vrac') > -1) or (p['categ_id'][0] in vcats):
p['categ'] = 'V' p['categ'] = 'V'
...@@ -385,7 +405,7 @@ class CagetteProducts(models.Model): ...@@ -385,7 +405,7 @@ class CagetteProducts(models.Model):
@staticmethod @staticmethod
def get_fl_products(withCandidate=False, fields=[]): def get_fl_products(withCandidate=False, fields=[]):
api = OdooAPI() api = OdooAPI()
flv_cats = [settings.CATEG_FRUIT, settings.CATEG_LEGUME] flv_cats = settings.FR_CATEGS + settings.VEG_CATEGS
cond = [['active', '=', True], cond = [['active', '=', True],
['available_in_pos', '=', True], ['available_in_pos', '=', True],
['categ_id', 'in', flv_cats]] ['categ_id', 'in', flv_cats]]
...@@ -396,7 +416,7 @@ class CagetteProducts(models.Model): ...@@ -396,7 +416,7 @@ class CagetteProducts(models.Model):
@staticmethod @staticmethod
def get_products_for_label_appli(withCandidate=False): def get_products_for_label_appli(withCandidate=False):
fields = ['sale_ok', 'uom_id', 'barcode', fields = ['sale_ok', 'uom_id', 'barcode',
'name', 'display_name', 'list_price', 'categ_id', 'image_medium'] 'name', 'display_name', 'list_price', 'categ_id', 'image_small']
if getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False) is True: if getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False) is True:
fields.append('pos_categ_id') fields.append('pos_categ_id')
to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields) to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields)
...@@ -442,11 +462,12 @@ class CagetteProducts(models.Model): ...@@ -442,11 +462,12 @@ class CagetteProducts(models.Model):
# transcode result to compact format (for bandwith save and browser memory) # transcode result to compact format (for bandwith save and browser memory)
# real size / 4 (for 2750 products) # real size / 4 (for 2750 products)
# following 2 lines is only useful for La Cagette (changing uom_id in Database has cascade effects...) # following 2 lines is only useful for La Cagette (changing uom_id in Database has cascade effects...)
# TODO : Use mapping list in config.py if getattr(settings, 'COMPANY_CODE', '') == 'lacagette':
if p['uom_id'] == 3: if p['uom_id'] == 3:
p['uom_id'] = 21 p['uom_id'] = 21
if p['uom_id'] == 20: if p['uom_id'] == 20:
p['uom_id'] = 1 p['uom_id'] = 1
result['pdts'][p['barcode']] = [ result['pdts'][p['barcode']] = [
p['display_name'], p['display_name'],
p['sale_ok'], p['sale_ok'],
...@@ -469,8 +490,8 @@ class CagetteProducts(models.Model): ...@@ -469,8 +490,8 @@ class CagetteProducts(models.Model):
api = OdooAPI() api = OdooAPI()
try: try:
cond = [['active', '=', True]] cond = [['active', '=', True]]
fields = ['display_name', 'uom_type'] fields = ['name', 'uom_type', 'factor']
res = api.search_read('product.uom', cond, fields) res = api.search_read('uom.uom', cond, fields)
result['list'] = res result['list'] = res
except Exception as e: except Exception as e:
result['error'] = str(e) result['error'] = str(e)
...@@ -597,7 +618,7 @@ class CagetteProducts(models.Model): ...@@ -597,7 +618,7 @@ class CagetteProducts(models.Model):
return res return res
@staticmethod @staticmethod
def get_products_for_order_helper(supplier_ids, pids = [], stats_from = None): def get_products_for_order_helper(supplier_ids, pids = [], stats_from = None, with_fakedata=False):
""" """
supplier_ids: Get products by supplier if one or more supplier id is set. If set, pids is ignored. supplier_ids: Get products by supplier if one or more supplier id is set. If set, pids is ignored.
pids: If set & supplier_ids is None/empty, get products specified in pids. In this case, suppliers info won't be fetched. pids: If set & supplier_ids is None/empty, get products specified in pids. In this case, suppliers info won't be fetched.
...@@ -636,6 +657,7 @@ class CagetteProducts(models.Model): ...@@ -636,6 +657,7 @@ class CagetteProducts(models.Model):
"qty_available", "qty_available",
"incoming_qty", "incoming_qty",
"uom_id", "uom_id",
"uom_po_id",
"purchase_ok", "purchase_ok",
"supplier_taxes_id", "supplier_taxes_id",
"product_variant_ids", "product_variant_ids",
...@@ -643,7 +665,11 @@ class CagetteProducts(models.Model): ...@@ -643,7 +665,11 @@ class CagetteProducts(models.Model):
] ]
c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]] c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]]
products_t = api.search_read('product.template', c, f) products_t = api.search_read('product.template', c, f)
filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
# state is no more in product_template table
# filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
# TODO : Find out how to retrieve this attribute
filtered_products_t = products_t
sales_average_params = { sales_average_params = {
'ids': ptids, 'ids': ptids,
...@@ -676,6 +702,8 @@ class CagetteProducts(models.Model): ...@@ -676,6 +702,8 @@ class CagetteProducts(models.Model):
'product_code': psi_item["product_code"], 'product_code': psi_item["product_code"],
'sequence': psi_item["sequence"] 'sequence': psi_item["sequence"]
}) })
if len(sales) == 0:
filtered_products_t[i]['daily_conso'] = 0
for s in sales: for s in sales:
if s["id"] == fp["id"]: if s["id"] == fp["id"]:
...@@ -683,6 +711,10 @@ class CagetteProducts(models.Model): ...@@ -683,6 +711,10 @@ class CagetteProducts(models.Model):
filtered_products_t[i]['sigma'] = s["sigma"] filtered_products_t[i]['sigma'] = s["sigma"]
filtered_products_t[i]['vpc'] = s["vpc"] filtered_products_t[i]['vpc'] = s["vpc"]
if with_fakedata is True:
for p in filtered_products_t:
p['daily_conso'] = 100
res["products"] = filtered_products_t res["products"] = filtered_products_t
except Exception as e: except Exception as e:
coop_logger.error('get_products_for_order_helper %s (%s)', str(e)) coop_logger.error('get_products_for_order_helper %s (%s)', str(e))
......
...@@ -269,7 +269,7 @@ def labels_appli_csv(request, params): ...@@ -269,7 +269,7 @@ def labels_appli_csv(request, params):
for c in file_copies: for c in file_copies:
copyfile(os_file, c) copyfile(os_file, c)
res['fichiers_generes'] = len(file_copies) + 1
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
return JsonResponse({'res': res}) return JsonResponse({'res': res})
......
from django.contrib import admin from django.contrib import admin
from outils.common_imports import *
from outils.for_view_imports import *
from django.views.generic import View
from django.http import HttpResponse
from django.http import JsonResponse
# Register your models here. # Register your models here.
import os
from datetime import date
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.styles import Alignment
from reception.models import CagetteReception
from outils.common import OdooAPI
from members.models import CagetteUser
from products.models import CagetteProduct
def index(request):
"""Accueil admin"""
if 'reception' in settings.COUCHDB['dbs']:
context = {
'title': 'Admin Reception',
}
template = loader.get_template('reception/admin.html')
return HttpResponse(template.render(context, request))
else:
return HttpResponse("Need to configure reception couchdb db in settings_secret.py")
def get_backups(request):
orders = []
po_ids = []
for file in os.listdir('data/receptions_backup'):
if '.json' in file:
with open('data/receptions_backup/' + file, 'r') as json_file:
bup = json.load(json_file)
for oid, o in bup['orders'].items():
if 'br_valid' in file:
[bup['id'], timestamp] = file.split('_br_valid_')
else:
[bup['id'], timestamp] = file.split('_qty_valid_')
bup['date'] = datetime.datetime.fromtimestamp(int(timestamp.replace('.json', ''))/1000).strftime("%d/%m/%Y")
if len(o['po']) > 0:
bup['supplier'] = o['po'][0]['partner_id'][1]
bup['id'] = o['po'][0]['id_po']
else:
po_ids.append(int(bup['id']))
orders.append(bup)
if len(po_ids) > 0:
api = OdooAPI()
cond = [['id', 'in', po_ids]]
fields = ['partner_id']
res = api.search_read('purchase.order', cond, fields)
for r in res:
for order in orders:
if 'id' in order:
if str(order['id']) == str(r['id']):
order['supplier'] = r['partner_id'][1]
continue
return JsonResponse({'data': orders, 'po_ids': po_ids}, safe=False)
...@@ -35,14 +35,18 @@ class CagetteReception(models.Model): ...@@ -35,14 +35,18 @@ class CagetteReception(models.Model):
pids.append(int(r['purchase_id'][0])) pids.append(int(r['purchase_id'][0]))
if len(pids): if len(pids):
f=["id","name","date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "x_reception_status", 'create_uid'] f=["id","name","date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "reception_status", 'create_uid']
# Only get orders that need to be treated in Reception # Only get orders that need to be treated in Reception
c = [['id', 'in', pids], ["x_reception_status", "in", [False, 'qty_valid', 'valid_pending', 'br_valid']]] c = [
['id', 'in', pids],
["reception_status", "in", [False, 'qty_valid', 'valid_pending', 'br_valid']],
["state", "not in", ["cancel", "done"]]
]
orders = api.search_read('purchase.order', c, f) orders = api.search_read('purchase.order', c, f)
except Exception as e: except Exception as e:
print(str(e)) coop_logger.error("CagetteReception.get_orders : %s", str(e))
return orders return orders
def get_order_unprocessable_products(id_po): def get_order_unprocessable_products(id_po):
...@@ -68,7 +72,7 @@ class CagetteReception(models.Model): ...@@ -68,7 +72,7 @@ class CagetteReception(models.Model):
except Exception as e: except Exception as e:
print(str(e)) coop_logger.error("CagetteReception.get_mail_create_po : %s", str(e))
return res return res
...@@ -77,7 +81,8 @@ class CagetteReception(models.Model): ...@@ -77,7 +81,8 @@ class CagetteReception(models.Model):
lines_data = Order(self.id).get_lines() lines_data = Order(self.id).get_lines()
bc_pattern = re.compile('^0493|0499') # TODO : Adjust for other pattern (such as Supercoop) bc_pattern = re.compile('^0493|0499') # TODO : Adjust for other pattern (such as Supercoop)
for l in lines_data['lines']: for l in lines_data['lines']:
if not (bc_pattern.match(str(l['barcode'])) is None): if 'barcode' in l and not (bc_pattern.match(str(l['barcode'])) is None):
# "'barcode' in l" has been added after actual case where it was missing !
answer = True answer = True
# print ('answer=' + str(answer)) # print ('answer=' + str(answer))
return answer return answer
...@@ -111,7 +116,7 @@ class CagetteReception(models.Model): ...@@ -111,7 +116,7 @@ class CagetteReception(models.Model):
def update_order_status(self, id_po, updateType): def update_order_status(self, id_po, updateType):
"""Update purchase.order with new reception status """ """Update purchase.order with new reception status """
f = {'x_reception_status':updateType} f = {'reception_status':updateType}
res = self.o_api.update('purchase.order', int(id_po), f) res = self.o_api.update('purchase.order', int(id_po), f)
return res return res
...@@ -173,31 +178,6 @@ class CagetteReception(models.Model): ...@@ -173,31 +178,6 @@ class CagetteReception(models.Model):
return processed_lines return processed_lines
def update_products_price(self):
processed = 0
errors = []
order_lines_data = CagetteReception.get_order_lines_by_po(self.id)
order_lines = order_lines_data['lines']
if order_lines and len(order_lines) > 0:
# Exceptions are due to the fact API returns None whereas the action is really done !...
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
processed = 0
for line in order_lines:
try:
self.o_api.execute('purchase.order.line','update_po_price_to_vendor_price',[int(line['id'])])
processed += 1
except Exception as e:
if not (marshal_none_error in str(e)):
errors.append(str(e))
else:
processed += 1
if processed == len(order_lines):
success = True
else:
success = False
return {'errors': errors, 'processed': processed, 'success': success, 'lines': order_lines}
def print_shelf_labels_for_updated_prices(self, lines): def print_shelf_labels_for_updated_prices(self, lines):
import requests import requests
# don't print barcode which begin with these codes # don't print barcode which begin with these codes
...@@ -222,53 +202,56 @@ class CagetteReception(models.Model): ...@@ -222,53 +202,56 @@ class CagetteReception(models.Model):
if len(to_reset) > 0: if len(to_reset) > 0:
self.o_api.update('product.product', to_reset, {'to_print': 0}) self.o_api.update('product.product', to_reset, {'to_print': 0})
def update_products_price_v12(self):
result = {'success': False}
try:
result['success'] = self.o_api.execute('purchase.order', 'update_po_price_to_vendor_price', [self.id])
except Exception as e:
coop_logger.error("update_products_price_v12 : %s", str(e))
result['error'] = str(e)
return result
def finalyze_picking(self): def stock_picking_update(self, order_line):
"""stock_picking is created to make,
stock immediate transfer is done,
products are updated with new vendor prices"""
result = None result = None
try:
res = self.o_api.search_read('stock.move', [['purchase_line_id', '=', order_line['id']]], ['id'])
if res:
fields = {
'package_qty': float(order_line['package_qty']),
'product_qty_package': float(order_line['product_qty_package']),
'product_uom_qty': float(order_line['package_qty']) * float(order_line['product_qty_package']),
'price_unit': float(order_line['price_unit'])
}
ids = []
for r in res:
ids.append(r['id'])
self.o_api.update('stock.move', ids, fields)
result = True
if self.o_api == None: except Exception as e:
return 'error : cant reach odoo' coop_logger.error("Stock picking update : %s ", str(e))
result = False
res = self.o_api.execute('purchase.order', 'action_view_picking', [self.id]) return result
new_x_reception_status = ''
if res:
sp = self.o_api.search_read('stock.picking',[['id','=', int(res['res_id'])]],['pack_operation_ids','state'],1)
if sp:
if sp[0]['state'] == 'assigned':
pack_operation_ids = sp[0]['pack_operation_ids']
cpt = self.make_immediate_transfer(pack_operation_ids)
if cpt == len(pack_operation_ids):
try:
self.o_api.execute('stock.picking','do_transfer', [int(sp[0]['id'])])
result = 'processed'
except:
result = 'error: transfer'
new_x_reception_status = 'error_transfer'
else: def finalyze_picking_v12(self):
result = 'error: pack operations' result = None
new_x_reception_status = 'error_pack_op' try:
else: res = self.o_api.execute('purchase.order', 'stock_immediate_transfer', [self.id])
result = 'already done' done = 0
else: for r in res:
result = 'error: cant access stock picking' if r in ['done', 'cancel']:
new_x_reception_status = 'error_picking' done += 1
if result == 'processed': if done == len(res):
price_update = self.update_products_price() result = 'processed'
if price_update['success'] is False: price_update = self.update_products_price_v12()
result = 'error: price update' if price_update and price_update['success'] is False:
new_x_reception_status += '/error_uprice' result = 'error: price update'
if new_x_reception_status == '': else:
new_x_reception_status = 'done' result = False
if getattr(settings, 'RECEPTION_SHELF_LABEL_PRINT', False) is True:
self.print_shelf_labels_for_updated_prices(price_update['lines']) except Exception as e:
coop_logger.error("Finalyze picking : %s ", str(e))
if result != 'already done':
self.o_api.update('purchase.order', [self.id], {'x_reception_status': new_x_reception_status})
return result return result
...@@ -303,7 +286,7 @@ class CagetteReception(models.Model): ...@@ -303,7 +286,7 @@ class CagetteReception(models.Model):
if len(to_process) > 0: if len(to_process) > 0:
for p in to_process: for p in to_process:
m = CagetteReception(int(p['id'])) m = CagetteReception(int(p['id']))
fp = m.finalyze_picking() fp = m.finalyze_picking_v12()
if fp == 'processed': if fp == 'processed':
print_label = m.implies_scale_file_generation() print_label = m.implies_scale_file_generation()
if fp == 'processed' or fp == 'already done': if fp == 'processed' or fp == 'already done':
......
var orders = [],
table_orders = null,
op_details = null;
function replay() {
}
/**
* Display the main orders table
*/
function display_orders_table() {
if (table_orders) {
table_orders.clear().destroy();
$('#orders').empty();
}
table_orders = $('#orders').DataTable({
data: orders,
columns:[
{
data:"id",
title:"Sélectionner",
className:"dt-body-center",
render: function (data) {
return '<input type="checkbox" id="select_bc_'+data+'" value="'+data+'">';
},
width: "4%",
orderable: false
},
{data:"date", "title":"Date traitement", "width": "8%", "className":"dt-body-center"},
{
data: "id",
title: "Id. commande",
},
{
data:"supplier",
title:"Fournisseur",
render: function (data, type, full) {
// Add tooltip with PO over partner name
return '<div class="tooltip">' + data + ' <span class="tooltiptext">' + full.id + '</span> </div>';
}
},
{
title: "Etape",
data:"update_type",
className:"dt-body-center",
orderable: false,
width: "20%"
},
],
dom: 'rtip',
order: [
[
1,
"asc"
]
],
iDisplayLength: 25,
language: {url : '/static/js/datatables/french.json'}
});
}
$('#orders').on('click', 'tbody td', function () {
var row_data = table_orders.row($(this)).data();
var data_to_show = [];
if (op_details) {
op_details.clear().destroy();
$('#operation_details').empty();
}
var modal_content = $('#show_detail').clone()
modal_content.find('table').attr('id', 'operation_details')
modal_content.find('h3').html(row_data.supplier)
modal_content.find('p').html("Commande id. " + row_data.id + ", étape " + row_data.update_type)
for (oid in row_data.orders) {
row_data.orders[oid]['po'].forEach((r) => {data_to_show.push(r)})
}
var table_columns = [{
data: "barcode",
title: "Code-barre",
},
{
title: "Article",
render: function (data, type, full) {
return full.product_id[1];
}
},
{
title: "UdM",
render: function (data, type, full) {
return full.product_uom[1];
}
}
];
if (row_data.update_type == 'qty_valid') {
table_columns.push({data: "old_qty", title: "Qté prévue"});
table_columns.push({data: "product_qty", title: "Qté reçue"});
} else {
table_columns.push({data: "old_price_unit", title: "Prix prévu"});
table_columns.push({data: "price_unit", title: "Prix constaté"});
}
console.log(data_to_show)
openModal(modal_content.html(), replay, 'Rejouer', false);
op_details = $('#operation_details').DataTable({
data: data_to_show,
columns: table_columns,
dom: 'rtip',
order: [
[
1,
"asc"
]
],
iDisplayLength: 25,
language: {url : '/static/js/datatables/french.json'}
});
})
$(document).ready(function() {
if (coop_is_connected()) {
openModal();
// Set date format for DataTable so date ordering can work
$.fn.dataTable.moment('D/M/Y');
// Get orders
$.ajax({
type: 'GET',
url: "/reception/get_backups",
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
orders = data.data;
display_orders_table();
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des commandes", ctx: 'get_list_orders'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
alert('Erreur lors de la récupération des commandes, rechargez la page plus tard.');
}
});
}
});
""".""" """."""
from django.conf.urls import url from django.conf.urls import url
from . import views from . import views
from . import admin
urlpatterns = [ urlpatterns = [
url(r'^$', views.home), url(r'^$', views.home),
...@@ -17,5 +18,9 @@ urlpatterns = [ ...@@ -17,5 +18,9 @@ urlpatterns = [
url(r'^reception_pricesValidated', views.reception_pricesValidated), url(r'^reception_pricesValidated', views.reception_pricesValidated),
# url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status), # url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status),
url(r'^po_process_picking$', views.po_process_picking), url(r'^po_process_picking$', views.po_process_picking),
url(r'^send_mail_no_barcode', views.send_mail_no_barcode) url(r'^send_mail_no_barcode', views.send_mail_no_barcode),
url(r'^check/prices$', views.check_prices),
# Admin
url(r'^admin$', admin.index),
url(r'^get_backups$', admin.get_backups)
] ]
...@@ -10,6 +10,9 @@ from datetime import date ...@@ -10,6 +10,9 @@ from datetime import date
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl import load_workbook from openpyxl import load_workbook
from openpyxl.styles import Alignment from openpyxl.styles import Alignment
from openpyxl.writer.excel import save_virtual_workbook
import dateutil.parser
from reception.models import CagetteReception from reception.models import CagetteReception
from outils.common import OdooAPI from outils.common import OdooAPI
...@@ -69,7 +72,7 @@ def get_list_orders(request): ...@@ -69,7 +72,7 @@ def get_list_orders(request):
"date_planned" : order["date_planned"], "date_planned" : order["date_planned"],
"amount_untaxed" : round(order["amount_untaxed"],2), "amount_untaxed" : round(order["amount_untaxed"],2),
"amount_total" : round(order["amount_total"],2), "amount_total" : round(order["amount_total"],2),
"reception_status" : str(order["x_reception_status"]) "reception_status" : str(order["reception_status"])
} }
if get_order_lines is True: if get_order_lines is True:
...@@ -199,7 +202,7 @@ def update_orders(request): ...@@ -199,7 +202,7 @@ def update_orders(request):
float(order_line['package_qty']), float(order_line['package_qty']),
float(order_line['product_qty_package']), float(order_line['product_qty_package']),
float(order_line['price_unit'])) float(order_line['price_unit']))
if not (update is True): if update is not True:
# indicative_package may have been changed since data have been loaded in browser, retry # indicative_package may have been changed since data have been loaded in browser, retry
m.remove_package_restriction(order_line) m.remove_package_restriction(order_line)
update = m.update_line(int(order_line['id']), update = m.update_line(int(order_line['id']),
...@@ -207,9 +210,12 @@ def update_orders(request): ...@@ -207,9 +210,12 @@ def update_orders(request):
float(order_line['package_qty']), float(order_line['package_qty']),
float(order_line['product_qty_package']), float(order_line['product_qty_package']),
float(order_line['price_unit'])) float(order_line['price_unit']))
if not (update is True): if update is not True:
errors.append(order_line['id']) errors.append(order_line['id'])
if update is True:
spu = m.stock_picking_update(order_line)
# If update succeded, and supplier shortage set, try to register the supplier shortage # If update succeded, and supplier shortage set, try to register the supplier shortage
if update is True and 'supplier_shortage' in order_line: if update is True and 'supplier_shortage' in order_line:
try: try:
...@@ -661,3 +667,64 @@ def send_mail_no_barcode(request): ...@@ -661,3 +667,64 @@ def send_mail_no_barcode(request):
return JsonResponse("ok", safe=False) return JsonResponse("ok", safe=False)
def check_prices(request):
"""Check if stock transfers and price for br_valid purchase orders have been completed."""
# Grouped orders are not processed
data = {}
found = []
try:
oids = []
api = OdooAPI()
min_date = datetime.datetime.now() - datetime.timedelta(weeks=2)
cond = [['reception_status', '=', 'br_valid'], ['write_date', '>=', min_date.isoformat()]]
fields = ['name', 'parent_id', 'order_line']
orders = api.search_read('purchase.order', cond, fields)
if len(orders) > 0:
id_fn_pattern = re.compile(r'^([0-9]+)_br_valid_([0-9]+).json')
pdt_oids = []
for o in orders:
oids.append(o['id'])
for file in os.listdir('data/receptions_backup'):
id_search = id_fn_pattern.search(file)
if id_search:
current_id = id_search.group(1)
if int(current_id) in oids:
found.append(str(current_id))
with open('data/receptions_backup/' + file, 'r') as json_file:
bup = json.load(json_file)
for oid, o in bup['orders'].items():
if len(o['po']) > 0:
products = []
for pol in o['po']:
products.append({'id': pol['product_id'],
'partner_id': pol['partner_id'],
'price': pol['price_unit'],
'update': datetime.datetime.fromtimestamp(int(id_search.group(2))/1000).strftime("%d/%m/%Y")})
if int(pol['product_id'][0]) not in pdt_oids:
pdt_oids.append(pol['product_id'][0])
data[oid] = {'products': products}
if len(pdt_oids) > 0:
# WARNING !!! Supplier discount is not considered !!!
# (need to get product_tmpl_id and request product.supplierinfo ! (or make Odoo special api method))
cond = [['id', 'in', pdt_oids]]
fields = ['base_price']
p_res = api.search_read('product.product', cond, fields)
for p in p_res:
for oid, d in data.items():
i = 0
for pdt in d['products']:
if pdt['id'][0] == p['id']:
if float(p['price']) == float(pdt['base_price']):
data[oid]['products'][i]['price_ok'] = True
else:
data[oid]['products'][i]['price_ok'] = False
data[oid]['products'][i]['found_base_price'] = p['base_price']
i += 1
except Exception as e1:
coop_logger.error("check PO %s", str(e1))
return JsonResponse({'data': data})
...@@ -50,6 +50,18 @@ def delete(request): ...@@ -50,6 +50,18 @@ def delete(request):
result['error'] = "Forbidden" result['error'] = "Forbidden"
return JsonResponse({'res': result}) return JsonResponse({'res': result})
def print_labels(request, shelf_id):
result = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
try:
result = Shelf(shelf_id).print_labels()
except Exception as e:
result['error'] = str(e)
else:
result['error'] = "Forbidden"
return JsonResponse({'res': result})
def add_products(request): def add_products(request):
import json import json
result = {} result = {}
......
...@@ -2,7 +2,7 @@ from django.db import models ...@@ -2,7 +2,7 @@ from django.db import models
from outils.common_imports import * from outils.common_imports import *
from outils.common import OdooAPI from outils.common import OdooAPI
from products.models import CagetteProducts from products.models import CagetteProducts, CagetteProduct
from inventory.models import CagetteInventory from inventory.models import CagetteInventory
import os import os
...@@ -38,7 +38,7 @@ class Shelf(models.Model): ...@@ -38,7 +38,7 @@ class Shelf(models.Model):
res['error'] = "Le rayon n'a pas pu être trouvé (" + str(e) + ")" res['error'] = "Le rayon n'a pas pu être trouvé (" + str(e) + ")"
return res return res
def get_products(self): def get_products(self, additional_fields=[]):
res = {} res = {}
try: try:
c = [['shelf_id', '=', self.id]] c = [['shelf_id', '=', self.id]]
...@@ -53,6 +53,7 @@ class Shelf(models.Model): ...@@ -53,6 +53,7 @@ class Shelf(models.Model):
'active' 'active'
] ]
f += additional_fields
pdts = self.o_api.search_read('product.product', c, f) pdts = self.o_api.search_read('product.product', c, f)
for p in pdts: for p in pdts:
for k, v in p.items(): for k, v in p.items():
...@@ -116,7 +117,11 @@ class Shelf(models.Model): ...@@ -116,7 +117,11 @@ class Shelf(models.Model):
if 'shelf_losses' in params: if 'shelf_losses' in params:
f['last_inv_losses_percentage'] = params['shelf_losses'] f['last_inv_losses_percentage'] = params['shelf_losses']
res['update'] = self.o_api.update('product.shelfs', self.id, f) try:
res['update'] = self.o_api.update('product.shelfs', self.id, f)
except Exception as e:
coop_logger.error("Error while updating products shelf %s", stre(e))
res['error'] = "Error while updating products shelf (" + str(e) + ")"
return res return res
...@@ -148,6 +153,16 @@ class Shelf(models.Model): ...@@ -148,6 +153,16 @@ class Shelf(models.Model):
res['error'] = "Le rayon n'a pas pu être détruit" res['error'] = "Le rayon n'a pas pu être détruit"
return res return res
def print_labels(self):
res = {}
try:
for p in self.get_products(['product_tmpl_id'])['data']:
CagetteProduct().generate_label_for_printing(p['product_tmpl_id'][0], '/labels/')
res['success'] = "Le rayon a correctement été imprimé"
except Exception as e:
coop_logger.error("Rayon, print labels : %s", str(e))
res['error'] = "Le rayon n'a pas pu être imprimé"
return res
def _get_pdts_from_barcodes(self, barcodes): def _get_pdts_from_barcodes(self, barcodes):
c = [['barcode', 'in', barcodes]] c = [['barcode', 'in', barcodes]]
...@@ -541,16 +556,23 @@ class Shelf(models.Model): ...@@ -541,16 +556,23 @@ class Shelf(models.Model):
class Shelfs(models.Model): class Shelfs(models.Model):
def get_all(precision='full'): def get_all(precision='full'):
res = [] shelfs = []
try: try:
api = OdooAPI() api = OdooAPI()
if precision == 'simple': if precision == 'simple':
res = api.search_read('product.shelfs', [], ['name', 'sort_order'], order='sort_order asc') res = api.search_read('product.shelfs', [], ['name', 'sort_order'], order='sort_order asc')
else: else:
res = api.execute('product.shelfs', 'get', {}) res = api.execute('product.shelfs', 'get', {})
for r in res:
if r['ongoing_inv_start_datetime'] == "1-01-01 00:00:00":
r['ongoing_inv_start_datetime'] = ""
if r['date_last_product_added'] == "1-01-01":
r['date_last_product_added'] = ""
coop_logger.info(str(r))
shelfs.append(r)
except Exception as e: except Exception as e:
coop_logger.error("Rayons, get_all : %s", str(e)) coop_logger.error("Rayons, get_all : %s", str(e))
return res return shelfs
@staticmethod @staticmethod
def get_shelfs_sortorder(shelf_ids=[]): def get_shelfs_sortorder(shelf_ids=[]):
......
...@@ -24,7 +24,7 @@ function init_datatable() { ...@@ -24,7 +24,7 @@ function init_datatable() {
if (type == "sort" || type == 'type') if (type == "sort" || type == 'type')
return data; return data;
if (data == '0001-01-01 00:00:00') if (data == '0001-01-01 00:00:00' || data == '')
return ""; return "";
else { else {
var date = new Date(data); var date = new Date(data);
...@@ -49,7 +49,7 @@ function init_datatable() { ...@@ -49,7 +49,7 @@ function init_datatable() {
if (type == "sort" || type == 'type') if (type == "sort" || type == 'type')
return data; return data;
if (data == '0001-01-01') if (data == '0001-01-01' || data == '')
return ""; return "";
else { else {
var date = new Date(data); var date = new Date(data);
......
...@@ -26,4 +26,5 @@ urlpatterns = [ ...@@ -26,4 +26,5 @@ urlpatterns = [
url(r'^admin/update$', admin.update), url(r'^admin/update$', admin.update),
url(r'^admin/delete$', admin.delete), url(r'^admin/delete$', admin.delete),
url(r'^admin/add_products$', admin.add_products), url(r'^admin/add_products$', admin.add_products),
url(r'^print_labels/(?P<shelf_id>\d+)$', admin.print_labels),
] ]
...@@ -233,7 +233,7 @@ def do_shelf_inventory(request): ...@@ -233,7 +233,7 @@ def do_shelf_inventory(request):
except Exception as e: except Exception as e:
# Don't validate if error anywhere in inventory process # Don't validate if error anywhere in inventory process
res['error'] = type(e).__name__ res['error'] = str(e)
coop_logger.error("Shelf inv. : %s", str(e)) coop_logger.error("Shelf inv. : %s", str(e))
except Exception as err_json: except Exception as err_json:
res['error'] = "Unable to parse received JSON" res['error'] = "Unable to parse received JSON"
......
...@@ -96,7 +96,7 @@ def _is_middled_filled_considered(reserved, max): ...@@ -96,7 +96,7 @@ def _is_middled_filled_considered(reserved, max):
def get_list_shift_calendar(request, partner_id): def get_list_shift_calendar(request, partner_id):
cs = CagetteShift() cs = CagetteShift()
registerPartner = cs.get_shift_partner(partner_id) [registerPartner, is_ftop] = cs.get_shift_partner(partner_id)
use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False) use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False)
remove_15_minutes_at_shift_end = getattr(settings, 'REMOVE_15_MINUTES_AT_SHIFT_END', True) remove_15_minutes_at_shift_end = getattr(settings, 'REMOVE_15_MINUTES_AT_SHIFT_END', True)
...@@ -109,7 +109,7 @@ def get_list_shift_calendar(request, partner_id): ...@@ -109,7 +109,7 @@ def get_list_shift_calendar(request, partner_id):
start = request.GET.get('start') start = request.GET.get('start')
end = request.GET.get('end') end = request.GET.get('end')
listService = cs.get_shift_calendar(partner_id, start, end) listService = cs.get_shift_calendar(is_ftop, start, end)
events = [] events = []
for value in listService: for value in listService:
...@@ -134,7 +134,7 @@ def get_list_shift_calendar(request, partner_id): ...@@ -134,7 +134,7 @@ def get_list_shift_calendar(request, partner_id):
event["start"] = dateIsoUTC(value['date_begin_tz']) event["start"] = dateIsoUTC(value['date_begin_tz'])
datetime_object = datetime.datetime.strptime(value['date_end_tz'], "%Y-%m-%d %H:%M:%S") datetime_object = datetime.datetime.strptime(value['date_end_tz'], "%Y-%m-%d %H:%M:%S") - datetime.timedelta(minutes=15)
if remove_15_minutes_at_shift_end is True: if remove_15_minutes_at_shift_end is True:
datetime_object -= datetime.timedelta(minutes=15) datetime_object -= datetime.timedelta(minutes=15)
event["end"] = dateIsoUTC(datetime_object.strftime("%Y-%m-%d %H:%M:%S")) event["end"] = dateIsoUTC(datetime_object.strftime("%Y-%m-%d %H:%M:%S"))
...@@ -178,7 +178,7 @@ def get_list_shift_calendar(request, partner_id): ...@@ -178,7 +178,7 @@ def get_list_shift_calendar(request, partner_id):
def get_list_shift_partner(request, partner_id): def get_list_shift_partner(request, partner_id):
cs = CagetteShift() cs = CagetteShift()
shiftData = cs.get_shift_partner(partner_id) [shiftData, is_ftop] = cs.get_shift_partner(partner_id)
for value in shiftData: for value in shiftData:
value['date_begin'] = value['date_begin'] + "Z" value['date_begin'] = value['date_begin'] + "Z"
...@@ -281,15 +281,28 @@ def add_shift(request): ...@@ -281,15 +281,28 @@ def add_shift(request):
cs = CagetteShift() cs = CagetteShift()
if 'idNewShift' in request.POST and 'idPartner' in request.POST: if 'idNewShift' in request.POST and 'idPartner' in request.POST:
partner_id = int(request.POST['idPartner'])
id_shift = int(request.POST['idNewShift'])
data = { data = {
"idPartner": int(request.POST['idPartner']), "idPartner": partner_id,
"idShift":int(request.POST['idNewShift']), "idShift": id_shift,
"shift_type":request.POST['shift_type'], "shift_type": request.POST['shift_type'],
"is_makeup": False "is_makeup": False
} }
if 'is_makeup' in request.POST and request.POST['is_makeup'] == "1": if 'is_makeup' in request.POST and request.POST['is_makeup'] == "1":
data['is_makeup'] = True data['is_makeup'] = True
if request.POST['shift_type'] == "ftop":
if cs.is_matching_ftop_rules(partner_id, id_shift) is True:
# Need to find out if a makeup has to be choosen
makeups_to_do = cs.get_member_makeups_to_do(partner_id)
if makeups_to_do != 0:
data['is_makeup'] = True
else:
response = {'msg': "FTOP rules not respected"}
return JsonResponse(response, status=422)
#Insertion du nouveau shift #Insertion du nouveau shift
st_r_id = False st_r_id = False
...@@ -304,17 +317,15 @@ def add_shift(request): ...@@ -304,17 +317,15 @@ def add_shift(request):
response = {'result': False} response = {'result': False}
# decrement makeups_to_do # decrement makeups_to_do
res_decrement = False response["decrement_makeups"] = False
try: if data['is_makeup'] is True:
res_decrement = cs.decrement_makeups_to_do(int(request.POST['idPartner'])) try:
except Exception as e: res_decrement = cs.decrement_makeups_to_do(int(request.POST['idPartner']))
coop_logger.error("Decrement makeups to do : %s, %s", str(e), str(data)) except Exception as e:
coop_logger.error("Decrement makeups to do : %s, %s", str(e), str(data))
if res_decrement:
response["decrement_makeups"] = res_decrement
else:
response["decrement_makeups"] = False
if res_decrement:
response["decrement_makeups"] = res_decrement
else: else:
response = {'result': False} response = {'result': False}
return JsonResponse(response) return JsonResponse(response)
...@@ -450,3 +461,7 @@ def get_list(request): ...@@ -450,3 +461,7 @@ def get_list(request):
liste.append(val[fields[0]]) liste.append(val[fields[0]])
return JsonResponse(liste, safe=False) return JsonResponse(liste, safe=False)
def get_current_cycle_week(request):
cs = CagetteShift()
return JsonResponse(cs.get_current_cycle_week_data(), safe=False)
...@@ -64,11 +64,11 @@ class CagetteStock(models.Model): ...@@ -64,11 +64,11 @@ class CagetteStock(models.Model):
fields = { fields = {
'company_id': 1, 'company_id': 1,
'name': picking_name, 'name': picking_name,
'picking_type_id' : picking_type, # mouvement type 'picking_type_id': picking_type, # mouvement type
'location_id': settings.STOCK_LOC_ID, # movement origin 'location_id': settings.STOCK_LOC_ID, # movement origin
'location_dest_id': destination, # movement dest 'location_dest_id': destination, # movement dest
'move_lines': [], 'move_lines': [],
'pack_operation_ids': [], # 'pack_operation_ids': [],
'operator_id': stock_movement_data['operator']['id'] 'operator_id': stock_movement_data['operator']['id']
} }
...@@ -95,35 +95,31 @@ class CagetteStock(models.Model): ...@@ -95,35 +95,31 @@ class CagetteStock(models.Model):
} }
]) ])
# Add stock.pack.operation to stock.picking # Add stock.pack.operation to stock.picking (field no more exists)
fields['pack_operation_ids'].append([ # fields['pack_operation_ids'].append([
0, # 0,
False, # False,
{ # {
"product_qty": str(qty), # "product_qty": str(qty),
"qty_done": str(qty), # "qty_done": str(qty),
"location_id": settings.STOCK_LOC_ID, # "location_id": settings.STOCK_LOC_ID,
"location_dest_id": destination, # "location_dest_id": destination,
"product_id": p['id'], # "product_id": p['id'],
"name": p['name'], # "name": p['name'],
"product_uom_id": p['uom_id'], # "product_uom_id": p['uom_id'],
"state": 'done', # "state": 'done',
"fresh_record": False # "fresh_record": False
} # }
]) # ])
# Exception rises when odoo method returns nothing # Exception rises when odoo method returns nothing
marshal_none_error = 'cannot marshal None unless allow_none is enabled' marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try: try:
picking = api.create('stock.picking', fields) picking = api.create('stock.picking', fields)
if not (picking is None): if not (picking is None):
# Set stock.picking done
api.execute('stock.picking', 'action_done', [picking])
# Generate accounting writings for this picking # Generate accounting writings for this picking
api.execute('stock.picking', 'generate_expense_entry', picking) api.execute('stock.picking', 'stock_immediate_transfer', [picking])
except Exception as e: except Exception as e:
if not (marshal_none_error in str(e)): if not (marshal_none_error in str(e)):
......
...@@ -12,7 +12,9 @@ from django.shortcuts import render ...@@ -12,7 +12,9 @@ from django.shortcuts import render
def movements_page(request): def movements_page(request):
"""Page de selection de produits pour créer des mouvements de stock""" """Page de selection de produits pour créer des mouvements de stock"""
context = { context = {
'title': 'Mouvements de stock' 'title': 'Mouvements de stock',
'autoconso': getattr(settings, 'AUTOCONSO_LOC_ID', None),
'formeals': getattr(settings, 'MEALS_LOC_ID', None)
} }
template = loader.get_template('stock/stock_movements.html') template = loader.get_template('stock/stock_movements.html')
...@@ -21,7 +23,9 @@ def movements_page(request): ...@@ -21,7 +23,9 @@ def movements_page(request):
def movements_view(request): def movements_view(request):
"""Page d'extraction des mouvements de stocks""" """Page d'extraction des mouvements de stocks"""
context = { context = {
'title': 'Mouvements de stock' 'title': 'Mouvements de stock',
'autoconso': getattr(settings, 'AUTOCONSO_LOC_ID', None),
'formeals': getattr(settings, 'MEALS_LOC_ID', None)
} }
template = loader.get_template('stock/stock_movements_view.html') template = loader.get_template('stock/stock_movements_view.html')
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
history.pushState(null, null, location.pathname) history.pushState(null, null, location.pathname)
} }
const False = false // prevent error if api return python False as a value const False = false // prevent error if api return python False as a value
const week_a_date = "{{week_a_date}}";
</script> </script>
<script src="{% static "js/fp.js" %}"></script> <script src="{% static "js/fp.js" %}"></script>
<script src="{% static "js/jquery-3.3.1.min.js" %}"></script> <script src="{% static "js/jquery-3.3.1.min.js" %}"></script>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% block additionnal_scripts %} {% 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 '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/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% block additionnal_scripts %} {% 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 '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/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% block additionnal_scripts %} {% 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 '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/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -66,6 +66,8 @@ ...@@ -66,6 +66,8 @@
{% include "members/job_input.html" %} {% include "members/job_input.html" %}
</p> </p>
{% endif %} {% endif %}
{% if ask_for_capital_payment %}
<p> <p>
<input type="number" step="10" min="10" placeholder="Montant souscription" name="subs_cap" id="subs_cap" required/> <input type="number" step="10" min="10" placeholder="Montant souscription" name="subs_cap" id="subs_cap" required/>
<select name="payment_meaning" id="payment_meaning" autocomplete="off" required > <select name="payment_meaning" id="payment_meaning" autocomplete="off" required >
...@@ -76,8 +78,8 @@ ...@@ -76,8 +78,8 @@
{% endfor %} {% endfor %}
</select> </select>
<input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/> <input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/>
</p> </p>
{% endif %}
{% if input_barcode %} {% if input_barcode %}
<p> <p>
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
</style> </style>
{% endblock %} {% endblock %}
{% block additionnal_scripts %} {% block additionnal_scripts %}
<script src="{% static "js/geoloc.js" %}"?v=></script>
<script type="text/javascript"> <script type="text/javascript">
var type = 2; var type = 2;
var context = 'validation'; var context = 'validation';
...@@ -93,6 +94,7 @@ ...@@ -93,6 +94,7 @@
<div style="display:none" id="admin_elts"> <div style="display:none" id="admin_elts">
</div> </div>
<div class="geo_suggestions" style="display:none" ></div>
<script src="{% static "js/pouchdb.min.js" %}"></script> <script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
var couchdb_dbname = '{{db}}'; var couchdb_dbname = '{{db}}';
...@@ -106,6 +108,7 @@ ...@@ -106,6 +108,7 @@
var mag_place_string = '{{mag_place_string}}'; var mag_place_string = '{{mag_place_string}}';
var office_place_string = '{{office_place_string}}' var office_place_string = '{{office_place_string}}'
var max_begin_hour = '{{max_begin_hour}}' var max_begin_hour = '{{max_begin_hour}}'
$('[name="address"]').addSearchAutocomplete();
var committees_shift_id = '{{committees_shift_id}}'; var committees_shift_id = '{{committees_shift_id}}';
var exemptions_shift_id = '{{exemptions_shift_id}}'; var exemptions_shift_id = '{{exemptions_shift_id}}';
</script> </script>
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
{% include "members/job_input.html" %} {% include "members/job_input.html" %}
</p> </p>
{% endif %} {% endif %}
{% if ask_for_capital_payment %}
<p> <p>
<input type="number" step="1" min="1" placeholder="Nombre de parts" name="shares_nb" id="shares_nb" class="b_yellow" required disabled/> <input type="number" step="1" min="1" placeholder="Nombre de parts" name="shares_nb" id="shares_nb" class="b_yellow" required disabled/>
<label for="shares_nb">Parts sociales</label> <label for="shares_nb">Parts sociales</label>
...@@ -60,6 +61,7 @@ ...@@ -60,6 +61,7 @@
<div class="check_details"> <div class="check_details">
</div> </div>
</div> </div>
{% endif %}
{% if input_barcode %} {% if input_barcode %}
<p> <p>
<input type="text" name="m_barcode" id="m_barcode" disabled/> <input type="text" name="m_barcode" id="m_barcode" disabled/>
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
</h1> </h1>
</div> </div>
{% if company_code == "lacagette" %}
<div class="tiles_container"> <div class="tiles_container">
<div class="tile full_width_tile"> <div class="tile full_width_tile">
<div class="tile_content"> <div class="tile_content">
...@@ -521,6 +522,15 @@ ...@@ -521,6 +522,15 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% else %}
<p class="txtcenter">
Merci de lire le manuel des membres, dans lequel toutes les règles concernant les services sont décrites.
{% if MEMBERS_GUIDE_URL %}
<br/>
Vous pouvez le lire en cliquant <a href="{{MEMBERS_GUIDE_URL}}" target="_blank">ici</a>
{% endif %}
</p>
{% endif %}
</div> </div>
...@@ -9,7 +9,11 @@ ...@@ -9,7 +9,11 @@
<a href="javascript:void(0);" class="nav_item active" id="nav_home">Espace Membre</a> <a href="javascript:void(0);" class="nav_item active" id="nav_home">Espace Membre</a>
<a href="javascript:void(0);" class="nav_item" id="nav_my_info">Mes Infos</a> <a href="javascript:void(0);" class="nav_item" id="nav_my_info">Mes Infos</a>
<a href="javascript:void(0);" class="nav_item" id="nav_my_shifts">Mes Services</a> <a href="javascript:void(0);" class="nav_item" id="nav_my_shifts">Mes Services</a>
{% if company_code == "lacoope" %}
<a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Choix et reports de services</a>
{% else %}
<a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Échange de services</a> <a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Échange de services</a>
{% endif %}
{% if show_faq %} {% if show_faq %}
<a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a> <a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a>
{% endif %} {% endif %}
......
...@@ -67,18 +67,30 @@ ...@@ -67,18 +67,30 @@
<div class="tile small_tile" id="home_tile_services_exchange"> <div class="tile small_tile" id="home_tile_services_exchange">
<div class="tile_title"> <div class="tile_title">
<i class="fas fa-exchange-alt tile_icon"></i> <i class="fas fa-exchange-alt tile_icon"></i>
{% if company_code == "lacoope" %}
Choix et reports de services
{% else %}
Échange de services Échange de services
{% endif %}
</div> </div>
<div class="tile_content"> <div class="tile_content">
<div class="block_service_exchange"> <div class="block_service_exchange">
Un empêchement ? J'anticipe et déplace mes services jusqu'à 24h avant leur début ! Un empêchement ? J'anticipe et déplace mes services jusqu'à 24h avant leur début !
</div> </div>
<div class="free_service_exchange"> <div class="free_service_exchange">
{% if company_code == "lacoope" %}
Je suis volant ? j'ai un empêchement ? Je sélectionne ou déplace mes services
{% else %}
Un empêchement ? J'anticipe et déplace mon service le plus tôt possible ! Un empêchement ? J'anticipe et déplace mon service le plus tôt possible !
{% endif %}
</div> </div>
<div class="home_link_button_area"> <div class="home_link_button_area">
<button type="button" class="btn--primary home_link_button" id="go_to_shifts_calendar"> <button type="button" class="btn--primary home_link_button" id="go_to_shifts_calendar">
Accéder au calendrier d'échange de services {% if company_code == "lacoope" %}
Accéder au calendrier de choix et reports de services
{% else %}
Accéder au calendrier d'échange de services
{% endif %}
</button> </button>
</div> </div>
</div> </div>
......
...@@ -118,6 +118,7 @@ ...@@ -118,6 +118,7 @@
<script> <script>
var app_env = '{{app_env}}'; var app_env = '{{app_env}}';
var ftop_can_delete_shift = "{{ALLOW_FTOP_TO_DELETE_SHIFT}}";
var forms_link = '{{forms_link}}'; var forms_link = '{{forms_link}}';
var unsuscribe_form_link = '{{unsuscribe_form_link}}'; var unsuscribe_form_link = '{{unsuscribe_form_link}}';
var request_form_link = '{{request_form_link}}'; var request_form_link = '{{request_form_link}}';
...@@ -142,8 +143,6 @@ ...@@ -142,8 +143,6 @@
"partner_id":"{{partnerData.id}}", "partner_id":"{{partnerData.id}}",
"name":"{{partnerData.display_name|safe}}", "name":"{{partnerData.display_name|safe}}",
"shift_type":"{{partnerData.shift_type}}", "shift_type":"{{partnerData.shift_type}}",
"final_ftop_point":{{partnerData.final_ftop_point}},
"final_standard_point":{{partnerData.final_standard_point}},
"date_delay_stop":"{{partnerData.date_delay_stop}}", "date_delay_stop":"{{partnerData.date_delay_stop}}",
"cooperative_state":"{{partnerData.cooperative_state}}", "cooperative_state":"{{partnerData.cooperative_state}}",
"regular_shift_name":"{{partnerData.regular_shift_name}}", "regular_shift_name":"{{partnerData.regular_shift_name}}",
...@@ -167,12 +166,15 @@ ...@@ -167,12 +166,15 @@
"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),
"final_ftop_point": parseInt("{{partnerData.final_ftop_point}}", 10),
"final_standard_point": parseInt("{{partnerData.final_standard_point}}", 10)
}; };
var block_actions_for_attached_people = '{{block_actions_for_attached_people}}'; var block_actions_for_attached_people = '{{block_actions_for_attached_people}}';
var block_service_exchange_24h_before = '{{block_service_exchange_24h_before}}'; var block_service_exchange_24h_before = '{{block_service_exchange_24h_before}}';
const canAddShift = {{canAddShift}}; const canAddShift = {{canAddShift}};
const extension_duration = {{extension_duration}}; const extension_duration = {{extension_duration}};
const not_allowed_shift_op = `{{not_allowed_shift_op|safe}}`;
</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/common.js" %}?v=1651853225"></script> <script src="{% static "js/common.js" %}?v=1651853225"></script>
......
<div id="faqBDM" class=" mt-3">
<div class="page_title txtcenter"><h1> Problèmes et demandes </h1></div>
<div class="txtcenter">
<p>
Si vous rencontrez des difficultés à vous inscrire, <br/>si vous avez des interrogations, ou si tout simplement vous avez besoin d’accompagnement,<br/> contactez le Bureau des Membres :
<br/>
<img src="/static/img/telephone.png" alt="Téléphone" style="height:25px;"> 02 38 22 54 65
<br/>hello@la-gabare-orleans.coop
</p>
<p>
Des tutoriels sont à votre disposition :
<div>
Tutoriel Espace Membre<br>
Tutoriel Borne d’Entrée<br>
Tutoriel Coopérateur & son binôme<br>
</div>
<div>
Pour y accéder, cliquez sur le lien ici : <a href="https://cloud.la-gabare-orleans.coop/s/nQj7dLF6p2xCXfb" target="_blank">Tutoriels</a>
</div>
</p>
</div>
</div>
<div id="faqBDM" class=" mt-3">
<div class="page_title txtcenter"><h1> Problèmes et demandes </h1></div>
<div class="txtcenter">
<p>
En cas de difficulté à vous inscrire en suivant la <a href="https://www.le-troglo.fr/wp-content/uploads/2023/03/Tuto-espace-membre.pdf">procédure</a>,<br />
ou si vous rencontrez un dysfonctionnement, vous pouvez écrire à bureau.coops@le-troglo.fr
</p>
</div>
</div>
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
<div class="login_area"> <div class="login_area">
{% include "common/conn_admin.html" %} {% include "common/conn_admin.html" %}
</div> </div>
{% if can_customize_parameters %}
<div class="preferences_area" style="display:none;">
<button type="button" id="preferences-settings">Paramétrage</button>
</div>
{% endif %}
<div id="new_order_area"> <div id="new_order_area">
<h2>Créer une nouvelle commande</h2> <h2>Créer une nouvelle commande</h2>
<div class="txtcenter" id="not_connected_content" style="display:none;"> <div class="txtcenter" id="not_connected_content" style="display:none;">
...@@ -71,9 +77,11 @@ ...@@ -71,9 +77,11 @@
</button> </button>
</div> </div>
</div> </div>
{% if metabase_url != "" %}
<a class='btn--warning link_as_button' id="access_metabase" style="display:none;" href="{{metabase_url}}" target="_blank"> <a class='btn--warning link_as_button' id="access_metabase" style="display:none;" href="{{metabase_url}}" target="_blank">
Stats Métabase Stats Métabase
</a> </a>
{% endif %}
</div> </div>
</div> </div>
...@@ -359,8 +367,25 @@ ...@@ -359,8 +367,25 @@
</div> </div>
</div> </div>
</div> </div>
</div> <div id="modal_preferences_form">
</div> <div class="modal_input_area">
<form>
<div>
<label>Forcer commande de 1 colis si stock = 0 et conso = 0 ?</label>
<div class="input-wrapper checkboxes">
<div>
<input type="radio" class="radio" name="force_0s0c_order" value="1"> Oui
</div>
<div>
<input type="radio" class="radio" name="force_0s0c_order" value="0"> Non
</div>
</div>
</div>
</form>
</div>
</div>
</div> <!-- templates -->
</div> <!-- page body -->
<script src="{% static "js/pouchdb.min.js" %}"></script> <script src="{% static "js/pouchdb.min.js" %}"></script>
...@@ -369,6 +394,9 @@ ...@@ -369,6 +394,9 @@
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname; var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var odoo_server = '{{odoo_server}}'; var odoo_server = '{{odoo_server}}';
var metabase_url = '{{metabase_url}}'; var metabase_url = '{{metabase_url}}';
var uoms = {{uoms|safe}};
var company_code = '{{company_code}}';
var preferences = {{preferences|safe}};
</script> </script>
<script src="{% static "js/all_common.js" %}?v=1651853225"></script> <script src="{% static "js/all_common.js" %}?v=1651853225"></script>
<script type="text/javascript" src="{% static 'js/orders_helper.js' %}?v=1651853225"></script> <script type="text/javascript" src="{% static 'js/orders_helper.js' %}?v=1651853225"></script>
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/reception_style.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/moment.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugin.moment_sorting.js' %}"></script>
{% endblock %}
{% block content %}
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Commandes réceptionnées</h1>
</div>
<br>
<div class="main">
<table id="orders" class="display" width="90%" cellspacing="0" ></table>
</div>
<div id="templates" style="display:none;">
<div id="show_detail">
<h3></h3>
<p></p>
<table class="display" width="90%" cellspacing="0" ></table>
</div>
</div>
<script src="{% static "js/all_common.js" %}?v="></script>
<script type="text/javascript" src="{% static 'js/reception_admin.js' %}?v="></script>
{% endblock %}
...@@ -27,14 +27,18 @@ ...@@ -27,14 +27,18 @@
Pertes Pertes
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span> <span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br> </button><br>
{% if autoconso %}
<button type="button" class="btn--primary movement_type_button" id="autoconso_type_button"> <button type="button" class="btn--primary movement_type_button" id="autoconso_type_button">
Autoconsommation Autoconsommation
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span> <span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br> </button><br>
{% endif %}
{% if formeals %}
<button type="button" class="btn--primary movement_type_button" id="meals_type_button"> <button type="button" class="btn--primary movement_type_button" id="meals_type_button">
Repas salariés Repas salariés
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span> <span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button> </button>
{% endif %}
</div> </div>
</div> </div>
......
...@@ -30,8 +30,12 @@ ...@@ -30,8 +30,12 @@
<select class="select_movement_element select_movement_input" id="movement_type_selector" name=""> <select class="select_movement_element select_movement_input" id="movement_type_selector" name="">
<option value="">-- Choisissez un type de mouvement --</option> <option value="">-- Choisissez un type de mouvement --</option>
<option value="losses">Pertes</option> <option value="losses">Pertes</option>
{% if formeals %}
<option value="meals">Repas salariés</option> <option value="meals">Repas salariés</option>
{% endif %}
{% if autoconso %}
<option value="autoconso">Autoconsomation</option> <option value="autoconso">Autoconsomation</option>
{% endif %}
</select> </select>
<p class="select_movement_element">De : <input type="text" id="from" class="select_movement_input"></p> <p class="select_movement_element">De : <input type="text" id="from" class="select_movement_input"></p>
<p class="select_movement_element">À : <input type="text" id="to" class="select_movement_input"></p> <p class="select_movement_element">À : <input type="text" id="to" class="select_movement_input"></p>
......
<div>
<span id="ask_new_password">Changer de mot de passe</span> <span id="passwd_helper"><i class="fa fa-info-circle fa-lg change_passwd_info"> </i></span>
<div style="display:none;" id="help_content">
Pour changer de mot de passe, remplissez le champ "Email", <br/>
et cliquez sur "Changer de mot de passe"
</div>
</div>
<script>
//Minimum JS has been loaded, so write in pure JS (no extra lib dependent)
let last_call;
const ask_pwd_span = document.querySelector("#ask_new_password");
let getCookie = function(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
let is_time_to_call = function() {
let answer = false;
var last_date = last_call || 0;
console.log(last_date)
var d = new Date();
var now = d.getTime();
if (last_date == 0 || (now - last_date) >= 5000) {
answer = true;
last_call = now;
}
return answer;
}
let display_change_password_help = function() {
let help_content = document.querySelector("#help_content");
help_content.style.display = 'block';
setTimeout(function() {
help_content.style.display = 'none'}, 5000);
}
let ask_for_new_password = function() {
if (is_time_to_call() === true) {
try {
const email = document.querySelector('input[name="login"]').value;
if (email.trim().length > 0) {
ask_pwd_span.textContent = "Traitement en cours...."
let load = {'email' : email,
'csrfmiddlewaretoken': document.querySelector('input[name="csrfmiddlewaretoken"]').value}
let xhr = new XMLHttpRequest();
xhr.open('POST', '/members/ask_for_new_password', true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
xhr.onreadystatechange = function() {
//readyState => (0,UNSENT), (1,OPENED), (2, HEADER_RECEIVED), (3, LOADING), (4, DONE)
if (this.readyState == 4) {
if (this.status == 200) {
const response = JSON.parse(xhr.response);
if (response.succeeded === true) {
alert("Un email vient de vous être envoyé à l'adresse indiquée pour réinitialiser le mot de passe.");
} else {
alert("Il n'a pas été possible de générer un mail pour réinitialiser le mot de passe correspondant à cette adresse.");
}
} else {
alert("Une erreur est survenue pendant le traitement de la demande.");
}
ask_pwd_span.textContent = "Changer de mot de passe";
}
};
xhr.send(JSON.stringify(load));
} else {
alert("Veuillez remplir le champ 'Email' avant de cliquer ici.");
}
} catch(e) {
alert("Une erreur est survenue.")
}
}
}
document.querySelector("#passwd_helper").addEventListener('click', display_change_password_help);
ask_pwd_span.addEventListener('click', ask_for_new_password);
</script>
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% block content %} {% block content %}
<div id="change_pwd_form_template" style="text-align:center;"> <div id="change_pwd_form_template" style="text-align:center;">
<form method="POST"> <form method="POST" id="change_pwd_form">
<p><input type="password" name="password" placeholder="{{password_placeholder}}" /></p> <p><input type="password" name="password" placeholder="{{password_placeholder}}" /></p>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="fp" value="" /> <input type="hidden" name="fp" value="" />
...@@ -11,11 +11,27 @@ ...@@ -11,11 +11,27 @@
<script> <script>
try { try {
const new_passwd_input = document.querySelector('input[name="password"]'),
form = document.querySelector('#change_pwd_form'),
external_msg = '{{external_msg}}';
window.addEventListener("DOMContentLoaded", (event) => { window.addEventListener("DOMContentLoaded", (event) => {
var fp = document.getElementsByName('fp') var fp = document.getElementsByName('fp')
if (fp.length == 1) if (fp.length == 1)
fp[0].value = new Fingerprint({canvas: true}).get() fp[0].value = new Fingerprint({canvas: true}).get()
}) })
form.addEventListener("submit", e => {
e.preventDefault();
if (new_passwd_input.value.length >= 10) {
form.submit()
} else {
alert('Le mot de passe doit faire au moins 10 caractères.')
}
});
if (external_msg == "reset_password_failure") {
alert("Erreur lors de l'enregistrement.")
}
} catch (e) { } catch (e) {
var msg = 'Ce navigateur ne permet pas de vous identifier. Merci de signaler l\'erreur suivante:\n' var msg = 'Ce navigateur ne permet pas de vous identifier. Merci de signaler l\'erreur suivante:\n'
msg += JSON.stringify(e) msg += JSON.stringify(e)
......
...@@ -14,8 +14,12 @@ ...@@ -14,8 +14,12 @@
<p> <p>
<input type="password" name="password" placeholder="{{password_placeholder}}" /> <input type="password" name="password" placeholder="{{password_placeholder}}" />
{% if password_notice %} {% if password_notice %}
<br/> <br/>
<span style="font-size: small;font-style: italic;">{{password_notice}}</span> <span style="font-size: small;font-style: italic;">{{password_notice}}</span>
{% endif %}
{% if reset_password_available %}
<br/>
{% include "website/change_password_availibility.html" %}
{% endif %} {% endif %}
</p> </p>
...@@ -47,7 +51,9 @@ ...@@ -47,7 +51,9 @@
<script> <script>
// For the members space, reset url to home when accessing connect page // For the members space, reset url to home when accessing connect page
const is_member_space = '{{is_member_space}}'; const is_member_space = '{{is_member_space}}',
external_msg = '{{external_msg}}';
if (is_member_space === "True") { if (is_member_space === "True") {
var app_env = '{{app_env}}'; var app_env = '{{app_env}}';
...@@ -58,5 +64,8 @@ ...@@ -58,5 +64,8 @@
history.replaceState({}, '', 'home'); history.replaceState({}, '', 'home');
} }
} }
if (external_msg == "successful_reset_password") {
alert("Changement de mot de passe réussi.")
}
</script> </script>
{% endblock %} {% endblock %}
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
sex : '{{data.sex}}', sex : '{{data.sex}}',
firstname : '{{data.firstname}}', firstname : '{{data.firstname}}',
lastname : '{{data.lastname}}', lastname : '{{data.lastname}}',
birthdate : '{{data.birthdate}}', birthdate : '{{data.birthdate_date}}',
street : '{{data.street}}', street : '{{data.street}}',
street2 : '{{data.street2}}', street2 : '{{data.street2}}',
zip : '{{data.zip}}', zip : '{{data.zip}}',
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<select name="jj"></select> <select name="jj"></select>
<select name="mm"></select> <select name="mm"></select>
<select name="yyyy"></select> <select name="yyyy"></select>
<input type="hidden" name="birthdate" value="{{data.birthdate}}" /> <input type="hidden" name="birthdate" value="{{data.birthdate_date}}" />
</p> </p>
<p> <p>
{% include "members/job_input.html" %} {% include "members/job_input.html" %}
......
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
{% load static %} {% load static %}
<link rel="shortcut icon" type="image/png" href="{% static "favicon.ico" %}"/> <link rel="shortcut icon" type="image/png" href="{% static "favicon.ico" %}"/>
<link rel="stylesheet" href="{% static "fontawesome/css/fa-svg-with-js.css" %}?v=">
{% block additionnal_css %}{% endblock %} {% block additionnal_css %}{% endblock %}
<script src="{% static "js/fp.js" %}"></script> <script src="{% static "js/fp.js" %}"></script>
<script src="{% static "fontawesome/js/fontawesome-all.min.js" %}"></script>
{% block additionnal_scripts %}{% endblock %} {% block additionnal_scripts %}{% endblock %}
</head> </head>
<style> <style>
...@@ -55,8 +57,19 @@ ...@@ -55,8 +57,19 @@
* and the following is set, and everything will render well in iOS. * and the following is set, and everything will render well in iOS.
*/ */
/*-webkit-appearance: none;*/ /*-webkit-appearance: none;*/
width: 15em
}
.change_passwd_info {color: blue;}
#help_content {
width: 15em;
margin: auto;
border: blue 2px solid;
border-radius: 20px;
background-color: #c7e7ff;
color: black;
} }
#ask_new_password {cursor: pointer;}
</style> </style>
<body> <body>
......
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