Commit 0fedc22d by Damien Moulard

get modifications from third-party-v12 repo

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