decidim-assemblies 0.20.1 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/assemblies/assembly_m_cell.rb +1 -5
  3. data/app/commands/decidim/assemblies/admin/create_assemblies_type.rb +45 -0
  4. data/app/commands/decidim/assemblies/admin/create_assembly.rb +1 -2
  5. data/app/commands/decidim/assemblies/admin/destroy_assemblies_type.rb +45 -0
  6. data/app/commands/decidim/assemblies/admin/update_assemblies_type.rb +46 -0
  7. data/app/commands/decidim/assemblies/admin/update_assembly.rb +1 -2
  8. data/app/controllers/concerns/decidim/assemblies/admin/filterable.rb +30 -0
  9. data/app/controllers/decidim/assemblies/admin/assemblies_controller.rb +8 -23
  10. data/app/controllers/decidim/assemblies/admin/assemblies_types_controller.rb +107 -0
  11. data/app/controllers/decidim/assemblies/assemblies_controller.rb +1 -1
  12. data/app/forms/decidim/assemblies/admin/assemblies_type_form.rb +17 -0
  13. data/app/forms/decidim/assemblies/admin/assembly_form.rb +19 -10
  14. data/app/helpers/decidim/assemblies/admin/assemblies_helper.rb +6 -0
  15. data/app/helpers/decidim/assemblies/filter_assemblies_helper.rb +10 -7
  16. data/app/models/decidim/assemblies_type.rb +24 -0
  17. data/app/models/decidim/assembly.rb +12 -2
  18. data/app/models/decidim/assembly_user_role.rb +1 -1
  19. data/app/permissions/decidim/assemblies/permissions.rb +25 -0
  20. data/app/presenters/decidim/assemblies/admin_log/assemblies_type_presenter.rb +43 -0
  21. data/app/presenters/decidim/assemblies/admin_log/assembly_presenter.rb +1 -2
  22. data/app/presenters/decidim/log/value_types/assembly_type_presenter.rb +29 -0
  23. data/app/queries/decidim/assemblies/filtered_assemblies.rb +2 -2
  24. data/app/queries/decidim/assemblies/parent_assemblies_for_select.rb +23 -0
  25. data/app/services/decidim/assemblies/assembly_search.rb +3 -3
  26. data/app/types/decidim/assemblies/assemblies_type_type.rb +17 -0
  27. data/app/types/decidim/assemblies/assembly_member_type.rb +29 -0
  28. data/app/types/decidim/assemblies/assembly_type.rb +67 -0
  29. data/app/views/decidim/assemblies/_filter_by_type.html.erb +14 -12
  30. data/app/views/decidim/assemblies/admin/assemblies/_form.html.erb +2 -6
  31. data/app/views/decidim/assemblies/admin/assemblies/index.html.erb +5 -2
  32. data/app/views/decidim/assemblies/admin/assemblies_types/_form.html.erb +11 -0
  33. data/app/views/decidim/assemblies/admin/assemblies_types/edit.html.erb +6 -0
  34. data/app/views/decidim/assemblies/admin/assemblies_types/index.html.erb +43 -0
  35. data/app/views/decidim/assemblies/admin/assemblies_types/new.html.erb +7 -0
  36. data/app/views/decidim/assemblies/admin/assembly_members/index.html.erb +30 -30
  37. data/app/views/decidim/assemblies/assemblies/show.html.erb +1 -5
  38. data/app/views/layouts/decidim/admin/assemblies.html.erb +17 -0
  39. data/app/views/layouts/decidim/admin/assembly.html.erb +2 -2
  40. data/config/locales/ar.yml +24 -0
  41. data/config/locales/ca.yml +32 -0
  42. data/config/locales/cs.yml +33 -1
  43. data/config/locales/de.yml +11 -0
  44. data/config/locales/el.yml +1 -0
  45. data/config/locales/en.yml +32 -0
  46. data/config/locales/es-MX.yml +32 -0
  47. data/config/locales/es-PY.yml +32 -0
  48. data/config/locales/es.yml +32 -0
  49. data/config/locales/eu.yml +11 -0
  50. data/config/locales/fi-plain.yml +32 -0
  51. data/config/locales/fi.yml +32 -0
  52. data/config/locales/fr.yml +11 -0
  53. data/config/locales/gl.yml +11 -0
  54. data/config/locales/hu.yml +32 -0
  55. data/config/locales/id-ID.yml +11 -0
  56. data/config/locales/is-IS.yml +11 -0
  57. data/config/locales/it.yml +20 -0
  58. data/config/locales/nl.yml +20 -0
  59. data/config/locales/no.yml +31 -0
  60. data/config/locales/pl.yml +11 -0
  61. data/config/locales/pt-BR.yml +11 -0
  62. data/config/locales/pt.yml +11 -0
  63. data/config/locales/ru.yml +11 -0
  64. data/config/locales/sv.yml +11 -0
  65. data/config/locales/tr-TR.yml +11 -0
  66. data/config/locales/uk.yml +11 -0
  67. data/db/migrate/20200108113855_create_decidim_assembly_types.rb +19 -0
  68. data/db/migrate/20200108123050_migrate_decidim_assembly_types.rb +83 -0
  69. data/lib/decidim/assemblies/admin_engine.rb +2 -0
  70. data/lib/decidim/assemblies/engine.rb +7 -0
  71. data/lib/decidim/assemblies/participatory_space.rb +3 -2
  72. data/lib/decidim/assemblies/query_extensions.rb +40 -0
  73. data/lib/decidim/assemblies/test/factories.rb +24 -2
  74. data/lib/decidim/assemblies/version.rb +1 -1
  75. metadata +29 -9
  76. data/app/views/decidim/assemblies/assembly_widgets/show.html.erb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bce95b5a50ca5a51acc8ec5006c31433e1221e6c4261797ceeb55a4440df460
4
- data.tar.gz: 33e3a8271d02f6f50816d6a554212877d50ecccd96835e291b9e24675011d1ad
3
+ metadata.gz: 59b7c6152c1d1c2c96dc32b800435039413c4a9ad9e91150dd5101251af5fdba
4
+ data.tar.gz: 9d4370974c2dacb07d232fee8dcde3aa92a3b74e86b7aaf836860c32a3120ec2
5
5
  SHA512:
6
- metadata.gz: fe93f7eab23f14845eb11443873be2dff139b860c021284197a50b1bbc0dd4e49715cd0a357cd903c0a5123221c74db4c2e832b8993878fba4b611613eaed698
7
- data.tar.gz: f2aa517dd035ff6c746e545f15f54e7c93ddc3778ae90c63f29444554ee1fc8c9d8d60679f69a2864f629ecb7e60721d36e7b7185ce0b9f305fbe1e72412f6d3
6
+ metadata.gz: 84c422de10b4da4aac9d0e3e7ec291bd32664641d8a163ab68072615c5d23816a7c804939b1023b930c9fbfec93cccdcb5a990a1ed62e28ba2ec5ce4c3e283ea
7
+ data.tar.gz: 918d1933d6bc85ff6d3044a4db73a7576b187b5731e00fc2c9789e671466bbe560590410c72a4178280c9c50336195650633a63bdfa28f29332aa382df8a0b33
@@ -64,11 +64,7 @@ module Decidim
64
64
  end
65
65
 
66
66
  def assembly_type
67
- if model.assembly_type == "others"
68
- translated_attribute(model.assembly_type_other)
69
- else
70
- t("assembly_types.#{model.assembly_type}", scope: "decidim.assemblies").to_s
71
- end
67
+ translated_attribute model.assembly_type.title
72
68
  end
73
69
  end
74
70
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ module Admin
6
+ # A command with all the business logic when creating a new assembly
7
+ # type in the system.
8
+ class CreateAssembliesType < Rectify::Command
9
+ # Public: Initializes the command.
10
+ #
11
+ # form - A form object with the params.
12
+ def initialize(form)
13
+ @form = form
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid.
19
+ # - :invalid if the form wasn't valid and we couldn't proceed.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if form.invalid?
24
+
25
+ create_assemblies_type!
26
+
27
+ broadcast(:ok)
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :form
33
+
34
+ def create_assemblies_type!
35
+ Decidim.traceability.create!(
36
+ AssembliesType,
37
+ form.current_user,
38
+ organization: form.current_organization,
39
+ title: form.title
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -67,7 +67,6 @@ module Decidim
67
67
  purpose_of_action: form.purpose_of_action,
68
68
  composition: form.composition,
69
69
  assembly_type: form.assembly_type,
70
- assembly_type_other: form.assembly_type_other,
71
70
  creation_date: form.creation_date,
72
71
  created_by: form.created_by,
73
72
  created_by_other: form.created_by_other,
@@ -104,7 +103,7 @@ module Decidim
104
103
  end
105
104
 
106
105
  def link_participatory_processes(assembly)
107
- assembly.link_participatory_spaces_resources(participatory_processes(assembly), "included_participatory_processes")
106
+ assembly.link_participatory_space_resources(participatory_processes(assembly), "included_participatory_processes")
108
107
  end
109
108
  end
110
109
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ module Admin
6
+ # A command with all the business logic when destroying an assembly
7
+ # type in the system.
8
+ class DestroyAssembliesType < Rectify::Command
9
+ # Public: Initializes the command.
10
+ #
11
+ # assemblies_type - the AssemblyMember to destroy
12
+ # current_user - the user performing the action
13
+ def initialize(assemblies_type, current_user)
14
+ @assemblies_type = assemblies_type
15
+ @current_user = current_user
16
+ end
17
+
18
+ # Executes the command. Broadcasts these events:
19
+ #
20
+ # - :ok when everything is valid.
21
+ # - :invalid if the form wasn't valid and we couldn't proceed.
22
+ #
23
+ # Returns nothing.
24
+ def call
25
+ destroy_assembly_type!
26
+ broadcast(:ok)
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :current_user
32
+
33
+ def destroy_assembly_type!
34
+ Decidim.traceability.perform_action!(
35
+ "delete",
36
+ @assemblies_type,
37
+ current_user
38
+ ) do
39
+ @assemblies_type.destroy!
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ module Admin
6
+ # A command with all the business logic when updating a new assembly
7
+ # type in the system.
8
+ class UpdateAssembliesType < Rectify::Command
9
+ # Public: Initializes the command.
10
+ #
11
+ # assemblies_type - A assemblies_type object to update.
12
+ # form - A form object with the params.
13
+ def initialize(assemblies_type, form)
14
+ @assemblies_type = assemblies_type
15
+ @form = form
16
+ end
17
+
18
+ # Executes the command. Broadcasts these events:
19
+ #
20
+ # - :ok when everything is valid.
21
+ # - :invalid if the form wasn't valid and we couldn't proceed.
22
+ #
23
+ # Returns nothing.
24
+ def call
25
+ return broadcast(:invalid) if form.invalid?
26
+
27
+ update_assemblies_type!
28
+
29
+ broadcast(:ok)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :form
35
+
36
+ def update_assemblies_type!
37
+ Decidim.traceability.update!(
38
+ @assemblies_type,
39
+ form.current_user,
40
+ title: form.title
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -84,7 +84,6 @@ module Decidim
84
84
  purpose_of_action: form.purpose_of_action,
85
85
  composition: form.composition,
86
86
  assembly_type: form.assembly_type,
87
- assembly_type_other: form.assembly_type_other,
88
87
  creation_date: form.creation_date,
89
88
  created_by: form.created_by,
90
89
  created_by_other: form.created_by_other,
@@ -108,7 +107,7 @@ module Decidim
108
107
  end
109
108
 
110
109
  def link_participatory_processes(assembly)
111
- assembly.link_participatory_spaces_resources(participatory_processes(assembly), "included_participatory_processes")
110
+ assembly.link_participatory_space_resources(participatory_processes(assembly), "included_participatory_processes")
112
111
  end
113
112
 
114
113
  # Resets the children counter cache to its correct value using an SQL count query.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Assemblies
7
+ module Admin
8
+ module Filterable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Decidim::Admin::Filterable
13
+
14
+ private
15
+
16
+ # Unless we are explicitly looking for child assemblies, we filter them out.
17
+ def base_query
18
+ return collection if ransack_params[:parent_id_eq]
19
+
20
+ collection.parent_assemblies
21
+ end
22
+
23
+ def extra_filters
24
+ [:parent_id_eq]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -6,13 +6,13 @@ module Decidim
6
6
  # Controller that allows managing assemblies.
7
7
  #
8
8
  class AssembliesController < Decidim::Assemblies::Admin::ApplicationController
9
- include Decidim::Paginable
10
- helper_method :current_assembly, :parent_assembly, :parent_assemblies, :current_participatory_space, :query
9
+ include Decidim::Assemblies::Admin::Filterable
10
+ helper_method :current_assembly, :parent_assembly, :current_participatory_space
11
11
  layout "decidim/admin/assemblies"
12
12
 
13
13
  def index
14
14
  enforce_permission_to :read, :assembly_list
15
- @assemblies = collection
15
+ @assemblies = filtered_collection
16
16
  end
17
17
 
18
18
  def new
@@ -28,7 +28,7 @@ module Decidim
28
28
  CreateAssembly.call(@form) do
29
29
  on(:ok) do |assembly|
30
30
  flash[:notice] = I18n.t("assemblies.create.success", scope: "decidim.admin")
31
- redirect_to assemblies_path(parent_id: assembly.parent_id)
31
+ redirect_to assemblies_path(q: { parent_id_eq: assembly.parent_id })
32
32
  end
33
33
 
34
34
  on(:invalid) do
@@ -70,35 +70,20 @@ module Decidim
70
70
 
71
71
  private
72
72
 
73
- def organization_assemblies
74
- @organization_assemblies ||= OrganizationAssemblies.new(current_user.organization).query
75
- end
76
-
77
- def query
78
- organization_assemblies
79
- .where(parent_id: params[:parent_id])
80
- .ransack(params[:q])
81
- end
82
-
83
73
  def collection
84
- @collection ||= paginate(query.result)
74
+ @collection ||= OrganizationAssemblies.new(current_user.organization).query
85
75
  end
86
76
 
87
77
  def current_assembly
88
- scope = organization_assemblies
89
- @current_assembly ||= scope.where(slug: params[:slug]).or(
90
- scope.where(id: params[:slug])
78
+ @current_assembly ||= collection.where(slug: params[:slug]).or(
79
+ collection.where(id: params[:slug])
91
80
  ).first
92
81
  end
93
82
 
94
83
  alias current_participatory_space current_assembly
95
84
 
96
85
  def parent_assembly
97
- @parent_assembly ||= organization_assemblies.find_by(id: params[:parent_id])
98
- end
99
-
100
- def parent_assemblies
101
- @parent_assemblies ||= organization_assemblies.where.not(id: current_assembly)
86
+ @parent_assembly ||= collection.find_by(id: ransack_params[:parent_id_eq])
102
87
  end
103
88
 
104
89
  def assembly_params
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ module Admin
6
+ # Controller used to manage the available assemblies types for the current
7
+ # organization.
8
+ # As this substitues former i18n simple hash we need to keep these i18n keys for migrations
9
+ # and rollbakcs. So let i18n-tasks know about:
10
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.government')
11
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.commission')
12
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.consultative_advisory')
13
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.executive')
14
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.others')
15
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.participatory')
16
+ # i18n-tasks-use t('decidim.assemblies.assembly_types.working_group')
17
+ # This comment (and the i18n keys) may be removed in future versions
18
+ class AssembliesTypesController < Decidim::Assemblies::Admin::ApplicationController
19
+ helper_method :available_assemblies_types, :current_assembly_type
20
+ layout "decidim/admin/assemblies"
21
+
22
+ # GET /admin/assemblies_types
23
+ def index
24
+ enforce_permission_to :index, :assembly_type
25
+ end
26
+
27
+ # GET /admin/assemblies_types/new
28
+ def new
29
+ enforce_permission_to :create, :assembly_type
30
+ @form = assembly_type_form.instance
31
+ end
32
+
33
+ # POST /admin/assemblies_types
34
+ def create
35
+ enforce_permission_to :create, :assembly_type
36
+ @form = assembly_type_form.from_params(params)
37
+
38
+ CreateAssembliesType.call(@form) do
39
+ on(:ok) do |_assembly_type|
40
+ flash[:notice] = I18n.t("assemblies_types.create.success", scope: "decidim.admin")
41
+ redirect_to assemblies_types_path
42
+ end
43
+
44
+ on(:invalid) do
45
+ flash.now[:alert] = I18n.t("assemblies_types.create.error", scope: "decidim.admin")
46
+ render :new
47
+ end
48
+ end
49
+ end
50
+
51
+ # GET /admin/assemblies_types/:id/edit
52
+ def edit
53
+ enforce_permission_to :edit, :assembly_type, assembly_type: current_assembly_type
54
+ @form = assembly_type_form
55
+ .from_model(current_assembly_type,
56
+ assembly_type: current_assembly_type)
57
+ end
58
+
59
+ # PUT /admin/assemblies_types/:id
60
+ def update
61
+ enforce_permission_to :update, :assembly_type, assembly_type: current_assembly_type
62
+
63
+ @form = assembly_type_form
64
+ .from_params(params, assembly_type: current_assembly_type)
65
+
66
+ UpdateAssembliesType.call(current_assembly_type, @form) do
67
+ on(:ok) do
68
+ flash[:notice] = I18n.t("assemblies_types.update.success", scope: "decidim.admin")
69
+ redirect_to assemblies_types_path
70
+ end
71
+
72
+ on(:invalid) do
73
+ flash.now[:alert] = I18n.t("assemblies_types.update.error", scope: "decidim.admin")
74
+ render :edit
75
+ end
76
+ end
77
+ end
78
+
79
+ # DELETE /admin/assemblies_types/:id
80
+ def destroy
81
+ enforce_permission_to :destroy, :assembly_type, assembly_type: current_assembly_type
82
+
83
+ DestroyAssembliesType.call(current_assembly_type, current_user) do
84
+ on(:ok) do
85
+ flash[:notice] = I18n.t("assemblies_types.destroy.success", scope: "decidim.admin")
86
+ redirect_to assemblies_types_path
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def available_assemblies_types
94
+ @available_assemblies_types ||= AssembliesType.where(organization: current_organization)
95
+ end
96
+
97
+ def current_assembly_type
98
+ @current_assembly_type ||= AssembliesType.find(params[:id])
99
+ end
100
+
101
+ def assembly_type_form
102
+ form(Decidim::Assemblies::Admin::AssembliesTypeForm)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -56,7 +56,7 @@ module Decidim
56
56
  {
57
57
  scope_id: nil,
58
58
  area_id: nil,
59
- assembly_type: "all"
59
+ type_id: nil
60
60
  }
61
61
  end
62
62
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ module Admin
6
+ # A form object used to create assembly type from the admin dashboard.
7
+ class AssembliesTypeForm < Form
8
+ include TranslatableAttributes
9
+
10
+ mimic :assemblies_type
11
+
12
+ translatable_attribute :title, String
13
+ validates :title, translatable_presence: true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -9,12 +9,10 @@ module Decidim
9
9
  class AssemblyForm < Form
10
10
  include TranslatableAttributes
11
11
 
12
- ASSEMBLY_TYPES = %w(government executive consultative_advisory participatory working_group commission others).freeze
13
12
  CREATED_BY = %w(city_council public others).freeze
14
13
 
15
14
  mimic :assembly
16
15
 
17
- translatable_attribute :assembly_type_other, String
18
16
  translatable_attribute :composition, String
19
17
  translatable_attribute :closing_date_reason, String
20
18
  translatable_attribute :created_by_other, String
@@ -32,7 +30,6 @@ module Decidim
32
30
  translatable_attribute :target, String
33
31
  translatable_attribute :title, String
34
32
 
35
- attribute :assembly_type, String
36
33
  attribute :created_by, String
37
34
  attribute :facebook_handler, String
38
35
  attribute :github_handler, String
@@ -42,6 +39,7 @@ module Decidim
42
39
  attribute :twitter_handler, String
43
40
  attribute :youtube_handler, String
44
41
 
42
+ attribute :decidim_assemblies_type_id, Integer
45
43
  attribute :area_id, Integer
46
44
  attribute :parent_id, Integer
47
45
  attribute :participatory_processes_ids, Array[Integer]
@@ -69,8 +67,8 @@ module Decidim
69
67
  validates :slug, presence: true, format: { with: Decidim::Assembly.slug_format }
70
68
 
71
69
  validate :slug_uniqueness
70
+ validate :same_type_organization, if: ->(form) { form.decidim_assemblies_type_id }
72
71
 
73
- validates :assembly_type_other, translatable_presence: true, if: ->(form) { form.assembly_type == "others" }
74
72
  validates :created_by_other, translatable_presence: true, if: ->(form) { form.created_by == "others" }
75
73
  validates :title, :subtitle, :description, :short_description, translatable_presence: true
76
74
 
@@ -94,12 +92,8 @@ module Decidim
94
92
  end
95
93
 
96
94
  def assembly_types_for_select
97
- ASSEMBLY_TYPES.map do |type|
98
- [
99
- I18n.t("assembly_types.#{type}", scope: "decidim.assemblies"),
100
- type
101
- ]
102
- end
95
+ @assembly_types_for_select ||= organization_assembly_types
96
+ &.map { |type| [translated_attribute(type.title), type.id] }
103
97
  end
104
98
 
105
99
  def created_by_for_select
@@ -121,8 +115,16 @@ module Decidim
121
115
  &.sort_by { |arr| arr[0] }
122
116
  end
123
117
 
118
+ def assembly_type
119
+ AssembliesType.find_by(id: decidim_assemblies_type_id)
120
+ end
121
+
124
122
  private
125
123
 
124
+ def organization_assembly_types
125
+ AssembliesType.where(organization: current_organization)
126
+ end
127
+
126
128
  def organization_participatory_processes
127
129
  Decidim.find_participatory_space_manifest(:participatory_processes)
128
130
  .participatory_spaces.call(current_organization)
@@ -140,6 +142,13 @@ module Decidim
140
142
 
141
143
  errors.add(:slug, :taken)
142
144
  end
145
+
146
+ def same_type_organization
147
+ return unless assembly_type
148
+ return if assembly_type.organization == current_organization
149
+
150
+ errors.add(:assembly_type, :invalid)
151
+ end
143
152
  end
144
153
  end
145
154
  end