workers-control 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. arbeitszeit/__init__.py +0 -0
  2. arbeitszeit/accountant_notifications.py +30 -0
  3. arbeitszeit/anonymization.py +52 -0
  4. arbeitszeit/control_thresholds.py +9 -0
  5. arbeitszeit/datetime_service.py +6 -0
  6. arbeitszeit/decimal.py +7 -0
  7. arbeitszeit/email.py +8 -0
  8. arbeitszeit/email_notifications.py +117 -0
  9. arbeitszeit/injector.py +205 -0
  10. arbeitszeit/interactors/__init__.py +0 -0
  11. arbeitszeit/interactors/accept_cooperation.py +80 -0
  12. arbeitszeit/interactors/accept_coordination_transfer.py +97 -0
  13. arbeitszeit/interactors/answer_company_work_invite.py +76 -0
  14. arbeitszeit/interactors/approve_plan.py +112 -0
  15. arbeitszeit/interactors/cancel_cooperation_solicitation.py +27 -0
  16. arbeitszeit/interactors/change_user_email_address.py +57 -0
  17. arbeitszeit/interactors/change_user_password.py +49 -0
  18. arbeitszeit/interactors/confirm_company.py +44 -0
  19. arbeitszeit/interactors/confirm_member.py +39 -0
  20. arbeitszeit/interactors/create_cooperation.py +75 -0
  21. arbeitszeit/interactors/create_draft_from_plan.py +45 -0
  22. arbeitszeit/interactors/create_plan_draft.py +72 -0
  23. arbeitszeit/interactors/delete_draft.py +32 -0
  24. arbeitszeit/interactors/deny_cooperation.py +68 -0
  25. arbeitszeit/interactors/edit_draft.py +66 -0
  26. arbeitszeit/interactors/end_cooperation.py +73 -0
  27. arbeitszeit/interactors/file_plan_with_accounting.py +70 -0
  28. arbeitszeit/interactors/get_accountant_dashboard.py +32 -0
  29. arbeitszeit/interactors/get_company_dashboard.py +78 -0
  30. arbeitszeit/interactors/get_company_summary.py +217 -0
  31. arbeitszeit/interactors/get_coop_summary.py +99 -0
  32. arbeitszeit/interactors/get_coordination_transfer_request_details.py +67 -0
  33. arbeitszeit/interactors/get_draft_details.py +48 -0
  34. arbeitszeit/interactors/get_member_account.py +29 -0
  35. arbeitszeit/interactors/get_member_dashboard.py +123 -0
  36. arbeitszeit/interactors/get_plan_details.py +26 -0
  37. arbeitszeit/interactors/get_statistics.py +107 -0
  38. arbeitszeit/interactors/get_user_account_details.py +70 -0
  39. arbeitszeit/interactors/hide_plan.py +25 -0
  40. arbeitszeit/interactors/invite_worker_to_company.py +81 -0
  41. arbeitszeit/interactors/list_active_plans_of_company.py +43 -0
  42. arbeitszeit/interactors/list_all_cooperations.py +41 -0
  43. arbeitszeit/interactors/list_available_languages.py +25 -0
  44. arbeitszeit/interactors/list_coordinations_of_company.py +57 -0
  45. arbeitszeit/interactors/list_coordinations_of_cooperation.py +62 -0
  46. arbeitszeit/interactors/list_my_cooperating_plans.py +62 -0
  47. arbeitszeit/interactors/list_pending_work_invites.py +43 -0
  48. arbeitszeit/interactors/list_plans_with_pending_review.py +47 -0
  49. arbeitszeit/interactors/list_registered_hours_worked.py +54 -0
  50. arbeitszeit/interactors/list_transfers.py +118 -0
  51. arbeitszeit/interactors/list_workers.py +48 -0
  52. arbeitszeit/interactors/log_in_accountant.py +56 -0
  53. arbeitszeit/interactors/log_in_company.py +67 -0
  54. arbeitszeit/interactors/log_in_member.py +71 -0
  55. arbeitszeit/interactors/preview_private_consumption.py +46 -0
  56. arbeitszeit/interactors/query_companies.py +91 -0
  57. arbeitszeit/interactors/query_company_consumptions.py +68 -0
  58. arbeitszeit/interactors/query_plans.py +123 -0
  59. arbeitszeit/interactors/query_private_consumptions.py +59 -0
  60. arbeitszeit/interactors/register_accountant.py +72 -0
  61. arbeitszeit/interactors/register_company.py +95 -0
  62. arbeitszeit/interactors/register_hours_worked.py +93 -0
  63. arbeitszeit/interactors/register_member.py +92 -0
  64. arbeitszeit/interactors/register_private_consumption.py +169 -0
  65. arbeitszeit/interactors/register_productive_consumption.py +176 -0
  66. arbeitszeit/interactors/reject_plan.py +49 -0
  67. arbeitszeit/interactors/remove_worker_from_company.py +76 -0
  68. arbeitszeit/interactors/request_cooperation.py +98 -0
  69. arbeitszeit/interactors/request_coordination_transfer.py +118 -0
  70. arbeitszeit/interactors/request_email_address_change.py +83 -0
  71. arbeitszeit/interactors/request_user_password_reset.py +52 -0
  72. arbeitszeit/interactors/resend_confirmation_mail.py +52 -0
  73. arbeitszeit/interactors/resend_work_invite.py +53 -0
  74. arbeitszeit/interactors/review_registered_consumptions.py +82 -0
  75. arbeitszeit/interactors/revoke_plan_filing.py +79 -0
  76. arbeitszeit/interactors/select_productive_consumption.py +70 -0
  77. arbeitszeit/interactors/send_accountant_registration_token.py +27 -0
  78. arbeitszeit/interactors/show_a_account_details.py +43 -0
  79. arbeitszeit/interactors/show_company_accounts.py +41 -0
  80. arbeitszeit/interactors/show_company_cooperations.py +102 -0
  81. arbeitszeit/interactors/show_company_work_invite_details.py +56 -0
  82. arbeitszeit/interactors/show_my_plans.py +132 -0
  83. arbeitszeit/interactors/show_p_account_details.py +43 -0
  84. arbeitszeit/interactors/show_prd_account_details.py +45 -0
  85. arbeitszeit/interactors/show_r_account_details.py +43 -0
  86. arbeitszeit/interactors/start_page.py +46 -0
  87. arbeitszeit/password_hasher.py +9 -0
  88. arbeitszeit/records.py +333 -0
  89. arbeitszeit/repositories.py +673 -0
  90. arbeitszeit/services/__init__.py +0 -0
  91. arbeitszeit/services/account_details.py +140 -0
  92. arbeitszeit/services/compensation_transfers.py +77 -0
  93. arbeitszeit/services/payout_factor.py +122 -0
  94. arbeitszeit/services/plan_details.py +79 -0
  95. arbeitszeit/services/price_calculator.py +51 -0
  96. arbeitszeit/services/psf_balance.py +37 -0
  97. arbeitszeit/transfers.py +22 -0
  98. arbeitszeit_db/__init__.py +30 -0
  99. arbeitszeit_db/db.py +54 -0
  100. arbeitszeit_db/migrations/env.py +89 -0
  101. arbeitszeit_db/migrations/script.py.mako +26 -0
  102. arbeitszeit_db/migrations/versions/480a749375de_drop_transaction_table.py +31 -0
  103. arbeitszeit_db/migrations/versions/4fd90069eb82_remove_social_accounting_account.py +52 -0
  104. arbeitszeit_db/models.py +354 -0
  105. arbeitszeit_db/repositories.py +2840 -0
  106. arbeitszeit_flask/__init__.py +125 -0
  107. arbeitszeit_flask/api/__init__.py +36 -0
  108. arbeitszeit_flask/api/auth.py +89 -0
  109. arbeitszeit_flask/api/authentication.py +14 -0
  110. arbeitszeit_flask/api/companies.py +49 -0
  111. arbeitszeit_flask/api/consumptions.py +66 -0
  112. arbeitszeit_flask/api/input_documentation.py +73 -0
  113. arbeitszeit_flask/api/plans.py +84 -0
  114. arbeitszeit_flask/api/response_handling.py +36 -0
  115. arbeitszeit_flask/api/schema_converter.py +106 -0
  116. arbeitszeit_flask/babel.py +39 -0
  117. arbeitszeit_flask/class_based_view.py +24 -0
  118. arbeitszeit_flask/commands.py +21 -0
  119. arbeitszeit_flask/config/__init__.py +0 -0
  120. arbeitszeit_flask/config/checks.py +69 -0
  121. arbeitszeit_flask/config/loader.py +26 -0
  122. arbeitszeit_flask/config/options.py +183 -0
  123. arbeitszeit_flask/config/production_defaults.py +38 -0
  124. arbeitszeit_flask/context_processors.py +24 -0
  125. arbeitszeit_flask/control_thresholds.py +10 -0
  126. arbeitszeit_flask/database.py +40 -0
  127. arbeitszeit_flask/datetime.py +39 -0
  128. arbeitszeit_flask/dependency_injection.py +144 -0
  129. arbeitszeit_flask/email_configuration.py +9 -0
  130. arbeitszeit_flask/extensions.py +5 -0
  131. arbeitszeit_flask/filters.py +102 -0
  132. arbeitszeit_flask/flask_colors.py +45 -0
  133. arbeitszeit_flask/flask_plotter.py +42 -0
  134. arbeitszeit_flask/flask_request.py +46 -0
  135. arbeitszeit_flask/flask_session.py +122 -0
  136. arbeitszeit_flask/forms.py +342 -0
  137. arbeitszeit_flask/language_repository.py +12 -0
  138. arbeitszeit_flask/mail_service/__init__.py +19 -0
  139. arbeitszeit_flask/mail_service/debug_mail_service.py +24 -0
  140. arbeitszeit_flask/mail_service/flask_mail_service.py +32 -0
  141. arbeitszeit_flask/mail_service/interface.py +21 -0
  142. arbeitszeit_flask/mail_service/smtp_mail_service.py +55 -0
  143. arbeitszeit_flask/notifications.py +9 -0
  144. arbeitszeit_flask/password_hasher.py +30 -0
  145. arbeitszeit_flask/payout_factor.py +10 -0
  146. arbeitszeit_flask/plots/__init__.py +0 -0
  147. arbeitszeit_flask/plots/routes.py +171 -0
  148. arbeitszeit_flask/profiling.py +11 -0
  149. arbeitszeit_flask/routes/__init__.py +0 -0
  150. arbeitszeit_flask/routes/accountant/__init__.py +6 -0
  151. arbeitszeit_flask/routes/accountant/blueprint.py +44 -0
  152. arbeitszeit_flask/routes/accountant/routes.py +112 -0
  153. arbeitszeit_flask/routes/auth/__init__.py +0 -0
  154. arbeitszeit_flask/routes/auth/routes.py +276 -0
  155. arbeitszeit_flask/routes/company/__init__.py +6 -0
  156. arbeitszeit_flask/routes/company/blueprint.py +36 -0
  157. arbeitszeit_flask/routes/company/routes.py +358 -0
  158. arbeitszeit_flask/routes/healthcheck/__init__.py +3 -0
  159. arbeitszeit_flask/routes/healthcheck/blueprint.py +10 -0
  160. arbeitszeit_flask/routes/member/__init__.py +6 -0
  161. arbeitszeit_flask/routes/member/blueprint.py +35 -0
  162. arbeitszeit_flask/routes/member/routes.py +111 -0
  163. arbeitszeit_flask/routes/user/__init__.py +4 -0
  164. arbeitszeit_flask/routes/user/blueprint.py +44 -0
  165. arbeitszeit_flask/routes/user/routes.py +175 -0
  166. arbeitszeit_flask/static/bulma.css +21564 -0
  167. arbeitszeit_flask/static/colors.css +22 -0
  168. arbeitszeit_flask/static/favicon.ico +0 -0
  169. arbeitszeit_flask/static/main.css +32 -0
  170. arbeitszeit_flask/static/main.js +63 -0
  171. arbeitszeit_flask/static/main.module.js +25 -0
  172. arbeitszeit_flask/templates/accountant/dashboard.html +40 -0
  173. arbeitszeit_flask/templates/accountant/notification-about-new-plan.html +11 -0
  174. arbeitszeit_flask/templates/accountant/plan_details.html +11 -0
  175. arbeitszeit_flask/templates/accountant/plans-to-review-list.html +46 -0
  176. arbeitszeit_flask/templates/auth/accountant_invitation.html +6 -0
  177. arbeitszeit_flask/templates/auth/activate.html +3 -0
  178. arbeitszeit_flask/templates/auth/help.html +96 -0
  179. arbeitszeit_flask/templates/auth/login_accountant.html +48 -0
  180. arbeitszeit_flask/templates/auth/login_company.html +53 -0
  181. arbeitszeit_flask/templates/auth/login_member.html +53 -0
  182. arbeitszeit_flask/templates/auth/signup_accountant.html +55 -0
  183. arbeitszeit_flask/templates/auth/signup_company.html +71 -0
  184. arbeitszeit_flask/templates/auth/signup_member.html +75 -0
  185. arbeitszeit_flask/templates/auth/start.html +27 -0
  186. arbeitszeit_flask/templates/auth/unconfirmed_company.html +23 -0
  187. arbeitszeit_flask/templates/auth/unconfirmed_member.html +21 -0
  188. arbeitszeit_flask/templates/base.html +54 -0
  189. arbeitszeit_flask/templates/company/create_cooperation.html +54 -0
  190. arbeitszeit_flask/templates/company/create_draft.html +39 -0
  191. arbeitszeit_flask/templates/company/dashboard.html +197 -0
  192. arbeitszeit_flask/templates/company/draft_details.html +39 -0
  193. arbeitszeit_flask/templates/company/invite_worker_to_company.html +82 -0
  194. arbeitszeit_flask/templates/company/list_pending_work_invites.html +44 -0
  195. arbeitszeit_flask/templates/company/list_registered_hours_worked.html +29 -0
  196. arbeitszeit_flask/templates/company/my_consumptions.html +53 -0
  197. arbeitszeit_flask/templates/company/my_cooperations.html +185 -0
  198. arbeitszeit_flask/templates/company/my_plans.html +226 -0
  199. arbeitszeit_flask/templates/company/plan_details.html +56 -0
  200. arbeitszeit_flask/templates/company/register_hours_worked.html +57 -0
  201. arbeitszeit_flask/templates/company/register_productive_consumption.html +86 -0
  202. arbeitszeit_flask/templates/company/rejected_plan_notification.html +13 -0
  203. arbeitszeit_flask/templates/company/remove_worker_from_company.html +42 -0
  204. arbeitszeit_flask/templates/company/request_cooperation.html +85 -0
  205. arbeitszeit_flask/templates/company/request_coordination_transfer.html +48 -0
  206. arbeitszeit_flask/templates/company/review_registered_consumptions.html +49 -0
  207. arbeitszeit_flask/templates/company/show_coordination_transfer_request.html +42 -0
  208. arbeitszeit_flask/templates/icons/at.html +3 -0
  209. arbeitszeit_flask/templates/icons/basket-shopping.html +3 -0
  210. arbeitszeit_flask/templates/icons/book.html +3 -0
  211. arbeitszeit_flask/templates/icons/calendar.html +3 -0
  212. arbeitszeit_flask/templates/icons/chart-line.html +3 -0
  213. arbeitszeit_flask/templates/icons/chart-pie.html +3 -0
  214. arbeitszeit_flask/templates/icons/check.html +3 -0
  215. arbeitszeit_flask/templates/icons/copy.html +3 -0
  216. arbeitszeit_flask/templates/icons/credit-card.html +3 -0
  217. arbeitszeit_flask/templates/icons/edit.html +3 -0
  218. arbeitszeit_flask/templates/icons/envelope.html +3 -0
  219. arbeitszeit_flask/templates/icons/exchange-alt.html +3 -0
  220. arbeitszeit_flask/templates/icons/exclamation-triangle.html +3 -0
  221. arbeitszeit_flask/templates/icons/eye-slashed.html +3 -0
  222. arbeitszeit_flask/templates/icons/eye.html +3 -0
  223. arbeitszeit_flask/templates/icons/file-circle-plus.html +3 -0
  224. arbeitszeit_flask/templates/icons/file.html +3 -0
  225. arbeitszeit_flask/templates/icons/hands-helping.html +3 -0
  226. arbeitszeit_flask/templates/icons/hourglass.html +3 -0
  227. arbeitszeit_flask/templates/icons/industry.html +3 -0
  228. arbeitszeit_flask/templates/icons/info-circle.html +3 -0
  229. arbeitszeit_flask/templates/icons/key.html +3 -0
  230. arbeitszeit_flask/templates/icons/list.html +3 -0
  231. arbeitszeit_flask/templates/icons/oil-can.html +3 -0
  232. arbeitszeit_flask/templates/icons/redo.html +3 -0
  233. arbeitszeit_flask/templates/icons/square-arrow-up-right.html +3 -0
  234. arbeitszeit_flask/templates/icons/times.html +3 -0
  235. arbeitszeit_flask/templates/icons/trash.html +3 -0
  236. arbeitszeit_flask/templates/icons/user.html +3 -0
  237. arbeitszeit_flask/templates/icons/users.html +3 -0
  238. arbeitszeit_flask/templates/macros/accountant_navigation.html +43 -0
  239. arbeitszeit_flask/templates/macros/anonymous_navigation.html +43 -0
  240. arbeitszeit_flask/templates/macros/company_navigation.html +47 -0
  241. arbeitszeit_flask/templates/macros/company_summary.html +169 -0
  242. arbeitszeit_flask/templates/macros/copy_toggle.html +16 -0
  243. arbeitszeit_flask/templates/macros/draft_form.html +130 -0
  244. arbeitszeit_flask/templates/macros/form_field_error.html +11 -0
  245. arbeitszeit_flask/templates/macros/language_selection.html +18 -0
  246. arbeitszeit_flask/templates/macros/member_navigation.html +50 -0
  247. arbeitszeit_flask/templates/macros/navbar.html +9 -0
  248. arbeitszeit_flask/templates/macros/pagination.html +23 -0
  249. arbeitszeit_flask/templates/macros/plan_details.html +145 -0
  250. arbeitszeit_flask/templates/macros/show_notifications.html +18 -0
  251. arbeitszeit_flask/templates/macros/tooltips.html +22 -0
  252. arbeitszeit_flask/templates/macros/transfers.html +27 -0
  253. arbeitszeit_flask/templates/member/consumptions.html +54 -0
  254. arbeitszeit_flask/templates/member/dashboard.html +139 -0
  255. arbeitszeit_flask/templates/member/my_account.html +32 -0
  256. arbeitszeit_flask/templates/member/notification-about-work-invitation.html +3 -0
  257. arbeitszeit_flask/templates/member/plan_details.html +25 -0
  258. arbeitszeit_flask/templates/member/register_private_consumption.html +80 -0
  259. arbeitszeit_flask/templates/member/show_company_work_invite_details.html +17 -0
  260. arbeitszeit_flask/templates/user/account_details.html +23 -0
  261. arbeitszeit_flask/templates/user/company_account_a.html +41 -0
  262. arbeitszeit_flask/templates/user/company_account_p.html +42 -0
  263. arbeitszeit_flask/templates/user/company_account_prd.html +41 -0
  264. arbeitszeit_flask/templates/user/company_account_r.html +41 -0
  265. arbeitszeit_flask/templates/user/company_accounts.html +80 -0
  266. arbeitszeit_flask/templates/user/company_summary.html +10 -0
  267. arbeitszeit_flask/templates/user/confirm_email_address_change.html +24 -0
  268. arbeitszeit_flask/templates/user/coop_summary.html +110 -0
  269. arbeitszeit_flask/templates/user/http_error.html +7 -0
  270. arbeitszeit_flask/templates/user/list_all_cooperations.html +38 -0
  271. arbeitszeit_flask/templates/user/list_coordinations_of_cooperation.html +52 -0
  272. arbeitszeit_flask/templates/user/list_transfers.html +75 -0
  273. arbeitszeit_flask/templates/user/query_companies.html +66 -0
  274. arbeitszeit_flask/templates/user/query_plans.html +97 -0
  275. arbeitszeit_flask/templates/user/request_email_address_change.html +38 -0
  276. arbeitszeit_flask/templates/user/request_email_change_notification.html +5 -0
  277. arbeitszeit_flask/templates/user/request_email_change_warning.html +7 -0
  278. arbeitszeit_flask/templates/user/statistics.html +90 -0
  279. arbeitszeit_flask/text_renderer.py +54 -0
  280. arbeitszeit_flask/token.py +37 -0
  281. arbeitszeit_flask/translations/de/LC_MESSAGES/messages.mo +0 -0
  282. arbeitszeit_flask/translations/de/LC_MESSAGES/messages.po +3132 -0
  283. arbeitszeit_flask/translations/en/LC_MESSAGES/messages.mo +0 -0
  284. arbeitszeit_flask/translations/en/LC_MESSAGES/messages.po +2661 -0
  285. arbeitszeit_flask/translations/es/LC_MESSAGES/messages.mo +0 -0
  286. arbeitszeit_flask/translations/es/LC_MESSAGES/messages.po +2594 -0
  287. arbeitszeit_flask/translations/messages.pot +2356 -0
  288. arbeitszeit_flask/translator.py +17 -0
  289. arbeitszeit_flask/types.py +6 -0
  290. arbeitszeit_flask/url_index.py +251 -0
  291. arbeitszeit_flask/views/__init__.py +17 -0
  292. arbeitszeit_flask/views/accept_cooperation_request_view.py +38 -0
  293. arbeitszeit_flask/views/accountant_invitation_email_view.py +23 -0
  294. arbeitszeit_flask/views/cancel_cooperation_request_view.py +34 -0
  295. arbeitszeit_flask/views/change_email_address_view.py +85 -0
  296. arbeitszeit_flask/views/company_dashboard_view.py +34 -0
  297. arbeitszeit_flask/views/company_work_invite_view.py +68 -0
  298. arbeitszeit_flask/views/coop_summary_view.py +34 -0
  299. arbeitszeit_flask/views/create_cooperation_view.py +45 -0
  300. arbeitszeit_flask/views/create_draft_view.py +63 -0
  301. arbeitszeit_flask/views/deny_cooperation_view.py +39 -0
  302. arbeitszeit_flask/views/draft_details_view.py +69 -0
  303. arbeitszeit_flask/views/end_cooperation_view.py +34 -0
  304. arbeitszeit_flask/views/get_statistics_view.py +23 -0
  305. arbeitszeit_flask/views/healthcheck_view.py +10 -0
  306. arbeitszeit_flask/views/http_error_view.py +27 -0
  307. arbeitszeit_flask/views/invite_worker_to_company.py +96 -0
  308. arbeitszeit_flask/views/list_all_cooperations_view.py +20 -0
  309. arbeitszeit_flask/views/list_coordinators_of_cooperation_view.py +30 -0
  310. arbeitszeit_flask/views/list_pending_work_invites_view.py +63 -0
  311. arbeitszeit_flask/views/list_registered_hours_worked_view.py +32 -0
  312. arbeitszeit_flask/views/list_transfers.py +26 -0
  313. arbeitszeit_flask/views/query_companies.py +66 -0
  314. arbeitszeit_flask/views/query_plans.py +56 -0
  315. arbeitszeit_flask/views/query_private_consumptions.py +34 -0
  316. arbeitszeit_flask/views/register_hours_worked_view.py +61 -0
  317. arbeitszeit_flask/views/register_private_consumption.py +65 -0
  318. arbeitszeit_flask/views/register_productive_consumption.py +93 -0
  319. arbeitszeit_flask/views/remove_worker_from_company_view.py +72 -0
  320. arbeitszeit_flask/views/request_cooperation_view.py +77 -0
  321. arbeitszeit_flask/views/request_coordination_transfer_view.py +66 -0
  322. arbeitszeit_flask/views/review_registered_consumptions_view.py +38 -0
  323. arbeitszeit_flask/views/show_a_account_details_view.py +30 -0
  324. arbeitszeit_flask/views/show_company_accounts_view.py +30 -0
  325. arbeitszeit_flask/views/show_coordination_transfer_request_view.py +65 -0
  326. arbeitszeit_flask/views/show_p_account_details_view.py +30 -0
  327. arbeitszeit_flask/views/show_prd_account_details_view.py +32 -0
  328. arbeitszeit_flask/views/show_r_account_details_view.py +30 -0
  329. arbeitszeit_flask/views/signup_accountant_view.py +56 -0
  330. arbeitszeit_flask/views/signup_company_view.py +57 -0
  331. arbeitszeit_flask/views/signup_member_view.py +58 -0
  332. arbeitszeit_flask/wsgi.py +6 -0
  333. arbeitszeit_web/__init__.py +0 -0
  334. arbeitszeit_web/api/__init__.py +0 -0
  335. arbeitszeit_web/api/authentication.py +16 -0
  336. arbeitszeit_web/api/controllers/__init__.py +0 -0
  337. arbeitszeit_web/api/controllers/get_plan_api_controller.py +24 -0
  338. arbeitszeit_web/api/controllers/liquid_means_consumption_controller.py +87 -0
  339. arbeitszeit_web/api/controllers/login_company_api_controller.py +37 -0
  340. arbeitszeit_web/api/controllers/login_member_api_controller.py +40 -0
  341. arbeitszeit_web/api/controllers/parameters.py +26 -0
  342. arbeitszeit_web/api/controllers/query_companies_api_controller.py +52 -0
  343. arbeitszeit_web/api/controllers/query_parser.py +12 -0
  344. arbeitszeit_web/api/controllers/query_plans_api_controller.py +58 -0
  345. arbeitszeit_web/api/presenters/__init__.py +0 -0
  346. arbeitszeit_web/api/presenters/get_plan_api_presenter.py +51 -0
  347. arbeitszeit_web/api/presenters/interfaces.py +68 -0
  348. arbeitszeit_web/api/presenters/liquid_means_consumption_presenter.py +36 -0
  349. arbeitszeit_web/api/presenters/login_company_api_presenter.py +36 -0
  350. arbeitszeit_web/api/presenters/login_member_api_presenter.py +36 -0
  351. arbeitszeit_web/api/presenters/query_companies_api_presenter.py +49 -0
  352. arbeitszeit_web/api/presenters/query_plans_api_presenter.py +58 -0
  353. arbeitszeit_web/api/response_errors.py +91 -0
  354. arbeitszeit_web/colors.py +18 -0
  355. arbeitszeit_web/email/__init__.py +17 -0
  356. arbeitszeit_web/email/accountant_invitation_presenter.py +38 -0
  357. arbeitszeit_web/email/company_notifier.py +40 -0
  358. arbeitszeit_web/email/cooperation_request_email_presenter.py +24 -0
  359. arbeitszeit_web/email/email_change_confirmation_presenter.py +37 -0
  360. arbeitszeit_web/email/email_change_warning_view.py +26 -0
  361. arbeitszeit_web/email/email_sender.py +76 -0
  362. arbeitszeit_web/email/invite_worker_presenter.py +31 -0
  363. arbeitszeit_web/email/notify_about_worker_removal_from_company_presenter.py +24 -0
  364. arbeitszeit_web/email/notify_accountant_about_new_plan_presenter.py +27 -0
  365. arbeitszeit_web/email/registration_email_presenter.py +45 -0
  366. arbeitszeit_web/email/request_coordination_transfer_presenter.py +34 -0
  367. arbeitszeit_web/formatters/__init__.py +0 -0
  368. arbeitszeit_web/formatters/datetime_formatter.py +14 -0
  369. arbeitszeit_web/formatters/plan_details_formatter.py +136 -0
  370. arbeitszeit_web/forms/__init__.py +104 -0
  371. arbeitszeit_web/forms/fields.py +38 -0
  372. arbeitszeit_web/forms/formfield_parsers.py +63 -0
  373. arbeitszeit_web/json.py +1 -0
  374. arbeitszeit_web/language_service.py +5 -0
  375. arbeitszeit_web/malformed_input_data.py +7 -0
  376. arbeitszeit_web/notification.py +9 -0
  377. arbeitszeit_web/pagination.py +69 -0
  378. arbeitszeit_web/plotter.py +18 -0
  379. arbeitszeit_web/request.py +37 -0
  380. arbeitszeit_web/session.py +28 -0
  381. arbeitszeit_web/text_renderer.py +25 -0
  382. arbeitszeit_web/token.py +8 -0
  383. arbeitszeit_web/translator.py +24 -0
  384. arbeitszeit_web/url_index.py +166 -0
  385. arbeitszeit_web/www/__init__.py +0 -0
  386. arbeitszeit_web/www/authentication.py +157 -0
  387. arbeitszeit_web/www/controllers/__init__.py +0 -0
  388. arbeitszeit_web/www/controllers/accept_coordination_transfer_controller.py +21 -0
  389. arbeitszeit_web/www/controllers/answer_company_work_invite_controller.py +29 -0
  390. arbeitszeit_web/www/controllers/approve_plan_controller.py +8 -0
  391. arbeitszeit_web/www/controllers/change_user_email_address_controller.py +44 -0
  392. arbeitszeit_web/www/controllers/confirm_company_controller.py +21 -0
  393. arbeitszeit_web/www/controllers/confirm_member_controller.py +20 -0
  394. arbeitszeit_web/www/controllers/create_draft_controller.py +42 -0
  395. arbeitszeit_web/www/controllers/create_draft_from_plan_controller.py +19 -0
  396. arbeitszeit_web/www/controllers/delete_draft_controller.py +28 -0
  397. arbeitszeit_web/www/controllers/draft_form_validator.py +207 -0
  398. arbeitszeit_web/www/controllers/edit_draft_controller.py +44 -0
  399. arbeitszeit_web/www/controllers/end_cooperation_controller.py +33 -0
  400. arbeitszeit_web/www/controllers/file_plan_with_accounting_controller.py +23 -0
  401. arbeitszeit_web/www/controllers/invite_worker_to_company_controller.py +48 -0
  402. arbeitszeit_web/www/controllers/list_pending_work_invites_controller.py +16 -0
  403. arbeitszeit_web/www/controllers/list_registered_hours_worked_controller.py +16 -0
  404. arbeitszeit_web/www/controllers/list_transfers_controller.py +20 -0
  405. arbeitszeit_web/www/controllers/list_workers_controller.py +14 -0
  406. arbeitszeit_web/www/controllers/log_in_accountant_controller.py +12 -0
  407. arbeitszeit_web/www/controllers/query_companies_controller.py +36 -0
  408. arbeitszeit_web/www/controllers/query_plans_controller.py +70 -0
  409. arbeitszeit_web/www/controllers/query_private_consumptions_controller.py +23 -0
  410. arbeitszeit_web/www/controllers/register_accountant_controller.py +31 -0
  411. arbeitszeit_web/www/controllers/register_company_controller.py +14 -0
  412. arbeitszeit_web/www/controllers/register_hours_worked_controller.py +55 -0
  413. arbeitszeit_web/www/controllers/register_member_controller.py +14 -0
  414. arbeitszeit_web/www/controllers/register_private_consumption_controller.py +80 -0
  415. arbeitszeit_web/www/controllers/register_private_consumption_form_validator.py +34 -0
  416. arbeitszeit_web/www/controllers/register_productive_consumption_controller.py +62 -0
  417. arbeitszeit_web/www/controllers/reject_plan_controller.py +8 -0
  418. arbeitszeit_web/www/controllers/remove_worker_from_company_controller.py +46 -0
  419. arbeitszeit_web/www/controllers/request_cooperation_controller.py +45 -0
  420. arbeitszeit_web/www/controllers/request_coordination_transfer_controller.py +32 -0
  421. arbeitszeit_web/www/controllers/request_email_address_change_controller.py +45 -0
  422. arbeitszeit_web/www/controllers/resend_work_invite_controller.py +27 -0
  423. arbeitszeit_web/www/controllers/review_registered_consumptions_controller.py +29 -0
  424. arbeitszeit_web/www/controllers/revoke_plan_filing_controller.py +15 -0
  425. arbeitszeit_web/www/controllers/select_productive_consumption_controller.py +95 -0
  426. arbeitszeit_web/www/controllers/show_a_account_details_controller.py +10 -0
  427. arbeitszeit_web/www/controllers/show_company_accounts_controller.py +10 -0
  428. arbeitszeit_web/www/controllers/show_company_work_invite_details_controller.py +25 -0
  429. arbeitszeit_web/www/controllers/show_p_account_details_controller.py +10 -0
  430. arbeitszeit_web/www/controllers/show_prd_account_details_controller.py +10 -0
  431. arbeitszeit_web/www/controllers/show_r_account_details_controller.py +10 -0
  432. arbeitszeit_web/www/controllers/user_account_details_controller.py +20 -0
  433. arbeitszeit_web/www/navbar.py +8 -0
  434. arbeitszeit_web/www/presenters/__init__.py +0 -0
  435. arbeitszeit_web/www/presenters/accept_cooperation_request_presenter.py +57 -0
  436. arbeitszeit_web/www/presenters/accept_coordination_transfer_presenter.py +78 -0
  437. arbeitszeit_web/www/presenters/answer_company_work_invite_presenter.py +39 -0
  438. arbeitszeit_web/www/presenters/approve_plan_presenter.py +32 -0
  439. arbeitszeit_web/www/presenters/cancel_cooperation_request_presenter.py +28 -0
  440. arbeitszeit_web/www/presenters/change_user_email_address_presenter.py +34 -0
  441. arbeitszeit_web/www/presenters/company_consumptions_presenter.py +64 -0
  442. arbeitszeit_web/www/presenters/create_cooperation_presenter.py +42 -0
  443. arbeitszeit_web/www/presenters/create_draft_from_plan_presenter.py +39 -0
  444. arbeitszeit_web/www/presenters/create_draft_presenter.py +28 -0
  445. arbeitszeit_web/www/presenters/delete_draft_presenter.py +33 -0
  446. arbeitszeit_web/www/presenters/deny_cooperation_presenter.py +58 -0
  447. arbeitszeit_web/www/presenters/edit_draft_presenter.py +37 -0
  448. arbeitszeit_web/www/presenters/end_cooperation_presenter.py +59 -0
  449. arbeitszeit_web/www/presenters/file_plan_with_accounting_presenter.py +34 -0
  450. arbeitszeit_web/www/presenters/get_accountant_dashboard_presenter.py +28 -0
  451. arbeitszeit_web/www/presenters/get_company_dashboard_presenter.py +82 -0
  452. arbeitszeit_web/www/presenters/get_company_summary_presenter.py +151 -0
  453. arbeitszeit_web/www/presenters/get_coop_summary_presenter.py +86 -0
  454. arbeitszeit_web/www/presenters/get_coordination_transfer_request_details_presenter.py +53 -0
  455. arbeitszeit_web/www/presenters/get_draft_details_presenter.py +34 -0
  456. arbeitszeit_web/www/presenters/get_member_account_presenter.py +31 -0
  457. arbeitszeit_web/www/presenters/get_member_dashboard_presenter.py +107 -0
  458. arbeitszeit_web/www/presenters/get_plan_details_accountant_presenter.py +31 -0
  459. arbeitszeit_web/www/presenters/get_plan_details_company_presenter.py +72 -0
  460. arbeitszeit_web/www/presenters/get_plan_details_member_presenter.py +35 -0
  461. arbeitszeit_web/www/presenters/get_statistics_presenter.py +101 -0
  462. arbeitszeit_web/www/presenters/hide_plan_presenter.py +21 -0
  463. arbeitszeit_web/www/presenters/invite_worker_to_company_presenter.py +67 -0
  464. arbeitszeit_web/www/presenters/list_all_cooperations_presenter.py +40 -0
  465. arbeitszeit_web/www/presenters/list_available_languages_presenter.py +40 -0
  466. arbeitszeit_web/www/presenters/list_coordinations_of_cooperation_presenter.py +77 -0
  467. arbeitszeit_web/www/presenters/list_pending_work_invites_presenter.py +45 -0
  468. arbeitszeit_web/www/presenters/list_plans_presenter.py +33 -0
  469. arbeitszeit_web/www/presenters/list_plans_with_pending_review_presenter.py +51 -0
  470. arbeitszeit_web/www/presenters/list_registered_hours_worked_presenter.py +37 -0
  471. arbeitszeit_web/www/presenters/list_transfers_presenter.py +134 -0
  472. arbeitszeit_web/www/presenters/list_workers_presenter.py +35 -0
  473. arbeitszeit_web/www/presenters/log_in_accountant_presenter.py +48 -0
  474. arbeitszeit_web/www/presenters/log_in_company_presenter.py +50 -0
  475. arbeitszeit_web/www/presenters/log_in_member_presenter.py +48 -0
  476. arbeitszeit_web/www/presenters/private_consumptions_presenter.py +43 -0
  477. arbeitszeit_web/www/presenters/query_companies_presenter.py +75 -0
  478. arbeitszeit_web/www/presenters/query_plans_presenter.py +90 -0
  479. arbeitszeit_web/www/presenters/register_accountant_presenter.py +38 -0
  480. arbeitszeit_web/www/presenters/register_company_presenter.py +37 -0
  481. arbeitszeit_web/www/presenters/register_hours_worked_presenter.py +49 -0
  482. arbeitszeit_web/www/presenters/register_member_presenter.py +52 -0
  483. arbeitszeit_web/www/presenters/register_private_consumption_presenter.py +79 -0
  484. arbeitszeit_web/www/presenters/register_productive_consumption_presenter.py +65 -0
  485. arbeitszeit_web/www/presenters/reject_plan_presenter.py +34 -0
  486. arbeitszeit_web/www/presenters/remove_worker_from_company_presenter.py +49 -0
  487. arbeitszeit_web/www/presenters/request_cooperation_presenter.py +75 -0
  488. arbeitszeit_web/www/presenters/request_coordination_transfer_presenter.py +91 -0
  489. arbeitszeit_web/www/presenters/request_email_address_change_presenter.py +70 -0
  490. arbeitszeit_web/www/presenters/resend_work_invite_presenter.py +29 -0
  491. arbeitszeit_web/www/presenters/review_registered_consumptions_presenter.py +69 -0
  492. arbeitszeit_web/www/presenters/revoke_plan_filing_presenter.py +24 -0
  493. arbeitszeit_web/www/presenters/select_productive_consumption_presenter.py +68 -0
  494. arbeitszeit_web/www/presenters/show_a_account_details_presenter.py +47 -0
  495. arbeitszeit_web/www/presenters/show_company_accounts_presenter.py +52 -0
  496. arbeitszeit_web/www/presenters/show_company_work_invite_details_presenter.py +35 -0
  497. arbeitszeit_web/www/presenters/show_my_cooperations_presenter.py +184 -0
  498. arbeitszeit_web/www/presenters/show_my_plans_presenter.py +241 -0
  499. arbeitszeit_web/www/presenters/show_p_account_details_presenter.py +50 -0
  500. arbeitszeit_web/www/presenters/show_prd_account_details_presenter.py +51 -0
  501. arbeitszeit_web/www/presenters/show_r_account_details_presenter.py +46 -0
  502. arbeitszeit_web/www/presenters/start_page_presenter.py +38 -0
  503. arbeitszeit_web/www/presenters/transfers.py +114 -0
  504. arbeitszeit_web/www/presenters/user_account_details_presenter.py +30 -0
  505. arbeitszeit_web/www/response.py +6 -0
  506. workers_control-0.1.1.dist-info/METADATA +67 -0
  507. workers_control-0.1.1.dist-info/RECORD +510 -0
  508. workers_control-0.1.1.dist-info/WHEEL +5 -0
  509. workers_control-0.1.1.dist-info/licenses/LICENSE +661 -0
  510. workers_control-0.1.1.dist-info/top_level.txt +4 -0
File without changes
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from uuid import UUID
3
+
4
+ from arbeitszeit.email_notifications import (
5
+ AccountantNotificationAboutNewPlan,
6
+ EmailSender,
7
+ )
8
+ from arbeitszeit.repositories import DatabaseGateway
9
+
10
+
11
+ @dataclass
12
+ class AccountantNotifier:
13
+ email_sender: EmailSender
14
+ database: DatabaseGateway
15
+
16
+ def notify_all_accountants_about_new_plan(
17
+ self, product_name: str, plan_id: UUID
18
+ ) -> None:
19
+ for (
20
+ accountant,
21
+ email,
22
+ ) in self.database.get_accountants().joined_with_email_address():
23
+ self.email_sender.send_email(
24
+ AccountantNotificationAboutNewPlan(
25
+ product_name=product_name,
26
+ plan_id=plan_id,
27
+ accountant_name=accountant.name,
28
+ accountant_email_address=email.address,
29
+ )
30
+ )
@@ -0,0 +1,52 @@
1
+ """
2
+ Sentinels to represent anonymized values.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Final, TypeAlias
8
+ from uuid import UUID
9
+
10
+
11
+ class AnonymizedUUID:
12
+ """Marker class for anonymized UUIDs.
13
+
14
+ Prefer using the singleton instance ANONYMIZED_UUID instead of creating
15
+ new instances. Identity checks ("is") are the recommended way to detect
16
+ anonymized values.
17
+ """
18
+
19
+ __slots__: tuple[str, ...] = ()
20
+
21
+ def __repr__(self) -> str:
22
+ return "<ANONYMIZED_UUID>"
23
+
24
+ def __bool__(self) -> bool: # pragma: no cover - defensive
25
+ raise TypeError("AnonymizedUUID cannot be used in a boolean context")
26
+
27
+
28
+ class AnonymizedStr:
29
+ """Marker class for anonymized strings.
30
+
31
+ Prefer using the singleton instance ANONYMIZED_STR instead of creating
32
+ new instances. Identity checks ("is") are the recommended way to detect
33
+ anonymized values.
34
+ """
35
+
36
+ __slots__: tuple[str, ...] = ()
37
+
38
+ def __repr__(self) -> str:
39
+ return "<ANONYMIZED_STR>"
40
+
41
+ def __bool__(self) -> bool: # pragma: no cover - defensive
42
+ raise TypeError("AnonymizedStr cannot be used in a boolean context")
43
+
44
+
45
+ # Singleton sentinel instances (preferred values to use and compare against)
46
+ ANONYMIZED_UUID: Final[AnonymizedUUID] = AnonymizedUUID()
47
+ ANONYMIZED_STR: Final[AnonymizedStr] = AnonymizedStr()
48
+
49
+
50
+ # Helpful type aliases
51
+ MaybeAnonymizedUUID: TypeAlias = UUID | AnonymizedUUID
52
+ MaybeAnonymizedStr: TypeAlias = str | AnonymizedStr
@@ -0,0 +1,9 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class ControlThresholds(Protocol):
5
+ def get_allowed_overdraw_of_member_account(self) -> int | None:
6
+ """Return the allowed overdraw limit in hours. None means unlimited overdraw."""
7
+ ...
8
+
9
+ def get_acceptable_relative_account_deviation(self) -> int: ...
@@ -0,0 +1,6 @@
1
+ from datetime import datetime
2
+ from typing import Protocol
3
+
4
+
5
+ class DatetimeService(Protocol):
6
+ def now(self) -> datetime: ...
arbeitszeit/decimal.py ADDED
@@ -0,0 +1,7 @@
1
+ import functools
2
+ from decimal import Decimal
3
+ from typing import Iterable
4
+
5
+
6
+ def decimal_sum(iterable: Iterable[Decimal]) -> Decimal:
7
+ return functools.reduce(lambda x, y: x + y, iterable, Decimal(0))
arbeitszeit/email.py ADDED
@@ -0,0 +1,8 @@
1
+ def is_possibly_an_email_address(text: str) -> bool:
2
+ return (
3
+ "@" in text
4
+ and not text.startswith("@")
5
+ and not text.endswith("@")
6
+ and not text.startswith(" ")
7
+ and not text.endswith(" ")
8
+ )
@@ -0,0 +1,117 @@
1
+ """This module contains all possible types of email notifications that
2
+ the workers control app can issue and the interface by which the interactors
3
+ request those notifications to be sent.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Protocol, Type, TypeAlias, Union, get_args
8
+ from uuid import UUID
9
+
10
+
11
+ @dataclass
12
+ class MemberRegistration:
13
+ email_address: str
14
+
15
+
16
+ @dataclass
17
+ class CompanyRegistration:
18
+ email_address: str
19
+
20
+
21
+ @dataclass
22
+ class AccountantNotificationAboutNewPlan:
23
+ product_name: str
24
+ plan_id: UUID
25
+ accountant_name: str
26
+ accountant_email_address: str
27
+
28
+
29
+ @dataclass
30
+ class AccountantInvitation:
31
+ email_address: str
32
+
33
+
34
+ @dataclass
35
+ class WorkerInvitation:
36
+ worker_email: str
37
+ invite: UUID
38
+
39
+
40
+ @dataclass
41
+ class WorkerRemovalNotification:
42
+ worker_email: str
43
+ worker_name: str
44
+ worker_id: UUID
45
+ company_email: str
46
+ company_name: str
47
+
48
+
49
+ @dataclass
50
+ class EmailChangeWarning:
51
+ old_email_address: str
52
+
53
+
54
+ @dataclass
55
+ class EmailChangeConfirmation:
56
+ old_email_address: str
57
+ new_email_address: str
58
+
59
+
60
+ @dataclass
61
+ class CooperationRequestEmail:
62
+ coordinator_email_address: str
63
+ coordinator_name: str
64
+
65
+
66
+ @dataclass
67
+ class CoordinationTransferRequest:
68
+ candidate_email: str
69
+ candidate_name: str
70
+ cooperation_name: str
71
+ transfer_request: UUID
72
+
73
+
74
+ @dataclass
75
+ class ResetPasswordRequest:
76
+ email_address: str
77
+ reset_token: str
78
+
79
+
80
+ @dataclass
81
+ class ResetPasswordConfirmation:
82
+ email_address: str
83
+
84
+
85
+ @dataclass
86
+ class RejectedPlanNotification:
87
+ planner_email_address: str
88
+ planning_company_name: str
89
+ plan_id: UUID
90
+ product_name: str
91
+
92
+
93
+ # This type definition can be used by implementations of the
94
+ # EmailSender protocol for static type checking purposes. Keep this
95
+ # list alphabetically sorted.
96
+ Message: TypeAlias = Union[
97
+ AccountantInvitation,
98
+ AccountantNotificationAboutNewPlan,
99
+ CompanyRegistration,
100
+ CooperationRequestEmail,
101
+ CoordinationTransferRequest,
102
+ EmailChangeWarning,
103
+ EmailChangeConfirmation,
104
+ MemberRegistration,
105
+ RejectedPlanNotification,
106
+ ResetPasswordConfirmation,
107
+ ResetPasswordRequest,
108
+ WorkerInvitation,
109
+ WorkerRemovalNotification,
110
+ ]
111
+ # Implementations can rely on this set to contain all possible message
112
+ # types.
113
+ all_message_types: set[Type] = set(get_args(Message))
114
+
115
+
116
+ class EmailSender(Protocol):
117
+ def send_email(self, message: Message) -> None: ...
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from typing import (
5
+ Any,
6
+ Callable,
7
+ Dict,
8
+ Generic,
9
+ Iterable,
10
+ List,
11
+ Optional,
12
+ Protocol,
13
+ Type,
14
+ TypeVar,
15
+ get_type_hints,
16
+ )
17
+
18
+ S = TypeVar("S")
19
+ T = TypeVar("T")
20
+ T_cov = TypeVar("T_cov", covariant=True)
21
+ CallableT = TypeVar("CallableT", bound=Callable)
22
+ TypeT = TypeVar("TypeT", bound=Type)
23
+ ProviderFunction = Callable[[Type[T]], "Provider[T]"]
24
+
25
+
26
+ class Injector:
27
+ def __init__(self, modules: List[Module]) -> None:
28
+ class _RecursionModule(Module):
29
+ def configure(module_self, binder: Binder) -> None:
30
+ super().configure(binder)
31
+ binder[Injector] = InstanceProvider(self)
32
+
33
+ recursion_module = _RecursionModule()
34
+
35
+ self.binder: Binder = Binder(Injector.default_provider)
36
+ recursion_module.configure(self.binder)
37
+ for module in modules:
38
+ module.configure(self.binder)
39
+
40
+ @staticmethod
41
+ def default_provider(cls: Type[T]) -> ClassProvider[T]:
42
+ return ClassProvider(cls)
43
+
44
+ def get(self, cls: Type[T]) -> T:
45
+ provider = self.binder.get(cls)
46
+ return provider.provide(self.binder)
47
+
48
+ def call_with_injection(
49
+ self,
50
+ f: Callable[..., T],
51
+ args: Optional[Iterable[Any]] = None,
52
+ kwargs: Optional[Dict[str, Any]] = None,
53
+ ) -> T:
54
+ if args is None:
55
+ args = []
56
+ if kwargs is None:
57
+ kwargs = {}
58
+ signature = inspect.signature(f)
59
+ bound_arguments = signature.bind_partial(*args)
60
+ dependencies = dict()
61
+ for key, value in get_type_hints(f).items():
62
+ if (
63
+ key != "return"
64
+ and key not in bound_arguments.arguments
65
+ and key not in kwargs
66
+ ):
67
+ dependencies[key] = self.get(value)
68
+ dependencies.update(kwargs)
69
+ return f(*bound_arguments.arguments.values(), **dependencies)
70
+
71
+ def create_object(self, cls: Type[T], additional_kwargs: Dict[str, Any]) -> T:
72
+ return self.call_with_injection(cls, kwargs=additional_kwargs)
73
+
74
+
75
+ class Binder:
76
+ def __init__(self, default: ProviderFunction) -> None:
77
+ self._default = default
78
+ self._bindings: Dict[Type, Provider] = dict()
79
+ self._instances: Dict[Type, Any] = dict()
80
+
81
+ def __getitem__(self, key: Type[T]) -> Provider[T]:
82
+ return self._bindings[key]
83
+
84
+ def __setitem__(self, key: Type[T], value: Provider[T]) -> None:
85
+ self._bindings[key] = value
86
+
87
+ def __contains__(self, key: Type[T]) -> bool:
88
+ return key in self._bindings
89
+
90
+ def bind(self, cls: Type[T], to: Provider[T]) -> None:
91
+ self._bindings[cls] = to
92
+
93
+ def get(self, cls: Type[T]) -> Provider[T]:
94
+ if cls not in self:
95
+ return self._default(cls)
96
+ else:
97
+ return self[cls]
98
+
99
+
100
+ class Provider(Protocol, Generic[T_cov]):
101
+ def provide(self, binder: Binder) -> T_cov: ...
102
+
103
+
104
+ class AliasProvider(Generic[T]):
105
+ """This provider provides instances by simply refering to another
106
+ class. This can be useful when specifying the concret
107
+ implementation of an abstract base class (ABC) or a Protocol.
108
+ """
109
+
110
+ def __init__(self, cls: Type[T]) -> None:
111
+ self.cls = cls
112
+
113
+ def provide(self, binder: Binder) -> T:
114
+ provider = binder.get(self.cls)
115
+ return provider.provide(binder)
116
+
117
+
118
+ class InstanceProvider(Provider[T]):
119
+ """Provide an instance of of a class by specifying the concrete
120
+ instance in the provider.
121
+ """
122
+
123
+ def __init__(self, instance: T) -> None:
124
+ self.instance = instance
125
+
126
+ def provide(self, binder: Binder) -> T:
127
+ return self.instance
128
+
129
+
130
+ class ClassProvider(Provider[T]):
131
+ """Provide an instance of a type by calling its constructor
132
+ (__init__ method) and providing its arguments from dependency
133
+ injection.
134
+
135
+ This provider usually doen't need to be specified directly since
136
+ it is assumed to be the default in case no other provider was
137
+ specified.
138
+ """
139
+
140
+ def __init__(self, cls: Type[T]) -> None:
141
+ self.cls = cls
142
+
143
+ def provide(self, binder: Binder) -> T:
144
+ if self.is_singleton:
145
+ instance = binder._instances.get(self.cls)
146
+ if instance is not None:
147
+ return instance
148
+ args = get_type_hints(self.cls.__init__)
149
+ kwargs = dict()
150
+ for name, annotation in args.items():
151
+ if name == "return":
152
+ continue
153
+ kwargs[name] = binder.get(annotation).provide(binder)
154
+ try:
155
+ instance = self.cls(**kwargs)
156
+ except TypeError as e:
157
+ raise TypeError(f"Could not create instance of class {self.cls}") from e
158
+ if self.is_singleton:
159
+ binder._instances[self.cls] = instance
160
+ return instance
161
+
162
+ @property
163
+ def is_singleton(self) -> bool:
164
+ return getattr(self.cls, "_injection_singleton", False)
165
+
166
+
167
+ class CallableProvider(Provider[T]):
168
+ """Provide an instance by calling a function. The arguments to
169
+ the function will be provide by dependency injection.
170
+ """
171
+
172
+ def __init__(self, f: Callable[..., T], is_singleton: bool = False) -> None:
173
+ self.f = f
174
+ self._is_singleton = is_singleton
175
+
176
+ def provide(self, binder: Binder) -> T:
177
+ if self._is_singleton:
178
+ instance = binder._instances.get(self.return_type)
179
+ if instance:
180
+ return instance
181
+ args = get_type_hints(self.f)
182
+ kwargs = dict()
183
+ for name, annotation in args.items():
184
+ if name == "return":
185
+ continue
186
+ kwargs[name] = binder.get(annotation).provide(binder)
187
+ instance = self.f(**kwargs)
188
+ if self._is_singleton:
189
+ binder._instances[self.return_type] = instance
190
+ return instance
191
+
192
+ @property
193
+ def return_type(self) -> Type:
194
+ hints = get_type_hints(self.f)
195
+ return hints["return"]
196
+
197
+
198
+ class Module:
199
+ def configure(self, binder: Binder) -> None:
200
+ pass
201
+
202
+
203
+ def singleton(cls: TypeT) -> TypeT:
204
+ cls._injection_singleton = True
205
+ return cls
File without changes
@@ -0,0 +1,80 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum, auto
3
+ from typing import Optional
4
+ from uuid import UUID
5
+
6
+ from arbeitszeit.datetime_service import DatetimeService
7
+ from arbeitszeit.repositories import DatabaseGateway
8
+
9
+
10
+ @dataclass
11
+ class AcceptCooperationRequest:
12
+ requester_id: UUID
13
+ plan_id: UUID
14
+ cooperation_id: UUID
15
+
16
+
17
+ @dataclass
18
+ class AcceptCooperationResponse:
19
+ class RejectionReason(Exception, Enum):
20
+ plan_not_found = auto()
21
+ cooperation_not_found = auto()
22
+ plan_inactive = auto()
23
+ plan_has_cooperation = auto()
24
+ plan_is_public_service = auto()
25
+ cooperation_was_not_requested = auto()
26
+ requester_is_not_coordinator = auto()
27
+
28
+ rejection_reason: Optional[RejectionReason]
29
+
30
+ @property
31
+ def is_rejected(self) -> bool:
32
+ return self.rejection_reason is not None
33
+
34
+
35
+ @dataclass
36
+ class AcceptCooperationInteractor:
37
+ database_gateway: DatabaseGateway
38
+ datetime_service: DatetimeService
39
+
40
+ def execute(self, request: AcceptCooperationRequest) -> AcceptCooperationResponse:
41
+ try:
42
+ self._validate_request(request)
43
+ except AcceptCooperationResponse.RejectionReason as reason:
44
+ return AcceptCooperationResponse(rejection_reason=reason)
45
+ plan = self.database_gateway.get_plans().with_id(request.plan_id)
46
+ plan.update().set_cooperation(request.cooperation_id).set_requested_cooperation(
47
+ None
48
+ ).perform()
49
+ return AcceptCooperationResponse(rejection_reason=None)
50
+
51
+ def _validate_request(self, request: AcceptCooperationRequest) -> None:
52
+ plan_and_cooperation = (
53
+ self.database_gateway.get_plans()
54
+ .with_id(request.plan_id)
55
+ .joined_with_cooperation()
56
+ .first()
57
+ )
58
+ cooperation_and_coordinator = (
59
+ self.database_gateway.get_cooperations()
60
+ .with_id(request.cooperation_id)
61
+ .joined_with_current_coordinator()
62
+ .first()
63
+ )
64
+ now = self.datetime_service.now()
65
+ if cooperation_and_coordinator is None:
66
+ raise AcceptCooperationResponse.RejectionReason.cooperation_not_found
67
+ cooperation, coordinator = cooperation_and_coordinator
68
+ if plan_and_cooperation is None:
69
+ raise AcceptCooperationResponse.RejectionReason.plan_not_found
70
+ plan, current_cooperation = plan_and_cooperation
71
+ if not plan.is_active_as_of(now):
72
+ raise AcceptCooperationResponse.RejectionReason.plan_inactive
73
+ if current_cooperation:
74
+ raise AcceptCooperationResponse.RejectionReason.plan_has_cooperation
75
+ if plan.is_public_service:
76
+ raise AcceptCooperationResponse.RejectionReason.plan_is_public_service
77
+ if plan.requested_cooperation != cooperation.id:
78
+ raise AcceptCooperationResponse.RejectionReason.cooperation_was_not_requested
79
+ if request.requester_id != coordinator.id:
80
+ raise AcceptCooperationResponse.RejectionReason.requester_is_not_coordinator
@@ -0,0 +1,97 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum, auto
3
+ from typing import Optional
4
+ from uuid import UUID
5
+
6
+ from arbeitszeit.datetime_service import DatetimeService
7
+ from arbeitszeit.records import Cooperation, CoordinationTransferRequest
8
+ from arbeitszeit.repositories import DatabaseGateway
9
+
10
+
11
+ @dataclass
12
+ class AcceptCoordinationTransferInteractor:
13
+ database_gateway: DatabaseGateway
14
+ datetime_service: DatetimeService
15
+
16
+ @dataclass
17
+ class Request:
18
+ transfer_request_id: UUID
19
+ accepting_company: UUID
20
+
21
+ @dataclass
22
+ class Response:
23
+ class RejectionReason(Exception, Enum):
24
+ transfer_request_not_found = auto()
25
+ transfer_request_closed = auto()
26
+ accepting_company_is_not_candidate = auto()
27
+
28
+ rejection_reason: Optional[RejectionReason]
29
+ cooperation_id: Optional[UUID]
30
+ transfer_request_id: UUID
31
+
32
+ @property
33
+ def is_rejected(self) -> bool:
34
+ return self.rejection_reason is not None
35
+
36
+ def accept_coordination_transfer(self, request: Request) -> Response:
37
+ try:
38
+ transfer_request, cooperation = self._validate_request(request)
39
+ except self.Response.RejectionReason as reason:
40
+ return self.Response(
41
+ rejection_reason=reason,
42
+ cooperation_id=None,
43
+ transfer_request_id=request.transfer_request_id,
44
+ )
45
+ cooperation_id = self._create_new_coordination_tenure(
46
+ transfer_request, cooperation
47
+ )
48
+ return self.Response(
49
+ rejection_reason=None,
50
+ cooperation_id=cooperation_id,
51
+ transfer_request_id=request.transfer_request_id,
52
+ )
53
+
54
+ def _validate_request(
55
+ self, request: Request
56
+ ) -> tuple[CoordinationTransferRequest, Cooperation]:
57
+ result = (
58
+ self.database_gateway.get_coordination_transfer_requests()
59
+ .with_id(request.transfer_request_id)
60
+ .joined_with_cooperation()
61
+ .first()
62
+ )
63
+ if result is None:
64
+ raise self.Response.RejectionReason.transfer_request_not_found
65
+ transfer_request, cooperation = result
66
+ if transfer_request.candidate != request.accepting_company:
67
+ raise self.Response.RejectionReason.accepting_company_is_not_candidate
68
+ if self._cooperation_has_a_coordination_tenure_starting_after_transfer_request(
69
+ cooperation=cooperation, transfer_request=transfer_request
70
+ ):
71
+ raise self.Response.RejectionReason.transfer_request_closed
72
+ return transfer_request, cooperation
73
+
74
+ def _create_new_coordination_tenure(
75
+ self, transfer_request: CoordinationTransferRequest, cooperation: Cooperation
76
+ ) -> UUID:
77
+ new_tenure = self.database_gateway.create_coordination_tenure(
78
+ company=transfer_request.candidate,
79
+ cooperation=cooperation.id,
80
+ start_date=self.datetime_service.now(),
81
+ )
82
+ return new_tenure.cooperation
83
+
84
+ def _cooperation_has_a_coordination_tenure_starting_after_transfer_request(
85
+ self, transfer_request: CoordinationTransferRequest, cooperation: Cooperation
86
+ ) -> bool:
87
+ latest_coordination_tenure_of_cooperation = (
88
+ self.database_gateway.get_coordination_tenures()
89
+ .of_cooperation(cooperation.id)
90
+ .ordered_by_start_date(ascending=False)
91
+ .first()
92
+ )
93
+ assert latest_coordination_tenure_of_cooperation
94
+ return (
95
+ latest_coordination_tenure_of_cooperation.start_date
96
+ > transfer_request.request_date
97
+ )
@@ -0,0 +1,76 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum, auto
3
+ from typing import Optional
4
+ from uuid import UUID
5
+
6
+ from arbeitszeit.repositories import DatabaseGateway
7
+
8
+
9
+ @dataclass
10
+ class AnswerCompanyWorkInviteRequest:
11
+ is_accepted: bool
12
+ invite_id: UUID
13
+ user: UUID
14
+
15
+
16
+ @dataclass
17
+ class AnswerCompanyWorkInviteResponse:
18
+ class Failure(Exception, Enum):
19
+ invite_not_found = auto()
20
+ member_was_not_invited = auto()
21
+
22
+ is_success: bool
23
+ is_accepted: bool
24
+ company_name: Optional[str]
25
+ failure_reason: Optional[Failure]
26
+
27
+
28
+ @dataclass
29
+ class AnswerCompanyWorkInviteInteractor:
30
+ database_gateway: DatabaseGateway
31
+
32
+ def execute(
33
+ self, request: AnswerCompanyWorkInviteRequest
34
+ ) -> AnswerCompanyWorkInviteResponse:
35
+ invite = (
36
+ self.database_gateway.get_company_work_invites()
37
+ .with_id(request.invite_id)
38
+ .first()
39
+ )
40
+ if invite is None:
41
+ return self._create_failure_response(
42
+ reason=AnswerCompanyWorkInviteResponse.Failure.invite_not_found
43
+ )
44
+ if invite.member != request.user:
45
+ return self._create_failure_response(
46
+ reason=AnswerCompanyWorkInviteResponse.Failure.member_was_not_invited
47
+ )
48
+ elif request.is_accepted:
49
+ self.database_gateway.get_companies().with_id(invite.company).add_worker(
50
+ invite.member
51
+ )
52
+ self.database_gateway.get_company_work_invites().with_id(
53
+ request.invite_id
54
+ ).delete()
55
+ elif not request.is_accepted:
56
+ self.database_gateway.get_company_work_invites().with_id(
57
+ request.invite_id
58
+ ).delete()
59
+ company = self.database_gateway.get_companies().with_id(invite.company).first()
60
+ assert company
61
+ return AnswerCompanyWorkInviteResponse(
62
+ is_success=True,
63
+ is_accepted=request.is_accepted,
64
+ company_name=company.name,
65
+ failure_reason=None,
66
+ )
67
+
68
+ def _create_failure_response(
69
+ self, reason: AnswerCompanyWorkInviteResponse.Failure
70
+ ) -> AnswerCompanyWorkInviteResponse:
71
+ return AnswerCompanyWorkInviteResponse(
72
+ is_success=False,
73
+ is_accepted=False,
74
+ company_name=None,
75
+ failure_reason=reason,
76
+ )