dscf-marketplace 0.8.4 → 0.8.6

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/dscf/marketplace/agents_controller.rb +75 -5
  3. data/app/controllers/dscf/marketplace/aggregator_listings_controller.rb +2 -2
  4. data/app/controllers/dscf/marketplace/delivery_stops_controller.rb +9 -9
  5. data/app/controllers/dscf/marketplace/listing_approvals_controller.rb +57 -0
  6. data/app/controllers/dscf/marketplace/listings_controller.rb +2 -2
  7. data/app/controllers/dscf/marketplace/orders_controller.rb +1 -1
  8. data/app/controllers/dscf/marketplace/product_inclusion_requests_controller.rb +127 -0
  9. data/app/controllers/dscf/marketplace/retailers_controller.rb +61 -0
  10. data/app/controllers/dscf/marketplace/sub_suppliers_controller.rb +16 -0
  11. data/app/controllers/dscf/marketplace/supplier_products_controller.rb +27 -2
  12. data/app/controllers/dscf/marketplace/suppliers_controller.rb +140 -2
  13. data/app/models/dscf/marketplace/agent.rb +11 -3
  14. data/app/models/dscf/marketplace/aggregator_listing.rb +4 -4
  15. data/app/models/dscf/marketplace/category.rb +2 -1
  16. data/app/models/dscf/marketplace/delivery_order.rb +6 -6
  17. data/app/models/dscf/marketplace/delivery_stop.rb +4 -4
  18. data/app/models/dscf/marketplace/listing.rb +9 -8
  19. data/app/models/dscf/marketplace/listing_approval.rb +30 -0
  20. data/app/models/dscf/marketplace/order_invoice.rb +5 -5
  21. data/app/models/dscf/marketplace/product_inclusion_request.rb +62 -0
  22. data/app/models/dscf/marketplace/retailer.rb +23 -0
  23. data/app/models/dscf/marketplace/sub_supplier.rb +43 -0
  24. data/app/models/dscf/marketplace/sub_supplier_product.rb +22 -0
  25. data/app/models/dscf/marketplace/supplier.rb +53 -1
  26. data/app/models/dscf/marketplace/supplier_product.rb +1 -1
  27. data/app/policies/dscf/marketplace/agent_policy.rb +8 -0
  28. data/app/policies/dscf/marketplace/listing_approval_policy.rb +9 -0
  29. data/app/policies/dscf/marketplace/product_inclusion_request_policy.rb +17 -0
  30. data/app/policies/dscf/marketplace/supplier_policy.rb +12 -0
  31. data/app/serializers/dscf/marketplace/agent_serializer.rb +9 -3
  32. data/app/serializers/dscf/marketplace/category_serializer.rb +1 -1
  33. data/app/serializers/dscf/marketplace/listing_approval_serializer.rb +11 -0
  34. data/app/serializers/dscf/marketplace/listing_serializer.rb +1 -0
  35. data/app/serializers/dscf/marketplace/product_inclusion_request_serializer.rb +38 -0
  36. data/app/serializers/dscf/marketplace/retailer_serializer.rb +10 -0
  37. data/app/serializers/dscf/marketplace/sub_supplier_serializer.rb +15 -0
  38. data/app/serializers/dscf/marketplace/supplier_product_serializer.rb +2 -1
  39. data/app/serializers/dscf/marketplace/supplier_serializer.rb +9 -1
  40. data/app/services/dscf/marketplace/dispute_service.rb +4 -4
  41. data/app/services/dscf/marketplace/invoice_pdf_generator.rb +1 -1
  42. data/config/routes.rb +36 -0
  43. data/db/demo_seeds.rb +120 -0
  44. data/db/migrate/20251130143731_create_dscf_marketplace_delivery_stops.rb +2 -2
  45. data/db/migrate/20251130144500_add_delivery_stop_to_delivery_order_items.rb +1 -1
  46. data/db/migrate/20260514000001_create_dscf_marketplace_suppliers.rb +2 -2
  47. data/db/migrate/20260514000002_add_supplier_id_to_supplier_products.rb +2 -2
  48. data/db/migrate/20260514000003_create_dscf_marketplace_order_invoices.rb +2 -2
  49. data/db/migrate/20260514000005_create_dscf_marketplace_aggregator_listings.rb +5 -5
  50. data/db/migrate/20260514000006_add_supplier_id_to_addresses.rb +2 -2
  51. data/db/migrate/20260527000001_add_orchestrator_fields_to_listings.rb +2 -2
  52. data/db/migrate/20260529000001_create_dscf_marketplace_agents.rb +1 -1
  53. data/db/migrate/20260616000002_create_dscf_marketplace_retailers.rb +17 -0
  54. data/db/migrate/20260616000003_create_dscf_marketplace_sub_suppliers.rb +20 -0
  55. data/db/migrate/20260616000004_create_dscf_marketplace_listing_approvals.rb +29 -0
  56. data/db/migrate/20260616000005_create_dscf_marketplace_product_inclusion_requests.rb +23 -0
  57. data/db/migrate/20260616000006_create_dscf_marketplace_sub_supplier_products.rb +32 -0
  58. data/db/migrate/20260616000007_add_approval_fields_to_dscf_marketplace_supplier_products.rb +7 -0
  59. data/db/migrate/20260616000008_add_rejection_and_location_to_dscf_marketplace_agents.rb +9 -0
  60. data/db/migrate/20260616000009_make_agent_id_nullable_in_dscf_marketplace_retailers.rb +9 -0
  61. data/db/migrate/20260616000010_add_code_to_dscf_marketplace_categories_and_gender_to_dscf_marketplace_agents.rb +24 -0
  62. data/lib/dscf/marketplace/version.rb +1 -1
  63. data/spec/factories/dscf/marketplace/categories.rb +1 -0
  64. data/spec/factories/dscf/marketplace/listing_approvals.rb +8 -0
  65. data/spec/factories/dscf/marketplace/product_inclusion_requests.rb +9 -0
  66. data/spec/factories/dscf/marketplace/retailers.rb +9 -0
  67. data/spec/factories/dscf/marketplace/sub_supplier_products.rb +10 -0
  68. data/spec/factories/dscf/marketplace/sub_suppliers.rb +11 -0
  69. metadata +34 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ccdf7ab2d80d24d3125fc70cd2967e82b64ceec4fecad26005cc38539416d3d
4
- data.tar.gz: 513a5ca0fc82efe0e44ed34d4f5ccc9360c6508747ae569bda5c36a8497f00d5
3
+ metadata.gz: 36275865e4363db165333b5274f3e16ef5ae38cd0c3b4fc3e4fe88f6a32f2d73
4
+ data.tar.gz: e440535b9bd3e6fdb5df853f47af02c43344dc05f8cca68f76d40235995bea25
5
5
  SHA512:
6
- metadata.gz: 6293f513a29b0e11b5300350af009c07504582dd3f29ef423cb3ee14ee87e8389397cad18ab7e7b505ff598f92a9cf06ba99f0e4cd44ee91917928f79e3579e3
7
- data.tar.gz: 68c91b028dbdce0862f7dafedf85e262469dc36bcdcd943e036edfe0ea3b271a56233870d4075272ae6da618d55c4ba0b2539bf74eff349ec83f339bd814d5ff
6
+ metadata.gz: 42cc494a65237a972384e89a19603183e7bd8521258d12c317cafdd134b0d965532c0b3d5a91393557573a618b5368b613b29c002743ddda0fcf56fb75051dc4
7
+ data.tar.gz: fd1469a19042fa3ffe9e0ad5b6c0e94077dc791052f79fb3cfa978f46bb67532ac4b8d6c650d23c86c1bb8b53f60dd4be0a5e95655bdce68e3f679d69ab64848
@@ -9,6 +9,7 @@ module Dscf
9
9
  obj = @clazz.new(registration_params)
10
10
  ActiveRecord::Base.transaction do
11
11
  if obj.save
12
+ create_registration_notification(obj)
12
13
  render_success("agent.success.register", data: obj, status: :created)
13
14
  else
14
15
  render_error("agent.errors.register", errors: obj.errors.full_messages[0], status: :unprocessable_entity)
@@ -23,29 +24,98 @@ module Dscf
23
24
  render_success("agent.success.destroy")
24
25
  end
25
26
 
27
+ # Verify (approve) or reject an agent based on presence of reason param
28
+ def verify
29
+ @obj = find_record
30
+ authorize @obj, :verify?
31
+
32
+ if params[:reason].present?
33
+ @obj.update!(
34
+ verification_status: :rejected,
35
+ rejection_reason: params[:reason],
36
+ rejected_at: Time.current
37
+ )
38
+ notification = Dscf::Core::Notification.create!(
39
+ notifiable: @obj,
40
+ recipient: current_user,
41
+ notification_type: :rejection,
42
+ title: "Agent Rejected",
43
+ body: "Agent '#{@obj.name}' (#{@obj.code}) has been rejected. Reason: #{params[:reason]}"
44
+ )
45
+ Dscf::Core::NotificationService.deliver(notification)
46
+ render_success("agent.success.rejected", data: @obj)
47
+ else
48
+ @obj.update!(verification_status: :verified)
49
+ render_success("agent.success.verified", data: @obj)
50
+ end
51
+ rescue StandardError => e
52
+ render_error(errors: e.message, status: :unprocessable_entity)
53
+ end
54
+
55
+ # Modify agent's location fields
56
+ def modify_location
57
+ @obj = find_record
58
+ authorize @obj, :modify_location?
59
+
60
+ @obj.update!(
61
+ service_area: params[:service_area],
62
+ sub_city: params[:sub_city],
63
+ woreda: params[:woreda],
64
+ modified_at: Time.current
65
+ )
66
+ notification = Dscf::Core::Notification.create!(
67
+ notifiable: @obj,
68
+ recipient: current_user,
69
+ notification_type: :modification,
70
+ title: "Agent Location Modified",
71
+ body: "Location for agent '#{@obj.name}' (#{@obj.code}) has been updated."
72
+ )
73
+ Dscf::Core::NotificationService.deliver(notification)
74
+ render_success("agent.success.location_modified", data: @obj)
75
+ rescue StandardError => e
76
+ render_error(errors: e.message, status: :unprocessable_entity)
77
+ end
78
+
26
79
  private
27
80
 
28
81
  def model_params
29
82
  params.require(:agent).permit(
30
- :name, :phone, :service_area, :fayda_number, :photo, :onboarded_by_id
83
+ :name, :phone, :service_area, :sub_city, :woreda, :fayda_number, :photo, :onboarded_by_id
31
84
  )
32
85
  end
33
86
 
34
87
  # Registration allows same fields but no authentication required
35
88
  def registration_params
36
89
  params.require(:agent).permit(
37
- :name, :phone, :service_area, :fayda_number, :onboarded_by_id
90
+ :name, :phone, :service_area, :fayda_number, :onboarded_by_id,
91
+ :gender, :national_id
92
+ )
93
+ end
94
+
95
+ def create_registration_notification(agent)
96
+ recipient = Dscf::Core::User.joins(:roles)
97
+ .where(dscf_core_roles: {code: "SUPER_ADMIN"})
98
+ .first || Dscf::Core::User.first
99
+
100
+ return unless recipient
101
+
102
+ Dscf::Core::Notification.create!(
103
+ notifiable: agent,
104
+ recipient: recipient,
105
+ notification_type: :general,
106
+ title: "New Agent Registration",
107
+ body: "Agent '#{agent.name}' (#{agent.code}) has registered and is pending review. Phone: #{agent.phone}, Area: #{agent.service_area}"
38
108
  )
39
109
  end
40
110
 
41
111
  def eager_loaded_associations
42
- [:onboarded_by]
112
+ [ :onboarded_by ]
43
113
  end
44
114
 
45
115
  def default_serializer_includes
46
116
  {
47
- index: [:onboarded_by],
48
- show: [:onboarded_by]
117
+ index: [ :onboarded_by ],
118
+ show: [ :onboarded_by ]
49
119
  }
50
120
  end
51
121
 
@@ -9,7 +9,7 @@ module Dscf
9
9
 
10
10
  options = {
11
11
  include: default_serializer_includes[:index] || [],
12
- meta: { resource_type: "aggregator_feed" }
12
+ meta: {resource_type: "aggregator_feed"}
13
13
  }
14
14
 
15
15
  render_success(data: listings, serializer_options: options)
@@ -22,7 +22,7 @@ module Dscf
22
22
 
23
23
  options = {
24
24
  include: default_serializer_includes[:index] || [],
25
- meta: { resource_type: "my_aggregator_listings" }
25
+ meta: {resource_type: "my_aggregator_listings"}
26
26
  }
27
27
 
28
28
  render_success(data: listings, serializer_options: options)
@@ -7,11 +7,11 @@ module Dscf
7
7
  super do
8
8
  if params[:delivery_order_id]
9
9
  stops = DeliveryStop.where(delivery_order_id: params[:delivery_order_id])
10
- options = { include: [:dropoff_address, :delivery_order_items] }
11
- [stops, options]
10
+ options = {include: [ :dropoff_address, :delivery_order_items ]}
11
+ [ stops, options ]
12
12
  else
13
13
  # Fallback or standard index if needed, though mostly used nested
14
- [DeliveryStop.all, {}]
14
+ [ DeliveryStop.all, {} ]
15
15
  end
16
16
  end
17
17
  end
@@ -21,8 +21,8 @@ module Dscf
21
21
  authorize @delivery_stop, :verify?
22
22
  if @delivery_stop.verify!(verified_by: current_user.id)
23
23
  render_success(
24
- data: @delivery_stop,
25
- serializer_options: { include: [:dropoff_address, :delivery_order_items] }
24
+ data: @delivery_stop,
25
+ serializer_options: {include: [ :dropoff_address, :delivery_order_items ]}
26
26
  )
27
27
  else
28
28
  render_error(
@@ -39,7 +39,7 @@ module Dscf
39
39
  end
40
40
 
41
41
  def eager_loaded_associations
42
- [:dropoff_address, :delivery_order_items]
42
+ [ :dropoff_address, :delivery_order_items ]
43
43
  end
44
44
 
45
45
  def allowed_order_columns
@@ -48,9 +48,9 @@ module Dscf
48
48
 
49
49
  def default_serializer_includes
50
50
  {
51
- index: [:dropoff_address, :delivery_order_items],
52
- show: [:dropoff_address, :delivery_order_items],
53
- verify: [:dropoff_address, :delivery_order_items]
51
+ index: [ :dropoff_address, :delivery_order_items ],
52
+ show: [ :dropoff_address, :delivery_order_items ],
53
+ verify: [ :dropoff_address, :delivery_order_items ]
54
54
  }
55
55
  end
56
56
  end
@@ -0,0 +1,57 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class ListingApprovalsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def set_price
7
+ set_object
8
+ authorize @obj, :set_price?
9
+
10
+ if @obj.update(set_price_params.merge(approved_by: current_user, approved_at: Time.current))
11
+ notification = Dscf::Core::Notification.create!(
12
+ notification_type: :listing,
13
+ title: "Price set for listing ##{@obj.listing_id}",
14
+ body: "Aggregator price: #{@obj.aggregator_price}, Supplier price: #{@obj.supplier_price}",
15
+ recipient: current_user,
16
+ notifiable: @obj
17
+ )
18
+ Dscf::Core::NotificationService.deliver(notification)
19
+ render_success(data: @obj, serializer_options: {include: serializer_includes_for_action(:show)})
20
+ else
21
+ render_error(errors: @obj.errors.full_messages[0], status: :unprocessable_entity)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def model_params
28
+ params.require(:listing_approval).permit(
29
+ :listing_id, :aggregator_price, :supplier_price, :approval_status
30
+ )
31
+ end
32
+
33
+ def set_price_params
34
+ params.require(:listing_approval).permit(
35
+ :aggregator_price, :supplier_price, :approval_status
36
+ )
37
+ end
38
+
39
+ def eager_loaded_associations
40
+ [ :listing, :approved_by ]
41
+ end
42
+
43
+ def default_serializer_includes
44
+ {
45
+ index: [ :listing ],
46
+ show: [ :listing, :approved_by ],
47
+ create: [ :listing ],
48
+ update: [ :listing ]
49
+ }
50
+ end
51
+
52
+ def allowed_order_columns
53
+ %w[id listing_id approval_status aggregator_price supplier_price approved_at created_at updated_at]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -68,7 +68,7 @@ module Dscf
68
68
 
69
69
  options = {
70
70
  include: default_serializer_includes[:index] || [],
71
- meta: { resource_type: "visible_listings" }
71
+ meta: {resource_type: "visible_listings"}
72
72
  }
73
73
 
74
74
  render_success(data: listings, serializer_options: options)
@@ -81,7 +81,7 @@ module Dscf
81
81
 
82
82
  options = {
83
83
  include: default_serializer_includes[:index] || [],
84
- meta: { resource_type: "my_listings" }
84
+ meta: {resource_type: "my_listings"}
85
85
  }
86
86
 
87
87
  render_success("listings.success.index", data: listings, serializer_options: options)
@@ -70,7 +70,7 @@ module Dscf
70
70
  @obj.order_items.update_all(status: OrderItem.statuses[:fulfilled])
71
71
  begin
72
72
  invoice = Dscf::Marketplace::InvoicePdfGenerator.new(@obj).generate!
73
- render_success("orders.success.completed", data: @obj, meta: { invoice_id: invoice.id })
73
+ render_success("orders.success.completed", data: @obj, meta: {invoice_id: invoice.id})
74
74
  rescue => e
75
75
  Rails.logger.error("Invoice generation failed for order #{@obj.id}: #{e.message}")
76
76
  render_success("orders.success.completed", data: @obj)
@@ -0,0 +1,127 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class ProductInclusionRequestsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ # Override create to set requested_by from current_user
7
+ def create
8
+ authorize @clazz.new, :create?
9
+
10
+ obj = @clazz.new(model_params)
11
+ obj.requested_by = current_user
12
+
13
+ ActiveRecord::Base.transaction do
14
+ if obj.save
15
+ @obj = obj
16
+ after_save_hook(obj) if respond_to?(:after_save_hook, true)
17
+
18
+ obj = @clazz.includes(eager_loaded_associations).find(obj.id) if eager_loaded_associations.present?
19
+ @obj = obj
20
+
21
+ includes = serializer_includes_for_action(:create)
22
+ options = {}
23
+ options[:include] = includes if includes.present?
24
+
25
+ render_success(data: obj, serializer_options: options, status: :created)
26
+ else
27
+ render_error(errors: obj.errors.full_messages[0], status: :unprocessable_entity)
28
+ end
29
+ end
30
+ rescue ::Pundit::NotAuthorizedError
31
+ raise
32
+ rescue Dscf::Core::FileUploadError => e
33
+ render_error(errors: e.message, status: :unprocessable_entity)
34
+ rescue StandardError => e
35
+ render_error(error: e.message)
36
+ end
37
+
38
+ # Approve a pending product inclusion request
39
+ def approve
40
+ set_object
41
+ authorize @obj, :approve?
42
+
43
+ @obj.approve!(current_user)
44
+ create_notification(:approved)
45
+
46
+ render_success("product_inclusion_requests.success.approve", data: @obj)
47
+ rescue StandardError => e
48
+ render_error(errors: e.message, status: :unprocessable_entity)
49
+ end
50
+
51
+ # Request modification with a review comment
52
+ def modify
53
+ set_object
54
+ authorize @obj, :modify?
55
+
56
+ comment = params[:review_comment]
57
+ raise ArgumentError, "Review comment is required for modification" if comment.blank?
58
+
59
+ @obj.modify!(current_user, comment)
60
+ create_notification(:modification)
61
+
62
+ render_success("product_inclusion_requests.success.modify", data: @obj)
63
+ rescue ArgumentError => e
64
+ render_error(errors: e.message, status: :unprocessable_entity)
65
+ rescue StandardError => e
66
+ render_error(errors: e.message, status: :unprocessable_entity)
67
+ end
68
+
69
+ # Decline a pending product inclusion request
70
+ def decline
71
+ set_object
72
+ authorize @obj, :decline?
73
+
74
+ comment = params[:review_comment]
75
+ raise ArgumentError, "Review comment is required for decline" if comment.blank?
76
+
77
+ @obj.decline!(current_user, comment)
78
+ create_notification(:rejection)
79
+
80
+ render_success("product_inclusion_requests.success.decline", data: @obj)
81
+ rescue ArgumentError => e
82
+ render_error(errors: e.message, status: :unprocessable_entity)
83
+ rescue StandardError => e
84
+ render_error(errors: e.message, status: :unprocessable_entity)
85
+ end
86
+
87
+ private
88
+
89
+ def model_params
90
+ params.require(:product_inclusion_request).permit(
91
+ :supplier_id, :product_name, :product_description, :unit_of_measure,
92
+ product_images: []
93
+ )
94
+ end
95
+
96
+ def eager_loaded_associations
97
+ %i[supplier requested_by reviewer]
98
+ end
99
+
100
+ def default_serializer_includes
101
+ {
102
+ index: %i[supplier requested_by],
103
+ show: %i[supplier requested_by reviewer],
104
+ create: %i[supplier requested_by],
105
+ update: %i[supplier requested_by]
106
+ }
107
+ end
108
+
109
+ def create_notification(action_type)
110
+ notification_type = case action_type
111
+ when :approved then :approval
112
+ when :modification then :modification
113
+ when :rejection then :rejection
114
+ end
115
+
116
+ notification = Dscf::Core::Notification.create!(
117
+ notifiable: @obj,
118
+ recipient: @obj.requested_by,
119
+ notification_type: notification_type,
120
+ title: "Product Inclusion Request #{action_type.to_s.humanize}",
121
+ body: "Your product inclusion request for '#{@obj.product_name}' has been #{action_type.to_s.humanize.downcase}."
122
+ )
123
+ Dscf::Core::NotificationService.deliver(notification)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,61 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class RetailersController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ skip_before_action :authenticate_user, only: [ :register ]
7
+
8
+ def register
9
+ ActiveRecord::Base.transaction do
10
+ user = Dscf::Core::User.new(
11
+ phone: registration_params[:phone],
12
+ password: registration_params[:password],
13
+ password_confirmation: registration_params[:password_confirmation]
14
+ )
15
+
16
+ retailer = Dscf::Marketplace::Retailer.new(
17
+ name: registration_params[:name],
18
+ phone: registration_params[:phone],
19
+ tin_number: registration_params[:tin_number],
20
+ location: registration_params[:location],
21
+ status: :active
22
+ )
23
+
24
+ user.save!
25
+ retailer.save!
26
+
27
+ render_success("retailers.success.register", data: retailer, status: :created)
28
+ end
29
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
30
+ errors = e.respond_to?(:record) && e.record ? e.record.errors.full_messages : [ e.message ]
31
+ render_error("retailers.errors.register", errors: errors, status: :unprocessable_entity)
32
+ end
33
+
34
+ def my_retailers
35
+ authorize @clazz.new, :index?
36
+ retailers = @clazz.where(agent_id: params[:agent_id])
37
+
38
+ options = {
39
+ include: default_serializer_includes[:index] || [],
40
+ meta: {resource_type: "my_retailers"}
41
+ }
42
+
43
+ render_success("retailers.success.index", data: retailers, serializer_options: options)
44
+ end
45
+
46
+ private
47
+
48
+ def model_params
49
+ params.require(:retailer).permit(
50
+ :name, :phone, :tin_number, :location, :agent_id
51
+ )
52
+ end
53
+
54
+ def registration_params
55
+ params.require(:retailer).permit(
56
+ :name, :phone, :tin_number, :location, :password, :password_confirmation
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class SubSuppliersController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ private
7
+
8
+ def model_params
9
+ params.require(:sub_supplier).permit(
10
+ :business_name, :contact_person_name, :contact_person_phone,
11
+ :city, :sub_city, :woreda, :supplier_id, :business_license
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -48,7 +48,11 @@ module Dscf
48
48
  set_object
49
49
  authorize @obj, :approve?
50
50
 
51
- if @obj.update(status: :active)
51
+ update_params = {status: :active, approved_at: Time.current}
52
+ update_params[:rejection_reason] = params[:reason] if params[:reason].present?
53
+
54
+ if @obj.update(update_params)
55
+ create_notification(:approved)
52
56
  render_success("supplier_products.success.approve", data: @obj)
53
57
  else
54
58
  render_error("supplier_products.errors.approve", errors: @obj.errors.full_messages, status: :unprocessable_entity)
@@ -60,11 +64,17 @@ module Dscf
60
64
  set_object
61
65
  authorize @obj, :approve?
62
66
 
63
- if @obj.update(status: :inactive)
67
+ reason = params[:reason]
68
+ raise ArgumentError, "Reason is required for rejection" if reason.blank?
69
+
70
+ if @obj.update(status: :inactive, rejection_reason: reason, rejected_at: Time.current)
71
+ create_notification(:rejected)
64
72
  render_success("supplier_products.success.reject", data: @obj)
65
73
  else
66
74
  render_error("supplier_products.errors.reject", errors: @obj.errors.full_messages, status: :unprocessable_entity)
67
75
  end
76
+ rescue ArgumentError => e
77
+ render_error(errors: e.message, status: :unprocessable_entity)
68
78
  end
69
79
 
70
80
  private
@@ -76,6 +86,21 @@ module Dscf
76
86
  )
77
87
  end
78
88
 
89
+ def create_notification(action_type)
90
+ notification_type = case action_type
91
+ when :approved then :approval
92
+ when :rejected then :rejection
93
+ end
94
+
95
+ Dscf::Core::Notification.create!(
96
+ notifiable: @obj,
97
+ recipient: @obj.business.user,
98
+ notification_type: notification_type,
99
+ title: "Supplier Product #{action_type.to_s.humanize}",
100
+ body: "Your supplier product '#{@obj.display_name}' has been #{action_type.to_s.humanize.downcase}."
101
+ )
102
+ end
103
+
79
104
  def eager_loaded_associations
80
105
  [ :business, :product, :supplier, :listings ]
81
106
  end