decidim-time_tracker 0.3

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 (134) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +174 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/admin/decidim_time_tracker_manifest.css +2 -0
  6. data/app/assets/config/admin/decidim_time_tracker_manifest.js +1 -0
  7. data/app/assets/config/decidim_time_tracker_manifest.css +3 -0
  8. data/app/assets/config/decidim_time_tracker_manifest.js +1 -0
  9. data/app/assets/images/decidim/time_tracker/icon.svg +1 -0
  10. data/app/assets/javascripts/decidim/time_tracker/activity_ui.js.es6 +112 -0
  11. data/app/assets/javascripts/decidim/time_tracker/admin/assignations.js.es6 +35 -0
  12. data/app/assets/javascripts/decidim/time_tracker/reports/reports.js.es6 +19 -0
  13. data/app/assets/javascripts/decidim/time_tracker/time_tracker.js.es6 +82 -0
  14. data/app/assets/javascripts/decidim/time_tracker/timer_api.js.es6 +55 -0
  15. data/app/assets/stylesheets/decidim/time_tracker/_variables.scss +1 -0
  16. data/app/assets/stylesheets/decidim/time_tracker/admin/time_tracker.scss +59 -0
  17. data/app/assets/stylesheets/decidim/time_tracker/time_tracker.scss +118 -0
  18. data/app/cells/decidim/time_tracker/milestone/show.erb +42 -0
  19. data/app/cells/decidim/time_tracker/milestone_cell.rb +54 -0
  20. data/app/commands/decidim/time_tracker/admin/create_activity.rb +39 -0
  21. data/app/commands/decidim/time_tracker/admin/create_assignation.rb +75 -0
  22. data/app/commands/decidim/time_tracker/admin/create_task.rb +33 -0
  23. data/app/commands/decidim/time_tracker/admin/create_time_tracker.rb +76 -0
  24. data/app/commands/decidim/time_tracker/admin/destroy_activity.rb +43 -0
  25. data/app/commands/decidim/time_tracker/admin/destroy_assignation.rb +43 -0
  26. data/app/commands/decidim/time_tracker/admin/destroy_task.rb +43 -0
  27. data/app/commands/decidim/time_tracker/admin/export_time_tracker.rb +150 -0
  28. data/app/commands/decidim/time_tracker/admin/update_activity.rb +49 -0
  29. data/app/commands/decidim/time_tracker/admin/update_assignation.rb +42 -0
  30. data/app/commands/decidim/time_tracker/admin/update_task.rb +44 -0
  31. data/app/commands/decidim/time_tracker/create_milestone.rb +57 -0
  32. data/app/commands/decidim/time_tracker/create_request_assignation.rb +39 -0
  33. data/app/commands/decidim/time_tracker/start_time_event.rb +104 -0
  34. data/app/commands/decidim/time_tracker/stop_last_time_event.rb +45 -0
  35. data/app/controllers/concerns/decidim/time_tracker/admin/filterable_assignations.rb +50 -0
  36. data/app/controllers/decidim/time_tracker/activity_questionnaire_controller.rb +76 -0
  37. data/app/controllers/decidim/time_tracker/admin/activities_controller.rb +82 -0
  38. data/app/controllers/decidim/time_tracker/admin/activity_questionnaire_controller.rb +52 -0
  39. data/app/controllers/decidim/time_tracker/admin/application_controller.rb +22 -0
  40. data/app/controllers/decidim/time_tracker/admin/assignations_controller.rb +84 -0
  41. data/app/controllers/decidim/time_tracker/admin/assignee_questionnaire_controller.rb +58 -0
  42. data/app/controllers/decidim/time_tracker/admin/stats_controller.rb +24 -0
  43. data/app/controllers/decidim/time_tracker/admin/tasks_controller.rb +112 -0
  44. data/app/controllers/decidim/time_tracker/admin/time_tracker_exports_controller.rb +25 -0
  45. data/app/controllers/decidim/time_tracker/application_controller.rb +24 -0
  46. data/app/controllers/decidim/time_tracker/assignations_controller.rb +38 -0
  47. data/app/controllers/decidim/time_tracker/assignee_questionnaire_controller.rb +80 -0
  48. data/app/controllers/decidim/time_tracker/milestones_controller.rb +62 -0
  49. data/app/controllers/decidim/time_tracker/reports/user_controller.rb +38 -0
  50. data/app/controllers/decidim/time_tracker/time_events_controller.rb +68 -0
  51. data/app/controllers/decidim/time_tracker/time_tracker_controller.rb +43 -0
  52. data/app/forms/decidim/time_tracker/admin/activity_form.rb +27 -0
  53. data/app/forms/decidim/time_tracker/admin/assignation_form.rb +29 -0
  54. data/app/forms/decidim/time_tracker/admin/task_form.rb +22 -0
  55. data/app/forms/decidim/time_tracker/milestone_form.rb +32 -0
  56. data/app/forms/decidim/time_tracker/time_event_form.rb +47 -0
  57. data/app/helpers/decidim/time_tracker/admin/application_helper.rb +33 -0
  58. data/app/helpers/decidim/time_tracker/application_helper.rb +95 -0
  59. data/app/jobs/decidim/time_tracker/stop_counter_job.rb +29 -0
  60. data/app/models/decidim/time_tracker/activity.rb +135 -0
  61. data/app/models/decidim/time_tracker/application_record.rb +10 -0
  62. data/app/models/decidim/time_tracker/assignation.rb +68 -0
  63. data/app/models/decidim/time_tracker/assignee.rb +29 -0
  64. data/app/models/decidim/time_tracker/assignee_data.rb +19 -0
  65. data/app/models/decidim/time_tracker/milestone.rb +27 -0
  66. data/app/models/decidim/time_tracker/task.rb +44 -0
  67. data/app/models/decidim/time_tracker/time_event.rb +62 -0
  68. data/app/models/decidim/time_tracker/time_tracker.rb +46 -0
  69. data/app/models/decidim/time_tracker/tos_acceptance.rb +16 -0
  70. data/app/permissions/decidim/time_tracker/admin/permissions.rb +69 -0
  71. data/app/permissions/decidim/time_tracker/permissions.rb +69 -0
  72. data/app/presenters/decidim/time_tracker/admin_log/activity_presenter.rb +44 -0
  73. data/app/presenters/decidim/time_tracker/admin_log/assignation_presenter.rb +48 -0
  74. data/app/presenters/decidim/time_tracker/admin_log/task_presenter.rb +46 -0
  75. data/app/presenters/decidim/time_tracker/admin_log/value_types/activity_presenter.rb +28 -0
  76. data/app/presenters/decidim/time_tracker/admin_log/value_types/time_tracker_presenter.rb +28 -0
  77. data/app/views/decidim/time_tracker/admin/_breadcrumbs.html.erb +56 -0
  78. data/app/views/decidim/time_tracker/admin/activities/_form.html.erb +41 -0
  79. data/app/views/decidim/time_tracker/admin/activities/_index.html.erb +73 -0
  80. data/app/views/decidim/time_tracker/admin/activities/edit.html.erb +5 -0
  81. data/app/views/decidim/time_tracker/admin/activities/new.html.erb +5 -0
  82. data/app/views/decidim/time_tracker/admin/assignations/_form.html.erb +30 -0
  83. data/app/views/decidim/time_tracker/admin/assignations/index.html.erb +67 -0
  84. data/app/views/decidim/time_tracker/admin/assignations/new.html.erb +9 -0
  85. data/app/views/decidim/time_tracker/admin/questionnaires/_index.html.erb +27 -0
  86. data/app/views/decidim/time_tracker/admin/questionnaires/_questionnaire.html.erb +21 -0
  87. data/app/views/decidim/time_tracker/admin/stats/index.html.erb +50 -0
  88. data/app/views/decidim/time_tracker/admin/tasks/_assignations.html.erb +60 -0
  89. data/app/views/decidim/time_tracker/admin/tasks/_form.html.erb +7 -0
  90. data/app/views/decidim/time_tracker/admin/tasks/_index.html.erb +61 -0
  91. data/app/views/decidim/time_tracker/admin/tasks/edit.html.erb +16 -0
  92. data/app/views/decidim/time_tracker/admin/tasks/index.html.erb +5 -0
  93. data/app/views/decidim/time_tracker/admin/tasks/new.html.erb +12 -0
  94. data/app/views/decidim/time_tracker/milestones/index.html.erb +60 -0
  95. data/app/views/decidim/time_tracker/reports/user/index.html.erb +41 -0
  96. data/app/views/decidim/time_tracker/time_tracker/_activity.html.erb +43 -0
  97. data/app/views/decidim/time_tracker/time_tracker/_answer_questionnaire.html.erb +7 -0
  98. data/app/views/decidim/time_tracker/time_tracker/_assignee_data.html.erb +9 -0
  99. data/app/views/decidim/time_tracker/time_tracker/_callout_status.html.erb +3 -0
  100. data/app/views/decidim/time_tracker/time_tracker/_idle_activity.html.erb +27 -0
  101. data/app/views/decidim/time_tracker/time_tracker/_milestone_form.html.erb +35 -0
  102. data/app/views/decidim/time_tracker/time_tracker/_request_activity.html.erb +22 -0
  103. data/app/views/decidim/time_tracker/time_tracker/_sign_in.html.erb +5 -0
  104. data/app/views/decidim/time_tracker/time_tracker/index.html.erb +59 -0
  105. data/config/activity_questionnaire.yml +26 -0
  106. data/config/assignee_questionnaire.yml +37 -0
  107. data/config/i18n-tasks.yml +10 -0
  108. data/config/locales/ca.yml +394 -0
  109. data/config/locales/cs.yml +394 -0
  110. data/config/locales/en.yml +419 -0
  111. data/config/locales/es.yml +394 -0
  112. data/db/migrate/20200302115642_create_decidim_time_tracker_time_trackers.rb +10 -0
  113. data/db/migrate/20200302115654_create_decidim_time_tracker_tasks.rb +11 -0
  114. data/db/migrate/20200302115658_create_decidim_time_tracker_assignees.rb +12 -0
  115. data/db/migrate/20200302115700_create_decidim_time_tracker_activities.rb +19 -0
  116. data/db/migrate/20200302115708_create_decidim_time_tracker_milestones.rb +13 -0
  117. data/db/migrate/20200302115831_create_decidim_time_tracker_assignations.rb +16 -0
  118. data/db/migrate/20200302115840_create_decidim_time_tracker_time_events.rb +15 -0
  119. data/db/migrate/20201118153327_create_decidim_time_tracker_assignee_data.rb +10 -0
  120. data/db/migrate/20201223115130_create_decidim_time_tracker_tos_acceptances.rb +18 -0
  121. data/lib/decidim/time_tracker.rb +45 -0
  122. data/lib/decidim/time_tracker/admin.rb +10 -0
  123. data/lib/decidim/time_tracker/admin_engine.rb +51 -0
  124. data/lib/decidim/time_tracker/component.rb +248 -0
  125. data/lib/decidim/time_tracker/engine.rb +59 -0
  126. data/lib/decidim/time_tracker/reports.rb +10 -0
  127. data/lib/decidim/time_tracker/reports_engine.rb +34 -0
  128. data/lib/decidim/time_tracker/test/admin_log_presenter_examples.rb +34 -0
  129. data/lib/decidim/time_tracker/test/factories.rb +110 -0
  130. data/lib/decidim/time_tracker/test/time_tracker_context.rb +18 -0
  131. data/lib/decidim/time_tracker/time_tracker_activity_questionnaire_answers_serializer.rb +74 -0
  132. data/lib/decidim/time_tracker/version.rb +9 -0
  133. data/vendor/assets/javascripts/jsrender.min.js +4 -0
  134. metadata +276 -0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class AssigneeQuestionnaireController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::Forms::Concerns::HasQuestionnaire
7
+
8
+ # only allows answers if not in preview mode
9
+ def preview
10
+ return show if request.method == "GET"
11
+
12
+ flash[":alert"] = I18n.t("questionnaire_in_preview_mode", scope: "decidim.time_tracker.time_tracker")
13
+ redirect_to preview_assignee_questionnaire_path
14
+ end
15
+
16
+ # overwrite this method to automatically accept TOS
17
+ def answer
18
+ enforce_permission_to :answer, :questionnaire
19
+
20
+ @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
21
+
22
+ Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
23
+ on(:ok) do
24
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.success")
25
+ TosAcceptance.create!(assignee: Assignee.for(current_user), time_tracker: time_tracker)
26
+ flash[:notice] = I18n.t("answer.success", scope: i18n_flashes_scope)
27
+ redirect_to after_answer_path
28
+ end
29
+
30
+ on(:invalid) do
31
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.invalid")
32
+ flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
33
+ render template: "decidim/forms/questionnaires/show"
34
+ end
35
+ end
36
+ end
37
+
38
+ def questionnaire_for
39
+ time_tracker.assignee_data
40
+ end
41
+
42
+ # only allows answers if not in preview mode
43
+ def allow_answers?
44
+ return false if current_user.blank?
45
+
46
+ return true if params[:action] == "preview" && current_user.admin?
47
+
48
+ current_component.published?
49
+ end
50
+
51
+ # Override so can answer only if is an assignation can view
52
+ # Also admins can preview it (but not answer)
53
+ def visitor_can_answer?
54
+ current_user.present?
55
+ end
56
+
57
+ def visitor_already_answered?
58
+ return false if params[:action] == "preview" && current_user.admin?
59
+
60
+ questionnaire.answered_by?(current_user)
61
+ end
62
+
63
+ # Returns the path to answer this questionnaire for normal users
64
+ # If the questionnaire is rendered in a preview route, then just do nothing with the responses
65
+ def update_url
66
+ return preview_assignee_questionnaire_path if params[:action] == "preview"
67
+
68
+ answer_assignee_questionnaire_path
69
+ end
70
+
71
+ def form_path
72
+ assignee_questionnaire_path
73
+ end
74
+
75
+ def after_answer_path
76
+ Decidim::EngineRouter.main_proxy(current_component).root_path
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class MilestonesController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+ include Decidim::TimeTracker::ApplicationHelper
8
+
9
+ helper_method :user, :activities
10
+
11
+ def index
12
+ return redirect_to root_path if user.blank?
13
+
14
+ enforce_permission_to :index, :milestones, user: user
15
+ end
16
+
17
+ # creates a milestone
18
+ def create
19
+ enforce_permission_to :create, :milestone, activity: activity
20
+
21
+ @form = form(MilestoneForm).from_params(milestone_params)
22
+
23
+ CreateMilestone.call(@form, current_user) do
24
+ on(:ok) do |_milestone|
25
+ flash[:notice] = I18n.t("milestones.create.success", scope: "decidim.time_tracker")
26
+ redirect_to milestones_path(nickname: current_user.nickname)
27
+ end
28
+ on(:invalid) do |_message|
29
+ flash[:alert] = I18n.t("milestones.create.error", scope: "decidim.time_tracker")
30
+ redirect_to root_path
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # copies the title to the attachment
38
+ def milestone_params
39
+ unsafe = params.to_unsafe_h
40
+ unsafe["milestone"]["attachment"]["title"] = { I18n.locale => params[:milestone][:title] } if unsafe["milestone"]["attachment"].present?
41
+ unsafe
42
+ end
43
+
44
+ def root_path
45
+ Decidim::EngineRouter.main_proxy(current_component).root_path
46
+ end
47
+
48
+ def activity
49
+ @activity ||= Activity.active.find_by(id: params[:milestone][:activity_id])
50
+ end
51
+
52
+ def activities
53
+ @activities ||= Activity.select("DISTINCT ON (id) *")
54
+ .where(id: Milestone.where(user: user).pluck(:activity_id))
55
+ end
56
+
57
+ def user
58
+ @user ||= Decidim::User.find_by(nickname: params[:nickname])
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Reports
6
+ # The controller to handle the user's time_tracker report page.
7
+ class UserController < Decidim::ApplicationController
8
+ include Decidim::ComponentPathHelper
9
+ include Decidim::UserProfile
10
+
11
+ helper Decidim::TimeTracker::ApplicationHelper
12
+ helper_method :activities, :assignations, :total_time, :activity_path
13
+
14
+ def index
15
+ enforce_permission_to :read, :user, current_user: current_user
16
+ end
17
+
18
+ private
19
+
20
+ def activities
21
+ Decidim::TimeTracker::Activity.joins(:assignations).where("decidim_time_tracker_assignations.decidim_user_id": current_user.id).group_by(&:id)
22
+ end
23
+
24
+ def assignations
25
+ Assignation.where(user: current_user).sorted_by_status(:accepted, :pending, :rejected)
26
+ end
27
+
28
+ def activity_path(assignation)
29
+ EngineRouter.main_proxy(assignation.task.time_tracker.component).root_path(locale: params[:locale], activity: assignation.activity)
30
+ end
31
+
32
+ def total_time
33
+ assignations.map(&:time_dedicated).sum
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeEventsController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+
8
+ helper_method :activity, :task, :assignation
9
+
10
+ def start
11
+ enforce_permission_to :start, :time_events
12
+
13
+ form = form(TimeEventForm).from_params(activity: activity, assignation: assignation)
14
+ StartTimeEvent.call(form) do
15
+ on(:ok) do |time_event|
16
+ render json: { message: I18n.t("time_events.start.success", scope: "decidim.time_tracker"),
17
+ id: time_event.id,
18
+ start: time_event.start,
19
+ startTime: time_event.start_time.iso8601,
20
+ elapsedSeconds: activity.user_total_seconds(current_user) }
21
+ end
22
+ on(:already_active) do |time_event|
23
+ render json: { message: I18n.t("time_events.start.already_started", scope: "decidim.time_tracker"),
24
+ error: I18n.t("time_events.start.already_started", scope: "decidim.time_tracker"),
25
+ id: time_event.id }
26
+ end
27
+ on(:invalid) do |message|
28
+ render json: { message: I18n.t("time_events.start.error", scope: "decidim.time_tracker"),
29
+ error: message }, status: :unprocessable_entity
30
+ end
31
+ end
32
+ end
33
+
34
+ def stop
35
+ enforce_permission_to :stop, :time_events
36
+
37
+ StopLastTimeEvent.call(current_user) do
38
+ on(:ok) do |time_event|
39
+ render json: { message: I18n.t("time_events.stop.success", scope: "decidim.time_tracker"),
40
+ timeEvent: time_event.to_json }
41
+ end
42
+ on(:already_stopped) do |_time_event|
43
+ render json: { message: I18n.t("time_events.stop.error", scope: "decidim.time_tracker"),
44
+ error: I18n.t("time_events.stop.already_stopped", scope: "decidim.time_tracker") }
45
+ end
46
+ on(:invalid) do |message|
47
+ render json: { message: I18n.t("time_events.stop.error", scope: "decidim.time_tracker"),
48
+ error: message }, status: :unprocessable_entity
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def task
56
+ Task.find(params[:task_id])
57
+ end
58
+
59
+ def activity
60
+ Activity.find(params[:activity_id])
61
+ end
62
+
63
+ def assignation
64
+ Assignation.find_by(user: current_user, activity: activity)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeTrackerController < Decidim::TimeTracker::ApplicationController
6
+ include Decidim::FormFactory
7
+
8
+ helper_method :tasks, :assignation_milestones, :start_endpoint, :stop_endpoint, :requests_path, :questionnaire_path
9
+
10
+ def index
11
+ @form = form(MilestoneForm).from_params(
12
+ attachment: form(AttachmentForm).instance
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ def tasks
19
+ time_tracker.tasks
20
+ end
21
+
22
+ def assignation_milestones(activity)
23
+ Milestone.where(activity: activity).order(created_at: :desc).select("DISTINCT ON (decidim_user_id, created_at) *")
24
+ end
25
+
26
+ def start_endpoint(activity)
27
+ Decidim::EngineRouter.main_proxy(current_component).task_activity_start_path(activity.task, activity.id)
28
+ end
29
+
30
+ def stop_endpoint(activity)
31
+ Decidim::EngineRouter.main_proxy(current_component).task_activity_stop_path(activity.task, activity.id)
32
+ end
33
+
34
+ def requests_path(activity)
35
+ Decidim::EngineRouter.main_proxy(current_component).assignations_path(activity_id: activity.id)
36
+ end
37
+
38
+ def questionnaire_path(activity)
39
+ Decidim::EngineRouter.main_proxy(current_component).new_assignation_path(task_id: activity.task, activity_id: activity.id, id: activity.questionnaire.id)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # This class holds a Form to create/update activity from Decidim's admin panel
7
+ class ActivityForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :description, String
11
+
12
+ attribute :active, Boolean
13
+ attribute :start_date, Decidim::Attributes::TimeWithZone
14
+ attribute :end_date, Decidim::Attributes::TimeWithZone
15
+ attribute :max_minutes_per_day, Integer
16
+ attribute :requests_start_at, Decidim::Attributes::TimeWithZone
17
+
18
+ validates :start_date, presence: true
19
+ validates :end_date, presence: true, date: { after: :start_date }
20
+ validates :requests_start_at, presence: true, date: { before: :start_date }
21
+
22
+ validates :max_minutes_per_day, presence: true
23
+ validates :description, translatable_presence: true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ class AssignationForm < Decidim::Form
7
+ mimic :assignation
8
+
9
+ attribute :user_id, Integer
10
+ attribute :existing_user, Boolean, default: false
11
+ attribute :name, String
12
+ attribute :email, String
13
+
14
+ validates :name, presence: true, unless: proc { |object| object.existing_user }
15
+ validates :email, presence: true, unless: proc { |object| object.existing_user }
16
+ validates :user, presence: true, if: proc { |object| object.existing_user }
17
+
18
+ def map_model(model)
19
+ self.user_id = model.decidim_user_id
20
+ self.existing_user = user_id.present?
21
+ end
22
+
23
+ def user
24
+ @user ||= current_organization.users.find_by(id: user_id)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ module Admin
6
+ # This class holds a Form to create/update task from Decidim's admin panel.
7
+ class TaskForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ mimic :task
11
+
12
+ translatable_attribute :name, String
13
+
14
+ validates :name, translatable_presence: true
15
+
16
+ def time_tracker
17
+ @time_tracker ||= Decidim::TimeTracker::TimeTracker.find_by(component: current_component)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class MilestoneForm < Decidim::Form
6
+ mimic :milestone
7
+
8
+ attribute :activity_id, Integer
9
+ attribute :title, String
10
+ attribute :description, String
11
+ attribute :attachment, AttachmentForm
12
+
13
+ validates :activity_id, :title, presence: true
14
+ validates :activity, presence: true, if: ->(form) { form.activity_id.present? }
15
+ validate :notify_missing_attachment_if_errored
16
+
17
+ def activity
18
+ @activity ||= Activity.find_by(id: activity_id)
19
+ end
20
+
21
+ private
22
+
23
+ # This method will add an error to the `attachment` field only if there's
24
+ # any error in any other field. This is needed because when the form has
25
+ # an error, the attachment is lost, so we need a way to inform the user of
26
+ # this problem.
27
+ def notify_missing_attachment_if_errored
28
+ errors.add(:attachment, :needs_to_be_reattached) if errors.any? && attachment.present?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TimeTracker
5
+ class TimeEventForm < Decidim::Form
6
+ mimic :time_event
7
+
8
+ attribute :activity, Decidim::TimeTracker::Activity
9
+ attribute :assignation, Decidim::TimeTracker::Assignation
10
+ attribute :user_id, Integer
11
+ attribute :start, Integer
12
+ attribute :stop, Integer
13
+
14
+ validates :activity, presence: true
15
+ validates :assignation, presence: true
16
+
17
+ validate :assigned_to_activity?
18
+ validate :activity_is_active
19
+ validate :dates_are_valid
20
+ validates :stop, date: { after: ->(form) { form.start } }, if: ->(form) { form.stop.present? && form.start.present? }
21
+
22
+ def user
23
+ return Decidim::User.find(user_id) if user_id.present?
24
+
25
+ assignation.user
26
+ end
27
+
28
+ private
29
+
30
+ def assigned_to_activity?
31
+ errors.add(:assignation, :unassigned) unless activity.assignations.accepted.find_by(id: assignation&.id)
32
+ end
33
+
34
+ def activity_is_active
35
+ errors.add(:activity, :inactive) unless activity.active?
36
+ end
37
+
38
+ def dates_are_valid
39
+ [:start, :stop].each do |key|
40
+ next unless attributes[key]
41
+
42
+ errors.add(key, :date_format) unless attributes[key].is_a?(Time)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end