project.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from datetime import timedelta
  2. from uuid import UUID
  3. from fastapi import APIRouter, Depends, HTTPException
  4. from sqlalchemy import delete, select
  5. from sqlalchemy.exc import IntegrityError
  6. from sqlalchemy.orm import Session
  7. from app.api import deps
  8. from app.models import Project, Slot, Sms, User, Volunteer
  9. from app.schemas.requests import (
  10. ProjectImportGsheetRequest,
  11. ProjectRequest,
  12. ProjectSMSBatchRequest,
  13. )
  14. from app.schemas.responses import ProjectListResponse, ProjectResponse, SMSResponse
  15. from app.gsheet import parseGsheet, extract_doc_uid
  16. router = APIRouter()
  17. @router.get("/projects", response_model=list[ProjectListResponse])
  18. async def list_project(
  19. current_user: User = Depends(deps.get_current_user),
  20. session: Session = Depends(deps.get_session),
  21. ):
  22. """Get project_list"""
  23. results = session.execute(select(Project))
  24. return results.scalars().all()
  25. @router.get("/public-projects", response_model=list[ProjectListResponse])
  26. async def list_public_project(
  27. session: Session = Depends(deps.get_session),
  28. ):
  29. """Get the list of public projects"""
  30. results = session.execute(select(Project).where(Project.is_public == True))
  31. return results.scalars().all()
  32. @router.post("/project", response_model=ProjectResponse)
  33. async def create_project(
  34. new_project: ProjectRequest,
  35. current_user: User = Depends(deps.get_current_user),
  36. session: Session = Depends(deps.get_session),
  37. ):
  38. """Create a new project"""
  39. project = Project(**new_project.dict())
  40. session.add(project)
  41. try:
  42. session.commit()
  43. except IntegrityError as e:
  44. raise HTTPException(400, "Project name already exist")
  45. session.refresh(project)
  46. return ProjectResponse.from_orm(project)
  47. @router.get("/public-project/{project_id}", response_model=ProjectResponse)
  48. async def get_public_project(
  49. project_id: UUID,
  50. session: Session = Depends(deps.get_session),
  51. ):
  52. """Get a project that is public"""
  53. result = session.get(Project, project_id)
  54. if (result is None) or not result.is_public:
  55. raise HTTPException(status_code=404, detail="Project not found")
  56. return result
  57. @router.get("/project/{project_id}", response_model=ProjectResponse)
  58. async def get_project(
  59. project_id: UUID,
  60. current_user: User = Depends(deps.get_current_user),
  61. session: Session = Depends(deps.get_session),
  62. ):
  63. """Get a project"""
  64. project = session.get(Project, project_id)
  65. if project is None:
  66. raise HTTPException(status_code=404, detail="Project not found")
  67. return ProjectResponse.from_orm(project)
  68. @router.post("/project/{project_id}", response_model=ProjectListResponse)
  69. async def update_project(
  70. project_id: UUID,
  71. edit_project: ProjectRequest,
  72. current_user: User = Depends(deps.get_current_user),
  73. session: Session = Depends(deps.get_session),
  74. ):
  75. """Edit project"""
  76. p = session.get(Project, project_id)
  77. if p is None:
  78. raise HTTPException(status_code=404, detail="Project not found")
  79. p.name = edit_project.name
  80. p.is_public = edit_project.is_public
  81. session.commit()
  82. return p
  83. @router.post("/project/{project_id}/import-gsheet", response_model=ProjectResponse)
  84. async def update_project_from_gsheet(
  85. project_id: UUID,
  86. gsheet: ProjectImportGsheetRequest,
  87. current_user: User = Depends(deps.get_current_user),
  88. session: Session = Depends(deps.get_session),
  89. ):
  90. """Edit project name"""
  91. p = session.get(Project, project_id)
  92. if p is None:
  93. raise HTTPException(status_code=404, detail="Project not found")
  94. doc_id = extract_doc_uid(gsheet.sheet_url)
  95. if gsheet.erase_data:
  96. p.slots = []
  97. p.sms = []
  98. p.volunteers = []
  99. df_contact, df_creneau, df_planning = parseGsheet(doc_id, gsheet.satursday_date)
  100. # Create the volunteer list
  101. volunteer_map: dict[str, Volunteer] = {}
  102. for _, row in df_contact.iterrows():
  103. volunteer = Volunteer(
  104. project_id=project_id,
  105. name=row["Prénom"],
  106. surname=row["Nom"],
  107. email=row["Mail"],
  108. phone_number=row["Tél"],
  109. automatic_sms=row["SMS"] == "Oui",
  110. )
  111. volunteer_map[row.key] = volunteer
  112. session.add(volunteer)
  113. creneau_names = df_creneau.nom.unique()
  114. # group planning entry per same name and timing
  115. date_format = "%Y/%m/%d %H:%M"
  116. df_planning["key"] = (
  117. df_planning.nom.str.strip()
  118. + "_"
  119. + df_planning.start.dt.strftime(date_format)
  120. + "-"
  121. + df_planning.end.dt.strftime(date_format)
  122. )
  123. df_slots = df_planning.groupby("key")
  124. # Create slots
  125. for key in df_slots.groups.keys():
  126. group = df_slots.get_group(key)
  127. slot = Slot(
  128. project_id=project_id,
  129. title=group.nom.iloc[0],
  130. starting_time=group.start.iloc[0],
  131. ending_time=group.end.iloc[0],
  132. )
  133. # Add volunteer to slots
  134. for benevole_key in group.benevole_nom.tolist():
  135. if benevole_key in volunteer_map:
  136. slot.volunteers.append(volunteer_map[benevole_key])
  137. # add detail information if available
  138. if slot.title in creneau_names:
  139. item = df_creneau[df_creneau.nom == slot.title].iloc[0]
  140. slot.description = item.description
  141. slot.place = item.lieu
  142. slot.responsible_contact = item.responsable
  143. session.add(slot)
  144. session.commit()
  145. session.refresh(p)
  146. return p
  147. @router.post("/project/{project_id}/create-all-sms", response_model=list[SMSResponse])
  148. async def create_sms_batch(
  149. project_id: UUID,
  150. sms_batch: ProjectSMSBatchRequest,
  151. current_user: User = Depends(deps.get_current_user),
  152. session: Session = Depends(deps.get_session),
  153. ):
  154. """Create SMS based on a template and the list of slots and volunteer associated to the project
  155. The placeholder that can be used in the template are
  156. - {titre} slot.title
  157. - {description} slot.description
  158. - {debut} slot.starting_time
  159. - {fin} slot.ending_ting
  160. - {prenom} volunteer.name
  161. - {nom} volunteer.surname
  162. """
  163. p = session.get(Project, project_id)
  164. if p is None:
  165. raise HTTPException(status_code=404, detail="Project not found")
  166. # Get all slots
  167. slots = session.execute(select(Slot).where(Slot.project_id == project_id))
  168. sms_list = []
  169. for slot in slots.scalars():
  170. # Replace the slot placeholder by their value
  171. slot_content = (
  172. sms_batch.template.replace("{titre}", slot.title)
  173. .replace("{description}", slot.description)
  174. .replace("{debut}", slot.starting_time.strftime("%Hh%M"))
  175. .replace("{fin}", slot.ending_time.strftime("%Hh%M"))
  176. )
  177. sending_time = slot.starting_time - timedelta(minutes=sms_batch.delta_t)
  178. for volunteer in slot.volunteers:
  179. if not volunteer.automatic_sms:
  180. continue
  181. # Create a new SMS customized for each user attache to the slot
  182. personalized_content = slot_content.replace("{prenom}", volunteer.name).replace(
  183. "{nom}", volunteer.surname
  184. )
  185. sms = Sms(
  186. project_id=project_id,
  187. volunteer_id=volunteer.id,
  188. content=personalized_content,
  189. phone_number=volunteer.phone_number,
  190. sending_time=sending_time,
  191. )
  192. sms_list.append(sms)
  193. session.add_all(sms_list)
  194. session.commit()
  195. return sms_list
  196. @router.delete("/project/{project_id}")
  197. async def delete_project(
  198. project_id: UUID,
  199. current_user: User = Depends(deps.get_current_user),
  200. session: Session = Depends(deps.get_session),
  201. ):
  202. """Delete project"""
  203. session.execute(delete(Project).where(Project.id == project_id))
  204. session.commit()