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
STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
COEFF_MAG_ID = 1
......
......@@ -42,6 +42,8 @@ STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
VRAC_CATEGS = [166, 167, 174, 179]
#EXPORT_POS_CAT_FOR_SCALES = True
FLV_CSV_NB = 2
......
......@@ -48,6 +48,8 @@ CASH_PAYMENT_ID = 18
STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
COEFF_MAG_ID = 1
......
......@@ -47,6 +47,8 @@ MEALS_PICKING_TYPE_ID = 17
CATEG_FRUIT = 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]
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']
......
......@@ -49,6 +49,8 @@ STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
FR_CATEGS = [CATEG_FRUIT]
VEG_CATEGS = [CATEG_LEGUME]
VRAC_CATEGS = [166, 167, 174, 179]
FLV_CSV_NB = 2
......
......@@ -2,6 +2,7 @@ from django.db import models
from outils.common import OdooAPI
from outils.common import CouchDB
from outils.common import MARSHALL_ERROR
from outils.common_imports import *
from decimal import *
import os
......@@ -418,8 +419,9 @@ class CagetteInventory(models.Model):
api.execute('stock.inventory', 'action_done', [inv])
done.append('Closed inventory')
except Exception as e:
coop_logger.error(str(e))
errors.append(str(e))
if not (MARSHALL_ERROR in str(e)):
coop_logger.error("update_stock_with_shelf_inventory_data %s",str(e))
errors.append(str(e))
return {'errors': errors,
'missed': missed,
......
......@@ -333,7 +333,7 @@ def manage_makeups(request):
context = {'title': 'BDM - Rattrapages',
'module': 'Membres',
'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))
......@@ -410,7 +410,7 @@ def update_members_makeups_core(members_data, res):
if member_data["member_shift_type"] == "standard":
# Set points to minus the number of makeups to do + the makeups to come (limited to -2)
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'])
if (target_points < -2):
target_points = -2
......
......@@ -63,3 +63,22 @@ p.important {border: #ff0000 1px solid; padding: 15px; margin-top:15px;}
#form_delete, #problem_delete {display:none;}
#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() {
ncoop_view.find('input[name="email"]').val(current_coop._id);
payment_meaning.find('option').removeAttr('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.show();
} else {
......
......@@ -737,5 +737,7 @@ $(document).ready(function() {
home();
}
});
$('[name="birthdate"]').helpFillDate();
$('[name="phone"]').helpFillTel();
$('[name="mobile"]').helpFillTel();
});
......@@ -37,6 +37,8 @@ urlpatterns = [
url(r'^verify_final_state$', views.verify_final_state),
url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes),
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
url(r'^search/([^\/.]+)/?([0-9]*)', views.search),
url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'),
......
......@@ -108,6 +108,7 @@ def inscriptions(request, type=1):
'mag_place_string': settings.MAG_NAME,
'office_place_string': settings.OFFICE_NAME,
'max_begin_hour': settings.MAX_BEGIN_HOUR,
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True),
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'force_firstname_hyphen': getattr(settings, 'FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME', True),
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
......@@ -118,6 +119,7 @@ def inscriptions(request, type=1):
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'ask_for_capital_payment': getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True),
'db': settings.COUCHDB['dbs']['member'],
'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', ''),
'can_create_binome': getattr(settings, 'CAN_CREATE_BINOME', True),
......@@ -160,6 +162,7 @@ def prepa_odoo(request):
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'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'],
'committees_shift_id': committees_shift_id,
'exemptions_shift_id': exemptions_shift_id,
......@@ -198,6 +201,7 @@ def validation_inscription(request, email):
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'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,
'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG,
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
......@@ -324,7 +328,7 @@ def record_service_presence(request):
overrided_date = ""
if app_env != "prod":
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:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
......@@ -336,7 +340,7 @@ def record_service_presence(request):
if res['rattrapage'] is True:
res['update'] = 'ok'
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'
else:
res['update'] = 'ko'
......@@ -413,6 +417,52 @@ def create_from_csv(request):
res['error'] = "Forbidden"
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):
"""Return INRA panel purchases (possible filter : month (w/wo year))"""
if request.method == 'GET':
......
......@@ -27,12 +27,18 @@ class CagetteMembersSpace(models.Model):
answer = True
return answer
def get_extension_duration(self):
"""Return nb of months"""
# TODO : add a unit parameter and convert if not month
extension_duration = OdooAPI().get_system_param('lacagette_membership.extension_duration')
nb, unit = extension_duration.split(' ')
return nb
def get_months_extension_duration(self):
duration = 2
try:
import re
p = self.o_api.get_system_param('lacagette_membership.extension_duration')
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):
""" Get partner shifts history """
......
......@@ -8,10 +8,15 @@ body {
@media screen and (max-width: 435px) {
.page_title {
margin: 4vw 0 3vw 0;
margin: 4vw 0 3vw 0;
}
}
/* -- Calendar */
#shift_choice .shift {
padding-left: 5px;
}
/* -- Tiles */
.tiles_container {
......
......@@ -25,21 +25,6 @@ $(document).on('click', "#shift_exchange_btn", () => {
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() {
if (block_actions_for_attached_people === "False") {
$(".attached-unblocked").show();
......
......@@ -91,6 +91,11 @@ function add_or_change_shift(new_shift_id) {
load_partner_shifts(partner_data.concerned_partner_id)
.then(() => {
init_shifts_list();
if (partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True") {
init_delete_registration_buttons();
}
closeModal();
setTimeout(() => {
......@@ -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. ` +
`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") {
alert(`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.`);
alert(not_allowed_shift_op);
} else if (error.status === 500 && 'msg' in error.responseJSON && error.responseJSON.msg === "Fail to create shift") {
// TODO differentiate error cases!
alert(`Une erreur est survenue. ` +
......@@ -138,6 +140,8 @@ function add_or_change_shift(new_shift_id) {
alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` +
`veuillez patienter quelques secondes puis vérifier vos services enregistrés.`);
} else if (error.status === 422) {
alert('Désolé ! Ce service ne peut pas être ajouté en raison des règles établies.');
} else {
alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` +
......@@ -373,8 +377,12 @@ function init_shifts_list() {
}
}
// Set delete registration button if shift isn't a makeup
if (partner_data.extra_shift_done > 0 && shift.is_makeup === false) {
// 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
|| partner_data.shift_type === 'ftop' && ftop_can_delete_shift === "True"
) {
if (shift_line_template.find(".delete_registration_button").length === 0) {
let delete_reg_button_template = $("#delete_registration_button_template");
......@@ -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
*/
......@@ -510,6 +532,10 @@ function init_calendar_page() {
$("#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 header_toolbar = {};
......@@ -600,7 +626,7 @@ function init_calendar_page() {
},
"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) {
/* could exchange shift but no old shift selected */
openModal(
......@@ -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 */
// Check if selected new shift is in less than extension end
if (partner_data.date_delay_stop !== 'False') {
......@@ -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
$(".loading-calendar").hide();
$("#calendar").show();
......@@ -667,6 +693,11 @@ function init_calendar_page() {
} else {
$(".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() {
eventDisplay: "block",
hiddenDays: hidden_days,
events: event_src,
eventDidMount: function() {
eventsSet: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide();
$("#calendar").show();
......@@ -781,7 +812,7 @@ function init_delete_registration_buttons() {
$(".delete_registration_button").off();
$(".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() {
let shift_name = $(this).closest("div")
.parent()
......
......@@ -10,6 +10,7 @@ from shifts.models import CagetteShift
from members_space.models import CagetteMembersSpace
import hashlib
import re
def _get_response_according_to_credentials(request, credentials, context, template):
......@@ -41,9 +42,14 @@ def index(request, exception=None):
# Bad credentials (or none)
template = loader.get_template('website/connect.html')
context['msg'] = ''
context['external_msg'] = request.GET.get('msg')
if 'msg' in credentials:
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
elif ('validation_state' in credentials) and credentials['validation_state'] == 'waiting_validation_member':
# First connection, until the member validated his account
......@@ -87,6 +93,11 @@ def index(request, exception=None):
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:
md5_calc = hashlib.md5(partnerData['create_date'].encode('utf-8')).hexdigest()
partnerData['verif_token'] = md5_calc
......@@ -124,19 +135,28 @@ def index(request, exception=None):
partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"]
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_abcd_calendar'] = getattr(settings, 'SHOW_ABCD_CALENDAR_TAB', True)
partnerData["comite"] = m.is_comite(partner_id)
context['partnerData'] = partnerData
context['mag_name'] = getattr(settings, 'MAG_NAME', '')
# Days to hide in the calendar
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', '')
if hasattr(settings, 'SHIFT_EXCHANGE_DAYS_TO_HIDE'):
days_to_hide = settings.SHIFT_EXCHANGE_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)
context['canAddShift'] = "true" if can_add_shift is True else "false"
......@@ -238,7 +258,7 @@ def shifts_exchange(request):
context = {
'title': 'Échange de Services',
'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))
......@@ -249,6 +269,7 @@ def faqBDM(request):
template = loader.get_template(template_path)
context = {
'title': 'foire aux questions',
'MEMBERS_GUIDE_URL': getattr(settings, 'MEMBERS_GUIDE_URL', None)
}
content = template.render(context, request)
......
......@@ -165,7 +165,7 @@ class Order(models.Model):
def export(self):
res = {'success': True}
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]]
order = self.o_api.search_read('purchase.order', c, f)
if order:
......@@ -226,6 +226,7 @@ class Order(models.Model):
def get_custom_barcode_labels_to_print(self):
import re
forced_quantity = getattr(settings, 'RECEPTION_PDT_LABELS_NB_FORCE_TO_NB', None)
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
labels_data = {'total': 0, 'details': []}
lines_data = self.get_lines()
......@@ -233,6 +234,8 @@ class Order(models.Model):
bc_pattern = re.compile('^' + fixed_prefix)
for l in lines:
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['total'] += l['product_qty']
return labels_data
......@@ -253,69 +256,75 @@ class Order(models.Model):
@staticmethod
def create(supplier_id, date_planned, order_lines):
order_data = {
"partner_id": int(supplier_id),
"partner_ref": False,
"currency_id": 1,
"date_order": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"origin": "Aide à la commande",
"company_id": 1,
"order_line": [],
"notes": False,
"date_planned": date_planned,
"picking_type_id": 1,
"dest_address_id": False,
"incoterm_id": False,
"payment_term_id": False,
"fiscal_position_id": False,
"message_follower_ids": False,
"message_ids": False
}
for line in order_lines:
product_line_name = line["name"]
if "product_code" in line and line["product_code"] is not False:
product_code = str(line["product_code"])
product_line_name = "[" + product_code + "] " + product_line_name
order_data["order_line"].append(
[
0,
False,
{
"package_qty": line["package_qty"],
"price_policy": "uom",
"indicative_package": True,
"product_id": line["product_variant_ids"][0],
"name": product_line_name,
"date_planned": date_planned,
"account_analytic_id": False,
"product_qty_package":line["product_qty_package"],
"product_qty": line["product_qty"],
"product_uom": line["product_uom"],
"price_unit": line["price_unit"],
"discount": 0,
"taxes_id": [
[
6,
False,
line["supplier_taxes_id"]
if len(list(order_lines)) > 0:
order_data = {
"partner_id": int(supplier_id),
"partner_ref": False,
"currency_id": 1,
"date_order": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"origin": "Aide à la commande",
"company_id": 1,
"order_line": [],
"notes": False,
"date_planned": date_planned,
"picking_type_id": 1,
"dest_address_id": False,
"incoterm_id": False,
"payment_term_id": False,
"fiscal_position_id": False,
"message_follower_ids": False,
"message_ids": False
}
for line in order_lines:
product_line_name = line["name"]
if "product_code" in line and line["product_code"] is not False:
product_code = str(line["product_code"])
product_line_name = "[" + product_code + "] " + product_line_name
order_data["order_line"].append(
[
0,
False,
{
"package_qty": line["package_qty"],
"price_policy": "uom",
"indicative_package": True,
"product_id": line["product_variant_ids"][0],
"name": product_line_name,
"date_planned": date_planned,
"account_analytic_id": False,
"product_qty_package":line["product_qty_package"],
"product_qty": line["product_qty"],
"product_uom": line["product_uom"],
"price_unit": line["price_unit"],
"discount": 0,
"taxes_id": [
[
6,
False,
line["supplier_taxes_id"]
]
]
]
}
]
)
}
]
)
api = OdooAPI()
id_po = api.create('purchase.order', order_data)
res_confirm = api.execute('purchase.order', 'button_confirm', [id_po])
res = {
'id_po': id_po,
'confirm_po': True,
'supplier_id': supplier_id,
'date_planned': date_planned
}
api = OdooAPI()
id_po = api.create('purchase.order', order_data)
res_confirm = api.execute('purchase.order', 'button_confirm', [id_po])
res = {
'id_po': id_po,
'confirm_po': True,
'supplier_id': supplier_id,
'date_planned': date_planned
}
else:
res = {
'id_po': 0,
'supplier_id': supplier_id
}
return res
......@@ -354,6 +363,7 @@ class Orders(models.Model):
import re
labels_data = {}
try:
forced_quantity = getattr(settings, 'RECEPTION_PDT_LABELS_NB_FORCE_TO_NB', None)
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
bc_pattern = re.compile('^' + fixed_prefix)
lines_data = Orders.get_lines(oids)
......@@ -361,7 +371,10 @@ class Orders(models.Model):
if not (bc_pattern.match(str(l['barcode'])) is None):
if not (l['product_tmpl_id'] in labels_data):
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:
coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e))
......
......@@ -13,6 +13,21 @@
top: 0;
left: 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 {
......@@ -495,6 +510,36 @@
/* - 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 {
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 = [
url(r'^export/([a-z]+)', views.export_regex),
url(r'^get_pdf_labels$', views.get_pdf_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_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
url(r'^end_supplier_product_association$', views.end_supplier_product_association),
url(r'^create_orders$', views.create_orders),
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
from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts
from members.models import CagetteUser
from openpyxl import 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 ""
def index(request):
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 = {
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': getattr(settings, 'ODOO_PUBLIC_URL', settings.ODOO['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_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):
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):
""" Get suppliers list """
res = {}
......@@ -44,10 +68,15 @@ def get_suppliers(request):
def get_supplier_products(request):
""" 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', '')
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:
return JsonResponse(res, status=500)
......
......@@ -7,20 +7,26 @@ import logging
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 to handle Odoo API requests."""
url = settings.ODOO['url']
user = settings.ODOO['user']
passwd = settings.ODOO['passwd']
db = settings.ODOO['db']
url = None
user = None
passwd = None
db = None
common = None
uid = None
models = None
def __init__(self):
def __init__(self, odoo=settings.ODOO):
"""Initialize xmlrpc connection."""
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)
object_proxy_url = '{}/xmlrpc/2/object'.format(self.url)
self.common = xmlrpc.client.ServerProxy(common_proxy_url)
......@@ -67,8 +73,11 @@ class OdooAPI:
def create(self, entity, fields):
"""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,
entity, 'create', [fields])
entity, 'create', [fields], context)
def delete(self, entity, ids):
"""Destroy entity instance by given ids."""
......@@ -76,8 +85,15 @@ class OdooAPI:
entity, 'unlink', [ids])
def execute(self, entity, method, ids, params={}):
return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, method, [ids], params)
res = []
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):
return self.common.authenticate(self.db, login, password, {})
......@@ -262,3 +278,24 @@ class Verification:
if token == md5_calc:
match = True
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 @@
"""Import which are used in most of modules files."""
from django.conf import settings
from django.core import validators
from .common_functions import *
import json, time, datetime, pytz
import logging
......
......@@ -152,6 +152,10 @@
- CATEG_LEGUME = 152
- FR_CATEGS = [CATEG_FRUIT]
- FR_CATEGS = [CATEG_LEGUME]
- FIXED_BARCODE_PREFIX = '0491'
- FLV_CSV_NB = 4
......@@ -299,6 +303,14 @@
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
If True, in shifts calendar view (to choose one or exchange one)
......@@ -327,6 +339,26 @@
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
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 @@
- 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
- DISPLAY_COL_AUTRES = True
......@@ -401,6 +437,12 @@
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
- STOCK_LOC_ID = 12
......@@ -464,6 +506,10 @@
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_SHOW_FTOP_BUTTON = True (by default)
......@@ -474,6 +520,12 @@
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
- EXPORT_COMPTA_FORMAT = 'Quadratus'
......
......@@ -34,6 +34,9 @@ footer { position: fixed;
font-style: italic;
color: blue;
}
.change_passwd_info {background-color: blue;}
#deconnect, #password_change {float:right; margin-left: 5px;}
/* The Overlay (background) */
......
......@@ -373,6 +373,7 @@ function show_admin_menu() {
function store_credentials(data) {
createCookie("authtoken", data.authtoken);
createCookie("uid", data.uid);
createCookie("login", data.login);
}
function coop_authenticate(callback) {
......@@ -499,26 +500,50 @@ Number.prototype.pad = function(size) {
return s;
};
// Accordions
var acc = document.getElementsByClassName("accordion");
var i;
Date.prototype.getABCDWeekLetter = function(debug) {
//high level const week_a_date must be declared (done in base.html template)
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++) {
acc[i].addEventListener("click", function() {
// Accordions
$(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;
this.classList.toggle("active");
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}
/* 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 report_JS_error(e, m) {
try {
......@@ -543,4 +568,34 @@ function isMacUser() {
if (isMacUser() && isSafari()) $('.mac-msg').show();
show_enqueued_messages();
\ No newline at end of file
show_enqueued_messages();
(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
import pymysql.cursors
import datetime
import re
import sys
import traceback
vcats = []
......@@ -55,8 +57,8 @@ class CagetteProduct(models.Model):
api = OdooAPI()
cond = [['product_tmpl_id.id', '=', template_id]]
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight', 'meal_voucher_ok']
'price_weight', 'price_volume', 'list_price',
'weight', 'volume', 'to_weight', 'meal_voucher_ok']
if not getattr(settings, 'SHOW_MEAL_VOUCHER_OK_LINE_IN_PRODUCT_INFO_FOR_LABEL', True):
fields.remove('meal_voucher_ok')
additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
......@@ -78,24 +80,36 @@ class CagetteProduct(models.Model):
res = {}
try:
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)):
product = p[0]
txt = ''
meal_voucher_found = False
if price is not None and len(price) == 0:
price = None
for k, v in product.items():
if type(v) == list and len(v) > 0 :
v = v[-1]
if k == 'product_tmpl_id':
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
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)
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)
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
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:
txt += 'nb_impression=' + str(nb) + "\r\n"
res['txt'] = txt
......@@ -107,7 +121,8 @@ class CagetteProduct(models.Model):
file.close()
except Exception as 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
@staticmethod
......@@ -334,13 +349,18 @@ class CagetteProducts(models.Model):
if ('image_medium' in p):
p['image'] = 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):
# p['image'] = __process_img_data(p, 'image')
if type(p['image']) is bool:
p['image'] = ''
if p['categ_id'][0] == settings.CATEG_FRUIT:
if p['categ_id'][0] in settings.FR_CATEGS:
p['categ'] = 'F'
elif p['categ_id'][0] == settings.CATEG_LEGUME:
elif p['categ_id'][0] in settings.VEG_CATEGS:
p['categ'] = 'L'
elif (p['name'].lower().find(' vrac') > -1) or (p['categ_id'][0] in vcats):
p['categ'] = 'V'
......@@ -385,7 +405,7 @@ class CagetteProducts(models.Model):
@staticmethod
def get_fl_products(withCandidate=False, fields=[]):
api = OdooAPI()
flv_cats = [settings.CATEG_FRUIT, settings.CATEG_LEGUME]
flv_cats = settings.FR_CATEGS + settings.VEG_CATEGS
cond = [['active', '=', True],
['available_in_pos', '=', True],
['categ_id', 'in', flv_cats]]
......@@ -396,7 +416,7 @@ class CagetteProducts(models.Model):
@staticmethod
def get_products_for_label_appli(withCandidate=False):
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:
fields.append('pos_categ_id')
to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields)
......@@ -442,11 +462,12 @@ class CagetteProducts(models.Model):
# transcode result to compact format (for bandwith save and browser memory)
# real size / 4 (for 2750 products)
# 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 p['uom_id'] == 3:
p['uom_id'] = 21
if p['uom_id'] == 20:
p['uom_id'] = 1
if getattr(settings, 'COMPANY_CODE', '') == 'lacagette':
if p['uom_id'] == 3:
p['uom_id'] = 21
if p['uom_id'] == 20:
p['uom_id'] = 1
result['pdts'][p['barcode']] = [
p['display_name'],
p['sale_ok'],
......@@ -469,8 +490,8 @@ class CagetteProducts(models.Model):
api = OdooAPI()
try:
cond = [['active', '=', True]]
fields = ['display_name', 'uom_type']
res = api.search_read('product.uom', cond, fields)
fields = ['name', 'uom_type', 'factor']
res = api.search_read('uom.uom', cond, fields)
result['list'] = res
except Exception as e:
result['error'] = str(e)
......@@ -597,7 +618,7 @@ class CagetteProducts(models.Model):
return res
@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.
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):
"qty_available",
"incoming_qty",
"uom_id",
"uom_po_id",
"purchase_ok",
"supplier_taxes_id",
"product_variant_ids",
......@@ -643,7 +665,11 @@ class CagetteProducts(models.Model):
]
c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]]
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 = {
'ids': ptids,
......@@ -676,6 +702,8 @@ class CagetteProducts(models.Model):
'product_code': psi_item["product_code"],
'sequence': psi_item["sequence"]
})
if len(sales) == 0:
filtered_products_t[i]['daily_conso'] = 0
for s in sales:
if s["id"] == fp["id"]:
......@@ -683,6 +711,10 @@ class CagetteProducts(models.Model):
filtered_products_t[i]['sigma'] = s["sigma"]
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
except Exception as e:
coop_logger.error('get_products_for_order_helper %s (%s)', str(e))
......
......@@ -269,7 +269,7 @@ def labels_appli_csv(request, params):
for c in file_copies:
copyfile(os_file, c)
res['fichiers_generes'] = len(file_copies) + 1
except Exception as e:
res['error'] = str(e)
return JsonResponse({'res': res})
......
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.
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):
pids.append(int(r['purchase_id'][0]))
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
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)
except Exception as e:
print(str(e))
coop_logger.error("CagetteReception.get_orders : %s", str(e))
return orders
def get_order_unprocessable_products(id_po):
......@@ -68,7 +72,7 @@ class CagetteReception(models.Model):
except Exception as e:
print(str(e))
coop_logger.error("CagetteReception.get_mail_create_po : %s", str(e))
return res
......@@ -77,7 +81,8 @@ class CagetteReception(models.Model):
lines_data = Order(self.id).get_lines()
bc_pattern = re.compile('^0493|0499') # TODO : Adjust for other pattern (such as Supercoop)
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
# print ('answer=' + str(answer))
return answer
......@@ -111,7 +116,7 @@ class CagetteReception(models.Model):
def update_order_status(self, id_po, updateType):
"""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)
return res
......@@ -173,31 +178,6 @@ class CagetteReception(models.Model):
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):
import requests
# don't print barcode which begin with these codes
......@@ -222,53 +202,56 @@ class CagetteReception(models.Model):
if len(to_reset) > 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):
"""stock_picking is created to make,
stock immediate transfer is done,
products are updated with new vendor prices"""
def stock_picking_update(self, order_line):
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:
return 'error : cant reach odoo'
res = self.o_api.execute('purchase.order', 'action_view_picking', [self.id])
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'
except Exception as e:
coop_logger.error("Stock picking update : %s ", str(e))
result = False
return result
else:
result = 'error: pack operations'
new_x_reception_status = 'error_pack_op'
else:
result = 'already done'
else:
result = 'error: cant access stock picking'
new_x_reception_status = 'error_picking'
if result == 'processed':
price_update = self.update_products_price()
if price_update['success'] is False:
result = 'error: price update'
new_x_reception_status += '/error_uprice'
if new_x_reception_status == '':
new_x_reception_status = 'done'
if getattr(settings, 'RECEPTION_SHELF_LABEL_PRINT', False) is True:
self.print_shelf_labels_for_updated_prices(price_update['lines'])
if result != 'already done':
self.o_api.update('purchase.order', [self.id], {'x_reception_status': new_x_reception_status})
def finalyze_picking_v12(self):
result = None
try:
res = self.o_api.execute('purchase.order', 'stock_immediate_transfer', [self.id])
done = 0
for r in res:
if r in ['done', 'cancel']:
done += 1
if done == len(res):
result = 'processed'
price_update = self.update_products_price_v12()
if price_update and price_update['success'] is False:
result = 'error: price update'
else:
result = False
except Exception as e:
coop_logger.error("Finalyze picking : %s ", str(e))
return result
......@@ -303,7 +286,7 @@ class CagetteReception(models.Model):
if len(to_process) > 0:
for p in to_process:
m = CagetteReception(int(p['id']))
fp = m.finalyze_picking()
fp = m.finalyze_picking_v12()
if fp == 'processed':
print_label = m.implies_scale_file_generation()
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 admin
urlpatterns = [
url(r'^$', views.home),
......@@ -17,5 +18,9 @@ urlpatterns = [
url(r'^reception_pricesValidated', views.reception_pricesValidated),
# url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status),
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
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.styles import Alignment
from openpyxl.writer.excel import save_virtual_workbook
import dateutil.parser
from reception.models import CagetteReception
from outils.common import OdooAPI
......@@ -69,7 +72,7 @@ def get_list_orders(request):
"date_planned" : order["date_planned"],
"amount_untaxed" : round(order["amount_untaxed"],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:
......@@ -199,7 +202,7 @@ def update_orders(request):
float(order_line['package_qty']),
float(order_line['product_qty_package']),
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
m.remove_package_restriction(order_line)
update = m.update_line(int(order_line['id']),
......@@ -207,9 +210,12 @@ def update_orders(request):
float(order_line['package_qty']),
float(order_line['product_qty_package']),
float(order_line['price_unit']))
if not (update is True):
if update is not True:
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 is True and 'supplier_shortage' in order_line:
try:
......@@ -661,3 +667,64 @@ def send_mail_no_barcode(request):
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):
result['error'] = "Forbidden"
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):
import json
result = {}
......
......@@ -2,7 +2,7 @@ from django.db import models
from outils.common_imports import *
from outils.common import OdooAPI
from products.models import CagetteProducts
from products.models import CagetteProducts, CagetteProduct
from inventory.models import CagetteInventory
import os
......@@ -38,7 +38,7 @@ class Shelf(models.Model):
res['error'] = "Le rayon n'a pas pu être trouvé (" + str(e) + ")"
return res
def get_products(self):
def get_products(self, additional_fields=[]):
res = {}
try:
c = [['shelf_id', '=', self.id]]
......@@ -53,6 +53,7 @@ class Shelf(models.Model):
'active'
]
f += additional_fields
pdts = self.o_api.search_read('product.product', c, f)
for p in pdts:
for k, v in p.items():
......@@ -116,7 +117,11 @@ class Shelf(models.Model):
if 'shelf_losses' in params:
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
......@@ -148,6 +153,16 @@ class Shelf(models.Model):
res['error'] = "Le rayon n'a pas pu être détruit"
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):
c = [['barcode', 'in', barcodes]]
......@@ -541,16 +556,23 @@ class Shelf(models.Model):
class Shelfs(models.Model):
def get_all(precision='full'):
res = []
shelfs = []
try:
api = OdooAPI()
if precision == 'simple':
res = api.search_read('product.shelfs', [], ['name', 'sort_order'], order='sort_order asc')
else:
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:
coop_logger.error("Rayons, get_all : %s", str(e))
return res
return shelfs
@staticmethod
def get_shelfs_sortorder(shelf_ids=[]):
......
......@@ -24,7 +24,7 @@ function init_datatable() {
if (type == "sort" || type == 'type')
return data;
if (data == '0001-01-01 00:00:00')
if (data == '0001-01-01 00:00:00' || data == '')
return "";
else {
var date = new Date(data);
......@@ -49,7 +49,7 @@ function init_datatable() {
if (type == "sort" || type == 'type')
return data;
if (data == '0001-01-01')
if (data == '0001-01-01' || data == '')
return "";
else {
var date = new Date(data);
......
......@@ -26,4 +26,5 @@ urlpatterns = [
url(r'^admin/update$', admin.update),
url(r'^admin/delete$', admin.delete),
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):
except Exception as e:
# 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))
except Exception as err_json:
res['error'] = "Unable to parse received JSON"
......
......@@ -96,7 +96,7 @@ def _is_middled_filled_considered(reserved, max):
def get_list_shift_calendar(request, partner_id):
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)
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):
start = request.GET.get('start')
end = request.GET.get('end')
listService = cs.get_shift_calendar(partner_id, start, end)
listService = cs.get_shift_calendar(is_ftop, start, end)
events = []
for value in listService:
......@@ -134,7 +134,7 @@ def get_list_shift_calendar(request, partner_id):
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:
datetime_object -= datetime.timedelta(minutes=15)
event["end"] = dateIsoUTC(datetime_object.strftime("%Y-%m-%d %H:%M:%S"))
......@@ -178,7 +178,7 @@ def get_list_shift_calendar(request, partner_id):
def get_list_shift_partner(request, partner_id):
cs = CagetteShift()
shiftData = cs.get_shift_partner(partner_id)
[shiftData, is_ftop] = cs.get_shift_partner(partner_id)
for value in shiftData:
value['date_begin'] = value['date_begin'] + "Z"
......@@ -281,15 +281,28 @@ def add_shift(request):
cs = CagetteShift()
if 'idNewShift' in request.POST and 'idPartner' in request.POST:
partner_id = int(request.POST['idPartner'])
id_shift = int(request.POST['idNewShift'])
data = {
"idPartner": int(request.POST['idPartner']),
"idShift":int(request.POST['idNewShift']),
"shift_type":request.POST['shift_type'],
"idPartner": partner_id,
"idShift": id_shift,
"shift_type": request.POST['shift_type'],
"is_makeup": False
}
if 'is_makeup' in request.POST and request.POST['is_makeup'] == "1":
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
st_r_id = False
......@@ -304,17 +317,15 @@ def add_shift(request):
response = {'result': False}
# decrement makeups_to_do
res_decrement = False
try:
res_decrement = cs.decrement_makeups_to_do(int(request.POST['idPartner']))
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
response["decrement_makeups"] = False
if data['is_makeup'] is True:
try:
res_decrement = cs.decrement_makeups_to_do(int(request.POST['idPartner']))
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 = {'result': False}
return JsonResponse(response)
......@@ -450,3 +461,7 @@ def get_list(request):
liste.append(val[fields[0]])
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):
fields = {
'company_id': 1,
'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_dest_id': destination, # movement dest
'move_lines': [],
'pack_operation_ids': [],
# 'pack_operation_ids': [],
'operator_id': stock_movement_data['operator']['id']
}
......@@ -95,35 +95,31 @@ class CagetteStock(models.Model):
}
])
# Add stock.pack.operation to stock.picking
fields['pack_operation_ids'].append([
0,
False,
{
"product_qty": str(qty),
"qty_done": str(qty),
"location_id": settings.STOCK_LOC_ID,
"location_dest_id": destination,
"product_id": p['id'],
"name": p['name'],
"product_uom_id": p['uom_id'],
"state": 'done',
"fresh_record": False
}
])
# Add stock.pack.operation to stock.picking (field no more exists)
# fields['pack_operation_ids'].append([
# 0,
# False,
# {
# "product_qty": str(qty),
# "qty_done": str(qty),
# "location_id": settings.STOCK_LOC_ID,
# "location_dest_id": destination,
# "product_id": p['id'],
# "name": p['name'],
# "product_uom_id": p['uom_id'],
# "state": 'done',
# "fresh_record": False
# }
# ])
# Exception rises when odoo method returns nothing
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try:
picking = api.create('stock.picking', fields)
if not (picking is None):
# Set stock.picking done
api.execute('stock.picking', 'action_done', [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:
if not (marshal_none_error in str(e)):
......
......@@ -12,7 +12,9 @@ from django.shortcuts import render
def movements_page(request):
"""Page de selection de produits pour créer des mouvements de stock"""
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')
......@@ -21,7 +23,9 @@ def movements_page(request):
def movements_view(request):
"""Page d'extraction des mouvements de stocks"""
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')
......
......@@ -29,6 +29,7 @@
history.pushState(null, null, location.pathname)
}
const False = false // prevent error if api return python False as a value
const week_a_date = "{{week_a_date}}";
</script>
<script src="{% static "js/fp.js" %}"></script>
<script src="{% static "js/jquery-3.3.1.min.js" %}"></script>
......
......@@ -11,6 +11,7 @@
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
......
......@@ -11,6 +11,7 @@
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
......
......@@ -11,6 +11,7 @@
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
......
......@@ -66,6 +66,8 @@
{% include "members/job_input.html" %}
</p>
{% endif %}
{% if ask_for_capital_payment %}
<p>
<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 >
......@@ -76,8 +78,8 @@
{% endfor %}
</select>
<input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/>
</p>
{% endif %}
{% if input_barcode %}
<p>
......
......@@ -9,6 +9,7 @@
</style>
{% endblock %}
{% block additionnal_scripts %}
<script src="{% static "js/geoloc.js" %}"?v=></script>
<script type="text/javascript">
var type = 2;
var context = 'validation';
......@@ -93,6 +94,7 @@
<div style="display:none" id="admin_elts">
</div>
<div class="geo_suggestions" style="display:none" ></div>
<script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript">
var couchdb_dbname = '{{db}}';
......@@ -106,6 +108,7 @@
var mag_place_string = '{{mag_place_string}}';
var office_place_string = '{{office_place_string}}'
var max_begin_hour = '{{max_begin_hour}}'
$('[name="address"]').addSearchAutocomplete();
var committees_shift_id = '{{committees_shift_id}}';
var exemptions_shift_id = '{{exemptions_shift_id}}';
</script>
......
......@@ -38,6 +38,7 @@
{% include "members/job_input.html" %}
</p>
{% endif %}
{% if ask_for_capital_payment %}
<p>
<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>
......@@ -60,6 +61,7 @@
<div class="check_details">
</div>
</div>
{% endif %}
{% if input_barcode %}
<p>
<input type="text" name="m_barcode" id="m_barcode" disabled/>
......
......@@ -8,6 +8,7 @@
</h1>
</div>
{% if company_code == "lacagette" %}
<div class="tiles_container">
<div class="tile full_width_tile">
<div class="tile_content">
......@@ -521,6 +522,15 @@
</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>
......@@ -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" id="nav_my_info">Mes Infos</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>
{% endif %}
{% if show_faq %}
<a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a>
{% endif %}
......
......@@ -67,18 +67,30 @@
<div class="tile small_tile" id="home_tile_services_exchange">
<div class="tile_title">
<i class="fas fa-exchange-alt tile_icon"></i>
{% if company_code == "lacoope" %}
Choix et reports de services
{% else %}
Échange de services
{% endif %}
</div>
<div class="tile_content">
<div class="block_service_exchange">
Un empêchement ? J'anticipe et déplace mes services jusqu'à 24h avant leur début !
</div>
<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 !
{% endif %}
</div>
<div class="home_link_button_area">
<button type="button" class="btn--primary home_link_button" id="go_to_shifts_calendar">
Accéder au calendrier d'échange de services
{% 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>
</div>
</div>
......
......@@ -118,6 +118,7 @@
<script>
var app_env = '{{app_env}}';
var ftop_can_delete_shift = "{{ALLOW_FTOP_TO_DELETE_SHIFT}}";
var forms_link = '{{forms_link}}';
var unsuscribe_form_link = '{{unsuscribe_form_link}}';
var request_form_link = '{{request_form_link}}';
......@@ -142,8 +143,6 @@
"partner_id":"{{partnerData.id}}",
"name":"{{partnerData.display_name|safe}}",
"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}}",
"cooperative_state":"{{partnerData.cooperative_state}}",
"regular_shift_name":"{{partnerData.regular_shift_name}}",
......@@ -167,12 +166,15 @@
"verif_token" : "{{partnerData.verif_token}}",
"leave_stop_date": "{{partnerData.leave_stop_date}}",
"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_service_exchange_24h_before = '{{block_service_exchange_24h_before}}';
const canAddShift = {{canAddShift}};
const extension_duration = {{extension_duration}};
const not_allowed_shift_op = `{{not_allowed_shift_op|safe}}`;
</script>
<script src="{% static "js/all_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 @@
<div class="login_area">
{% include "common/conn_admin.html" %}
</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">
<h2>Créer une nouvelle commande</h2>
<div class="txtcenter" id="not_connected_content" style="display:none;">
......@@ -71,9 +77,11 @@
</button>
</div>
</div>
{% if metabase_url != "" %}
<a class='btn--warning link_as_button' id="access_metabase" style="display:none;" href="{{metabase_url}}" target="_blank">
Stats Métabase
</a>
{% endif %}
</div>
</div>
......@@ -359,8 +367,25 @@
</div>
</div>
</div>
</div>
</div>
<div id="modal_preferences_form">
<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>
......@@ -369,6 +394,9 @@
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var odoo_server = '{{odoo_server}}';
var metabase_url = '{{metabase_url}}';
var uoms = {{uoms|safe}};
var company_code = '{{company_code}}';
var preferences = {{preferences|safe}};
</script>
<script src="{% static "js/all_common.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 @@
Pertes
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
{% if autoconso %}
<button type="button" class="btn--primary movement_type_button" id="autoconso_type_button">
Autoconsommation
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
{% endif %}
{% if formeals %}
<button type="button" class="btn--primary movement_type_button" id="meals_type_button">
Repas salariés
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button>
{% endif %}
</div>
</div>
......
......@@ -30,8 +30,12 @@
<select class="select_movement_element select_movement_input" id="movement_type_selector" name="">
<option value="">-- Choisissez un type de mouvement --</option>
<option value="losses">Pertes</option>
{% if formeals %}
<option value="meals">Repas salariés</option>
{% endif %}
{% if autoconso %}
<option value="autoconso">Autoconsomation</option>
{% endif %}
</select>
<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>
......
<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 @@
{% block content %}
<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>
{% csrf_token %}
<input type="hidden" name="fp" value="" />
......@@ -11,11 +11,27 @@
<script>
try {
const new_passwd_input = document.querySelector('input[name="password"]'),
form = document.querySelector('#change_pwd_form'),
external_msg = '{{external_msg}}';
window.addEventListener("DOMContentLoaded", (event) => {
var fp = document.getElementsByName('fp')
if (fp.length == 1)
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) {
var msg = 'Ce navigateur ne permet pas de vous identifier. Merci de signaler l\'erreur suivante:\n'
msg += JSON.stringify(e)
......
......@@ -14,8 +14,12 @@
<p>
<input type="password" name="password" placeholder="{{password_placeholder}}" />
{% if password_notice %}
<br/>
<span style="font-size: small;font-style: italic;">{{password_notice}}</span>
<br/>
<span style="font-size: small;font-style: italic;">{{password_notice}}</span>
{% endif %}
{% if reset_password_available %}
<br/>
{% include "website/change_password_availibility.html" %}
{% endif %}
</p>
......@@ -47,7 +51,9 @@
<script>
// 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") {
var app_env = '{{app_env}}';
......@@ -58,5 +64,8 @@
history.replaceState({}, '', 'home');
}
}
if (external_msg == "successful_reset_password") {
alert("Changement de mot de passe réussi.")
}
</script>
{% endblock %}
......@@ -14,7 +14,7 @@
sex : '{{data.sex}}',
firstname : '{{data.firstname}}',
lastname : '{{data.lastname}}',
birthdate : '{{data.birthdate}}',
birthdate : '{{data.birthdate_date}}',
street : '{{data.street}}',
street2 : '{{data.street2}}',
zip : '{{data.zip}}',
......@@ -69,7 +69,7 @@
<select name="jj"></select>
<select name="mm"></select>
<select name="yyyy"></select>
<input type="hidden" name="birthdate" value="{{data.birthdate}}" />
<input type="hidden" name="birthdate" value="{{data.birthdate_date}}" />
</p>
<p>
{% include "members/job_input.html" %}
......
......@@ -7,8 +7,10 @@
{% load static %}
<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 %}
<script src="{% static "js/fp.js" %}"></script>
<script src="{% static "fontawesome/js/fontawesome-all.min.js" %}"></script>
{% block additionnal_scripts %}{% endblock %}
</head>
<style>
......@@ -55,8 +57,19 @@
* and the following is set, and everything will render well in iOS.
*/
/*-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>
<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