Commit 23e45910 by François C.

Merge branch 'graoucoop_tmp' into graoucoop_prod

parents 52e20915 393893e8
Pipeline #3363 failed with stage
in 1 minute 4 seconds
......@@ -20,3 +20,4 @@ shop/errors.log
members/settings.json
.DS_Store
exec.*.log
scripts/src/ci_secrets.py
\ No newline at end of file
"""Company specific data values."""
MAG_NAME = ''
COMPANY_NAME = 'GRAOUCOOP'
OFFICE_NAME = ''
EMAIL_DOMAIN = 'graoucoop.fr'
COMPANY_LOGO = 'https://graoucoop.fr/wp-content/uploads/2023/02/logo-graoucoop-horiz-og.png'
# OPEN_ON_SUNDAY = True
MAX_BEGIN_HOUR = '19:00'
WELCOME_ENTRANCE_MSG = 'Bienvenue à Graoucoop !'
WELCOME_MAIL_SUBJECT = 'Dernière étape de votre inscription à Graoucoop.'
WELCOME_MAIL_TEMPLATE = 'members/graoucoop/bienvenue.html'
TOOLS_SERVER = 'outils.graoucoop.fr'
DAV_PATH = '/shared_dir/dav/'
ADMIN_IDS = [1,38]
BRINKS_MUST_IDENTIFY = True
CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
CAP_APPELE_VERSE_ACCOUNT_ID = 8
CAP_INVOICE_LINE_ACCOUNT_ID = 7
FUNDRAISING_CAT_ID = 1
UNITE_UOM_ID = 1
PARTS_A_PRODUCT_ID = 5
PARTS_B_PRODUCT_ID = 6
PARTS_C_PRODUCT_ID = 7
PARTS_PRICE_UNIT = 10.0
PARTS_A_PRICE_UNIT = PARTS_PRICE_UNIT
COOP_BARCODE_RULE_ID = 11
CHECK_PAYMENT_ID = 97
VIREMENT_PAYMENT_ID = 98
CASH_PAYMENT_ID = 18
#CB_PAYMENT_ID = 15
#HELLO_ASSO_PAYMENT_ID = 29
STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
VRAC_CATEGS = [166, 167, 174, 179]
#EXPORT_POS_CAT_FOR_SCALES = True
FLV_CSV_NB = 2
COEFF_MAG_ID = 1
RECEPTION_PDT_LABELS_FN = 'print_product_labels()'
RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits'
RECEPTION_PDT_LABELS_BTN_TEXT = 'Lancer l\'impression'
RECEPTION_SHELF_LABEL_PRINT = True
FIXED_BARCODE_PREFIX = '0490'
RECEPTION_ADD_ADMIN_MODE = True
RECEPTION_ADD_ALL_LEFT_IS_GOOD = True
RECEPTION_MERGE_ORDERS_PSWD='pass2makeApause'
DISPLAY_COL_AUTRES = False
RECEPTION_ADD_PRODUCTS_PSWD= 'pass2makeApause'
NO_BARCODE_MAIL_MSG = "contact@graoucoop.fr"
SUBSCRIPTION_PAYMENT_MEANINGS = [
{'code': 'cash', 'title': 'Espèces','journal_id': CASH_PAYMENT_ID},
{'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID},
{'code': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID},
]
CAN_CREATE_BINOME = False
SUBSCRIPTION_NAME_SEP = ', '
CONCAT_NAME_ORDER = 'LF'
SUBSCRIPTION_ASK_FOR_SEX = True
WITH_WEBSITE_MENU = True
SUBSCRIPTION_ADD_STREET2 = True
SUBSCRIPTION_ADD_SECOND_PHONE = True
FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME = False
ENTRANCE_EXTRA_BUTTONS_DISPLAY = False
ADMIN_BINOME_ACTIVE = False
SHOP_CAN_BUY = True
DELIVERY_CAN_BUY = True
SHOP_HEADER_IMG = 'https://graoucoop.fr/wp-content/uploads/2021/01/logo-graoucoop-horiz.png'
SHOP_OPENING = {'jeu.': [{'start': '15:45', 'end': '18:15'}, {'start': '18:30', 'end': '21:00'}],
'ven.': [{'start': '15:45', 'end': '18:15'}, {'start': '18:30', 'end': '21:00'}],
'sam.': [{'start': '10:15', 'end': '12:45'}, {'start': '13:00', 'end': '15:30'}]}
SHOP_SLOT_SIZE = 15 # minutes
SHOP_CATEGORIES = {
'epicerie': {'id': 75, 'label': 'Epicerie'},
'liquide': {'id': 96, 'label': 'Liquides'},
'produits_frais': {'id': 104, 'label': 'Frais'},
'surgeles': {'id': 115, 'label': 'Surgelés'},
'bazar': {'id': 122, 'label': 'Maison & Bazar'},
'droguerie': {'id': 127, 'label': 'Droguerie Hygiène'},
'parfumerie': {'id': 133, 'label': 'Parfumerie'}
}
ALLOW_NON_MEMBER_TO_CONNECT = True
EXCLUDE_SHOP_CATEGORIES=[]
DEFAULT_MAX_TIMESLOT_CARTS = 1
MIN_DELAY_FOR_SLOT = 0
HOURS_FOR_VALIDATION_SHOP = 2
SHOW_SUBSTITUTION_OPTION = False
CART_VALIDATION_BOTTOM_MSG = ""
SHOP_STOCK_WARNING = False
SHOP_BOTTOM_VALIDATION_MSG = 'Message personnalisable'
#SHOP_LIMIT_PRODUCTS = ['relatively_available', 'no_shelf']
#VALIDATION_ORDER_MAIL_TEMPLATE = 'shop/lgds_validation_mail.html'
EM_URL = ''
USE_NEW_MEMBERS_SPACE = True
USE_NEW_MEMBERS_SPACE = True
REMOVE_15_MINUTES_AT_SHIFT_END = False
CAN_ADD_SHIFT = True
COOP_CAN_CHANGE_SHIFT_TEMPLATE = True
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
SHIFT_INFO = """Un service est une plage de trois heures un jour en particulier, par exemple : le mardi 25/09/2018 à 13h15.
<br />A l'inverse, un créneau est une plage de trois heures régulière, par exemple, tous les mardi de semaine A à 13h15."""
PB_INSTRUCTIONS="""Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de se rendre au magasin"""
CALENDAR_NO_MORE_LINK = True
MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS = True
MEMBERS_SPACE_FAQ_TEMPLATE = 'members_space/graoucoop/faq.html'
MEMBERS_SPACE_UNDERSTAND_MY_STATUS_TEMPLATE = 'members_space/graoucoop/understand_my_status.html'
BRINKS_MUST_IDENTIFY = True
PROMOTE_SHELFS_IDS = []
DISCOUNT_SHELFS_IDS = []
FL_SHELFS = []
VRAC_SHELFS = []
ENTRANCE_FTOP_BUTTON_DISPLAY = False
#CUSTOM_CSS_FILES = {'all': ['common_lgds.css'],
# 'members': ['inscription_lgds.css']}
......@@ -146,3 +146,8 @@ AMNISTIE_DATE= "2021-11-24 00:00:00"
# BDM Admin
BDM_SHOW_FTOP_BUTTON = True
# Entree
MINIMUM_SECONDS_BETWEEN_TWO_COMITEE_VALIDATION = 0
SUBSCRIPTION_ASK_FOR_SEX = True
\ No newline at end of file
"""Company specific data values."""
COMPANY_CODE = 'supercafoutch'
"""Odoo coop specific constants ."""
COMPANY_LOGO = 'https://supercafoutch.fr/wp-content/uploads/2018/02/logo_SuperCafoutch_2021_500x225.png'
MAG_NAME = ''
OFFICE_NAME = ''
MAX_BEGIN_HOUR = '19:00'
COMPANY_NAME = 'SuperCafoutch'
WELCOME_ENTRANCE_MSG = 'Bienvenue à SuperCafoutch!'
WELCOME_MAIL_SUBJECT = 'Dernière étape de votre inscription à SuperCafoutch'
WELCOME_MAIL_TEMPLATE = 'members/bienvenue.html'
ENTRANCE_WITH_LATE_MODE = True
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True
USE_NEW_MEMBERS_SPACE = True
DEFAULT_SHIFT_TYPE = 'standard'
SHOW_FTOP_BUTTON = True
USE_STANDARD_SHIFT = True
ENTRANCE_EXTRA_BUTTONS_DISPLAY = False
PREPA_ODOO_URL = '/members/prepa-odoo/'
BDM_SHOW_FTOP_BUTTON = True
EMAIL_DOMAIN = 'supercafoutch.fr'
CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
......@@ -18,6 +36,7 @@ PARTS_A_PRICE_UNIT = PARTS_PRICE_UNIT
EXPORT_COMPTA_FORMAT = 'Quadratus'
COOP_BARCODE_RULE_ID = 11
ASSOCIATE_BARCODE_RULE_ID = 12
CHECK_PAYMENT_ID = 8
VIREMENT_PAYMENT_ID = 16
......@@ -36,12 +55,26 @@ FLV_CSV_NB = 2
COEFF_MAG_ID = 1
DAV_PATH = '/shared_dir/dav'
RECEPTION_PDT_LABELS_FN = 'print_product_labels()'
RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits'
RECEPTION_PDT_LABELS_BTN_TEXT = 'Lancer l\'impression'
RECEPTION_SHELF_LABEL_PRINT = False
FIXED_BARCODE_PREFIX = '0491'
DISPLAY_COL_AUTRES = False
RECEPTION_ADD_ALL_LEFT_IS_GOOD_QTIES = True
RECEPTION_ADD_ALL_LEFT_IS_GOOD_PRICES = True
RECEPTION_ADD_PRODUCTS_PSWD = 'pass2makeApause'
RECEPTION_UPDATE_QTY_PSWD = 'pass2makeApause'
NO_BARCODE_MAIL_SUBJECT = " Articles sans codebarre de la commande : {}"
NO_BARCODE_MAIL_MSG = """
Bonjour {0},
Vous avez crée la commande {1}, le {2}, pour {3}
Voici la liste des codes barres qui manquent :
"""
ASSOCIATE_MEMBER_SHIFT = 585
SUBSCRIPTION_PAYMENT_MEANINGS = [
{'code': 'cash', 'title': 'Espèces', 'journal_id': CASH_PAYMENT_ID},
......@@ -90,9 +123,42 @@ DISCOUNT_SHELFS_IDS = []
FL_SHELFS = []
VRAC_SHELFS = []
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = False
SHIFT_INFO = """Un service est une plage de trois heures un jour en particulier, par exemple : le mardi 25/09/2018 à 13h15.
<br />A l'inverse, un créneau est une plage de trois heures régulière, par exemple, tous les mardi de semaine A à 13h15."""
PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de ..."""
ENTRANCE_COME_FOR_SHOPING_MSG = "Bienvenue dans ton magasin !"
INPUT_PHONE_PATTERN = "^(((\+33(-| )\d{1})|\d{2})(\.| )\d{2}(\.| )\d{2}(\.| )\d{2}(\.| )\d{2})|(0\d{9})$"
CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence'
ENTRANCE_WITH_LATE_MODE = True
# Members space / shifts
UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de .....'
UNSUBSCRIBED_FORM_LINK = ''
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
ENTRANCE_VALIDATE_PRESENCE_MESSAGE = """
<div class="explanations">
Ta présence a bien été validée !
</div>
Ton prochain service <span class="service_verb">est prévu</span> le <span class="next_shift"></span>
"""
MEMBERS_SPACE_FAQ_TEMPLATE = 'members_space/supercafoutch/faq.html'
MEMBERS_SPACE_HOME_TEMPLATE = 'members_space/supercafoutch/home.html'
MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS = False
BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE = False
CAN_ADD_SHIFT = True
ALLOW_FOUR_DIGITS_IN_RECEPTION_PRICE = True
REMOVE_15_MINUTES_AT_SHIFT_END = False
\ No newline at end of file
REMOVE_15_MINUTES_AT_SHIFT_END = False
# Fix incompatibility between meal voucher module and an old version of michel bibikoff product label printer software used in supercafoutch
SHOW_MEAL_VOUCHER_OK_LINE_IN_PRODUCT_INFO_FOR_LABEL = False
LOSSES_LOC_ID = 33
LOSSES_PICKING_TYPE_ID = 10
AUTOCONSO_LOC_ID = 27
AUTOCONSO_PICKING_TYPE_ID = 7
SUBSCRIPTION_ASK_FOR_SEX = True
SUBSCRIPTION_ASK_FOR_JOB = True
\ No newline at end of file
......@@ -321,9 +321,11 @@ def admin(request):
def manage_makeups(request):
""" Administration des membres """
template = loader.get_template('members/admin/manage_makeups.html')
committees_shift_id = CagetteServices.get_committees_shift_id()
m = CagetteMembersSpace()
context = {'title': 'BDM - Rattrapages',
'module': 'Membres',
'has_committe_shift': committees_shift_id is not None,
'extension_duration': m.get_extension_duration()
}
return HttpResponse(template.render(context, request))
......@@ -347,6 +349,7 @@ def manage_regular_shifts(request):
template = loader.get_template('members/admin/manage_regular_shifts.html')
committees_shift_id = CagetteServices.get_committees_shift_id()
committees_shift_name = getattr(settings, 'COMMITTEES_SHIFT_NAME', "service des Comités")
exemptions_shift_id = CagetteServices.get_exemptions_shift_id()
context = {
'title': 'BDM - Créneaux',
'module': 'Membres',
......@@ -359,7 +362,8 @@ def manage_regular_shifts(request):
'has_committe_shift': committees_shift_id is not None,
'committees_shift_id': committees_shift_id,
'committees_shift_name': committees_shift_name,
'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', '')
'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', ''),
'exemptions_shift_id': exemptions_shift_id,
}
return HttpResponse(template.render(context, request))
......@@ -381,51 +385,62 @@ def update_members_makeups(request):
members_data = json.loads(request.body.decode())
res["res"] = []
for member_data in members_data:
cm = CagetteMember(int(member_data["member_id"]))
update_members_makeups_core(members_data, res)
response = JsonResponse(res)
else:
res["message"] = "Unauthorized"
response = JsonResponse(res, status=403)
return response
res["res"].append(cm.update_member_makeups(member_data))
# Update member standard points, for standard members only
if member_data["member_shift_type"] == "standard":
# Set points to minus the number of makeups to do (limited to -2)
target_points = - int(member_data["target_makeups_nb"])
if (target_points < -2) :
target_points = -2
def update_members_makeups_core(members_data, res):
for member_data in members_data:
cm = CagetteMember(int(member_data["member_id"]))
member_points = cm.get_member_points("standard")
points_diff = abs(member_points - target_points)
res["res"].append(cm.update_member_makeups(member_data))
# Don't update if no change
if points_diff == 0:
continue
# Update member standard points, for standard members only
if member_data["member_shift_type"] == "standard":
# Set points to minus the number of makeups to do + the makeups to come (limited to -2)
cs = CagetteShift()
shift_data = cs.get_shift_partner(int(member_data["member_id"]))
target_points = - int(member_data["target_makeups_nb"]) - sum(1 for value in shift_data if value['is_makeup'])
if (target_points < -2):
target_points = -2
if member_points > target_points:
points_update = - points_diff
else:
points_update = points_diff
member_points = cm.get_member_points("standard")
points_diff = abs(member_points - target_points)
data = {
'name': "Admin BDM - " + member_data["description"],
'shift_id': False,
'type': member_data["member_shift_type"],
'partner_id': int(member_data["member_id"]),
'point_qty': points_update
}
# Don't update if no change
if points_diff == 0:
res["res"][-1]['standard_points'] = member_points
continue
if member_points > target_points:
points_update = - points_diff
else:
points_update = points_diff
data = {
'name': "Admin BDM - " + member_data["description"],
'shift_id': False,
'type': member_data["member_shift_type"],
'partner_id': int(member_data["member_id"]),
'point_qty': points_update
}
cm.update_member_points(data)
#There are some odoo process happening here that could change the actual number of points
#so we want to fetch it again to send it back
res["res"][-1]['standard_points'] = cm.get_member_points("standard")
# Better to call run_process_target_status now, otherwise partner remains
# in alert until routine is called (every 5 minutes). It is a bit weird for users and
# allocation of rattrapages before the routine is executed will not trigger change to delay state !
# (the parner would have to go back to espace membre and click on "j'affecte mes rattrapage"
# even though it shows 'J'ai 0 rattrapages à effecter' for the delay state change to be eventually triggered)
api = OdooAPI()
api.execute('res.partner', 'run_process_target_status', [])
cm.update_member_points(data)
# Better to call run_process_target_status now, otherwise partner remains
# in alert until routine is called (every 5 minutes). It is a bit weird for users and
# allocation of rattrapages before the routine is executed will not trigger change to delay state !
# (the parner would have to go back to espace membre and click on "j'affecte mes rattrapage"
# even though it shows 'J'ai 0 rattrapages à effecter' for the delay state change to be eventually triggered)
api = OdooAPI()
api.execute('res.partner', 'run_process_target_status', [])
response = JsonResponse(res)
else:
res["message"] = "Unauthorized"
response = JsonResponse(res, status=403)
return response
def regenerate_member_delay(request):
""" From BDM admin, close existing extension if exists & recreate for 6 months """
......@@ -498,15 +513,16 @@ def delete_shift_registration(request):
member_id = int(data["member_id"])
shift_registration_id = int(data["shift_registration_id"])
shift_is_makeup = data["shift_is_makeup"]
cancellation_description = data["cancellation_description"]
# Note: 'upcoming_registration_count' in res.partner won't change because the _compute method
# in odoo counts canceled shift registrations.
m = CagetteShift()
res["cancel_shift"] = m.cancel_shift([shift_registration_id], origin='bdm')
res["cancel_shift"] = m.cancel_shift([shift_registration_id], origin='bdm', description=cancellation_description)
if shift_is_makeup is True:
fields = {
'name': "Admin BDM - Suppression d'un rattrapage",
'name': "Admin BDM (annulation de rattrapage par une annulation de présence) - " + cancellation_description,
'shift_id': False,
'type': data["member_shift_type"],
'partner_id': member_id,
......@@ -549,6 +565,13 @@ def delete_shift_template_registration(request):
if permanent_unsuscribe is True:
res["set_done"] = cm.set_cooperative_state("gone")
if res["set_done"]:
""" Delete pair(s?) of partner if it is a parent to improve statistics (#4810) """
api = OdooAPI()
associated_members = api.search_read('res.partner', [['parent_id', '=', partner_id]], ['id'])
for am in associated_members:
data = {"child": {"id": am["id"]}, "gone": ["parent", "child"]}
delete_pair_core(data)
except Exception as e:
res["error"] = str(e)
......@@ -574,12 +597,15 @@ def shift_subscription(request):
if 'success' in credentials and credentials['success'] is True and credentials['id'] == partner_id:
is_allowed = True
if is_allowed is True:
api = OdooAPI()
partner_id = int(data["partner_id"])
shift_type = data["shift_type"]
moving_from_std_to_ftop = False
if shift_type == 1:
# 1 = standard
shift_template_id = int(data["shift_template_id"])
else:
elif shift_type == 2:
# 2 = ftop
# First try to get committees shift
......@@ -588,6 +614,14 @@ def shift_subscription(request):
if shift_template_id is None:
shift_template_id = CagetteServices.get_first_ftop_shift_id()
c = [['id', '=', partner_id]]
f = ['shift_type']
moving_from_std_to_ftop = api.search_read('res.partner', c, f)[0]['shift_type'] == 'standard'
else:
# 3 = exempté
# Get exemptions shift
shift_template_id = CagetteServices.get_exemptions_shift_id()
m = CagetteMember(partner_id)
unsubscribe_first = data["unsubscribe_first"]
......@@ -603,13 +637,15 @@ def shift_subscription(request):
},
status=409
)
changing_shift = not moving_from_std_to_ftop
res["unsubscribe_member"] = m.unsubscribe_member(changing_shift)
res["unsubscribe_member"] = m.unsubscribe_member(changing_shift = True)
if moving_from_std_to_ftop:
lower_makeup_count_to_zero_if_moving_from_std_to_ftop(partner_id, res)
reg_id = m.create_coop_shift_subscription(shift_template_id, shift_type)
# Return necessary data
if reg_id is not None:
api = OdooAPI()
c = [['id', '=', shift_template_id]]
f = ['id', 'name']
res["shift_template"] = api.search_read('shift.template', c, f)[0]
......@@ -626,6 +662,20 @@ def shift_subscription(request):
return response
def lower_makeup_count_to_zero_if_moving_from_std_to_ftop(partner_id, res):
members_data = []
members_data.append({
'member_id': partner_id,
'member_shift_type': 'standard',
'target_makeups_nb': 0,
'description': 'reset automatique du compteur rattrapages suite changement créneau standard vers non standard'
})
res["res"] = []
update_members_makeups_core(members_data, res)
if res["res"][0]["update"]:
res["makeups_to_do"] = 0
# --- Gestion des binômes
def get_member_info(request, id):
......@@ -888,26 +938,42 @@ def delete_pair(request):
return HttpResponse(template.render(context, request))
elif request.method == 'POST':
if CagetteUser.are_credentials_ok(request):
api = OdooAPI()
data = json.loads(request.body.decode())
child_id = int(data['child']['id'])
child = api.search_read('res.partner', [['id', '=', child_id]], ['email', 'id', 'parent_id'])[0]
child_accounts = api.search_read('res.partner', [['email', '=', child['email']]], ['id', 'email'])
prev_child = [x['id'] for x in child_accounts if x['id'] != child_id]
parent = api.search_read('res.partner', [['id', '=', child['parent_id'][0]]], ['cooperative_state'])[0]
api.update('res.partner', [child_id], {"parent_id": False, "is_associated_people": False, "active": False, "is_former_associated_people": True})
child_update_fields = {'cooperative_state': "unsubscribed", "is_former_associated_people": True}
if 'gone' in data and 'child' in data['gone']:
child_update_fields['cooperative_state'] = "gone"
for id in prev_child:
api.update("res.partner", [id], child_update_fields)
if 'gone' in data and 'parent' in data['gone']:
api.update("res.partner", [parent['id']], {'cooperative_state': "gone", "is_former_associated_people": True})
delete_pair_core(data)
response = JsonResponse({"message": "Succesfuly unpaired members"}, status=200)
else:
response = JsonResponse({"message": "Unauthorized"}, status=403)
return response
else:
return JsonResponse({"message": "Method Not Allowed"}, status=405)
def delete_pair_core(data):
"""
Core of delete_pair
argument example :
{
"child": {
"id": "1620"
},
"gone": [
"parent",
"child"
]
}
"""
api = OdooAPI()
child_id = int(data['child']['id'])
child = api.search_read('res.partner', [['id', '=', child_id]], ['email', 'id', 'parent_id'])[0]
child_accounts = api.search_read('res.partner', [['email', '=', child['email']]], ['id', 'email'])
prev_child = [x['id'] for x in child_accounts if x['id'] != child_id]
parent = api.search_read('res.partner', [['id', '=', child['parent_id'][0]]], ['cooperative_state'])[0]
api.update('res.partner', [child_id], {"parent_id": False, "is_associated_people": False, "active": False,
"is_former_associated_people": True})
child_update_fields = {'cooperative_state': "unsubscribed", "is_former_associated_people": True}
if 'gone' in data and 'child' in data['gone']:
child_update_fields['cooperative_state'] = "gone"
for id in prev_child:
api.update("res.partner", [id], child_update_fields)
if 'gone' in data and 'parent' in data['gone']:
api.update("res.partner", [parent['id']], {'cooperative_state': "gone", "is_former_associated_people": True})
......@@ -403,6 +403,8 @@ class CagetteMember(models.Model):
}
if ('sex' in post_data):
received_data['sex'] = post_data['sex']
if ('function' in post_data):
received_data['function'] = post_data['function']
if 'street2' in post_data:
received_data['street2'] = post_data['street2']
if 'phone' in post_data:
......@@ -450,11 +452,15 @@ class CagetteMember(models.Model):
partner_id = None
name_sep = getattr(settings, 'SUBSCRIPTION_NAME_SEP', ' ')
ask_4_sex = getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False)
ask_4_job = getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False)
concat_order = getattr(settings, 'CONCAT_NAME_ORDER', 'FL')
sex = 'o'
function = ''
if ask_4_sex is True:
sex = post_data['sex']
if ask_4_job is True:
function = post_data['function']
# With input type="date", transmitted value is YYYY-mm-dd
# But, it could be dd/mm/YYYY if not supported by browser
......@@ -484,7 +490,8 @@ class CagetteMember(models.Model):
'zip': post_data['zip'],
'city': post_data['city'],
'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
'barcode_rule_id': settings.COOP_BARCODE_RULE_ID
'barcode_rule_id': settings.COOP_BARCODE_RULE_ID,
'function': function
}
if ('_id' in post_data):
f['email'] = post_data['_id']
......@@ -556,7 +563,8 @@ class CagetteMember(models.Model):
'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
'barcode_rule_id': settings.ASSOCIATE_BARCODE_RULE_ID,
'parent_id' : post_data['parent_id'],
'is_associated_people': True
'is_associated_people': True,
'function': function
}
associated_member_id = api.create('res.partner', associated_member)
am = CagetteMember(associated_member_id)
......@@ -845,8 +853,10 @@ class CagetteMember(models.Model):
elif search_type == "makeups_data":
fields = CagetteMember.m_short_default_fields
fields = fields + ['shift_type', 'makeups_to_do', 'display_ftop_points', 'display_std_points', 'shift_type']
return api.search_read('res.partner', cond, fields)
cond.append(['shift_type', '=', 'standard'])
res = api.search_read('res.partner', cond, fields)
CagetteMembers.add_makeups_to_come_to_member_data(res)
return res
elif search_type == "shift_template_data":
fields = CagetteMember.m_short_default_fields
fields = fields + ['id', 'makeups_to_do', 'cooperative_state','parent_name']
......@@ -1282,9 +1292,40 @@ class CagetteMembers(models.Model):
fields = ['id', 'name', 'display_std_points', 'display_ftop_points', 'shift_type', 'makeups_to_do', 'date_delay_stop']
res = api.search_read('res.partner', cond, fields)
# There are two things we need to do now :
# 1 : fetching members with no makeups to do but with some makeups to come
# 2 : providing makeups to come to all members
cs = CagetteShift()
makeups_to_come_per_partner = cs.get_partners_with_makeups_to_come()
# 1 : fetching members with no makeups to do but with some makeups to come
cond = [['makeups_to_do', '=', 0], ['id', 'in', list(makeups_to_come_per_partner.keys())]]
if len(ids) > 0:
cond.append(['id','in', ids])
res = res + api.search_read('res.partner', cond, fields)
# 2 : providing makeups to come to all members
for idx, partner in enumerate(res):
if partner['id'] in makeups_to_come_per_partner:
res[idx]['makeups_to_come'] = makeups_to_come_per_partner[partner['id']]
else:
res[idx]['makeups_to_come'] = 0
return res
@staticmethod
def add_makeups_to_come_to_member_data(res):
if res:
cs = CagetteShift()
for idx, partner in enumerate(res):
shift_data = cs.get_shift_partner(int(partner['id']))
res[idx]['makeups_to_come'] = sum(1 for value in shift_data if value['is_makeup'])
@staticmethod
def get_attached_members():
api = OdooAPI()
cond = [['is_associated_people','=', True]]
......@@ -1343,3 +1384,6 @@ class CagetteUser(models.Model):
pass
return answer
from shifts.models import CagetteShift
\ No newline at end of file
......@@ -29,7 +29,7 @@ video {max-width:none;}
#webcam_button {min-width:115px; max-width:115px;}
#multi_results_preview button {margin:2px;}
#member_slide {grid-gap:0;padding:5px;display:none;}
#member_slide .coop-info {background: #fbfbd5;}
#member_slide .coop-info {background: #449d44;}
#image_medium {width:128px;float:left;}
#image_medium:hover {cursor: url(/static/img/ip-camera.png), url(/static/img/ip-camera.svg) 5 5, pointer;}
#barcode {height:128px;}
......
......@@ -41,10 +41,10 @@ function display_makeups_members() {
$('#makeups_members_table').empty();
}
// Remove members with 0 makeups to do
// Remove members with 0 makeups to do and 0 makeups to come
ids_to_remove = [];
for (member_item of makeups_members) {
if (member_item.makeups_to_do == 0) {
for (let member_item of makeups_members) {
if (member_item.makeups_to_do === 0 && member_item.makeups_to_come === 0) {
ids_to_remove.push(member_item.id);
}
}
......@@ -59,8 +59,12 @@ function display_makeups_members() {
title: "",
className: "dt-body-center",
orderable: false,
render: function (data) {
return `<input type="checkbox" class="select_member_cb" id="select_member_${data}" value="${data}">`;
render: function (data, type, full) {
var html = ``;
if(full.shift_type == 'standard') { //prevent checking of ftop members so that they can't be target of makeup removal
html = `<input type="checkbox" class="select_member_cb" id="select_member_${data}" value="${data}">`;
}
return html;
},
width: "3%"
},
......@@ -89,32 +93,61 @@ function display_makeups_members() {
},
{
data: "shift_type",
title: "<div class='title_center'>Nb de points</div>",
title: "<div class='title_center'>Type de créneau</div>",
className: "dt-body-center",
width: "5%",
render: function (data, type, row) {
render: function (data) {
if (data == 'ftop') {
return row.display_ftop_points;
} else if (data == 'standard') {
return row.display_std_points;
return (has_committe_shift === "True") ? "Comités" : "Volant";
} else {
return 'ABCD';
}
return null;
}
},
{
data: "makeups_to_come",
title: "<div class='title_center' title=\"Services de rattrapage à venir déjà choisis depuis l'espace membre.\">Rattrapages à venir déjà choisis</div>",
className: "dt-body-center",
width: "8%",
render: function (data, type, full) {
return `<b>${data}</b>`;
}
},
{
data: "makeups_to_do",
title: "<div class='title_center'>Nb rattrapages</div>",
title: "<div class='title_center' title=\"Rattrapages en attente de sélection sur l'espace membre.\">Rattrapages pas encore choisis</div>",
className: "dt-body-center",
width: "10%",
render: function (data, type, full) {
return `<b>${data}</b>
<button class="decrement_makeup btn--primary" id="decrement_member_${full.id}">
<i class="fas fa-minus"></i>
</button>
var html = `<b>${data}</b>`
if(full.shift_type == 'standard') {
if(data > 0) {
html += `
<button class="decrement_makeup btn--primary" id="decrement_member_${full.id}">
<i class="fas fa-minus"></i>
</button>`;
}
html += `
<button class="increment_makeup btn--primary" id="increment_member_${full.id}">
<i class="fas fa-plus"></i>
</button>`;
}
return html;
}
},
{
data: "shift_type",
title: "<div class='title_center' title=\"Information d'interprétation délicate réservée aux utilisateurs avancés.\">Points (avancé)</div>",
className: "dt-body-center",
width: "5%",
render: function (data, type, row) {
if (data == 'ftop') {
return row.display_ftop_points;
} else if (data == 'standard') {
return row.display_std_points;
}
return null;
}
}
],
......@@ -292,21 +325,6 @@ function update_members_makeups(member_ids, action, description) {
} else {
makeups_members[member_index].makeups_to_do -= 1;
}
if (makeups_members[member_index].shift_type === 'standard') {
if (action === "increment") {
if (makeups_members[member_index].display_std_points >= -1)
makeups_members[member_index].display_std_points -= 1;
} else if (makeups_members[member_index].display_std_points < 0) {
makeups_members[member_index].display_std_points += 1;
}
} else {
if (action === "increment") {
if (makeups_members[member_index].display_ftop_points >= -1)
makeups_members[member_index].display_ftop_points -= 1;
} else {
makeups_members[member_index].display_ftop_points += 1;
}
}
data.push({
member_id: mid,
......@@ -325,8 +343,17 @@ function update_members_makeups(member_ids, action, description) {
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function() {
success: function(res) {
selected_rows = [];
for (element of res['res']) {
member_index = makeups_members.findIndex(m => m.id == element['mid']);
if (makeups_members[member_index].shift_type === 'standard') {
makeups_members[member_index].display_std_points = element['standard_points']
}
//there is no point change when shift_type is not standard
//and I think operation on rattrapages should definitely be forbidden for ftop because it
//has no sense !!
}
display_makeups_members();
closeModal();
},
......@@ -433,7 +460,8 @@ function display_possible_members() {
makeups_to_do: 0,
shift_type: member.shift_type,
display_std_points: member.display_std_points,
display_ftop_points: member.display_ftop_points
display_ftop_points: member.display_ftop_points,
makeups_to_come: member.makeups_to_come
});
let modal_template = $("#modal_incr_makeup_counter");
......
......@@ -64,7 +64,7 @@ function remove_from_shift_template() {
* Send the request to register a member to a shift template.
* Ask to unsuscribe first if the member was subscribed.
*
* @param {int} shift_type 1 === standard ; 2 === ftop
* @param {int} shift_type 1 === standard ; 2 === ftop ; 3 === exempté
* @param {int} shift_template_id null for ftop shift type
* @param {String} shift_template_name selected shift template name
*/
......@@ -92,6 +92,9 @@ function shift_subscrition(shift_type, shift_template_id = null, shift_template_
stdata.name
];
selected_member.cooperative_state = data.cooperative_state;
if(data.makeups_to_do !== undefined) {
selected_member.makeups_to_do = data.makeups_to_do;
}
display_member_info();
$("#shifts_calendar_area").hide();
......@@ -216,6 +219,22 @@ function set_subscription_area() {
false
);
});
$(document).off("click", "#shifts_calendar_area button[data-select='Exemption']");
$(document).on("click", "#shifts_calendar_area button[data-select='Exemption']", function() {
// Subscribe to comitee/ftop shift
msg = `Inscrire ${selected_member.name} en Éxempté ?`;
openModal(
msg,
() => {
shift_subscrition(3);
},
"Confirmer",
false
);
});
$(document).off("click", ".shift");
$(document).on("click", ".shift", function() {
// Subscribe to shift template
......
......@@ -102,16 +102,21 @@ function display_member_shifts() {
const shift_reg_id = row_data.id;
const shift_is_makeup = row_data.is_makeup;
let msg = `<p>Enlever la présence de <b>${member.name}</b> au service du <b>${row_data.shift_id[1]}</b> ?</p>`;
let modal_template = $("#modal_delete_shift_registration");
modal_template.find(".member_name").text(member.name);
modal_template.find(".service_name").text(row_data.shift_id[1]);
if (shift_is_makeup === true) {
msg += `<p><i class="fas fa-exclamation-triangle"></i> Ce service est un rattrapage. Le supprimer ajoutera un point au compteur de ce.tte membre.</p>`;
modal_template.find("#makeup_case_explanation").show();
}
openModal(
msg,
$("#modal_delete_shift_registration").html(),
() => {
delete_shift_registration(shift_reg_id, shift_is_makeup);
delete_shift_registration(
shift_reg_id,
shift_is_makeup,
($("#cancellation-explanation")[0].value || "pas d'explication") + ' : ' + ($("#cancellation-signature")[0].value || "auteur inconnu"),
);
},
"Confirmer",
false
......@@ -123,15 +128,17 @@ function display_member_shifts() {
* Send request to delete shift registration
* @param {Int} shift_reg_id Id of the shift_registration to delete
* @param {Boolean} shift_is_makeup Is the shift a makeup?
* @param {String} description explanation and signature for the cancellation from bdm
*/
function delete_shift_registration(shift_reg_id, shift_is_makeup) {
function delete_shift_registration(shift_reg_id, shift_is_makeup, description) {
openModal();
data = {
member_id: selected_member.id,
member_shift_type: selected_member.shift_type,
shift_registration_id: shift_reg_id,
shift_is_makeup: shift_is_makeup
shift_is_makeup: shift_is_makeup,
cancellation_description: description
};
$.ajax({
......
......@@ -17,6 +17,7 @@ var latest_odoo_coop_bb = null,
subs_cap = $('#subs_cap'),
m_barcode = $('#m_barcode'),
sex = $('#sex'),
job = $('#job'),
self_records = [],
selected_associate=null,
associated_old_choice= null,
......@@ -158,7 +159,7 @@ function swipe_to_shift_choice() {
retrieve_and_draw_shift_tempates();
schoice_view.show();
}
function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex, mjob) {
var coop = current_coop || {};
......@@ -214,6 +215,7 @@ function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
if (m_barcode.length > 0) coop.m_barcode = bc;
if (sex.length > 0) coop.sex = msex;
if (job.length > 0) coop.function = mjob;
coop.validation_state = "to_fill";
dbc.put(coop, function callback(err, result) {
if (!err) {
......@@ -244,6 +246,7 @@ function store_new_coop(event) {
var errors = [],
bc = '', // barcode may not be present
msex = '', // sex may not be present
mjob = '', //job may not be present
active_asso_area = $('#associate_area .choice_active'); // need to ckeck if associated data are available
// 1- Un coop avec le meme mail ne doit pas exister dans odoo (dans base intermediaire, le cas est géré par l'erreur à l'enregistrement)
let email = $('input[name="email"]').val()
......@@ -260,6 +263,9 @@ function store_new_coop(event) {
if (sex.length > 0) {
msex = $('input[name="sex"]:checked').val();
}
if (job.length > 0) {
mjob = $('input[name="job"]').val();
}
if (payment_meaning.val() == 'ch') {
if (ch_qty.val() <1) {
......@@ -319,7 +325,7 @@ function store_new_coop(event) {
if (errors.length == 0) {
_really_save_new_coop(
email, fname, lname,
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex, mjob
);
} else {
......@@ -331,7 +337,7 @@ function store_new_coop(event) {
if (errors.length == 0) {
_really_save_new_coop(
email, fname, lname,
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex
subs_cap.val(), payment_meaning.val(), ch_qty.val(), bc, msex, mjob
);
} else {
......@@ -405,6 +411,9 @@ function modify_current_coop() {
reset_sex_radios();
$('#' + current_coop.sex + '_sex').prop('checked', true);
}
if (job.length > 0) {
ncoop_view.find('input[name="job"]').val(current_coop.function);
}
ncoop_view.show();
}
......
......@@ -101,18 +101,22 @@ function fill_member_slide(member) {
}
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
html_elts.cooperative_state.html(member.cooperative_state);
if (member.cooperative_state == 'Rattrapage') {
if (member.cooperative_state == 'Suspendu(e)') {
var explanation = "Tu as dû manquer un service! Pour pouvoir faire tes courses aujourd'hui, tu dois d'abord sélectionner un rattrapage sur ton espace membre.";
html_elts.status_explanation.html(explanation);
}
if (member.cooperative_state == 'Désinscrit(e)' || member.cooperative_state == 'Parti(e)') coop_info.addClass('b_red');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_orange');
if (member.cooperative_state == 'Désinscrit(e)' || member.cooperative_state == 'Parti(e)' || member.cooperative_state == 'Rattrapage' || member.cooperative_state == 'Suspendu(e)') {
coop_info.addClass('b_red');
}
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé') {
coop_info.addClass('b_yellow')
}
if (member.shifts.length > 0) {
html_elts.next_shifts.append('Prochains services : ');
var slist = $('<ul>');
for (i in member.shifts) {
var s = $('<li>').text(member.shifts[i].start);
......
......@@ -163,6 +163,7 @@ function submit_full_coop_form() {
var form_data = new FormData(form.get(0)),
m_barcode = form.find('[name="m_barcode"]'),
sex = $('#sex'),
job = $('#job'),
has_empty_values = false;
for (var pair of form_data.entries()) {
......@@ -205,6 +206,12 @@ function submit_full_coop_form() {
if (sex.length > 0) {
form_data.set('sex', $('input[name="sex"]:checked').val());
}
if (job.length > 0) {
form_data.set(
'function',
vform.find('input[name="job"]').val()
);
}
post_form(
'/members/coop_validated_data', form_data,
function(err) {
......@@ -246,7 +253,8 @@ function save_current_coop(callback) {
let form = coop_validation_form,
_id = form.find('[name="email"]').val(),
m_barcode = form.find('[name="m_barcode"]'),
sex = form.find('[name="sex"]');
sex = form.find('[name="sex"]'),
job = form.find('[name="job"]');
if (current_coop != null && _id.length > 0) {
//Birthdate verification
......@@ -300,6 +308,9 @@ function save_current_coop(callback) {
current_coop.sex = $('input[name="sex"]:checked').val();
if (typeof current_coop.sex == "undefined") sex_error = true;
}
if (job.length > 0) {
current_coop.function = $('input[name="job"]').val();
}
if (street2_input.length > 0) {
current_coop.street2 = street2_input.val();
}
......
......@@ -28,7 +28,8 @@ function display_current_coop_form() {
chgt_shift_btn = $('#change_shift_template');
var ftop_shift = $('#choosen_shift [name="ftop"]'),
m_barcode = form.find('[name="m_barcode"]'),
sex = $('#sex');
sex = $('#sex'),
job = $('#job');
let street2_input = form.find('[name="street2"]'),
phone_input = form.find('[name="phone"]');
......@@ -52,17 +53,20 @@ function display_current_coop_form() {
if (sex.length > 0 && typeof current_coop.sex != "undefined") {
$('input[name="sex"][value="' + current_coop.sex + '"]').prop('checked', true);
}
if (job.length > 0) {
form.find('[name="job"]').val(current_coop.function);
}
// form.find('[name="barcode_base"]').val(current_coop.barcode_base);
form.find('[name="email"]').val(current_coop._id);
if (current_coop.shift_template &&
current_coop.shift_template.data.type == 2 &&
typeof manage_ftop != "undefined" && manage_ftop == true) {
current_coop.shift_template.data.type == 2 &&
typeof manage_ftop != "undefined" && manage_ftop == true) {
$('#choosen_shift input').hide();
ftop_shift.val('Volant');
ftop_shift.show();
} else {
// Bien laisser dans cet ordre
// Bien laisser dans cet ordre
$('#choosen_shift input').show();
ftop_shift.hide();
......@@ -100,13 +104,11 @@ function display_current_coop_form() {
$('#checks').show();
for (var i = 1; i <= current_coop.checks_nb; i++) {
$(check_details).append('<p>Chèque #' + i +' : <input type="text" name="check_' + i + '" class="b_green check_item" required/> € </p>');
$(check_details).append('<p>Chèque #' + i + ' : <input type="text" name="check_' + i + '" class="b_green check_item" required/> € </p>');
}
}
}
var show_change_shift = false;
if (current_coop.shift_template) {
var st = current_coop.shift_template.data;
......@@ -121,25 +123,20 @@ function display_current_coop_form() {
place = 'Bureau';
}
form.find('[name="place"]').val(place);
if (current_coop.coop_msg) {
show_change_shift = true;
}
} else {
show_change_shift = true;
}
let show_change_shift = current_coop.validation_state === 'waiting_validation_employee';
if (show_change_shift == true) {
chgt_shift_btn.show();
chgt_shift_btn.on('click', open_shift_choice);
}
if (typeof(coop_page) != "undefined") {
if (typeof (coop_page) != "undefined") {
coop_page.show();
}
}
$('#payment_meaning').change(function() {
$('#payment_meaning').change(function () {
if ($(this).val() == 'ch') {
show_checks_nb();
} else {
......
......@@ -48,6 +48,15 @@ def index(request):
return HttpResponse("Le créneau des comités n'est pas configuré dans Odoo !")
else:
context['committees_shift_id'] = committees_shift_id
if getattr(settings, 'USE_EXEMPTIONS_SHIFT_TEMPLATE', False) is True:
exemptions_shift_id = CagetteServices.get_exemptions_shift_id()
if exemptions_shift_id is None:
return HttpResponse("Le créneau des exemptions n'est pas configuré dans Odoo !")
else:
context['exemptions_shift_id'] = exemptions_shift_id
else:
context['exemptions_shift_id'] = 0
if 'no_picture_member_advice' in msettings:
if len(msettings['no_picture_member_advice']['value']) > 0:
......@@ -88,6 +97,10 @@ def inscriptions(request, type=1):
template = loader.get_template('members/inscriptions.html')
committees_shift_id = CagetteServices.get_committees_shift_id()
if getattr(settings, 'USE_EXEMPTIONS_SHIFT_TEMPLATE', False) is True:
exemptions_shift_id = CagetteServices.get_exemptions_shift_id()
else:
exemptions_shift_id = 0
context = {
'type': type, 'title': 'Inscriptions',
'couchdb_server': settings.COUCHDB['url'],
......@@ -99,6 +112,7 @@ def inscriptions(request, type=1):
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
'email_domain': getattr(settings, 'EMAIL_DOMAIN', 'lacagette-coop.fr'),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_job': getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False),
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
......@@ -108,6 +122,7 @@ def inscriptions(request, type=1):
'can_create_binome': getattr(settings, 'CAN_CREATE_BINOME', True),
'prepa_odoo_url' : getattr(settings, 'PREPA_ODOO_URL', '/members/prepa-odoo'),
'committees_shift_id': committees_shift_id,
'exemptions_shift_id': exemptions_shift_id,
}
response = HttpResponse(template.render(context, request))
......@@ -124,6 +139,12 @@ def get_shift_templates_next_shift(request, id):
def prepa_odoo(request):
"""Generate coop subscription form, to be fill by BDM."""
template = loader.get_template('members/prepa_odoo.html')
committees_shift_id = CagetteServices.get_committees_shift_id()
if getattr(settings, 'USE_EXEMPTIONS_SHIFT_TEMPLATE', False) is True:
exemptions_shift_id = CagetteServices.get_exemptions_shift_id()
else:
exemptions_shift_id = 0
context = {'title': 'Préparation Odoo Inscriptions',
'warning_placeholder': 'Par exemple, il manque un chèque',
'couchdb_server': settings.COUCHDB['url'],
......@@ -134,10 +155,14 @@ def prepa_odoo(request):
'input_phone_pattern': getattr(settings, 'INPUT_PHONE_PATTERN', default_input_phone_pattern),
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_job': getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
'db': settings.COUCHDB['dbs']['member'],
'committees_shift_id': committees_shift_id,
'exemptions_shift_id': exemptions_shift_id,
}
# with_addr_complement
# with_second_phone
......@@ -168,6 +193,7 @@ def validation_inscription(request, email):
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'input_phone_pattern': getattr(settings, 'INPUT_PHONE_PATTERN', default_input_phone_pattern),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_job': getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
......@@ -290,7 +316,8 @@ def record_service_presence(request):
stid = int(request.POST.get("stid", 0)) # shift_ticket_id
cancel = request.POST.get("cancel") == 'true'
typeAction = str(request.POST.get("type"))
coop_logger.info("Enregistrement presence : mid = %s, rid = %s, sid = %s, stid = %s, cancel = %s, typeAction = %s",
str(mid), str(rid), str(sid), str(stid), str(cancel), typeAction)
app_env = getattr(settings, 'APP_ENV', "prod")
if (rid > -1 and mid > 0):
overrided_date = ""
......@@ -312,6 +339,7 @@ def record_service_presence(request):
res['update'] = 'ok'
else:
res['update'] = 'ko'
coop_logger.info("Résultat update record_service_presence : %s", res['update'])
if res['update'] == 'ok':
members = CagetteMember.search('id', mid)
m = members[0]
......@@ -327,6 +355,7 @@ def record_service_presence(request):
except Exception as e:
res['error'] = str(e)
coop_logger.error("Erreur record_service_presence : %s", str(e))
return JsonResponse({'res': res})
def easy_validate_shift_presence(request):
......
......@@ -81,6 +81,12 @@
gap: 10px;
}
.member_job_area {
display: flex;
flex-direction: column;
gap: 10px;
}
.edit-btn{
cursor: pointer;
}
......@@ -97,6 +103,9 @@
#edit_phone_form #mobile_form {
margin: 5px 0;
}
#edit_job_form {
display: none ;
}
@media screen and (max-width: 992px) {
......
......@@ -87,6 +87,10 @@ body {
}
}
#comite_my_shifs_message {
min-width: 20em;
}
#home_incoming_services {
min-height: 80px;
display: flex;
......
......@@ -4,19 +4,36 @@ function init_my_shifts_tile() {
if (incoming_shifts.length === 0) {
$("#home_tile_my_services #home_incoming_services").text("Aucun service à venir...");
} else {
$("#home_tile_my_services #home_incoming_services").empty();
let cpt = 0;
if (partner_data.comite === "True") {
let message = $('#comite_my_shifs_message').clone()
message.find('[data-type="nb_of_shifs_state"] [data-type="shifts_nb"]').text(partner_data.final_ftop_point)
if (Math.abs(partner_data.final_ftop_point) > 1) {
message.find('[data-type="nb_of_shifs_state"] [data-type="service_txt"]').text("services")
}
// let's get next ftop shift (incoming_shifts is ordered)
if (incoming_shifts.length > 0) {
const next_shift = incoming_shifts[0]
let ns_date = new Date(next_shift.date_begin)
const date_options = {dateStyle: "short"}
message.find('[data-type="next_ftop_shift_date"]').text(ns_date.toLocaleDateString('fr-FR', date_options))
}
$("#home_tile_my_services .tile_content").html(message)
} else {
$("#home_tile_my_services #home_incoming_services").empty();
let cpt = 0;
for (shift of incoming_shifts) {
if (cpt === 3) {
break;
} else {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
for (shift of incoming_shifts) {
if (cpt === 3) {
break;
} else {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#home_tile_my_services #home_incoming_services").append(shift_line_template.html());
$("#home_tile_my_services #home_incoming_services").append(shift_line_template.html());
cpt++;
cpt++;
}
}
}
}
......
......@@ -49,6 +49,19 @@ function init_my_info() {
$(".member_phone_line").hide();
}
$(".member_job").empty();
if (partner_data.function !== "" && partner_data.function !== "False" && partner_data.function !== false && partner_data.function !== null) {
$(".member_job")
.append(partner_data.function)
.show();
} else {
$(".member_job").hide();
}
if ($(".member_job").text() === "") {
$(".member_job_line").hide();
}
$('#edit_address').off('click')
.on('click', () => {
$("#street_form").val(partner_data.street.replace(/&#39;/g, "'"));
......@@ -90,12 +103,30 @@ function init_my_info() {
});
$('#save_edit_phone').off('click')
.on('click', () => {
console.log('ici');
data =[];
data['phone']= $("#phone_form").val();
data['mobile']= $("#mobile_form").val();
saveInfo(data, 'phone');
});
$('#edit_job').off('click')
.on('click', () => {
if (partner_data.function === "False") partner_data.function = "";
$("#job_form").val(partner_data.function);
$('#edit_job_value').hide();
$('#edit_job_form').show();
});
$('#cancel_edit_job').off('click')
.on('click', () => {
$('#edit_job_form').hide();
$('#edit_job_value').show();
});
$('#save_edit_job').off('click')
.on('click', () => {
console.log('ici');
data =[];
data['function']= $("#job_form").val();
saveInfo(data, 'function');
});
}
......@@ -127,6 +158,10 @@ function saveInfo(data, field) {
$('#edit_phone_form').hide();
$('#edit_phone_value').show();
}
if (field == 'function') {
$('#edit_job_form').hide();
$('#edit_job_value').show();
}
},
error: function(error) {
console.log(error);
......
......@@ -177,40 +177,56 @@ function init_history() {
function init_incoming_shifts() {
$(".loading-incoming-shifts").hide();
$("#incoming_shifts").show();
if (incoming_shifts.length === 0) {
$("#incoming_shifts").text("Aucun service à venir...");
if (partner_data.comite === "True") {
let message = $('#comite_my_shifs_message').clone()
message.find('[data-type="nb_of_shifs_state"] [data-type="shifts_nb"]').text(partner_data.final_ftop_point)
if (Math.abs(partner_data.final_ftop_point) > 1) {
message.find('[data-type="nb_of_shifs_state"] [data-type="service_txt"]').text("services")
}
// let's get next ftop shift (incoming_shifts is ordered)
if (incoming_shifts.length > 0) {
const next_shift = incoming_shifts[0]
let ns_date = new Date(next_shift.date_begin)
const date_options = {dateStyle: "short"}
message.find('[data-type="next_ftop_shift_date"]').text(ns_date.toLocaleDateString('fr-FR', date_options))
}
$("#incoming_shifts_area").html(message)
} else {
$("#incoming_shifts").empty();
if (incoming_shifts.length === 0) {
$("#incoming_shifts").text("Aucun service à venir...");
} else {
$("#incoming_shifts").empty();
for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
if (partner_data.associated_partner_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.associated_partner_name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
}
} else if (partner_data.parent_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.parent_name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
if (partner_data.associated_partner_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.associated_partner_name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
}
} else if (partner_data.parent_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.parent_name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
}
}
}
$("#incoming_shifts").append(shift_line_template.html());
$("#incoming_shifts").append(shift_line_template.html());
}
}
}
}
}
function init_my_shifts() {
......
......@@ -119,7 +119,6 @@ function add_or_change_shift(new_shift_id) {
error: function(error) {
closeModal();
selected_shift = null;
if (error.status === 400 && 'msg' in error.responseJSON && error.responseJSON.msg === "Old service in less than 24hours.") {
alert(`Désolé ! Le service que tu souhaites échanger démarre dans moins de 24h. ` +
`Afin de faciliter la logistique des services, il n'est plus possible de l'échanger. ` +
......@@ -464,7 +463,7 @@ function init_shifts_list() {
*/
function init_calendar_page() {
let template_explanations = $("#calendar_explaination_template");
let event_src = '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id;
if (vw <= 992) {
$(".loading-calendar").show();
......@@ -556,7 +555,7 @@ function init_calendar_page() {
contentHeight: "auto",
eventDisplay: "block",
hiddenDays: hidden_days,
events: '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id,
events: event_src,
eventClick: function(info) {
if (!$(info.el).hasClass("shift_booked") && !$(info.el).hasClass("shift_booked_makeup")) {
const new_shift_id = info.event.id;
......@@ -656,7 +655,7 @@ function init_calendar_page() {
calendar.render();
}
function init_read_only_calendar_page() {
async function init_read_only_calendar_page() {
let template_explanations = $("#calendar_explaination_template");
if (vw <= 992) {
......@@ -680,8 +679,8 @@ function init_read_only_calendar_page() {
if (incoming_shifts !== null) {
init_shifts_list();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_shifts_list);
await load_partner_shifts(partner_data.concerned_partner_id)
init_shifts_list();
}
if (should_select_makeup()) {
......@@ -718,7 +717,16 @@ function init_read_only_calendar_page() {
const hidden_days = days_to_hide.length > 0 ? $.map(days_to_hide.split(", "), Number) : [];
const calendarEl = document.getElementById('read_only_calendar');
let event_src = '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id;
if (partner_data.comite === "True") {
let next_evts = []
if (incoming_shifts.length > 0) {
incoming_shifts.forEach((s) => {
next_evts.push({id: s.id, title: 'Prélèvement 1 point', allDay: true, start: s.date_begin})
});
}
event_src = next_evts
}
calendar = new FullCalendar.Calendar(calendarEl, {
locale: 'fr',
initialView: default_initial_view,
......@@ -734,7 +742,7 @@ function init_read_only_calendar_page() {
contentHeight: "auto",
eventDisplay: "block",
hiddenDays: hidden_days,
events: '/shifts/get_list_shift_calendar/' + partner_data.concerned_partner_id,
events: event_src,
eventDidMount: function() {
// Calendar is hidden at first on mobile to hide header change when data is loaded
$(".loading-calendar").hide();
......
......@@ -69,6 +69,8 @@ def index(request, exception=None):
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
if hasattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX'):
context['ask_for_sex'] = settings.SUBSCRIPTION_ASK_FOR_SEX
if hasattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB'):
context['ask_for_job'] = settings.SUBSCRIPTION_ASK_FOR_JOB
if hasattr(settings, 'SUBSCRIPTION_ADD_STREET2'):
context['ask_for_street2'] = settings.SUBSCRIPTION_ADD_STREET2
if hasattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE'):
......@@ -162,6 +164,14 @@ def index(request, exception=None):
context['helper_unsubscribe_form_link'] = msettings['helper_unsubscribe_form_link']['value'] if 'helper_unsubscribe_form_link' in msettings else ''
context['covid_form_link'] = msettings['covid_form_link']['value'] if 'covid_form_link' in msettings else ''
context['covid_end_form_link'] = msettings['covid_end_form_link']['value'] if 'covid_end_form_link' in msettings else ''
if getattr(settings, 'USE_EXEMPTIONS_SHIFT_TEMPLATE', False) is True:
exemptions_shift_id = CagetteServices.get_exemptions_shift_id()
if exemptions_shift_id is None:
return HttpResponse("Le créneau des exemptions n'est pas configuré dans Odoo !")
else:
context['exemptions_shift_id'] = exemptions_shift_id
else:
context['exemptions_shift_id'] = 0
else:
# may arrive when switching database without cleaning cookie
return redirect('/website/deconnect')
......
......@@ -64,6 +64,7 @@ class Order(models.Model):
for p in res:
pids.append(p['product_id'][0])
partner_id = p['partner_id'][0]
used_coeffs = None
if len(pids) > 0:
# Adding barcode and other data for every purchased product
f = ['barcode', 'product_tmpl_id', 'shelf_id']
......@@ -356,7 +357,7 @@ class Orders(models.Model):
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
bc_pattern = re.compile('^' + fixed_prefix)
lines_data = Orders.get_lines(oids)
for l in lines_data['lines']:
for l in lines_data:
if not (bc_pattern.match(str(l['barcode'])) is None):
if not (l['product_tmpl_id'] in labels_data):
labels_data[l['product_tmpl_id']] = 0
......
......@@ -20,7 +20,7 @@ def helper(request):
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': settings.ODOO['url'],
'odoo_server': getattr(settings, 'ODOO_PUBLIC_URL', settings.ODOO['url']),
'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''),
'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')
......
......@@ -182,7 +182,7 @@ def retrieve_odoo_coop_data (coop_ids):
for coop in coops:
if not (str(coop['barcode_base']) in coops_dict.keys()):
coops_dict[str(coop['barcode_base'])] = \
str(coop['barcode_base']) + ' - ' + coop['name']
str(coop['barcode_base']) + ' - ' + coop['name'].replace(';','')
return coops_dict
......
.green {color:#60B000;}
.b_green, .b_more_than_50pc {background:#d9ebd2}
.b_green, .b_more_than_50pc {background:#d9ebd2 !important;}
.orange {color:#FFA500;}
.b_orange, .b_less_than_50pc {background:#F0B000 !important;}
.yellow {color:#F0B000;}
.b_yellow {background: #fcf3cc;}
.b_yellow {background: #fcf3cc !important;}
.red {color:#FF0000;}
.b_red, .b_less_than_25pc {background:#ff3333 !important;}
.loading {background-image: url("/static/img/ajax-loader.gif"); background-repeat:no-repeat; background-position: center; background-color: #efefef;}
......
......@@ -447,17 +447,21 @@ function shift_loc_selection() {
st_loc_buttons.removeClass('highlighted');
clicked.addClass('highlighted');
if (clicked.data('select') != 'Volant') {
if (clicked.data('select') !== 'Volant' && clicked.data('select') !== 'Exemption') {
retrieve_and_draw_shift_tempates();
} else {
} else if (clicked.data('select') === 'Volant') {
//shift_templates[volant] is not always set (when call from bdm interface)
if (typeof volant !== "undefined" && typeof shift_templates[volant] !== "undefined") {
subscribe_shift(volant);
}
} else if (clicked.data('select') === 'Exemption') {
subscribe_shift(exemptions_shift_id);
}
}
st_loc_buttons.click(shift_loc_selection);
week_types.find('input').change(filter_weeks);
week_types.find('input').change(() => {
filter_weeks({shift_listener: true});
});
......@@ -60,6 +60,11 @@
.replace( /ã/g, 'a' )
.replace( /õ/g, 'o' )
.replace( /ç/g, 'c' )
.replace( /â/g, 'a' )
.replace( /à/g, 'a' )
.replace( /ù/g, 'u' )
.replace( /ï/g, 'i' )
.replace( /œ/g, 'oe' )
.replace( /ì/g, 'i' );
}
......
......@@ -57,6 +57,8 @@ class CagetteProduct(models.Model):
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight', 'meal_voucher_ok']
if not getattr(settings, 'SHOW_MEAL_VOUCHER_OK_LINE_IN_PRODUCT_INFO_FOR_LABEL', True):
fields.remove('meal_voucher_ok')
additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
fields += additionnal_fields
product_data = api.search_read('product.product', cond, fields)
......
......@@ -230,6 +230,9 @@ class CagetteReception(models.Model):
products are updated with new vendor prices"""
result = None
if self.o_api == None:
return 'error : cant reach odoo'
res = self.o_api.execute('purchase.order', 'action_view_picking', [self.id])
new_x_reception_status = ''
if res:
......@@ -261,7 +264,8 @@ class CagetteReception(models.Model):
new_x_reception_status += '/error_uprice'
if new_x_reception_status == '':
new_x_reception_status = 'done'
self.print_shelf_labels_for_updated_prices(price_update['lines'])
if getattr(settings, 'RECEPTION_SHELF_LABEL_PRINT', False) is True:
self.print_shelf_labels_for_updated_prices(price_update['lines'])
if result != 'already done':
self.o_api.update('purchase.order', [self.id], {'x_reception_status': new_x_reception_status})
......
......@@ -221,11 +221,19 @@ div#container_edition {
}
tr.even td.row_product_no_qty {
background-color: #f0ad4e; /*#ec971f*/
background-color: #F2B969 !important;
}
tr.odd td.row_product_no_qty {
background-color: rgb(236, 182, 106); /*#ec971f*/
background-color: #f0ad4e !important;
}
tr.even td.row_product_qty_changed {
background-color: #ffc0b7 !important;
}
tr.odd td.row_product_qty_changed {
background-color: #FFB7AD !important;
}
.add_products_button_container {
......
......@@ -40,6 +40,8 @@ var dbc = null,
sync = null,
fingerprint = null;
let lastKeypressTime = 0;
/* UTILS */
function back() {
......@@ -269,9 +271,9 @@ function resetPopUpButtons() {
/* FETCH SERVER DATA */
function store_received_product_coeffs(coeffs) {
for (let i=0;i<coeffs.length;i++) {
if(product_coeffs.indexOf(coeffs[i]) == -1)
product_coeffs.push(coeffs[i])
for (let i=0; i<coeffs.length; i++) {
if (product_coeffs.indexOf(coeffs[i]) == -1)
product_coeffs.push(coeffs[i]);
}
}
......@@ -525,10 +527,16 @@ function initLists() {
if ('barcode' in full) {
display_barcode = full.barcode;
}
let supplier_code = "Aucune";
if ('supplier_code' in full && full.supplier_code) {
supplier_code = full.supplier_code;
}
return '<div class="tooltip">' + data
+ ' <span class="tooltiptext tt_twolines">Code barre : '
+ display_barcode + '</span> </div>';
+ ' <span class="tooltiptext tooltip-lg tt_twolines">Code barre : '
+ display_barcode
+ ' Réf. fournisseur : '
+ supplier_code + '</span> </div>';
}
},
{ data:"product_uom.1",
......@@ -604,10 +612,17 @@ function initLists() {
if ('barcode' in full) {
display_barcode = full.barcode;
}
let supplier_code = "Aucune";
if ('supplier_code' in full && full.supplier_code) {
supplier_code = full.supplier_code;
}
let display = '<div class="tooltip">' + data
+ ' <span class="tooltiptext tt_twolines">Code barre : '
+ display_barcode + '</span> </div>';
+ ' <span class="tooltiptext tooltip-lg tt_twolines">Code barre : '
+ display_barcode
+ ' Réf. fournisseur : '
+ supplier_code + '</span> </div>';
if (full.supplier_shortage) {
display += ' <div class="tooltip"><i class="fas fa-info-circle"></i>'
......@@ -704,6 +719,17 @@ function initLists() {
$(cell_node).addClass('row_product_no_qty');
}
} else if (
row_data !== undefined
&& row_data.product_qty !== 0
&& 'old_qty' in row_data
&& row_data.old_qty != row_data.product_qty
) {
for (var j = 0; j < row.cells.length; j++) {
const cell_node = row.cells[j];
$(cell_node).addClass('row_product_qty_changed');
}
}
}
});
......@@ -735,6 +761,17 @@ function initLists() {
$(cell_node).addClass('row_product_no_qty');
}
} else if (
row_data !== undefined
&& row_data.product_qty !== 0
&& 'old_qty' in row_data
&& row_data.old_qty != row_data.product_qty
) {
for (var j = 0; j < row.cells.length; j++) {
const cell_node = row.cells[j];
$(cell_node).addClass('row_product_qty_changed');
}
}
}
});
......@@ -1248,7 +1285,7 @@ function editProductInfo (productToEdit, value = null, batch = false) {
// Check if the product is already in the 'updated' list
var index = searchUpdatedProduct();
var firstUpdate = false;
var isValid = false;
var isValid = false; // "valid" == no change from initial value
let newValue = value;
var addition = false;
......@@ -1327,7 +1364,8 @@ function editProductInfo (productToEdit, value = null, batch = false) {
firstUpdate = (index == -1); //first update
if (productToEdit.product_qty != newValue) {
if (firstUpdate) {
// If no old_qty in productToEdit, product qty wasn't edited before
if (productToEdit.old_qty === undefined) {
productToEdit.old_qty = productToEdit.product_qty;
} else {
//if it is not the first update AND newValue is equal to the validation qty then the product is valid (qty not changed)
......@@ -1358,28 +1396,27 @@ function editProductInfo (productToEdit, value = null, batch = false) {
try {
// Let's compute product final price, using coeffs.
let computing_shelf_price_details = {base_value: productToEdit.new_shelf_price, intermediate_values: []};
for (let k = 1; k <10; k++) {
if (typeof productToEdit['coeff' + k + '_id'] !== "undefined") {
product_coeffs.forEach(
(coeff) => {
if (coeff.id == productToEdit['coeff' + k + '_id']) {
if (coeff.operation_type == "fixed") {
productToEdit.new_shelf_price += coeff.value;
computing_shelf_price_details.intermediate_values.push({msg: "Found fixed coeff " + coeff.value, new_value: productToEdit.new_shelf_price})
} else if (coeff.operation_type == "multiplier") {
productToEdit.new_shelf_price *= (1 + coeff.value);
computing_shelf_price_details.intermediate_values.push({msg: "Found multiplier coeff " + coeff.value, new_value: productToEdit.new_shelf_price})
}
product_coeffs.forEach((coeff) => {
if (coeff.id == productToEdit['coeff' + k + '_id']) {
if (coeff.operation_type == "fixed") {
productToEdit.new_shelf_price += coeff.value;
computing_shelf_price_details.intermediate_values.push({msg: "Found fixed coeff " + coeff.value, new_value: productToEdit.new_shelf_price});
} else if (coeff.operation_type == "multiplier") {
productToEdit.new_shelf_price *= (1 + coeff.value);
computing_shelf_price_details.intermediate_values.push({msg: "Found multiplier coeff " + coeff.value, new_value: productToEdit.new_shelf_price});
}
}
)
});
}
}
productToEdit.new_shelf_price *= productToEdit.tax_coeff;
computing_shelf_price_details.intermediate_values.push({msg: "Applying tax coeff " + productToEdit.tax_coeff, new_value: productToEdit.new_shelf_price})
computing_shelf_price_details.intermediate_values.push({msg: "Applying tax coeff " + productToEdit.tax_coeff, new_value: productToEdit.new_shelf_price});
productToEdit.new_shelf_price = productToEdit.new_shelf_price.toFixed(2);
computing_shelf_price_details.final_value = productToEdit.new_shelf_price
productToEdit.computing_shelf_price_details = computing_shelf_price_details
computing_shelf_price_details.final_value = productToEdit.new_shelf_price;
productToEdit.computing_shelf_price_details = computing_shelf_price_details;
} catch (e) {
productToEdit.new_shelf_price = null;
err = {msg: e.name + ' : ' + e.message, ctx: 'computing new_shelf_price'};
......@@ -1397,9 +1434,7 @@ function editProductInfo (productToEdit, value = null, batch = false) {
// If the product info has been updated and for the first time
if (firstUpdate) {
updatedProducts.push(productToEdit);
//if product is validated thru edition -> add to valid_products
//if product is validated thru edition without change -> add to valid_products
if (isValid) {
// Create 'valid_products' list in order if not exists
if (!orders[productToEdit.id_po]['valid_products']) {
......@@ -1407,6 +1442,8 @@ function editProductInfo (productToEdit, value = null, batch = false) {
}
orders[productToEdit.id_po]['valid_products'].push(productToEdit['id']);
} else {
updatedProducts.push(productToEdit);
// Create 'updated_products' list in order if not exists
if (!orders[productToEdit.id_po]['updated_products']) {
orders[productToEdit.id_po]['updated_products'] = [];
......@@ -1513,6 +1550,7 @@ function print_product_labels() {
$.ajax("../../orders/print_product_labels?oids=" + group_ids.join(','))
.done(function(data) {
let success = false;
if (typeof data.res !== "undefined") {
if (typeof data.res.error === "undefined") {
success = true;
......@@ -1522,7 +1560,7 @@ function print_product_labels() {
alert("l' impression des étiquettes à coller sur les articles vient d'être lancée.");
$('#barcodesToPrint').hide();
} else {
alert("Une erreur est survenue.")
alert("Une erreur est survenue.");
}
});
} else {
......@@ -2560,6 +2598,19 @@ function init_dom(partners_display_data) {
// Barcode reader
$(document).pos();
$(document).on('keydown','#edition_input',function(event) {
const keypressTime = event.timeStamp;
const timeDifference = keypressTime - lastKeypressTime;
lastKeypressTime = keypressTime;
// Assuming a scanner would input faster than 50ms between keystrokes
if (timeDifference < 50) {
// Looks like scanner input, ignore or handle differently
event.preventDefault();
// You can display a message or handle the input differently
alert("Vous ne pouvez pas scanner pour saisir une quantité.");
}
});
$(document).on('scan.pos.barcode', function(event) {
//access `event.code` - barcode data
var barcode = event.code;
......
argon2-cffi==20.1.0
certifi==2020.11.8
cffi==1.14.4
chardet==3.0.4
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
certifi==2022.6.15
cffi==1.15.1
charset-normalizer==2.1.0
CouchDB==1.2
Django==2.2.17
django-cors-headers==3.5.0
et-xmlfile==1.0.1
idna==2.10
jdcal==1.4.1
openpyxl==3.0.5
Pillow==8.0.1
pycparser==2.20
PyMySQL==0.10.1
python-dateutil==2.8.1
pytz==2020.4
reportlab==3.5.55
requests==2.25.0
six==1.15.0
sqlparse==0.4.1
urllib3==1.26.2
Django==2.2.28
django-cors-headers==3.11.0
et-xmlfile==1.1.0
idna==3.3
openpyxl==3.0.10
Pillow==9.2.0
pycparser==2.21
PyMySQL==1.0.2
python-dateutil==2.8.2
pytz==2022.1
reportlab==3.6.10
requests==2.28.1
six==1.16.0
sqlparse==0.4.2
urllib3==1.26.9
......@@ -17,7 +17,8 @@ class CagetteSales(models.Model):
# Get pos orders
cond = [['date_order', '>=', date_from], ['date_order', '<=', date_to]]
fields = ['partner_id', 'statement_ids', 'name']
orders = self.o_api.search_read('pos.order', cond, fields)
orders = self.o_api.search_read('pos.order', cond, fields, limit=50000)
# Get bank statements of these sessions
statements = []
statements_partners = {}
......@@ -25,11 +26,16 @@ class CagetteSales(models.Model):
for o in orders:
statements = statements + o["statement_ids"]
for s in o["statement_ids"]:
statements_partners[s] = o["partner_id"][1]
if o["partner_id"] is False:
statements_partners[s] = "Membre non renseigné"
else:
statements_partners[s] = o["partner_id"][1]
statements_orders[s] = o["name"]
# Get payment lines
cond = [['id', 'in', statements]]
fields = ["amount", "journal_id", "create_date"]
fields = ["amount", "journal_id", "create_date", "meal_voucher_issuer"]
payments = self.o_api.search_read('account.bank.statement.line', cond, fields, order="create_date ASC", limit=50000)
try:
for payment in payments:
......@@ -41,7 +47,8 @@ class CagetteSales(models.Model):
"payments": [
{
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
"journal_id": payment["journal_id"],
"meal_voucher_issuer": payment["meal_voucher_issuer"]
}
]
})
......
......@@ -49,13 +49,11 @@ function display_orders(orders) {
{
data:"partner",
title:"Membre",
width: "40%"
title:"Membre"
},
{
data:"total_amount",
title: "Montant dû",
className:"dt-body-center",
render: function (data) {
return parseFloat(data).toFixed(2) + ' €';
}
......@@ -72,9 +70,16 @@ function display_orders(orders) {
}
res += "</ul>";
return res;
return res;
}
}
},
{
data:"payments[0].meal_voucher_issuer",
title: "Émetteur CB Déj",
render: function (data) {
return data === 'false' || data == false ? '' : data;
}
},
],
order: [
[
......
mail_server_password = "change me !"
coops_mails = {
'lacagette': 'change me on preprod only !',
'supercafoutch': 'change me on preprod only !',
'graoucoop': 'change me on preprod only !'
}
coops_usernames = {
'lacagette' : 'Antonin',
'supercafoutch' : 'Marin',
'graoucoop' : 'Julien'
}
coops_preprod_urls = {
'lacagette': 'https://preprod.odoo.lacagette-coop.fr',
'supercafoutch': 'https://preprod.odoo.supercafoutch.cooperatic.fr/',
'graoucoop': 'https://preprod.odoo.graoucoop.cooperatic.fr'
}
\ No newline at end of file
#!/bin/bash
ci_dir="/home/django/third-party/scripts/src"
ci_data_dir="/home/django/third-party/scripts/data"
coop=$1
instance=$2
#move to the third argument
shift
shift
send_mails=true
#https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
while [[ "$#" -gt 0 ]]; do
case $1 in
-n|--nomail) send_mails=false ;;
*) echo "arguments : ${coop} ${instance} $1 not supported (Unknown parameter passed: $1)"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "arguments : ${coop} ${instance} $1 not supported (Unknown parameter passed: $1)"
fi
exit 1 ;;
esac
shift
done
if ! python3 "${ci_dir}/supported_coops.py" "${coop}" "${instance}"; then
echo "arguments : ${coop} ${instance} not supported"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "arguments : ${coop} ${instance} not supported"
fi
exit 1
fi
now=$( date +%Y%m%d_%H%M%S )
source "${ci_dir}/prepare_mail.sh"
source "${ci_dir}/update_instance.sh"
if [[ "${instance}" == "preprod" ]]; then
repo="third-party"
echo "Prepare mail" "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}"
prepare_mail "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}"|| exit 1
repo="Odoo"
echo "Prepare mail" "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}"
prepare_mail "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}" || exit 1
fi
repo="third-party"
echo "Update instance" "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}"
update_instance "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}" || exit 1
repo="Odoo"
echo "Update instance" "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}"
update_instance "${coop}" "${instance}" "${repo}" "${now}" "${send_mails}" "${ci_dir}" "${ci_data_dir}" || exit 1
if [[ "${instance}" == "preprod" ]] && ${send_mails}; then
python3 "${ci_dir}/send_mail.py" "${ci_data_dir}/ci_diff_third-party_${now}.txt" "${ci_data_dir}/ci_diff_Odoo_${now}.txt" "${coop}"
fi
//Ces notes ont été prises lors de la conversion de l\'architecture cagette préprod vers la nouvelle architecture.
//Elles peuvent servir à faire de même sur la prod !
//USERS, HOME DIRS, FILES, RIGHTS
useradd -m odoo
usermod -s /bin/bash odoo
useradd -m django
usermod -s /bin/bash django
cd /home/lacagette
mv Odoo odoo-server.conf odoo-server.log odoo-server.log.1 odoo-server.log.2 scripts .local /home/odoo/.
//un nouveau odoo-server.log a été créé automatiquement, il faudra gérer ça
mv -f third-party cronlogs .virtualenvs /home/django/.
mkdir /home/odoo/cronlogs
cp -r /home/odoo/scripts /home/django/scripts
cp -r /home/lacagette/.ssh /home/django
cp -r /home/lacagette/.ssh /home/odoo
chown -R odoo:odoo /home/odoo
chown -R django:django /home/django
chown -R django:django /shared_dir/django_static
//PYTHON VENV
su django
cd /home/django/.virtualenvs
rm -rf third-party .gitignore bin lib pyvenv.cfg
source /usr/local/bin/virtualenvwrapper.sh
mkvirtualenv -p python3.8 -r /home/django/third-party/requirements.locked.txt third-party
exit
//PSQL
//key to understand : "A user is a role with the ability to log in (the role has the LOGIN attribute)."
su postgres
psql
create role odoo login;
grant lacagette to odoo;
alter database lacagette owner to odoo;
select pg_terminate_backend(pid) from pg_stat_activity where pid <> pg_backend_pid() and datname = 'lacagette';
alter database lacagette rename to odoo;
GRANT CONNECT ON DATABASE odoo TO metabase;
GRANT USAGE ON SCHEMA public TO metabase;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase;
exit
nano /etc/postgresql/10/main/pg_hba.conf puis remplacer lacagette par odoo sur la ligne qui concerne metabase
exit
service postgresql restart
//CONFIGS
sed -i -e 's/\/lacagette\//\/odoo\//g' /home/odoo/odoo-server.conf
sed -i -e 's/db_name = lacagette/db_name = odoo/g' /home/odoo/odoo-server.conf
sed -i -e 's/db_user = lacagette/db_user = odoo/g' /home/odoo/odoo-server.conf
sed -i -e 's/dbfilter = lacagette/dbfilter = odoo/g' /home/odoo/odoo-server.conf
sed -i -e "s/'lacagette'/'odoo'/g" /home/django/third-party/outils/settings_secret.py
sed -i -e 's/lacagette/django/g' /etc/scripts/django.sh
sed -i -e 's/lacagette/django/g' /etc/init.d/django
sed -i -e 's/lacagette/odoo/g' /etc/init.d/odoo
//CRONS
crontab -l -u lacagette | grep -v "sync_files.sh" | crontab -u django -
crontab -l -u lacagette | grep "sync_files.sh" | crontab -u odoo -
crontab -r -u lacagette
//FILESTORE ISSUE
su postgres
psql
\c odoo
delete from ir_attachment where mimetype in ('text/css','application/javascript');
//puis deux restarts nécessaires... de odoo
restart django.
//METABASE : change database name in settings
\ No newline at end of file
prepare_mail() {
#Read arguments and set variables
coop=$1
instance=$2
repo=$3
now=$4
send_mails=$5
ci_dir=$6
ci_data_dir=$7
if [[ ${repo} == "third-party" ]]; then
user="django"
elif [[ ${repo} == "Odoo" ]]; then
user="odoo"
else
echo "Incorrect repo name : ${repo}"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : incorrect repo name"
fi
exit 1
fi
cd /home/${user}/${repo}
su ${user} -c "git fetch --all"
#Check branch
branch_found_str=$( su ${user} -c "git ls-remote origin ${coop}_prod" ) #seems that git ls-remote is not "failing" so we need to check output
if [ -z "${branch_found_str}" ]; then
#branch prod does not exist, cannot go on
echo "${coop} ${instance} ${repo}: branch ${coop}_prod does not exist"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : branch ${coop}_prod does not exist"
fi
exit 1
fi
###COMPARE BRANCHES AND DECIDE IF WE NEED TO CREATE A FILE CONTAINING NEW COMMITS###
#initialize file
if ! test -f "${ci_data_dir}/ci_diff_${repo}_last.txt"; then
touch "${ci_data_dir}/ci_diff_${repo}_last.txt"
fi
#new_commits="$( git log origin/${coop}_prod...origin/dev_cooperatic --no-merges --oneline )" (old version)
#Let's break down the new big line below :
#origin/${coop}_prod...origin/dev_cooperatic : we look for difference between develop branch and prod branch
#--pretty=format:'%s' : get rid of commit id
#the big grep+sed group : discard lines not maching pattern "merge [0-9]\+-... into dev_cooperatic" and only keep the [0-9]\+-.... part with a link to redmine
new_commits="$( git log origin/${coop}_prod...origin/dev_cooperatic --merges --oneline --pretty=format:'%s' | grep "into 'dev_cooperatic'" | sed "s/Merge branch '//g" | sed "s/' into 'dev_cooperatic'//g" | grep "^[0-9]\+" | sed -E 's/(^[0-9]+)/\<a href=\"https\:\/\/redmine.coopdev.fr\/issues\/\1\"\>\1\<\/a\>/g' )"
if [ -z "${new_commits}" ]; then
echo "${coop} ${instance} ${repo} : no difference between prod and dev_cooperatic"
#There are no difference between prod and dev_cooperatic therefore we don't want to send email for this repo.
#To inform the mailer that there is nothing new, we do not create ci_diff file at current time.
#The mailer will therefore use ci_diff_last to get the list of commits for the client.
#Not only there is nothing new, but also there is just no difference,
#therefore we need to erase the content the ci_diff_last file if not already empty (save time of first time it was erased)
if [[ -s "${ci_data_dir}/ci_diff_${repo}_last.txt" ]]; then
> "${ci_data_dir}/ci_diff_${repo}_last.txt"
fi
else
echo "${new_commits}" > "${ci_data_dir}/ci_diff_${repo}_${now}.txt"
#See if commit difference is the same than last time
if cmp "${ci_data_dir}/ci_diff_${repo}_last.txt" "${ci_data_dir}/ci_diff_${repo}_${now}.txt"; then
#As there are no new commits in prod, we don't want to send email for this repo.
#To indicate that, we delete just created ci_diff file at current time.
rm "${ci_data_dir}/ci_diff_${repo}_${now}.txt"
echo "${coop} ${instance} ${repo} : ci_diff_${repo} files are identical"
else
#We have created new ci_diff.
#Replace old ci_diff_last by new one
rm "${ci_data_dir}/ci_diff_${repo}_last.txt"
cp "${ci_data_dir}/ci_diff_${repo}_${now}.txt" "${ci_data_dir}/ci_diff_${repo}_last.txt"
fi
fi
}
\ No newline at end of file
import smtplib
import sys
from email.message import EmailMessage
import ci_secrets
def run(body):
msg = EmailMessage()
msg['Subject'] = "CoopdevCI : script error"
msg['From'] = "brevo@ksuite-coopdev.fr"
msg['To'] = "yvon.kerdoncuff@gmail.com"
msg.set_content(body)
s = smtplib.SMTP('smtp-relay.sendinblue.com', port=587)
s.login("brevo@ksuite-coopdev.fr", ci_secrets.mail_server_password)
s.send_message(msg)
s.quit()
if __name__ == '__main__':
arg = ""
if len(sys.argv) > 1:
arg = sys.argv[1]
run(arg)
import smtplib
import sys
from email.message import EmailMessage
import ci_secrets
import os
def run(tp_file,od_file,coop):
msg = EmailMessage()
msg['Subject'] = "Coopdev : évolutions à tester"
msg['From'] = "brevo@ksuite-coopdev.fr"
msg['To'] = ci_secrets.coops_mails[coop]
msg['CC'] = "assistance-redmine@coopdev.fr"
msg['Reply-to'] = "assistance-redmine@coopdev.fr"
tp_file_exists = os.path.exists(tp_file)
od_file_exists = os.path.exists(od_file)
if not tp_file_exists and not od_file_exists:
print("MAIL : rien à envoyer")
return
elif not tp_file_exists:
tp_file = "/home/django/third-party/scripts/data/ci_diff_third-party_last.txt"
tp_file_exists = os.path.exists(tp_file)
elif not od_file_exists:
od_file = "/home/django/third-party/scripts/data/ci_diff_Odoo_last.txt"
od_file_exists = os.path.exists(od_file)
body = "Bonjour " + ci_secrets.coops_usernames[coop] +",\n\n"
body = body + "De nouveaux développements en attente de mise en production ont été mis en ligne sur " + ci_secrets.coops_preprod_urls[coop] + ".\n"
body = body + "On te remercie de bien vouloir tester ces évolutions et de nous faire un retour via les liens redmine ci-dessous (si possible), ou en répondant à assistance-redmine@coopdev.fr." + "\n\n\n"
if tp_file_exists:
tp_f = open(tp_file,"r")
body = body + "Evolutions des applications tierces :\n\n" + tp_f.read() + "\n\n"
tp_f.close()
if od_file_exists:
od_f = open(od_file,"r")
body = body + "Evolutions d'odoo :\n\n" + od_f.read() + "\n\n"
tp_f.close()
body = body + "Coopérativement.\nCoopdev Foodcoops"
msg.set_content(body)
s = smtplib.SMTP('smtp-relay.sendinblue.com', port=587)
s.login("brevo@ksuite-coopdev.fr", ci_secrets.mail_server_password)
s.send_message(msg)
s.quit()
print("MAIL : envoi effectué")
if __name__ == '__main__':
arg = ""
if len(sys.argv) > 3:
arg1 = sys.argv[1]
arg2 = sys.argv[2]
arg3 = sys.argv[3]
run(arg1,arg2,arg3)
import sys
from ci_secrets import coops_mails
def is_coop_supported(coop):
return coop in coops_mails and coops_mails[coop]
def is_instance_supported(instance):
return instance == "preprod" or instance == "test" or instance == "dev" or instance == "prod"
def get_mail(coop):
return coops_mails[coop]
if __name__ == '__main__':
coop = ""
instance = ""
if len(sys.argv) == 3:
coop = sys.argv[1]
instance = sys.argv[2]
if is_coop_supported(coop) and is_instance_supported(instance):
sys.exit(0)
else:
sys.exit(1)
update_instance() {
#Read arguments and set variables
coop=$1
instance=$2
repo=$3
now=$4
send_mails=$5
ci_dir=$6
ci_data_dir=$7
if [[ ${repo} == "third-party" ]]; then
user="django"
service="django"
elif [[ ${repo} == "Odoo" ]]; then
user="odoo"
service="odoo"
else
echo "Incorrect repo name : ${repo}"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : incorrect repo name"
fi
exit 1
fi
if [[ "${instance}" == "prod" ]]; then
target="${coop}_prod"
else
target="dev_cooperatic"
fi
#Move to repository directory, fetch all and make sure coop_prod branch exists
cd /home/${user}/${repo}
su ${user} -c "git fetch --all"
branch_found_str=$( su ${user} -c "git ls-remote origin ${coop}_prod" ) #seems that git ls-remote is not "failing" so we need to check output
if [ -z "${branch_found_str}" ]; then
#branch prod does not exist, cannot go on
echo "${coop} ${instance} ${repo}: branch ${coop}_prod does not exist"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : branch ${coop}_prod does not exist"
fi
exit 1
fi
#Make sure config file exists if repo is third-party
if [[ ${repo} == "third-party" ]]; then
if ! test -f "coops_configurations/config_${coop}.py"; then
echo "${coop} ${instance} ${repo}: coops_configurations/config_${coop}.py does not exists"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : coops_configurations/config_${coop}.py does not exists"
fi
exit 1
fi
fi
#Save filepaths of files that are going to be updated if repo is odoo
if [[ ${repo} == "Odoo" ]]; then
echo "git diff --name-only origin/${target}.."
updated_filepaths="$( git diff --name-only origin/${target}.. )"
echo "${updated_filepaths}"
fi
#Update code if needed
new_commits="$( git log origin/${target}... )"
if [ -z "${new_commits}" ]; then
echo "${coop} ${instance} ${repo} : already up to date"
else
#Stop instance
/etc/init.d/${service} stop
#One could argue that git stash is risky on prod,
#but it's way easier to go that way to get rid of template file changes generated on django service restart
if ! su ${user} -c "git stash"; then
#We can not finish the update : restart the system
/etc/init.d/${service} start
echo "${coop} ${instance} ${repo}: could not git stash"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : could not git stash"
fi
exit 1
fi
#Make script robust to any branch change : switch to target !
if ! su ${user} -c "git checkout ${target}"; then
#We can not finish the update : restart the system
#TODO : detect if we have stashed something on previous step to revert it (git stash pop)
/etc/init.d/${service} start
echo "${coop} ${instance} ${repo}: could not git checkout ${target}"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : could not git checkout ${target}"
fi
exit 1
fi
#Pull
if ! su ${user} -c "git pull"; then
#We can not finish the update : restart the system
#TODO : detect if we have stashed something on previous step to revert it (git stash pop)
#TODO : undo previous git checkout if necessary
echo "${coop} ${instance} ${repo}: could not git pull"
if ${send_mails}; then
python3 "${ci_dir}/send_error_mail.py" "${coop} ${instance} ${repo} : could not git pull"
fi
exit 1
fi
#Additionnal update actions
#push tags to remember this prod update point (only on prod)
if [[ "${instance}" == "prod" ]]; then
#TODO : handle errors
su ${user} -c "git tag -a ${coop}_${now} -m autotag"
su ${user} -c "git push origin ${coop}_${now}"
fi
if [[ ${repo} == "third-party" ]]; then
#Replace config file by proper one only if required
if ! cmp "outils/config.py" "coops_configurations/config_${coop}.py"; then
#Config files are different, go for it
mv "outils/config.py" "${ci_data_dir}/ci_old_cfg_file_${now}.py"
cp "coops_configurations/config_${coop}.py" "outils/config.py"
else
echo "config files are identical"
fi
fi
if [[ ${repo} == "Odoo" ]]; then
#Loop on updated_filenames, for each filename, look for the directory of __openerp__.py file
#Ff found, just store the parent directory
SAVEIFS=$IFS
IFS=$'\n'
if [[ ${updated_filepaths} == "" ]]; then
updated_filepaths_array=()
else
read -rd '' -a updated_filepaths_array <<<"${updated_filepaths}"
fi
IFS='/'
l=${#updated_filepaths_array[@]}
declare -A modules #Modules to update
modules_str=""
for (( i=0; i<l; i++ ));
do
read -rd '' -a one_path_array <<<"${updated_filepaths_array[$i]}"
j=0
accu_path="${one_path_array[0]}"
pathlen=${#one_path_array[@]}
while [[ $j -lt ${pathlen} ]] && ! test -f "${accu_path}/__openerp__.py"; do
((j++))
accu_path="${accu_path}/${one_path_array[j]}"
done
#Update key of modules if (we have found openerp.py (j<path) + module has not been added already),
#so that we do not add twice same module in modules_str
#and add module in modules_str
if [[ $j -lt ${pathlen} ]] && [[ ! -v modules["${one_path_array[$j]}"] ]]; then
modules["${one_path_array[$j]}"]=
if [[ ${modules_str} == "" ]]; then
modules_str="${one_path_array[$j]}"
else
modules_str="${modules_str},${one_path_array[$j]}"
fi
fi
done
IFS=$SAVEIFS
echo "${modules_str}"
restart_odoo_cmd="/home/odoo/Odoo/odoo/odoo.py -c /home/odoo/odoo-server.conf"
if [[ -n "${modules_str}" ]]; then
echo "restart odoo service with module update"
su odoo -c "${restart_odoo_cmd} -u${modules_str} --stop-after-init"
fi
fi
#Restart service
echo "Starting ${service} service"
/etc/init.d/${service} start
fi
}
\ No newline at end of file
......@@ -35,6 +35,7 @@ var shelf = null,
all_shelfs = null, // Use get_all_shelfs to access it's value
debounce_timeout = null;
let lastKeypressTime = 0;
/* UTILS */
......@@ -1241,6 +1242,19 @@ function init() {
});
$(document).pos();
$(document).on('keydown','#edition_input',function(event) {
const keypressTime = event.timeStamp;
const timeDifference = keypressTime - lastKeypressTime;
lastKeypressTime = keypressTime;
// Assuming a scanner would input faster than 50ms between keystrokes
if (timeDifference < 50) {
// Looks like scanner input, ignore or handle differently
event.preventDefault();
// You can display a message or handle the input differently
alert("Vous ne pouvez pas scanner pour saisir une quantité.");
}
});
$(document).on('scan.pos.barcode', function(event) {
//access `event.code` - barcode data
var barcode = event.code;
......
......@@ -82,6 +82,8 @@ function init_datatable() {
{
data:"shelf_value",
title:"Valeur théorique du rayon",
visible: false,
name: "shelf_value",
render: function (data, type) {
if (type == "sort" || type == 'type')
return data;
......@@ -324,7 +326,6 @@ $(document).ready(function() {
}
shelfs_table = init_datatable();
get_shelfs_extra_data();
$(document).on('click', 'button.do_shelf_inventory', go_to_shelf_inventory);
$(document).on('click', 'tbody td .delete_ongoing_inv_icon', pre_delete_ongoing_inventory);
......@@ -337,4 +338,8 @@ $(document).ready(function() {
.draw();
});
$(document).on('click', '#show_and_compute_shelf_value', function () {
shelfs_table.column('shelf_value:name').visible(true);
get_shelfs_extra_data();
});
});
......@@ -120,10 +120,9 @@ class CagetteShift(models.Model):
'shift_type', 'date_alert_stop', 'date_delay_stop', 'extension_ids',
'cooperative_state', 'final_standard_point', 'create_date',
'final_ftop_point', 'shift_type', 'leave_ids', 'makeups_to_do', 'barcode_base',
'street', 'street2', 'zip', 'city', 'mobile', 'phone', 'email',
'street', 'street2', 'zip', 'city', 'mobile', 'phone', 'function', 'email',
'is_associated_people', 'parent_id', 'extra_shift_done']
partnerData = self.o_api.search_read('res.partner', cond, fields, 1)
if partnerData:
partnerData = partnerData[0]
if partnerData['is_associated_people']:
......@@ -186,6 +185,19 @@ class CagetteShift(models.Model):
shiftData = self.o_api.search_read('shift.registration', cond, fields, order ="date_begin ASC")
return shiftData
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"""
fields = ['partner_id']
cond = [['state', '=', 'open'], ['date_begin', '>', datetime.datetime.now().isoformat()], ['is_makeup', '=', True]]
shift_data = self.o_api.search_read('shift.registration', cond, fields)
count_dic = {}
for value in shift_data:
if value['partner_id'][0] in count_dic:
count_dic[value['partner_id'][0]] = count_dic[value['partner_id'][0]] + 1
else:
count_dic[value['partner_id'][0]] = 1
return count_dic
def shift_is_makeup(self, id):
"""vérifie si une shift est un rattrapage"""
fields = ["is_makeup", "id"]
......@@ -293,12 +305,12 @@ class CagetteShift(models.Model):
coop_logger.error("Model affect shift nobody found : %s", str(cond))
return response
def cancel_shift(self, idsRegisteur, origin='memberspace'):
def cancel_shift(self, idsRegisteur, origin='memberspace', description=''):
"""Annule un shift"""
fieldsDatas = { "related_shift_state": 'cancel',
"origin": origin,
"state": 'cancel'}
"state": 'cancel',
"cancellation_description": description}
return self.o_api.update('shift.registration', idsRegisteur, fieldsDatas)
def reopen_shift(self, data):
......@@ -307,13 +319,26 @@ class CagetteShift(models.Model):
cond = [['partner_id', '=', int(data['idPartner'])],
['shift_id', '=', int(data['idShift'])],
['state', '=', 'cancel']]
fields = ['id']
fields = ['id','origin']
try:
canceled_res = self.o_api.search_read('shift.registration', cond, fields, 1)
if (len(canceled_res) == 1):
shift_res = canceled_res[0]
fieldsDatas = { "related_shift_state":'open',
"state": 'open'}
"state": 'open',
"is_makeup":data['is_makeup'],
"origin":canceled_res[0]['origin'] + ' reopened from memberspace'}
#following code is required to properly set template_created.
#TODO : factor with set_shift code
shift_type = "standard"
if data['shift_type'] == "ftop" or getattr(settings, 'USE_STANDARD_SHIFT', True) == False:
shift_type = "ftop"
if (shift_type == "standard" and data['is_makeup'] is not True) or shift_type == "ftop":
fieldsDatas['template_created'] = 1 # It's not true but otherwise, presence add 1 standard point , which is not wanted
else: #the else does not exist in set_shift but is mandatory here as template_created value in reopened registration can be any value
fieldsDatas["template_created"] = False
response = self.o_api.update('shift.registration', [shift_res['id']], fieldsDatas)
except Exception as e:
coop_logger.error("Reopen shift : %s", str(e))
......@@ -829,6 +854,23 @@ class CagetteServices(models.Model):
return shift_id
@staticmethod
def get_exemptions_shift_id():
shift_id = None
try:
api = OdooAPI()
res = api.search_read('ir.config_parameter',
[['key','=', 'lacagette_exemptions.exemptions_shift_id']],
['value'])
if len(res) > 0:
try:
shift_id = int(res[0]['value'])
except:
pass
except:
pass
return shift_id
@staticmethod
def get_first_ftop_shift_id():
shift_id = None
try:
......@@ -870,27 +912,36 @@ class CagetteServices(models.Model):
if (len(shift_templ_res) > 0
and
shift_templ_res[0]['shift_template_id'][0] == committees_shift_id):
evt_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
c = [['partner_id', '=', coop_id], ['name', '=', evt_name]]
f = ['create_date']
last_point_mvts = api.search_read('shift.counter.event', c, f,
order ="create_date DESC", limit=1)
ok_for_adding_pt = False
if len(last_point_mvts):
now = datetime.datetime.now()
past = datetime.datetime. strptime(last_point_mvts[0]['create_date'],
'%Y-%m-%d %H:%M:%S')
if (now - past).total_seconds() >= 3600 * 24:
mininum_seconds_interval = getattr(settings, 'MINIMUM_SECONDS_BETWEEN_TWO_COMITEE_VALIDATION', 3600 * 24)
evt_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
if mininum_seconds_interval > 0:
# A constraint has been set to prevent from adding more than 1 point during a time period
# Let's find out when was the last time a "special point" has been addes
c = [['partner_id', '=', coop_id], ['name', '=', evt_name]]
f = ['create_date']
last_point_mvts = api.search_read('shift.counter.event', c, f,
order ="create_date DESC", limit=1)
if len(last_point_mvts):
now = datetime.datetime.now()
past = datetime.datetime. strptime(last_point_mvts[0]['create_date'],
'%Y-%m-%d %H:%M:%S')
if (now - past).total_seconds() >= mininum_seconds_interval:
ok_for_adding_pt = True
else:
ok_for_adding_pt = True
else:
# mininum_seconds_interval is 0 : Point can we added without any condition
ok_for_adding_pt = True
if ok_for_adding_pt is True:
res['evt_id'] = CagetteMember(coop_id).add_pts('ftop', 1, evt_name)
else:
res['error'] = "One point has been added less then 24 hours ago"
res['error'] = "Un point a déjà été ajouté trop récemment."
else:
res['error'] = "Unallowed coop"
res['error'] = "Vous n'avez pas le droit d'ajouter un point."
else:
res['error'] = "Unregistred coop"
else:
......
......@@ -179,14 +179,11 @@ def get_list_shift_calendar(request, partner_id):
def get_list_shift_partner(request, partner_id):
cs = CagetteShift()
shiftData = cs.get_shift_partner(partner_id)
empty_data = False
for value in shiftData:
value['date_begin'] = value['date_begin'] + "Z"
value['date_end'] = value['date_end'] + "Z"
if "Services des comités" in value['shift_id'][1]:
empty_data = True
if empty_data is True:
shiftData = []
return JsonResponse(shiftData, safe=False)
def change_shift(request):
......
......@@ -373,6 +373,8 @@ class CagetteStock(models.Model):
cond = [['qty_available','>', 0], ['active', '=', True]]
fields = ["barcode", "display_name", "qty_available", "standard_price"]
articles = api.search_read('product.product', cond, fields, 1000000)
for a in articles:
a['total'] = a['qty_available'] * a['standard_price']
except Exception as e:
coop_logger.error("Erreur get_valuable_stock : %s", str(e))
return articles
......
......@@ -6,14 +6,29 @@ $(document).ready(function() {
"data": ""
},
"columns":[
{data:"barcode", "title":"Code-barre", "width": "50%"},
{data:"barcode", "title":"Code-barre", "width": "10em"},
{data:"display_name", "title":"Article", "width": "50%"},
{data:"qty_available", "title":"Stock", "width":"15%"
{data:"qty_available", "title":"Stock", "width":"5em",
render: function(data) {
if (data == parseInt(data,10)) {
return data
} else {
return data.toFixed(3)
}
}
},
{data:"standard_price", "title":"Prix achat", "width":"15%"
}
{data:"standard_price", "title":"Prix Achat", "width":"4em",
render: function(data) {
return data.toFixed(2)
}
},
{data: "total", "title": "Total",
render: function(data, type, full) {
return data.toFixed(2)
}
}
],
......@@ -24,7 +39,7 @@ $(document).ready(function() {
"desc"
]
],
"iDisplayLength": 50,
"paging": true,
"language": {
"emptyTable": "Pas de donnée",
"info": "Affiché : lignes _START_ à _END_ sur _TOTAL_",
......@@ -55,6 +70,6 @@ $(document).ready(function() {
className: 'btn--primary btn_export'
},
],
dom: '<lr<t>ip><"clear"><B>',
dom: '<lr<t>ip><"clear"><B>'
});
});
\ No newline at end of file
});
......@@ -28,7 +28,7 @@
<div class="page_content">
<div id="table_top_area">
<h3>Liste des membres devant effectuer un rattrapage</h3>
<h3>Liste des membres ayant au moins un rattrapage à faire</h3>
<div class="table_grouped_action">
<button type="button" class="btn--primary" id="decrement_selected_members_makeups">
-1 rattrapage pour les membres sélectionnés
......@@ -40,7 +40,7 @@
</div>
<div id="add_members_area">
<div id="add_members_form_area">
<h4>Ou, ajouter un rattrapage à un.e membre</h4>
<h4>Ou, ajouter un rattrapage à un.e membre standard</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
......@@ -85,6 +85,7 @@
<script src='{% static "js/all_common.js" %}?v=1651853225'></script>
<script src='{% static "js/admin/manage_makeups.js" %}?v=1651853225'></script>
<script>
var has_committe_shift = '{{has_committe_shift}}'
const extension_duration = {{extension_duration}};
</script>
{% endblock %}
......@@ -96,6 +96,7 @@
var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var committees_shift_id = '{{committees_shift_id}}';
var exemptions_shift_id = '{{exemptions_shift_id}}';
const committees_shift_name = '{{committees_shift_name}}';
var ASSOCIATE_MEMBER_SHIFT = '{{ASSOCIATE_MEMBER_SHIFT}}';
</script>
......
......@@ -51,7 +51,16 @@
</div>
</div>
<div id="templates" style="display:none;"></div>
<div id="templates" style="display:none;">
<div id="modal_delete_shift_registration">
<p>Enlever la présence de <b><span class="member_name"></span></b> au service du <b><span class="service_name"></span></b> ?</p>
<div id="makeup_case_explanation" style="display:none">
<p><i class="fas fa-exclamation-triangle"></i> Ce service est un rattrapage. Le supprimer ajoutera un point au compteur de ce.tte membre.</p>
</div>
<br><label for="cancellation-explanation">Explication : </label><input class="" type="text" id="cancellation-explanation">
<br><br><label for="cancellation-signature">Signature : </label><input class="" type="text" id="cancellation-signature">
</div>
</div>
</div>
<script src='{% static "js/all_common.js" %}?v=1651853225'></script>
......
......@@ -61,6 +61,11 @@
<input type="text" placeholder="email" name="email" value="" pattern="(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*)@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])" required autocomplete="off" />
(*)
</p>
{% if ask_for_job %}
<p>
{% include "members/job_input.html" %}
</p>
{% endif %}
<p>
<input type="number" step="10" min="10" placeholder="Montant souscription" name="subs_cap" id="subs_cap" required/>
<select name="payment_meaning" id="payment_meaning" autocomplete="off" required >
......@@ -81,7 +86,7 @@
{% endif %}
{% if can_create_binome %}
<div>Voulez-vous inscrire la personne en tant que suppléante d'un binôme ?</div>
<div>Voulez-vous inscrire la personne en tant que deuxième personne d'un binôme ?</div>
<label>
<input type="radio" name="binome" id="no_binome" required>
Non
......@@ -197,6 +202,7 @@
var max_begin_hour = '{{max_begin_hour}}';
var email_domain = '{{email_domain}}';
var committees_shift_id = '{{committees_shift_id}}';
var exemptions_shift_id = '{{exemptions_shift_id}}';
</script>
<script src="{% static "js/all_common.js" %}?v=1651853225"></script>
......
<div id="job">
<input type="text" placeholder="Profession" name="job" autocomplete="off" />
</div>
......@@ -106,6 +106,8 @@
var mag_place_string = '{{mag_place_string}}';
var office_place_string = '{{office_place_string}}'
var max_begin_hour = '{{max_begin_hour}}'
var committees_shift_id = '{{committees_shift_id}}';
var exemptions_shift_id = '{{exemptions_shift_id}}';
</script>
<script src="{% static "js/all_common.js" %}"?v=1651853225></script>
<script src="{% static "js/common.js" %}"?v=1651853225></script>
......
......@@ -7,6 +7,11 @@
ou ...
<br />
<button class="flex-container--column" data-select="Volant">Volant</button>
{% if exemptions_shift_id != 0 %}
ou ...
<br />
<button class="flex-container--column" data-select="Exemption">Exempté</button>
{% endif %}
<div class="info"></div>
</div>
<div class="col-5 main_content">
......
......@@ -9,6 +9,11 @@
<br />
<button class="flex-container--column" data-select="Volant">Volant</button>
{% endif %}
{% if exemptions_shift_id != 0 %}
ou ...
<br />
<button class="flex-container--column" data-select="Exemption">Exempté</button>
{% endif %}
<div class="info"></div>
</div>
<div class="col-5 main_content">
......
......@@ -33,6 +33,11 @@
<a id="change_shift_template" class="btn--primary">Changer</a>
</span>
</p>
{% if ask_for_job %}
<p>
{% include "members/job_input.html" %}
</p>
{% endif %}
<p>
<input type="number" step="1" min="1" placeholder="Nombre de parts" name="shares_nb" id="shares_nb" class="b_yellow" required disabled/>
<label for="shares_nb">Parts sociales</label>
......
<div id="faqBDM" class=" mt-3">
<div class="page_title txtcenter">
......@@ -157,8 +158,8 @@
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button ask_bdm_form_link"
id="request_form_link_btn3"
class="btn--primary faq_link_button"
id="late_service_form_link_btn"
>
Oubli validation service
</a>
......
......@@ -132,7 +132,8 @@
</div>
<script>
var max_begin_hour = "{{max_begin_hour}}",
type = 2;
type = 2,
exemptions_shift_id = '{{exemptions_shift_id}}';
</script>
{% endif %}
</div>
......@@ -101,6 +101,14 @@
<h3>Vous êtes inscrit.e dans le service des comités, vous n'avez pas accès au calendrier d'échange des services car vous vous organisez directement avec le responsable du comité. Si vous avez des rattrapages à réaliser, merci de contacter le responsable du comité qui vous aidera à planifier les rattrapages ou trouver une solution.</h3>
<h3>Le calendrier ci-dessous est en lecture seule</h3>
</div>
<div id="comite_my_shifs_message">
<p data-type="nb_of_shifs_state">
Vous avez actuellement <span data-type="shifts_nb"></span> <span data-type="service_txt">service</span> d'avance.
</p>
<p data-type="next_ftop_shift">
Le système informatique viendra prélever un service sur votre compteur le <span data-type="next_ftop_shift_date"></span>.
</p>
</div>
</div>
</div>
......@@ -129,6 +137,8 @@
"partner_id":"{{partnerData.id}}",
"name":"{{partnerData.display_name|safe}}",
"shift_type":"{{partnerData.shift_type}}",
"final_ftop_point":{{partnerData.final_ftop_point}},
"final_standard_point":{{partnerData.final_standard_point}},
"date_delay_stop":"{{partnerData.date_delay_stop}}",
"cooperative_state":"{{partnerData.cooperative_state}}",
"regular_shift_name":"{{partnerData.regular_shift_name}}",
......@@ -141,6 +151,7 @@
"city" : "{{partnerData.city|safe}}",
"mobile" : "{{partnerData.mobile}}",
"phone" : "{{partnerData.phone}}",
"function" : "{{partnerData.function}}",
"email" : "{{partnerData.email}}",
"is_associated_people" : "{{partnerData.is_associated_people}}",
"parent_id" : "{{partnerData.parent_id}}",
......
......@@ -88,6 +88,20 @@
<span id="save_edit_phone"><i class="fas fa-check tile_icon edit-btn"></i></span>
</div>
</div>
<div class="my_info_line member_job_line">
<div class="my_info_line_left">
Profession
</div>
<div class="my_info_line_right member_job_area" id="edit_job_value">
<span class="member_job member_info"></span>
<span id="edit_job"><i class="fas fa-edit tile_icon edit-btn" ></i></span>
</div>
<div class="my_info_line_right member_job_area1" id="edit_job_form">
<input type="text" name="job" id="job_form" placeholder="Profession"> <br>
<span id="cancel_edit_job"><i class="fas fa-times tile_icon edit-btn"></i></span>
<span id="save_edit_job"><i class="fas fa-check tile_icon edit-btn"></i></span>
</div>
</div>
</div>
</div>
<div class="tile full_width_tile" id="attached_info_area">
......
......@@ -47,7 +47,7 @@
temporaire, n’oublie pas de recontacter le Bureau des Membres à ton retour pour te
réinscrire sur un créneau. </p>
</div>
<div class="grp_text"><h3><b>Je m'absente plus longtemps pour une autre raison (plus de 4 semaines)</b></h3>
<div class="grp_text"><h3><b>Je m'absente plus longtemps pour une autre raison (plus de 2 mois)</b></h3>
<p class="attached-unblocked"> Envoie un mail au Bureau des Membres
<a href="mailto:bdm@supercafoutch.fr?subject=Incapacité à faire mes services pour cause d'absence prolongée">bdm@supercafoutch.fr</a>
......
......@@ -39,6 +39,10 @@
</div>
</div>
<div id="footer_btn">
<button id='show_and_compute_shelf_value' class='btn--primary'>Afficher et calculer la valeur théorique des rayons</button>
</div>
<div id="templates" style="display:none;">
<div id="modal_delete_ongoing_inv">
<h4>Attention !</h4>
......
......@@ -21,7 +21,8 @@
city : '{{data.city}}',
email: '{{data.email}}',
mobile : '{{data.mobile}}',
phone : '{{data.phone}}'
phone : '{{data.phone}}',
function: '{{data.job}}'
},
name_sep = '{{data.name_sep}}'
</script>
......@@ -70,6 +71,9 @@
<select name="yyyy"></select>
<input type="hidden" name="birthdate" value="{{data.birthdate}}" />
</p>
<p>
{% include "members/job_input.html" %}
</p>
</div>
</div>
<div id="address">
......
......@@ -65,6 +65,8 @@ def index(request):
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
if hasattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX'):
context['ask_for_sex'] = settings.SUBSCRIPTION_ASK_FOR_SEX
if hasattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB'):
context['ask_for_job'] = settings.SUBSCRIPTION_ASK_FOR_JOB
if hasattr(settings, 'SUBSCRIPTION_ADD_STREET2'):
context['ask_for_street2'] = settings.SUBSCRIPTION_ADD_STREET2
if hasattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE'):
......
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