Commit e5bff7b4 by Yvon Kerdoncuff

Merge branch 'migration-v12' into refonte_espace_membre_sc

parents 550ea71e 3277594d
Pipeline #4065 canceled with stage
......@@ -10,6 +10,7 @@ from shifts.models import CagetteShift
from members_space.models import CagetteMembersSpace
from outils.common import MConfig
from datetime import datetime, date
import shifts.fonctions
default_msettings = {'msg_accueil': {'title': 'Message borne accueil',
'type': 'textarea',
......@@ -394,6 +395,38 @@ def update_members_makeups(request):
if is_connected_user is True:
members_data = json.loads(request.body.decode())
# Perform checks and compute some data in a loop on members.
cs = CagetteShift()
api = OdooAPI()
unsubscription_limit = api.get_system_param('lacagette_membership.points_limit_to_get_unsubscribed')
error = []
for member_data in members_data:
cm = CagetteMember(int(member_data["member_id"]))
target_makeups_nb = int(member_data["target_makeups_nb"])
if target_makeups_nb < 0:
# Prevent setting a negative number of makeups_to_do https://redmine.coopdev.fr/issues/6090
# This could happen when bdm has two screens open and clicks on minus btn on a coop line
# with exactly 1 makeup_to_do on both screens
error["error"] = "L'opération de rattrapage a été annulée car le nombre de rattrapage cible est négatif."
return JsonResponse(error, status=400)
makeup_change_count = target_makeups_nb - cs.get_member_makeups_to_do(cm.id)
points_target = cm.get_member_points("standard") - makeup_change_count
if points_target <= unsubscription_limit:
error["error"]\
= "L'ajout de rattrapage a été annulé car il provoquerait la désinscription d'un membre."
return JsonResponse(error, status=400)
res_exempted = shifts.fonctions.get_exempted_ids_from(api, [member_data["member_id"]])
if res_exempted:
error["error"] = "L'opération de rattrapage a été annulée car le membre ciblé est exempté."
return JsonResponse(error, status=400)
if cm.has_state_unsubscribed_gone_or_associated():
error["error"] = ("L'opération de rattrapage a été annulée car le membre ciblé est désinscrit, parti ou"
"associé.")
return JsonResponse(error, status=400)
# Save computed data in member_data. It will be useful to update makeups and points.
members_data[member_data["member_id"]]["points_diff"] = - makeup_change_count
# Update makeups and points
res["res"] = []
update_members_makeups_core(members_data, res)
response = JsonResponse(res)
......@@ -407,50 +440,21 @@ def update_members_makeups_core(members_data, res):
for member_data in members_data:
cm = CagetteMember(int(member_data["member_id"]))
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 + the makeups to come (limited to -2)
cs = CagetteShift()
[shift_data, is_ftop] = cs.get_shift_partner(int(member_data["member_id"]))
target_points = - int(member_data["target_makeups_nb"]) - sum(1 for value in shift_data if value['is_makeup'])
if (target_points < -2):
target_points = -2
member_points = cm.get_member_points("standard")
points_diff = abs(member_points - target_points)
# 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
}
# If points are added, we need to manage makeups count change here,
# otherwise this is handled automatically by odoo when points are changed
if member_data["points_diff"] > 0:
res["res"].append(cm.update_member_makeups(member_data))
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', [])
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': member_data['points_diff']
}
cm.update_member_points(data)
res["res"][-1]['standard_points'] = cm.get_member_points("standard")
def regenerate_member_delay(request):
""" From BDM admin, close existing extension if exists & recreate for 6 months """
......@@ -554,23 +558,15 @@ def delete_shift_template_registration(request):
try:
data = json.loads(request.body.decode())
partner_id = int(data["partner_id"])
shift_template_id = int(data["shift_template_id"])
makeups_to_do = int(data["makeups_to_do"])
permanent_unsuscribe = data["permanent_unsuscribe"]
cm = CagetteMember(partner_id)
# Get partner nb of future makeup shifts
partner_makeups = cm.get_member_selected_makeups()
target_makeup = makeups_to_do + len(partner_makeups)
if target_makeup > 2:
target_makeup = 2
# Update partner makeups to do
res["update_makeups"] = cm.update_member_makeups({'target_makeups_nb': target_makeup})
# Knowing that the total number of makeups will always be 2 or less,
# old code here would only increment the number of makeups...
# which is not needed as makeup increment is now handled
# by odoo.
# Delete all shift registrations & shift template registration
cm = CagetteMember(partner_id)
res["unsubscribe_member"] = cm.unsubscribe_member()
if permanent_unsuscribe is True:
......@@ -578,10 +574,12 @@ def delete_shift_template_registration(request):
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'])
associated_members = api.search_read('res.partner', [['parent_id', '=', partner_id]], ['id', 'suppleant_member_id'])
for am in associated_members:
data = {"child": {"id": am["id"]}, "gone": ["parent", "child"]}
delete_pair_core(data)
# Do not call delete_pair_core if contact is not a binom
if am['suppleant_member_id']:
data = {"child": {"id": am["id"]}, "gone": ["parent", "child"]}
delete_pair_core(data)
except Exception as e:
res["error"] = str(e)
......@@ -674,12 +672,15 @@ def shift_subscription(request):
def lower_makeup_count_to_zero_if_moving_from_std_to_ftop(partner_id, res):
cs = CagetteShift()
cm = CagetteMember(partner_id)
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'
'description': 'reset automatique du compteur rattrapages suite changement créneau standard vers non standard',
'points_diff': cs.get_member_makeups_to_do(cm.id)
})
res["res"] = []
update_members_makeups_core(members_data, res)
......@@ -707,6 +708,7 @@ def get_member_info(request, id):
'current_template_name',
'shift_type',
'parent_id',
'suppleant_member_id',
'is_associated_people',
'parent_name',
"makeups_to_do",
......@@ -716,8 +718,9 @@ def get_member_info(request, id):
member = api.search_read('res.partner', cond, fields)
if member:
member = member[0]
parent = None
if member['parent_id']:
if not member['suppleant_member_id']:
return JsonResponse({"message": "Accès non supporté (rattaché non suppléant)."}, status=404)
res_parent = api.search_read('res.partner', [['id', '=', int(member['parent_id'][0])]], ['barcode_base', 'email'])
if res_parent:
parent = res_parent[0]
......@@ -780,6 +783,7 @@ def create_pair(request):
"nb_associated_people",
"current_template_name",
"parent_id",
"suppleant_member_id",
"is_associated_people",
"makeups_to_do",
"final_standard_points",
......@@ -795,28 +799,33 @@ def create_pair(request):
"final_standard_points",
"final_ftop_points",
'shift_type'
'parent_id'])[0]
'parent_id',
'suppleant_member_id'])[0]
case = isPartnerInvolvedInBinom(api, child)
errors = []
if child['nb_associated_people'] > 0:
# le membre est déjà titulaire d'un binôme
errors.append("Le membre suppléant sélectionné est titulaire d'un bînome")
if case == "MEMBER_SUPPLEANT_OF_BINOM":
errors.append("Le suppléant sélectionné est déjà suppléant d'un binôme.")
elif case == "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM":
errors.append("Le suppléant sélectionné est déjà (le contact d'un membre) suppléant d'un binôme.")
elif case == "TITULAIRE_OF_BINOM":
errors.append("Le suppléant sélectionné est déjà titulaire d'un binôme.")
# le membre suppléant fait parti du commité?
if child['current_template_name'] == "Services des comités":
errors.append("Le membre suppléant séléctionné fait parti du comité")
# Verifier que le suppléant n'est pas déjà en binôme soit titulaire soit suppléant
for m in api.search_read('res.partner', [['email', '=', child['email']]]):
if m['is_associated_people']:
errors.append('Le membre suppléant est déjà en bînome')
if m['child_ids']:
errors.append("Le membre suppléant sélectionné est titulaire d'un binôme")
# le membre titulaire a déjà un/des suppléants?
if parent['nb_associated_people'] >= 1:
# On récupère le/s suppléant(s)
associated_members = api.search_read('res.partner', [['parent_id', '=', parent_id]], ['id', 'age'])
# le suppléant est un mineur?
for m in associated_members:
if m['age'] > 18:
errors.append("Le membre titulaire sélectionné a déjà un suppléant")
case = isPartnerInvolvedInBinom(api, parent)
errors = []
if case == "MEMBER_SUPPLEANT_OF_BINOM":
errors.append("Le titulaire sélectionné est déjà suppléant d'un binôme.")
elif case == "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM":
errors.append("Le titulaire sélectionné est déjà (le contact d'un membre) suppléant d'un binôme.")
elif case == "TITULAIRE_OF_BINOM":
errors.append("Le titulaire sélectionné est déjà titulaire d'un binôme.")
if errors:
return JsonResponse({"errors": errors}, status=409)
......@@ -829,6 +838,8 @@ def create_pair(request):
child[field] = False
child['is_associated_people'] = True
child['parent_id'] = parent['id']
#Link the newly created attached suppleant contact and the suppleant member
child['suppleant_member_id'] = child_id
# Following lines are useful if parent or child is unsubscribed
if not 'shift_type' in parent:
parent['shift_type'] = 'standard'
......@@ -922,6 +933,27 @@ def create_pair(request):
return JsonResponse({"message": "Method Not Allowed"}, status=405)
def isPartnerInvolvedInBinom(api, partner):
# First make sure the selected partner "is not already a suppleant"
# Let's be prudent : cover both case of partner being suppleant member,
# and case of partner being the contact of a suppleant member
contact_of_partner_suppleant = api.search_read('res.partner', [['suppleant_member_id', '=', partner['id']]])
if contact_of_partner_suppleant:
# partner has a suppleant contact therefore it is a suppleant member
return "MEMBER_SUPPLEANT_OF_BINOM"
if partner['parent_id'] and partner['suppleant_member_id']:
# partner is the contact of a suppleant member
return "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM"
# Second make sure the selected partner is not a titulaire
children_of_partner = api.search_read('res.partner', [['parent_id', '=', partner['id']]])
suppleant_of_partner = [x for x in children_of_partner if x['suppleant_member_id']]
if suppleant_of_partner:
# partner is titulaire
return "TITULAIRE_OF_BINOM"
return None
def delete_pair(request):
"""
Administration des binômes membres
......@@ -972,17 +1004,14 @@ def delete_pair_core(data):
}
"""
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}
child_contact_id = int(data['child']['id'])
child_contact = api.search_read('res.partner', [['id', '=', child_contact_id]], ['email', 'id', 'parent_id', 'suppleant_member_id'])[0]
parent = api.search_read('res.partner', [['id', '=', child_contact['parent_id'][0]]], ['cooperative_state'])[0]
api.update('res.partner', [child_contact_id], {"parent_id": False, "is_associated_people": False, "active": False,
"is_former_associated_people": True, "suppleant_member_id": None})
child_member_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)
child_member_update_fields['cooperative_state'] = "gone"
api.update("res.partner", [child_contact['suppleant_member_id']], child_member_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})
......@@ -8,6 +8,7 @@ from outils.common import CouchDB
from outils.common import Verification
from products.models import OFF
from envelops.models import CagetteEnvelops
import shifts.fonctions
import sys
import pytz
......@@ -203,6 +204,7 @@ class CagetteMember(models.Model):
if getattr(settings, 'ALLOW_NON_MEMBER_TO_CONNECT', False) is False:
cond.append('|')
cond.append(['is_member', '=', True])
# TODO : consider replacing is_associated_people by suppleant_member_id to exclude mineurs rattachés
cond.append(['is_associated_people', '=', True])
fields = ['name', 'email', 'birthdate_date', 'create_date', 'cooperative_state', 'is_associated_people', 'barcode_base']
......@@ -212,6 +214,7 @@ class CagetteMember(models.Model):
if (res and len(res) >= 1):
coop_id = None
hashed_password = None
# TODO : add comment to explain why there is a loop here
for item in res:
coop = item
if 'hashed_password' in item:
......@@ -219,6 +222,7 @@ class CagetteMember(models.Model):
if item["birthdate_date"] is not False:
coop_birthdate = item['birthdate_date']
coop_state = item['cooperative_state']
# TODO : consider replacing is_associated_people check by suppleant_member_id to exclude mineurs rattachés
if item["is_associated_people"] == True:
coop_id = item['id']
......@@ -460,18 +464,12 @@ class CagetteMember(models.Model):
def is_associated(id_parent):
api = OdooAPI()
cond = [['parent_id', '=', int(id_parent)]]
fields = ['id','name','parent_id','birthdate_date']
fields = ['id','name','parent_id','birthdate_date','suppleant_member_id']
res = api.search_read('res.partner', cond, fields, 10, 0, 'id DESC')
already_have_adult_associated = False
for partner in res:
birthdate = partner['birthdate_date']
if(birthdate):
today = date.today()
date1 = datetime.datetime.strptime(birthdate, "%Y-%m-%d")
age = today.year - date1.year - ((today.month, today.day) < (date1.month, date1.day))
if age > 17 :
already_have_adult_associated = True
return already_have_adult_associated
if partner['suppleant_member_id']:
return True
return False
@staticmethod
def finalize_coop_creation(post_data):
......@@ -657,7 +655,8 @@ class CagetteMember(models.Model):
'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.ASSOCIATE_BARCODE_RULE_ID,
'parent_id' : post_data['parent_id'],
'parent_id': post_data['parent_id'],
'suppleant_member_id': partner_id,
'is_associated_people': True,
'function': function
}
......@@ -901,6 +900,7 @@ class CagetteMember(models.Model):
cond = [['name', 'ilike', str(key)]]
cond.append('|')
cond.append(['is_member', '=', True])
#TODO : replace is_associated_people check by suppleant_member_id check to exclude mineurs rattachés
if search_type != 'members' and search_type != 'envelops':
cond.append(['is_associated_people', '=', True])
else:
......@@ -953,7 +953,7 @@ class CagetteMember(models.Model):
fields = fields + ['shift_type', 'makeups_to_do', 'display_ftop_points', 'display_std_points', 'shift_type']
cond.append(['shift_type', '=', 'standard'])
res = api.search_read('res.partner', cond, fields)
CagetteMembers.add_makeups_to_come_to_member_data(res)
CagetteMembers.add_makeups_to_come_to_member_data(api, res)
return res
elif search_type == "shift_template_data":
fields = CagetteMember.m_short_default_fields
......@@ -1044,25 +1044,19 @@ class CagetteMember(models.Model):
res = {}
c = [["parent_id", "=", self.id]]
f = ["id", "name", "barcode_base"]
f = ["id", "name", "barcode_base", 'suppleant_member_id']
res = self.o_api.search_read('res.partner', c, f)
try:
return res[0]
except:
return None
for partner in res:
if partner['suppleant_member_id']:
return partner
return None
def update_member_makeups(self, member_data):
api = OdooAPI()
res = {}
# Prevent setting a negative number of makeups_to_do
# https://redmine.coopdev.fr/issues/6090
# This could happen when bdm has two screens open and clicks on minus btn on a coop line
# with exactly 1 makeup_to_do on both screens
makeups_to_do = max(0,int(member_data["target_makeups_nb"]))
f = { 'makeups_to_do': makeups_to_do }
makeups_to_do = int(member_data["target_makeups_nb"])
f = {'makeups_to_do': makeups_to_do}
res_item = api.update('res.partner', [self.id], f)
res = {
'mid': self.id,
......@@ -1164,6 +1158,12 @@ class CagetteMember(models.Model):
else:
return False
def has_state_unsubscribed_gone_or_associated(self):
c = [['id', '=', self.id]]
f = ['cooperative_state']
state = self.o_api.search_read("res.partner", c, f)[0]["cooperative_state"]
return state in ("unsubscribed", "gone", "associated")
class CagetteMembers(models.Model):
"""Class to manage operations on all members or part of them."""
......@@ -1400,8 +1400,7 @@ class CagetteMembers(models.Model):
# 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()
makeups_to_come_per_partner = shifts.fonctions.get_partners_with_makeups_to_come(api)
# 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())]]
......@@ -1421,20 +1420,20 @@ class CagetteMembers(models.Model):
return res
@staticmethod
def add_makeups_to_come_to_member_data(res):
def add_makeups_to_come_to_member_data(api, res):
if res:
cs = CagetteShift()
for idx, partner in enumerate(res):
[shift_data, is_ftop] = cs.get_shift_partner(int(partner['id']))
[shift_data, is_ftop] = shifts.fonctions.get_shift_partner(api, 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]]
fields = ['id', 'name', 'parent_name']
cond = [['is_associated_people', '=', True]]
fields = ['id', 'name', 'parent_name', 'suppleant_member_id']
res = api.search_read('res.partner', cond, fields)
return res
# Exclude mineurs rattachés
return [x for x in res if res['suppleant_member_id']]
class CagetteUser(models.Model):
......@@ -1524,6 +1523,4 @@ class CagetteUser(models.Model):
except Exception as e:
res['error'] = str(e)
return res
from shifts.models import CagetteShift
return res
\ No newline at end of file
......@@ -218,7 +218,7 @@ def home(request):
if partnerData['cooperative_state'] == "unsubscribed":
coop_can_change_shift_template = False
if getattr(settings, 'ASSOCIATE_PEOPLE_CAN_CHANGE_SHIFT_TEMPLE_REGISTRATION', False) is False:
if partnerData['is_associated_people'] is True:
if partnerData['suppleant_member_id']:
coop_can_change_shift_template = False
context = {
'title': 'Espace Membres',
......
import datetime, pytz
tz = pytz.timezone("Europe/Paris")
def dateIsoUTC(myDate):
tz = pytz.timezone("Europe/Paris")
tDate = tz.localize(datetime.datetime.strptime(myDate, '%Y-%m-%d %H:%M:%S'))
return dDate.isoformat()
return tDate.isoformat()
def get_partners_with_makeups_to_come(api):
"""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 = 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 get_shift_partner(api, id, start_date=None, end_date=None):
"""Récupère les shift du membre"""
shifts = []
is_ftop = False
not_before = datetime.datetime.now().isoformat()
if start_date:
not_before = start_date.isoformat()
fields = ['date_begin', 'date_end',
'shift_id', 'shift_type', 'partner_id', "id", "associate_registered", "is_makeup"] # res.partner
cond = [['partner_id.id', '=', id], ['state', '=', 'open'],
['date_begin', '>', not_before]]
if end_date:
cond.append(['date_begin', '<', end_date.isoformat()])
shiftData = api.search_read('shift.registration', cond, fields, order="date_begin ASC")
for s in shiftData:
if not ('Equipe volante' in s['shift_id'][1]):
shifts.append(s)
else:
is_ftop = True
return [shifts, is_ftop]
def get_exempted_ids_from(api, partner_ids):
cond = [['id', 'in', partner_ids],
['cooperative_state', 'in', ['exempted']]]
fields = ['id']
return api.search_read('res.partner', cond, fields)
\ No newline at end of file
......@@ -5,6 +5,7 @@ from outils.common import OdooAPI
from outils.common import Verification
from members.models import CagetteMember
from members.models import CagetteUser
import shifts.fonctions
from pytz import timezone
......@@ -58,7 +59,7 @@ class CagetteShift(models.Model):
try:
now = datetime.datetime.now(tz)
# Have to retrive shifts (from now to a cycle period forward to check rules respect)
[shift_registrations, is_ftop] = self.get_shift_partner(partner_id, now + datetime.timedelta(weeks=4))
[shift_registrations, is_ftop] = shifts.fonctions.get_shift_partner(self.o_api, partner_id, now + datetime.timedelta(weeks=4))
new_shift = self.get_shift(idNewShift) # WARNING : use date_begin_tz while shift_registrations use date_begin (UTC)
if "successive_shifts_allowed" in rules:
min_duration = getattr(settings, 'MIN_SHIFT_DURATION', 2)
......@@ -76,7 +77,7 @@ class CagetteShift(models.Model):
if 'start_date' in cw:
sd = cw['start_date']
ed = cw['start_date'] + datetime.timedelta(weeks=4)
[cycle_shift_regs, is_ftop] = self.get_shift_partner(partner_id, start_date=sd, end_date=ed)
[cycle_shift_regs, is_ftop] = shifts.fonctions.get_shift_partner(self.o_api, partner_id, start_date=sd, end_date=ed)
if len(cycle_shift_regs) >= int(rules['max_shifts_per_cycle']):
answer = False
coop_logger.info("services max par cycle atteint pour partner_id %s", str(partner_id))
......@@ -127,7 +128,7 @@ class CagetteShift(models.Model):
try:
now = datetime.datetime.now(tz)
# Have to retrive shifts (from now to a cycle period forward to check rules respect)
[shift_registrations, is_ftop] = self.get_shift_partner(partner_id, now + datetime.timedelta(weeks=4))
[shift_registrations, is_ftop] = shifts.fonctions.get_shift_partner(self.o_api, partner_id, now + datetime.timedelta(weeks=4))
new_shift = self.get_shift(idNewShift) # WARNING : use date_begin_tz while shift_registrations use date_begin (UTC)
if "successive_shifts_allowed" in rules:
min_duration = getattr(settings, 'MIN_SHIFT_DURATION', 2)
......@@ -145,7 +146,7 @@ class CagetteShift(models.Model):
if 'start_date' in cw:
sd = cw['start_date']
ed = cw['start_date'] + datetime.timedelta(weeks=4)
[cycle_shift_regs, is_ftop] = self.get_shift_partner(partner_id, start_date=sd, end_date=ed)
[cycle_shift_regs, is_ftop] = shifts.fonctions.get_shift_partner(self.o_api, partner_id, start_date=sd, end_date=ed)
if len(cycle_shift_regs) >= int(rules['max_shifts_per_cycle']):
answer = False
coop_logger.info("services max par cycle atteint pour partner_id %s", str(partner_id))
......@@ -181,11 +182,11 @@ class CagetteShift(models.Model):
'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', 'function', 'email',
'is_associated_people', 'parent_id', 'extra_shift_done']
'is_associated_people', 'parent_id', 'suppleant_member_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']:
if partnerData['suppleant_member_id']:
cond = [['id', '=', partnerData['parent_id'][0]]]
fields = ['create_date', 'makeups_to_do', 'date_delay_stop', 'extra_shift_done']
parentData = self.o_api.search_read('res.partner', cond, fields, 1)
......@@ -198,14 +199,7 @@ class CagetteShift(models.Model):
if partnerData['shift_type'] == 'standard':
partnerData['in_ftop_team'] = False
# Because 'in_ftop_team' doesn't seem to be reset to False in Odoo
else:
if partnerData['final_ftop_point'] < 0 and partnerData['makeups_to_do'] == 0 and partnerData['cooperative_state'] == "suspended":
partnerData['makeups_to_do'] = int(abs(partnerData['final_ftop_point']))
try:
self.o_api.update('res.partner', [id], {'makeups_to_do': partnerData['makeups_to_do']})
except Exception as e:
coop_logger.error("update res.partner.makeups_to_do %s", str(e))
if partnerData['is_associated_people']:
if partnerData['suppleant_member_id']:
cond = [['partner_id.id', '=', partnerData['parent_id'][0]]]
else:
cond = [['partner_id.id', '=', id]]
......@@ -243,39 +237,6 @@ class CagetteShift(models.Model):
return partnerData
def get_shift_partner(self, id, start_date=None, end_date=None):
"""Récupère les shift du membre"""
shifts = []
is_ftop = False
not_before = datetime.datetime.now().isoformat()
if start_date:
not_before = start_date.isoformat()
fields = ['date_begin', 'date_end',
'shift_id', 'shift_type','partner_id', "id", "associate_registered", "is_makeup"] # res.partner
cond = [['partner_id.id', '=', id],['state', '=', 'open'],
['date_begin', '>', not_before]]
if end_date:
cond.append(['date_begin', '<', end_date.isoformat()])
shiftData = self.o_api.search_read('shift.registration', cond, fields, order ="date_begin ASC")
for s in shiftData:
if not ('Equipe volante' in s['shift_id'][1]):
shifts.append(s)
else:
is_ftop = True
return [shifts, is_ftop]
def get_partners_with_makeups_to_come(self):
"""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"""
......@@ -284,7 +245,6 @@ class CagetteShift(models.Model):
shiftData = self.o_api.search_read('shift.registration', cond, fields)
return shiftData[0]["is_makeup"]
def get_shift_calendar(self, is_ftop, start, end):
"""Récupère les shifts à partir de maintenant pour le calendier"""
......@@ -366,10 +326,10 @@ class CagetteShift(models.Model):
# partner_id can be 'associated_people' one, which is never use as shift partner_id reference
# So, let's first retrieved data about the res.partner involved
cond = [['id', '=', int(data['idPartner'])]]
fields = ['parent_id']
fields = ['parent_id', 'suppleant_member_id']
partner = self.o_api.search_read('res.partner', cond, fields, 1)
if partner:
if partner[0]['parent_id']:
if partner[0]['suppleant_member_id']:
partner_id = partner[0]['parent_id'][0]
else:
partner_id = int(data['idPartner'])
......@@ -690,8 +650,9 @@ class CagetteServices(models.Model):
for m in s['members']:
mids.append(m['partner_id'][0])
cond = [['parent_id', 'in', mids]]
fields = ['id', 'parent_id', 'name','barcode_base']
associated = api.search_read('res.partner', cond, fields)
fields = ['id', 'parent_id', 'name', 'barcode_base', 'suppleant_member_id']
attached = api.search_read('res.partner', cond, fields)
associated = [x for x in attached if x['suppleant_member_id']]
if len(associated) > 0:
for m in s['members']:
......@@ -832,32 +793,17 @@ class CagetteServices(models.Model):
['state', '=', 'open']]
fields = ['state', 'partner_id', 'date_begin', 'shift_id']
res = api.search_read('shift.registration', cond, fields)
ids = []
partner_ids = []
excluded_partner = []
canceled_reg_ids = [] # for exempted people
shift_ids = []
for r in res:
partner_ids.append(int(r['partner_id'][0]))
shift_id = int(r['shift_id'][0])
if shift_id not in shift_ids:
shift_ids.append(shift_id)
cond = [['id', 'in', partner_ids],
['cooperative_state', 'in', ['exempted']]]
fields = ['id']
res_exempted = api.search_read('res.partner', cond, fields)
for r in res_exempted:
excluded_partner.append(int(r['id']))
for r in res:
if not (int(r['partner_id'][0]) in excluded_partner):
d_begin = r['date_begin']
(d, h) = d_begin.split(' ')
(_h, _m, _s) = h.split(':')
if int(_h) < 21:
ids.append(int(r['id']))
else:
canceled_reg_ids.append(int(r['id']))
# coop_logger.info("Traitement absences shift_registration ids %s", ids)
#TODO : improve name of following method
canceled_reg_ids, excluded_partner, ids = CagetteServices.fetch_registrations_infos_excluding_exempted_people(
api, partner_ids, res
)
f = {'state': absence_status, 'date_closed': now.isoformat()}
update_shift_reg_result = {'update': False, 'reg_shift': res, 'errors': []}
individual_update_result = {}
......@@ -884,6 +830,25 @@ class CagetteServices(models.Model):
return update_shift_reg_result
@staticmethod
def fetch_registrations_infos_excluding_exempted_people(api, partner_ids, res):
#TODO : document this method
ids = []
excluded_partner = []
canceled_reg_ids = [] # for exempted people
res_exempted = shifts.fonctions.get_exempted_ids_from(api, partner_ids)
for r in res_exempted:
excluded_partner.append(int(r['id']))
for r in res:
if not (int(r['partner_id'][0]) in excluded_partner):
d_begin = r['date_begin']
(d, h) = d_begin.split(' ')
(_h, _m, _s) = h.split(':')
if int(_h) < 21:
ids.append(int(r['id']))
else:
canceled_reg_ids.append(int(r['id']))
return canceled_reg_ids, excluded_partner, ids
@staticmethod
def close_ftop_service():
......@@ -1086,30 +1051,13 @@ class CagetteService(models.Model):
['state', '=', 'open']]
fields = ['state', 'partner_id', 'date_begin']
res = self.o_api.search_read('shift.registration', cond, fields)
ids = []
partner_ids = []
excluded_partner = []
canceled_reg_ids = [] # for exempted people
for r in res:
partner_ids.append(int(r['partner_id'][0]))
cond = [['id', 'in', partner_ids],
['cooperative_state', 'in', ['exempted']]]
fields = ['id']
res_exempted = self.o_api.search_read('res.partner', cond, fields)
for r in res_exempted:
excluded_partner.append(int(r['id']))
for r in res:
if not (int(r['partner_id'][0]) in excluded_partner):
d_begin = r['date_begin']
(d, h) = d_begin.split(' ')
(_h, _m, _s) = h.split(':')
if int(_h) < 21:
ids.append(int(r['id']))
else:
canceled_reg_ids.append(int(r['id']))
# coop_logger.info("Traitement absences shift_registration ids %s", ids)
#TODO : improve name of following method
canceled_reg_ids, excluded_partner, ids = CagetteServices.fetch_registrations_infos_excluding_exempted_people(
api, partner_ids, res
)
f = {'state': absence_status, 'date_closed': now.isoformat()}
update_shift_reg_result = {'update': self.o_api.update('shift.registration', ids, f), 'reg_shift': res, 'errors': []}
if update_shift_reg_result['update'] is True:
......
......@@ -4,16 +4,13 @@ from outils.common import Verification
from shifts.models import CagetteShift
from members.models import CagetteMember
import shifts.fonctions
# working_state = ['up_to_date', 'alert', 'exempted', 'delay', 'suspended']
state_shift_allowed = ["up_to_date", "alert", "delay"]
tz = pytz.timezone("Europe/Paris")
def dateIsoUTC(myDate):
tDate = tz.localize(datetime.datetime.strptime(myDate, '%Y-%m-%d %H:%M:%S'))
return tDate.isoformat()
def home(request, partner_id, hashed_date):
import hashlib
cs = CagetteShift()
......@@ -96,7 +93,7 @@ def _is_middled_filled_considered(reserved, max):
def get_list_shift_calendar(request, partner_id):
cs = CagetteShift()
[registerPartner, is_ftop] = cs.get_shift_partner(partner_id)
[registerPartner, is_ftop] = shifts.fonctions.get_shift_partner(cs.o_api, partner_id)
use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False)
remove_15_minutes_at_shift_end = getattr(settings, 'REMOVE_15_MINUTES_AT_SHIFT_END', True)
......@@ -132,12 +129,12 @@ def get_list_shift_calendar(request, partner_id):
event["title"] = title_prefix + str(value['seats_reserved']) + "/" + str(smax)
event["start"] = dateIsoUTC(value['date_begin_tz'])
event["start"] = shifts.fonctions.dateIsoUTC(value['date_begin_tz'])
datetime_object = datetime.datetime.strptime(value['date_end_tz'], "%Y-%m-%d %H:%M:%S") - datetime.timedelta(minutes=15)
if remove_15_minutes_at_shift_end is True:
datetime_object -= datetime.timedelta(minutes=15)
event["end"] = dateIsoUTC(datetime_object.strftime("%Y-%m-%d %H:%M:%S"))
event["end"] = shifts.fonctions.dateIsoUTC(datetime_object.strftime("%Y-%m-%d %H:%M:%S"))
if len(l) > 0:
if use_new_members_space is True:
......@@ -178,7 +175,7 @@ def get_list_shift_calendar(request, partner_id):
def get_list_shift_partner(request, partner_id):
cs = CagetteShift()
[shiftData, is_ftop] = cs.get_shift_partner(partner_id)
[shiftData, is_ftop] = shifts.fonctions.get_shift_partner(cs.o_api, partner_id)
for value in shiftData:
value['date_begin'] = value['date_begin'] + "Z"
......
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