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.
- arbeitszeit/__init__.py +0 -0
- arbeitszeit/accountant_notifications.py +30 -0
- arbeitszeit/anonymization.py +52 -0
- arbeitszeit/control_thresholds.py +9 -0
- arbeitszeit/datetime_service.py +6 -0
- arbeitszeit/decimal.py +7 -0
- arbeitszeit/email.py +8 -0
- arbeitszeit/email_notifications.py +117 -0
- arbeitszeit/injector.py +205 -0
- arbeitszeit/interactors/__init__.py +0 -0
- arbeitszeit/interactors/accept_cooperation.py +80 -0
- arbeitszeit/interactors/accept_coordination_transfer.py +97 -0
- arbeitszeit/interactors/answer_company_work_invite.py +76 -0
- arbeitszeit/interactors/approve_plan.py +112 -0
- arbeitszeit/interactors/cancel_cooperation_solicitation.py +27 -0
- arbeitszeit/interactors/change_user_email_address.py +57 -0
- arbeitszeit/interactors/change_user_password.py +49 -0
- arbeitszeit/interactors/confirm_company.py +44 -0
- arbeitszeit/interactors/confirm_member.py +39 -0
- arbeitszeit/interactors/create_cooperation.py +75 -0
- arbeitszeit/interactors/create_draft_from_plan.py +45 -0
- arbeitszeit/interactors/create_plan_draft.py +72 -0
- arbeitszeit/interactors/delete_draft.py +32 -0
- arbeitszeit/interactors/deny_cooperation.py +68 -0
- arbeitszeit/interactors/edit_draft.py +66 -0
- arbeitszeit/interactors/end_cooperation.py +73 -0
- arbeitszeit/interactors/file_plan_with_accounting.py +70 -0
- arbeitszeit/interactors/get_accountant_dashboard.py +32 -0
- arbeitszeit/interactors/get_company_dashboard.py +78 -0
- arbeitszeit/interactors/get_company_summary.py +217 -0
- arbeitszeit/interactors/get_coop_summary.py +99 -0
- arbeitszeit/interactors/get_coordination_transfer_request_details.py +67 -0
- arbeitszeit/interactors/get_draft_details.py +48 -0
- arbeitszeit/interactors/get_member_account.py +29 -0
- arbeitszeit/interactors/get_member_dashboard.py +123 -0
- arbeitszeit/interactors/get_plan_details.py +26 -0
- arbeitszeit/interactors/get_statistics.py +107 -0
- arbeitszeit/interactors/get_user_account_details.py +70 -0
- arbeitszeit/interactors/hide_plan.py +25 -0
- arbeitszeit/interactors/invite_worker_to_company.py +81 -0
- arbeitszeit/interactors/list_active_plans_of_company.py +43 -0
- arbeitszeit/interactors/list_all_cooperations.py +41 -0
- arbeitszeit/interactors/list_available_languages.py +25 -0
- arbeitszeit/interactors/list_coordinations_of_company.py +57 -0
- arbeitszeit/interactors/list_coordinations_of_cooperation.py +62 -0
- arbeitszeit/interactors/list_my_cooperating_plans.py +62 -0
- arbeitszeit/interactors/list_pending_work_invites.py +43 -0
- arbeitszeit/interactors/list_plans_with_pending_review.py +47 -0
- arbeitszeit/interactors/list_registered_hours_worked.py +54 -0
- arbeitszeit/interactors/list_transfers.py +118 -0
- arbeitszeit/interactors/list_workers.py +48 -0
- arbeitszeit/interactors/log_in_accountant.py +56 -0
- arbeitszeit/interactors/log_in_company.py +67 -0
- arbeitszeit/interactors/log_in_member.py +71 -0
- arbeitszeit/interactors/preview_private_consumption.py +46 -0
- arbeitszeit/interactors/query_companies.py +91 -0
- arbeitszeit/interactors/query_company_consumptions.py +68 -0
- arbeitszeit/interactors/query_plans.py +123 -0
- arbeitszeit/interactors/query_private_consumptions.py +59 -0
- arbeitszeit/interactors/register_accountant.py +72 -0
- arbeitszeit/interactors/register_company.py +95 -0
- arbeitszeit/interactors/register_hours_worked.py +93 -0
- arbeitszeit/interactors/register_member.py +92 -0
- arbeitszeit/interactors/register_private_consumption.py +169 -0
- arbeitszeit/interactors/register_productive_consumption.py +176 -0
- arbeitszeit/interactors/reject_plan.py +49 -0
- arbeitszeit/interactors/remove_worker_from_company.py +76 -0
- arbeitszeit/interactors/request_cooperation.py +98 -0
- arbeitszeit/interactors/request_coordination_transfer.py +118 -0
- arbeitszeit/interactors/request_email_address_change.py +83 -0
- arbeitszeit/interactors/request_user_password_reset.py +52 -0
- arbeitszeit/interactors/resend_confirmation_mail.py +52 -0
- arbeitszeit/interactors/resend_work_invite.py +53 -0
- arbeitszeit/interactors/review_registered_consumptions.py +82 -0
- arbeitszeit/interactors/revoke_plan_filing.py +79 -0
- arbeitszeit/interactors/select_productive_consumption.py +70 -0
- arbeitszeit/interactors/send_accountant_registration_token.py +27 -0
- arbeitszeit/interactors/show_a_account_details.py +43 -0
- arbeitszeit/interactors/show_company_accounts.py +41 -0
- arbeitszeit/interactors/show_company_cooperations.py +102 -0
- arbeitszeit/interactors/show_company_work_invite_details.py +56 -0
- arbeitszeit/interactors/show_my_plans.py +132 -0
- arbeitszeit/interactors/show_p_account_details.py +43 -0
- arbeitszeit/interactors/show_prd_account_details.py +45 -0
- arbeitszeit/interactors/show_r_account_details.py +43 -0
- arbeitszeit/interactors/start_page.py +46 -0
- arbeitszeit/password_hasher.py +9 -0
- arbeitszeit/records.py +333 -0
- arbeitszeit/repositories.py +673 -0
- arbeitszeit/services/__init__.py +0 -0
- arbeitszeit/services/account_details.py +140 -0
- arbeitszeit/services/compensation_transfers.py +77 -0
- arbeitszeit/services/payout_factor.py +122 -0
- arbeitszeit/services/plan_details.py +79 -0
- arbeitszeit/services/price_calculator.py +51 -0
- arbeitszeit/services/psf_balance.py +37 -0
- arbeitszeit/transfers.py +22 -0
- arbeitszeit_db/__init__.py +30 -0
- arbeitszeit_db/db.py +54 -0
- arbeitszeit_db/migrations/env.py +89 -0
- arbeitszeit_db/migrations/script.py.mako +26 -0
- arbeitszeit_db/migrations/versions/480a749375de_drop_transaction_table.py +31 -0
- arbeitszeit_db/migrations/versions/4fd90069eb82_remove_social_accounting_account.py +52 -0
- arbeitszeit_db/models.py +354 -0
- arbeitszeit_db/repositories.py +2840 -0
- arbeitszeit_flask/__init__.py +125 -0
- arbeitszeit_flask/api/__init__.py +36 -0
- arbeitszeit_flask/api/auth.py +89 -0
- arbeitszeit_flask/api/authentication.py +14 -0
- arbeitszeit_flask/api/companies.py +49 -0
- arbeitszeit_flask/api/consumptions.py +66 -0
- arbeitszeit_flask/api/input_documentation.py +73 -0
- arbeitszeit_flask/api/plans.py +84 -0
- arbeitszeit_flask/api/response_handling.py +36 -0
- arbeitszeit_flask/api/schema_converter.py +106 -0
- arbeitszeit_flask/babel.py +39 -0
- arbeitszeit_flask/class_based_view.py +24 -0
- arbeitszeit_flask/commands.py +21 -0
- arbeitszeit_flask/config/__init__.py +0 -0
- arbeitszeit_flask/config/checks.py +69 -0
- arbeitszeit_flask/config/loader.py +26 -0
- arbeitszeit_flask/config/options.py +183 -0
- arbeitszeit_flask/config/production_defaults.py +38 -0
- arbeitszeit_flask/context_processors.py +24 -0
- arbeitszeit_flask/control_thresholds.py +10 -0
- arbeitszeit_flask/database.py +40 -0
- arbeitszeit_flask/datetime.py +39 -0
- arbeitszeit_flask/dependency_injection.py +144 -0
- arbeitszeit_flask/email_configuration.py +9 -0
- arbeitszeit_flask/extensions.py +5 -0
- arbeitszeit_flask/filters.py +102 -0
- arbeitszeit_flask/flask_colors.py +45 -0
- arbeitszeit_flask/flask_plotter.py +42 -0
- arbeitszeit_flask/flask_request.py +46 -0
- arbeitszeit_flask/flask_session.py +122 -0
- arbeitszeit_flask/forms.py +342 -0
- arbeitszeit_flask/language_repository.py +12 -0
- arbeitszeit_flask/mail_service/__init__.py +19 -0
- arbeitszeit_flask/mail_service/debug_mail_service.py +24 -0
- arbeitszeit_flask/mail_service/flask_mail_service.py +32 -0
- arbeitszeit_flask/mail_service/interface.py +21 -0
- arbeitszeit_flask/mail_service/smtp_mail_service.py +55 -0
- arbeitszeit_flask/notifications.py +9 -0
- arbeitszeit_flask/password_hasher.py +30 -0
- arbeitszeit_flask/payout_factor.py +10 -0
- arbeitszeit_flask/plots/__init__.py +0 -0
- arbeitszeit_flask/plots/routes.py +171 -0
- arbeitszeit_flask/profiling.py +11 -0
- arbeitszeit_flask/routes/__init__.py +0 -0
- arbeitszeit_flask/routes/accountant/__init__.py +6 -0
- arbeitszeit_flask/routes/accountant/blueprint.py +44 -0
- arbeitszeit_flask/routes/accountant/routes.py +112 -0
- arbeitszeit_flask/routes/auth/__init__.py +0 -0
- arbeitszeit_flask/routes/auth/routes.py +276 -0
- arbeitszeit_flask/routes/company/__init__.py +6 -0
- arbeitszeit_flask/routes/company/blueprint.py +36 -0
- arbeitszeit_flask/routes/company/routes.py +358 -0
- arbeitszeit_flask/routes/healthcheck/__init__.py +3 -0
- arbeitszeit_flask/routes/healthcheck/blueprint.py +10 -0
- arbeitszeit_flask/routes/member/__init__.py +6 -0
- arbeitszeit_flask/routes/member/blueprint.py +35 -0
- arbeitszeit_flask/routes/member/routes.py +111 -0
- arbeitszeit_flask/routes/user/__init__.py +4 -0
- arbeitszeit_flask/routes/user/blueprint.py +44 -0
- arbeitszeit_flask/routes/user/routes.py +175 -0
- arbeitszeit_flask/static/bulma.css +21564 -0
- arbeitszeit_flask/static/colors.css +22 -0
- arbeitszeit_flask/static/favicon.ico +0 -0
- arbeitszeit_flask/static/main.css +32 -0
- arbeitszeit_flask/static/main.js +63 -0
- arbeitszeit_flask/static/main.module.js +25 -0
- arbeitszeit_flask/templates/accountant/dashboard.html +40 -0
- arbeitszeit_flask/templates/accountant/notification-about-new-plan.html +11 -0
- arbeitszeit_flask/templates/accountant/plan_details.html +11 -0
- arbeitszeit_flask/templates/accountant/plans-to-review-list.html +46 -0
- arbeitszeit_flask/templates/auth/accountant_invitation.html +6 -0
- arbeitszeit_flask/templates/auth/activate.html +3 -0
- arbeitszeit_flask/templates/auth/help.html +96 -0
- arbeitszeit_flask/templates/auth/login_accountant.html +48 -0
- arbeitszeit_flask/templates/auth/login_company.html +53 -0
- arbeitszeit_flask/templates/auth/login_member.html +53 -0
- arbeitszeit_flask/templates/auth/signup_accountant.html +55 -0
- arbeitszeit_flask/templates/auth/signup_company.html +71 -0
- arbeitszeit_flask/templates/auth/signup_member.html +75 -0
- arbeitszeit_flask/templates/auth/start.html +27 -0
- arbeitszeit_flask/templates/auth/unconfirmed_company.html +23 -0
- arbeitszeit_flask/templates/auth/unconfirmed_member.html +21 -0
- arbeitszeit_flask/templates/base.html +54 -0
- arbeitszeit_flask/templates/company/create_cooperation.html +54 -0
- arbeitszeit_flask/templates/company/create_draft.html +39 -0
- arbeitszeit_flask/templates/company/dashboard.html +197 -0
- arbeitszeit_flask/templates/company/draft_details.html +39 -0
- arbeitszeit_flask/templates/company/invite_worker_to_company.html +82 -0
- arbeitszeit_flask/templates/company/list_pending_work_invites.html +44 -0
- arbeitszeit_flask/templates/company/list_registered_hours_worked.html +29 -0
- arbeitszeit_flask/templates/company/my_consumptions.html +53 -0
- arbeitszeit_flask/templates/company/my_cooperations.html +185 -0
- arbeitszeit_flask/templates/company/my_plans.html +226 -0
- arbeitszeit_flask/templates/company/plan_details.html +56 -0
- arbeitszeit_flask/templates/company/register_hours_worked.html +57 -0
- arbeitszeit_flask/templates/company/register_productive_consumption.html +86 -0
- arbeitszeit_flask/templates/company/rejected_plan_notification.html +13 -0
- arbeitszeit_flask/templates/company/remove_worker_from_company.html +42 -0
- arbeitszeit_flask/templates/company/request_cooperation.html +85 -0
- arbeitszeit_flask/templates/company/request_coordination_transfer.html +48 -0
- arbeitszeit_flask/templates/company/review_registered_consumptions.html +49 -0
- arbeitszeit_flask/templates/company/show_coordination_transfer_request.html +42 -0
- arbeitszeit_flask/templates/icons/at.html +3 -0
- arbeitszeit_flask/templates/icons/basket-shopping.html +3 -0
- arbeitszeit_flask/templates/icons/book.html +3 -0
- arbeitszeit_flask/templates/icons/calendar.html +3 -0
- arbeitszeit_flask/templates/icons/chart-line.html +3 -0
- arbeitszeit_flask/templates/icons/chart-pie.html +3 -0
- arbeitszeit_flask/templates/icons/check.html +3 -0
- arbeitszeit_flask/templates/icons/copy.html +3 -0
- arbeitszeit_flask/templates/icons/credit-card.html +3 -0
- arbeitszeit_flask/templates/icons/edit.html +3 -0
- arbeitszeit_flask/templates/icons/envelope.html +3 -0
- arbeitszeit_flask/templates/icons/exchange-alt.html +3 -0
- arbeitszeit_flask/templates/icons/exclamation-triangle.html +3 -0
- arbeitszeit_flask/templates/icons/eye-slashed.html +3 -0
- arbeitszeit_flask/templates/icons/eye.html +3 -0
- arbeitszeit_flask/templates/icons/file-circle-plus.html +3 -0
- arbeitszeit_flask/templates/icons/file.html +3 -0
- arbeitszeit_flask/templates/icons/hands-helping.html +3 -0
- arbeitszeit_flask/templates/icons/hourglass.html +3 -0
- arbeitszeit_flask/templates/icons/industry.html +3 -0
- arbeitszeit_flask/templates/icons/info-circle.html +3 -0
- arbeitszeit_flask/templates/icons/key.html +3 -0
- arbeitszeit_flask/templates/icons/list.html +3 -0
- arbeitszeit_flask/templates/icons/oil-can.html +3 -0
- arbeitszeit_flask/templates/icons/redo.html +3 -0
- arbeitszeit_flask/templates/icons/square-arrow-up-right.html +3 -0
- arbeitszeit_flask/templates/icons/times.html +3 -0
- arbeitszeit_flask/templates/icons/trash.html +3 -0
- arbeitszeit_flask/templates/icons/user.html +3 -0
- arbeitszeit_flask/templates/icons/users.html +3 -0
- arbeitszeit_flask/templates/macros/accountant_navigation.html +43 -0
- arbeitszeit_flask/templates/macros/anonymous_navigation.html +43 -0
- arbeitszeit_flask/templates/macros/company_navigation.html +47 -0
- arbeitszeit_flask/templates/macros/company_summary.html +169 -0
- arbeitszeit_flask/templates/macros/copy_toggle.html +16 -0
- arbeitszeit_flask/templates/macros/draft_form.html +130 -0
- arbeitszeit_flask/templates/macros/form_field_error.html +11 -0
- arbeitszeit_flask/templates/macros/language_selection.html +18 -0
- arbeitszeit_flask/templates/macros/member_navigation.html +50 -0
- arbeitszeit_flask/templates/macros/navbar.html +9 -0
- arbeitszeit_flask/templates/macros/pagination.html +23 -0
- arbeitszeit_flask/templates/macros/plan_details.html +145 -0
- arbeitszeit_flask/templates/macros/show_notifications.html +18 -0
- arbeitszeit_flask/templates/macros/tooltips.html +22 -0
- arbeitszeit_flask/templates/macros/transfers.html +27 -0
- arbeitszeit_flask/templates/member/consumptions.html +54 -0
- arbeitszeit_flask/templates/member/dashboard.html +139 -0
- arbeitszeit_flask/templates/member/my_account.html +32 -0
- arbeitszeit_flask/templates/member/notification-about-work-invitation.html +3 -0
- arbeitszeit_flask/templates/member/plan_details.html +25 -0
- arbeitszeit_flask/templates/member/register_private_consumption.html +80 -0
- arbeitszeit_flask/templates/member/show_company_work_invite_details.html +17 -0
- arbeitszeit_flask/templates/user/account_details.html +23 -0
- arbeitszeit_flask/templates/user/company_account_a.html +41 -0
- arbeitszeit_flask/templates/user/company_account_p.html +42 -0
- arbeitszeit_flask/templates/user/company_account_prd.html +41 -0
- arbeitszeit_flask/templates/user/company_account_r.html +41 -0
- arbeitszeit_flask/templates/user/company_accounts.html +80 -0
- arbeitszeit_flask/templates/user/company_summary.html +10 -0
- arbeitszeit_flask/templates/user/confirm_email_address_change.html +24 -0
- arbeitszeit_flask/templates/user/coop_summary.html +110 -0
- arbeitszeit_flask/templates/user/http_error.html +7 -0
- arbeitszeit_flask/templates/user/list_all_cooperations.html +38 -0
- arbeitszeit_flask/templates/user/list_coordinations_of_cooperation.html +52 -0
- arbeitszeit_flask/templates/user/list_transfers.html +75 -0
- arbeitszeit_flask/templates/user/query_companies.html +66 -0
- arbeitszeit_flask/templates/user/query_plans.html +97 -0
- arbeitszeit_flask/templates/user/request_email_address_change.html +38 -0
- arbeitszeit_flask/templates/user/request_email_change_notification.html +5 -0
- arbeitszeit_flask/templates/user/request_email_change_warning.html +7 -0
- arbeitszeit_flask/templates/user/statistics.html +90 -0
- arbeitszeit_flask/text_renderer.py +54 -0
- arbeitszeit_flask/token.py +37 -0
- arbeitszeit_flask/translations/de/LC_MESSAGES/messages.mo +0 -0
- arbeitszeit_flask/translations/de/LC_MESSAGES/messages.po +3132 -0
- arbeitszeit_flask/translations/en/LC_MESSAGES/messages.mo +0 -0
- arbeitszeit_flask/translations/en/LC_MESSAGES/messages.po +2661 -0
- arbeitszeit_flask/translations/es/LC_MESSAGES/messages.mo +0 -0
- arbeitszeit_flask/translations/es/LC_MESSAGES/messages.po +2594 -0
- arbeitszeit_flask/translations/messages.pot +2356 -0
- arbeitszeit_flask/translator.py +17 -0
- arbeitszeit_flask/types.py +6 -0
- arbeitszeit_flask/url_index.py +251 -0
- arbeitszeit_flask/views/__init__.py +17 -0
- arbeitszeit_flask/views/accept_cooperation_request_view.py +38 -0
- arbeitszeit_flask/views/accountant_invitation_email_view.py +23 -0
- arbeitszeit_flask/views/cancel_cooperation_request_view.py +34 -0
- arbeitszeit_flask/views/change_email_address_view.py +85 -0
- arbeitszeit_flask/views/company_dashboard_view.py +34 -0
- arbeitszeit_flask/views/company_work_invite_view.py +68 -0
- arbeitszeit_flask/views/coop_summary_view.py +34 -0
- arbeitszeit_flask/views/create_cooperation_view.py +45 -0
- arbeitszeit_flask/views/create_draft_view.py +63 -0
- arbeitszeit_flask/views/deny_cooperation_view.py +39 -0
- arbeitszeit_flask/views/draft_details_view.py +69 -0
- arbeitszeit_flask/views/end_cooperation_view.py +34 -0
- arbeitszeit_flask/views/get_statistics_view.py +23 -0
- arbeitszeit_flask/views/healthcheck_view.py +10 -0
- arbeitszeit_flask/views/http_error_view.py +27 -0
- arbeitszeit_flask/views/invite_worker_to_company.py +96 -0
- arbeitszeit_flask/views/list_all_cooperations_view.py +20 -0
- arbeitszeit_flask/views/list_coordinators_of_cooperation_view.py +30 -0
- arbeitszeit_flask/views/list_pending_work_invites_view.py +63 -0
- arbeitszeit_flask/views/list_registered_hours_worked_view.py +32 -0
- arbeitszeit_flask/views/list_transfers.py +26 -0
- arbeitszeit_flask/views/query_companies.py +66 -0
- arbeitszeit_flask/views/query_plans.py +56 -0
- arbeitszeit_flask/views/query_private_consumptions.py +34 -0
- arbeitszeit_flask/views/register_hours_worked_view.py +61 -0
- arbeitszeit_flask/views/register_private_consumption.py +65 -0
- arbeitszeit_flask/views/register_productive_consumption.py +93 -0
- arbeitszeit_flask/views/remove_worker_from_company_view.py +72 -0
- arbeitszeit_flask/views/request_cooperation_view.py +77 -0
- arbeitszeit_flask/views/request_coordination_transfer_view.py +66 -0
- arbeitszeit_flask/views/review_registered_consumptions_view.py +38 -0
- arbeitszeit_flask/views/show_a_account_details_view.py +30 -0
- arbeitszeit_flask/views/show_company_accounts_view.py +30 -0
- arbeitszeit_flask/views/show_coordination_transfer_request_view.py +65 -0
- arbeitszeit_flask/views/show_p_account_details_view.py +30 -0
- arbeitszeit_flask/views/show_prd_account_details_view.py +32 -0
- arbeitszeit_flask/views/show_r_account_details_view.py +30 -0
- arbeitszeit_flask/views/signup_accountant_view.py +56 -0
- arbeitszeit_flask/views/signup_company_view.py +57 -0
- arbeitszeit_flask/views/signup_member_view.py +58 -0
- arbeitszeit_flask/wsgi.py +6 -0
- arbeitszeit_web/__init__.py +0 -0
- arbeitszeit_web/api/__init__.py +0 -0
- arbeitszeit_web/api/authentication.py +16 -0
- arbeitszeit_web/api/controllers/__init__.py +0 -0
- arbeitszeit_web/api/controllers/get_plan_api_controller.py +24 -0
- arbeitszeit_web/api/controllers/liquid_means_consumption_controller.py +87 -0
- arbeitszeit_web/api/controllers/login_company_api_controller.py +37 -0
- arbeitszeit_web/api/controllers/login_member_api_controller.py +40 -0
- arbeitszeit_web/api/controllers/parameters.py +26 -0
- arbeitszeit_web/api/controllers/query_companies_api_controller.py +52 -0
- arbeitszeit_web/api/controllers/query_parser.py +12 -0
- arbeitszeit_web/api/controllers/query_plans_api_controller.py +58 -0
- arbeitszeit_web/api/presenters/__init__.py +0 -0
- arbeitszeit_web/api/presenters/get_plan_api_presenter.py +51 -0
- arbeitszeit_web/api/presenters/interfaces.py +68 -0
- arbeitszeit_web/api/presenters/liquid_means_consumption_presenter.py +36 -0
- arbeitszeit_web/api/presenters/login_company_api_presenter.py +36 -0
- arbeitszeit_web/api/presenters/login_member_api_presenter.py +36 -0
- arbeitszeit_web/api/presenters/query_companies_api_presenter.py +49 -0
- arbeitszeit_web/api/presenters/query_plans_api_presenter.py +58 -0
- arbeitszeit_web/api/response_errors.py +91 -0
- arbeitszeit_web/colors.py +18 -0
- arbeitszeit_web/email/__init__.py +17 -0
- arbeitszeit_web/email/accountant_invitation_presenter.py +38 -0
- arbeitszeit_web/email/company_notifier.py +40 -0
- arbeitszeit_web/email/cooperation_request_email_presenter.py +24 -0
- arbeitszeit_web/email/email_change_confirmation_presenter.py +37 -0
- arbeitszeit_web/email/email_change_warning_view.py +26 -0
- arbeitszeit_web/email/email_sender.py +76 -0
- arbeitszeit_web/email/invite_worker_presenter.py +31 -0
- arbeitszeit_web/email/notify_about_worker_removal_from_company_presenter.py +24 -0
- arbeitszeit_web/email/notify_accountant_about_new_plan_presenter.py +27 -0
- arbeitszeit_web/email/registration_email_presenter.py +45 -0
- arbeitszeit_web/email/request_coordination_transfer_presenter.py +34 -0
- arbeitszeit_web/formatters/__init__.py +0 -0
- arbeitszeit_web/formatters/datetime_formatter.py +14 -0
- arbeitszeit_web/formatters/plan_details_formatter.py +136 -0
- arbeitszeit_web/forms/__init__.py +104 -0
- arbeitszeit_web/forms/fields.py +38 -0
- arbeitszeit_web/forms/formfield_parsers.py +63 -0
- arbeitszeit_web/json.py +1 -0
- arbeitszeit_web/language_service.py +5 -0
- arbeitszeit_web/malformed_input_data.py +7 -0
- arbeitszeit_web/notification.py +9 -0
- arbeitszeit_web/pagination.py +69 -0
- arbeitszeit_web/plotter.py +18 -0
- arbeitszeit_web/request.py +37 -0
- arbeitszeit_web/session.py +28 -0
- arbeitszeit_web/text_renderer.py +25 -0
- arbeitszeit_web/token.py +8 -0
- arbeitszeit_web/translator.py +24 -0
- arbeitszeit_web/url_index.py +166 -0
- arbeitszeit_web/www/__init__.py +0 -0
- arbeitszeit_web/www/authentication.py +157 -0
- arbeitszeit_web/www/controllers/__init__.py +0 -0
- arbeitszeit_web/www/controllers/accept_coordination_transfer_controller.py +21 -0
- arbeitszeit_web/www/controllers/answer_company_work_invite_controller.py +29 -0
- arbeitszeit_web/www/controllers/approve_plan_controller.py +8 -0
- arbeitszeit_web/www/controllers/change_user_email_address_controller.py +44 -0
- arbeitszeit_web/www/controllers/confirm_company_controller.py +21 -0
- arbeitszeit_web/www/controllers/confirm_member_controller.py +20 -0
- arbeitszeit_web/www/controllers/create_draft_controller.py +42 -0
- arbeitszeit_web/www/controllers/create_draft_from_plan_controller.py +19 -0
- arbeitszeit_web/www/controllers/delete_draft_controller.py +28 -0
- arbeitszeit_web/www/controllers/draft_form_validator.py +207 -0
- arbeitszeit_web/www/controllers/edit_draft_controller.py +44 -0
- arbeitszeit_web/www/controllers/end_cooperation_controller.py +33 -0
- arbeitszeit_web/www/controllers/file_plan_with_accounting_controller.py +23 -0
- arbeitszeit_web/www/controllers/invite_worker_to_company_controller.py +48 -0
- arbeitszeit_web/www/controllers/list_pending_work_invites_controller.py +16 -0
- arbeitszeit_web/www/controllers/list_registered_hours_worked_controller.py +16 -0
- arbeitszeit_web/www/controllers/list_transfers_controller.py +20 -0
- arbeitszeit_web/www/controllers/list_workers_controller.py +14 -0
- arbeitszeit_web/www/controllers/log_in_accountant_controller.py +12 -0
- arbeitszeit_web/www/controllers/query_companies_controller.py +36 -0
- arbeitszeit_web/www/controllers/query_plans_controller.py +70 -0
- arbeitszeit_web/www/controllers/query_private_consumptions_controller.py +23 -0
- arbeitszeit_web/www/controllers/register_accountant_controller.py +31 -0
- arbeitszeit_web/www/controllers/register_company_controller.py +14 -0
- arbeitszeit_web/www/controllers/register_hours_worked_controller.py +55 -0
- arbeitszeit_web/www/controllers/register_member_controller.py +14 -0
- arbeitszeit_web/www/controllers/register_private_consumption_controller.py +80 -0
- arbeitszeit_web/www/controllers/register_private_consumption_form_validator.py +34 -0
- arbeitszeit_web/www/controllers/register_productive_consumption_controller.py +62 -0
- arbeitszeit_web/www/controllers/reject_plan_controller.py +8 -0
- arbeitszeit_web/www/controllers/remove_worker_from_company_controller.py +46 -0
- arbeitszeit_web/www/controllers/request_cooperation_controller.py +45 -0
- arbeitszeit_web/www/controllers/request_coordination_transfer_controller.py +32 -0
- arbeitszeit_web/www/controllers/request_email_address_change_controller.py +45 -0
- arbeitszeit_web/www/controllers/resend_work_invite_controller.py +27 -0
- arbeitszeit_web/www/controllers/review_registered_consumptions_controller.py +29 -0
- arbeitszeit_web/www/controllers/revoke_plan_filing_controller.py +15 -0
- arbeitszeit_web/www/controllers/select_productive_consumption_controller.py +95 -0
- arbeitszeit_web/www/controllers/show_a_account_details_controller.py +10 -0
- arbeitszeit_web/www/controllers/show_company_accounts_controller.py +10 -0
- arbeitszeit_web/www/controllers/show_company_work_invite_details_controller.py +25 -0
- arbeitszeit_web/www/controllers/show_p_account_details_controller.py +10 -0
- arbeitszeit_web/www/controllers/show_prd_account_details_controller.py +10 -0
- arbeitszeit_web/www/controllers/show_r_account_details_controller.py +10 -0
- arbeitszeit_web/www/controllers/user_account_details_controller.py +20 -0
- arbeitszeit_web/www/navbar.py +8 -0
- arbeitszeit_web/www/presenters/__init__.py +0 -0
- arbeitszeit_web/www/presenters/accept_cooperation_request_presenter.py +57 -0
- arbeitszeit_web/www/presenters/accept_coordination_transfer_presenter.py +78 -0
- arbeitszeit_web/www/presenters/answer_company_work_invite_presenter.py +39 -0
- arbeitszeit_web/www/presenters/approve_plan_presenter.py +32 -0
- arbeitszeit_web/www/presenters/cancel_cooperation_request_presenter.py +28 -0
- arbeitszeit_web/www/presenters/change_user_email_address_presenter.py +34 -0
- arbeitszeit_web/www/presenters/company_consumptions_presenter.py +64 -0
- arbeitszeit_web/www/presenters/create_cooperation_presenter.py +42 -0
- arbeitszeit_web/www/presenters/create_draft_from_plan_presenter.py +39 -0
- arbeitszeit_web/www/presenters/create_draft_presenter.py +28 -0
- arbeitszeit_web/www/presenters/delete_draft_presenter.py +33 -0
- arbeitszeit_web/www/presenters/deny_cooperation_presenter.py +58 -0
- arbeitszeit_web/www/presenters/edit_draft_presenter.py +37 -0
- arbeitszeit_web/www/presenters/end_cooperation_presenter.py +59 -0
- arbeitszeit_web/www/presenters/file_plan_with_accounting_presenter.py +34 -0
- arbeitszeit_web/www/presenters/get_accountant_dashboard_presenter.py +28 -0
- arbeitszeit_web/www/presenters/get_company_dashboard_presenter.py +82 -0
- arbeitszeit_web/www/presenters/get_company_summary_presenter.py +151 -0
- arbeitszeit_web/www/presenters/get_coop_summary_presenter.py +86 -0
- arbeitszeit_web/www/presenters/get_coordination_transfer_request_details_presenter.py +53 -0
- arbeitszeit_web/www/presenters/get_draft_details_presenter.py +34 -0
- arbeitszeit_web/www/presenters/get_member_account_presenter.py +31 -0
- arbeitszeit_web/www/presenters/get_member_dashboard_presenter.py +107 -0
- arbeitszeit_web/www/presenters/get_plan_details_accountant_presenter.py +31 -0
- arbeitszeit_web/www/presenters/get_plan_details_company_presenter.py +72 -0
- arbeitszeit_web/www/presenters/get_plan_details_member_presenter.py +35 -0
- arbeitszeit_web/www/presenters/get_statistics_presenter.py +101 -0
- arbeitszeit_web/www/presenters/hide_plan_presenter.py +21 -0
- arbeitszeit_web/www/presenters/invite_worker_to_company_presenter.py +67 -0
- arbeitszeit_web/www/presenters/list_all_cooperations_presenter.py +40 -0
- arbeitszeit_web/www/presenters/list_available_languages_presenter.py +40 -0
- arbeitszeit_web/www/presenters/list_coordinations_of_cooperation_presenter.py +77 -0
- arbeitszeit_web/www/presenters/list_pending_work_invites_presenter.py +45 -0
- arbeitszeit_web/www/presenters/list_plans_presenter.py +33 -0
- arbeitszeit_web/www/presenters/list_plans_with_pending_review_presenter.py +51 -0
- arbeitszeit_web/www/presenters/list_registered_hours_worked_presenter.py +37 -0
- arbeitszeit_web/www/presenters/list_transfers_presenter.py +134 -0
- arbeitszeit_web/www/presenters/list_workers_presenter.py +35 -0
- arbeitszeit_web/www/presenters/log_in_accountant_presenter.py +48 -0
- arbeitszeit_web/www/presenters/log_in_company_presenter.py +50 -0
- arbeitszeit_web/www/presenters/log_in_member_presenter.py +48 -0
- arbeitszeit_web/www/presenters/private_consumptions_presenter.py +43 -0
- arbeitszeit_web/www/presenters/query_companies_presenter.py +75 -0
- arbeitszeit_web/www/presenters/query_plans_presenter.py +90 -0
- arbeitszeit_web/www/presenters/register_accountant_presenter.py +38 -0
- arbeitszeit_web/www/presenters/register_company_presenter.py +37 -0
- arbeitszeit_web/www/presenters/register_hours_worked_presenter.py +49 -0
- arbeitszeit_web/www/presenters/register_member_presenter.py +52 -0
- arbeitszeit_web/www/presenters/register_private_consumption_presenter.py +79 -0
- arbeitszeit_web/www/presenters/register_productive_consumption_presenter.py +65 -0
- arbeitszeit_web/www/presenters/reject_plan_presenter.py +34 -0
- arbeitszeit_web/www/presenters/remove_worker_from_company_presenter.py +49 -0
- arbeitszeit_web/www/presenters/request_cooperation_presenter.py +75 -0
- arbeitszeit_web/www/presenters/request_coordination_transfer_presenter.py +91 -0
- arbeitszeit_web/www/presenters/request_email_address_change_presenter.py +70 -0
- arbeitszeit_web/www/presenters/resend_work_invite_presenter.py +29 -0
- arbeitszeit_web/www/presenters/review_registered_consumptions_presenter.py +69 -0
- arbeitszeit_web/www/presenters/revoke_plan_filing_presenter.py +24 -0
- arbeitszeit_web/www/presenters/select_productive_consumption_presenter.py +68 -0
- arbeitszeit_web/www/presenters/show_a_account_details_presenter.py +47 -0
- arbeitszeit_web/www/presenters/show_company_accounts_presenter.py +52 -0
- arbeitszeit_web/www/presenters/show_company_work_invite_details_presenter.py +35 -0
- arbeitszeit_web/www/presenters/show_my_cooperations_presenter.py +184 -0
- arbeitszeit_web/www/presenters/show_my_plans_presenter.py +241 -0
- arbeitszeit_web/www/presenters/show_p_account_details_presenter.py +50 -0
- arbeitszeit_web/www/presenters/show_prd_account_details_presenter.py +51 -0
- arbeitszeit_web/www/presenters/show_r_account_details_presenter.py +46 -0
- arbeitszeit_web/www/presenters/start_page_presenter.py +38 -0
- arbeitszeit_web/www/presenters/transfers.py +114 -0
- arbeitszeit_web/www/presenters/user_account_details_presenter.py +30 -0
- arbeitszeit_web/www/response.py +6 -0
- workers_control-0.1.1.dist-info/METADATA +67 -0
- workers_control-0.1.1.dist-info/RECORD +510 -0
- workers_control-0.1.1.dist-info/WHEEL +5 -0
- workers_control-0.1.1.dist-info/licenses/LICENSE +661 -0
- workers_control-0.1.1.dist-info/top_level.txt +4 -0
arbeitszeit/__init__.py
ADDED
|
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: ...
|
arbeitszeit/decimal.py
ADDED
arbeitszeit/email.py
ADDED
|
@@ -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: ...
|
arbeitszeit/injector.py
ADDED
|
@@ -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
|
+
)
|