dscf-marketplace 0.1.7 → 0.1.9

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/dscf/marketplace/application_controller.rb +2 -0
  3. data/app/controllers/dscf/marketplace/categories_controller.rb +26 -0
  4. data/app/controllers/dscf/marketplace/listings_controller.rb +55 -0
  5. data/app/controllers/dscf/marketplace/orders_controller.rb +58 -0
  6. data/app/controllers/dscf/marketplace/products_controller.rb +30 -0
  7. data/app/controllers/dscf/marketplace/quotations_controller.rb +56 -0
  8. data/app/controllers/dscf/marketplace/request_for_quotations_controller.rb +46 -0
  9. data/app/models/dscf/marketplace/category.rb +9 -0
  10. data/app/models/dscf/marketplace/delivery_order.rb +116 -0
  11. data/app/models/dscf/marketplace/delivery_order_item.rb +201 -0
  12. data/app/models/dscf/marketplace/delivery_vehicle.rb +70 -0
  13. data/app/models/dscf/marketplace/listing.rb +9 -0
  14. data/app/models/dscf/marketplace/order.rb +35 -0
  15. data/app/models/dscf/marketplace/order_item.rb +17 -0
  16. data/app/models/dscf/marketplace/product.rb +9 -0
  17. data/app/models/dscf/marketplace/quotation.rb +9 -0
  18. data/app/models/dscf/marketplace/quotation_item.rb +10 -3
  19. data/app/models/dscf/marketplace/request_for_quotation.rb +9 -0
  20. data/app/models/dscf/marketplace/rfq_item.rb +9 -0
  21. data/app/models/dscf/marketplace/supplier_product.rb +9 -0
  22. data/app/models/dscf/marketplace/unit.rb +9 -0
  23. data/app/models/dscf/marketplace/unit_conversion.rb +9 -0
  24. data/app/serializers/dscf/marketplace/category_serializer.rb +11 -0
  25. data/app/serializers/dscf/marketplace/listing_serializer.rb +13 -0
  26. data/app/serializers/dscf/marketplace/order_serializer.rb +15 -0
  27. data/app/serializers/dscf/marketplace/product_serializer.rb +13 -0
  28. data/app/serializers/dscf/marketplace/quotation_serializer.rb +14 -0
  29. data/app/serializers/dscf/marketplace/request_for_quotation_serializer.rb +13 -0
  30. data/config/locales/en.yml +254 -0
  31. data/config/routes.rb +85 -0
  32. data/db/migrate/20250831110907_create_dscf_marketplace_delivery_orders.rb +21 -0
  33. data/db/migrate/20250831110921_create_dscf_marketplace_delivery_order_items.rb +27 -0
  34. data/db/migrate/20250831110933_create_dscf_marketplace_delivery_vehicles.rb +17 -0
  35. data/db/migrate/20250901074509_add_delivery_handoff_fields_to_delivery_order_items.rb +16 -0
  36. data/db/migrate/20250901074746_add_delivery_order_to_orders.rb +7 -0
  37. data/db/migrate/20250901075511_add_delivery_vehicle_to_delivery_orders.rb +7 -0
  38. data/db/migrate/20250901080134_add_fulfillment_type_to_orders.rb +6 -0
  39. data/lib/dscf/marketplace/version.rb +1 -1
  40. data/spec/factories/dscf/core/addresses.rb +15 -0
  41. data/spec/factories/dscf/marketplace/delivery_order_items.rb +46 -0
  42. data/spec/factories/dscf/marketplace/delivery_orders.rb +14 -0
  43. data/spec/factories/dscf/marketplace/delivery_vehicles.rb +37 -0
  44. data/spec/factories/dscf/marketplace/order_items.rb +1 -1
  45. data/spec/factories/dscf/marketplace/orders.rb +10 -1
  46. metadata +29 -3
  47. data/README.md +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60c2cbf7461bd253837b481338ec8105fc47e5ff3927855341efc9261bf193f5
4
- data.tar.gz: 3d10a078bab90804d176823e1cf0e44ed74f617ca3f0adeff657339438404642
3
+ metadata.gz: e5af5705a5f167b0e7f9f34ae086a62f92528d0634e53ca1b50e1ddd0894934b
4
+ data.tar.gz: c53e6675625b7a1225ed8cdaaf977e940325ff9d9fe934f84a47517b22b82302
5
5
  SHA512:
6
- metadata.gz: 96051d9c835dc17d0f8c7b495c28dd48b2b45a9690711be8d9a5650f6f36643498605d4d8bfdeaf6df6a7cea2f04567b6aff8479a444c525fb68ddbc60ebf41b
7
- data.tar.gz: 6e35c21f3b43d962d838b1f5bbfb577db65e2c004c0665e340356994a93fb1e89d594d02358209af2670a6ea8638fc909347f80a8cbdd470e40d975520433af1
6
+ metadata.gz: 69d7b1dc54ca8c0df0177243a8a9c2b9e410348b7aaf53464890c07f90c2e13a0c9313805992db5e7f80962c771cde51ab7889046fb93a7ad7c4d07c532880ad
7
+ data.tar.gz: c00490bed736cc44c717edc3cbb64de8f2ebac369fd6f1ec98ecf24f44d4c00d07b52aeda5062d04663ecd079948890d0fe160721c2f2295272790b588bde568
@@ -1,6 +1,8 @@
1
1
  module Dscf
2
2
  module Marketplace
3
3
  class ApplicationController < ActionController::API
4
+ include Dscf::Core::JsonResponse
5
+ include Dscf::Core::Authenticatable
4
6
  end
5
7
  end
6
8
  end
@@ -0,0 +1,26 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class CategoriesController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ private
7
+
8
+ def model_params
9
+ params.require(:category).permit(:name, :description, :parent_id)
10
+ end
11
+
12
+ def eager_loaded_associations
13
+ [ :parent, :subcategories, :products ]
14
+ end
15
+
16
+ def default_serializer_includes
17
+ {
18
+ index: [ :parent ],
19
+ show: [ :parent, :subcategories, :products ],
20
+ create: [ :parent ],
21
+ update: [ :parent ]
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class ListingsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def activate
7
+ @obj = find_record
8
+ if @obj.update(status: :active)
9
+ render_success("listings.success.activated", data: @obj)
10
+ else
11
+ render_error("listings.errors.activate_failed")
12
+ end
13
+ end
14
+
15
+ def pause
16
+ @obj = find_record
17
+ if @obj.update(status: :paused)
18
+ render_success("listings.success.paused", data: @obj)
19
+ else
20
+ render_error("listings.errors.pause_failed")
21
+ end
22
+ end
23
+
24
+ def sold_out
25
+ @obj = find_record
26
+ if @obj.update(status: :sold_out)
27
+ render_success("listings.success.sold_out", data: @obj)
28
+ else
29
+ render_error("listings.errors.sold_out_failed")
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def model_params
36
+ params.require(:listing).permit(
37
+ :business_id, :supplier_product_id, :price, :quantity, :status
38
+ )
39
+ end
40
+
41
+ def eager_loaded_associations
42
+ [ :business, :supplier_product, :order_items ]
43
+ end
44
+
45
+ def default_serializer_includes
46
+ {
47
+ index: [ :business, :supplier_product ],
48
+ show: [ :business, :supplier_product, :order_items ],
49
+ create: [ :business, :supplier_product ],
50
+ update: [ :business, :supplier_product ]
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,58 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class OrdersController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def confirm
7
+ @obj = find_record
8
+ if @obj.confirm!
9
+ render_success("orders.success.confirmed", data: @obj)
10
+ else
11
+ render_error("orders.errors.confirm_failed")
12
+ end
13
+ end
14
+
15
+ def cancel
16
+ @obj = find_record
17
+ if @obj.update(status: :cancelled)
18
+ @obj.order_items.update_all(status: OrderItem.statuses[:cancelled])
19
+ render_success("orders.success.cancelled", data: @obj)
20
+ else
21
+ render_error("orders.errors.cancel_failed")
22
+ end
23
+ end
24
+
25
+ def complete
26
+ @obj = find_record
27
+ if @obj.can_be_completed? && @obj.update(status: :completed)
28
+ @obj.order_items.update_all(status: OrderItem.statuses[:completed])
29
+ render_success("orders.success.completed", data: @obj)
30
+ else
31
+ render_error("orders.errors.complete_failed")
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def model_params
38
+ params.require(:order).permit(
39
+ :quotation_id, :listing_id, :user_id, :delivery_order_id,
40
+ :order_type, :status, :fulfillment_type
41
+ )
42
+ end
43
+
44
+ def eager_loaded_associations
45
+ [ :quotation, :listing, :user, :delivery_order, :order_items ]
46
+ end
47
+
48
+ def default_serializer_includes
49
+ {
50
+ index: [ :user, :quotation, :listing ],
51
+ show: [ :user, :quotation, :listing, :delivery_order, :order_items ],
52
+ create: [ :user, :quotation, :listing ],
53
+ update: [ :user, :quotation, :listing ]
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class ProductsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ private
7
+
8
+ def model_params
9
+ params.require(:product).permit(
10
+ :sku, :name, :description, :category_id, :unit_id,
11
+ :base_price, :base_quantity, :business_only,
12
+ :thumbnail, images: []
13
+ )
14
+ end
15
+
16
+ def eager_loaded_associations
17
+ [ :category, :unit, :supplier_products ]
18
+ end
19
+
20
+ def default_serializer_includes
21
+ {
22
+ index: [ :category, :unit ],
23
+ show: [ :category, :unit, :supplier_products ],
24
+ create: [ :category, :unit ],
25
+ update: [ :category, :unit ]
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class QuotationsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def accept
7
+ @obj = find_record
8
+ if @obj.accept!
9
+ render_success("quotations.success.accepted", data: @obj)
10
+ else
11
+ render_error("quotations.errors.accept_failed")
12
+ end
13
+ end
14
+
15
+ def reject
16
+ @obj = find_record
17
+ if @obj.reject!
18
+ render_success("quotations.success.rejected", data: @obj)
19
+ else
20
+ render_error("quotations.errors.reject_failed")
21
+ end
22
+ end
23
+
24
+ def send_quotation
25
+ @obj = find_record
26
+ if @obj.send_quotation!
27
+ render_success("quotations.success.sent", data: @obj)
28
+ else
29
+ render_error("quotations.errors.send_failed")
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def model_params
36
+ params.require(:quotation).permit(
37
+ :request_for_quotation_id, :business_id, :total_price,
38
+ :delivery_date, :valid_until, :status, :notes
39
+ )
40
+ end
41
+
42
+ def eager_loaded_associations
43
+ [ :request_for_quotation, :business, :quotation_items, :order ]
44
+ end
45
+
46
+ def default_serializer_includes
47
+ {
48
+ index: [ :request_for_quotation, :business ],
49
+ show: [ :request_for_quotation, :business, :quotation_items, :order ],
50
+ create: [ :request_for_quotation, :business ],
51
+ update: [ :request_for_quotation, :business ]
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class RequestForQuotationsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def send_rfq
7
+ @obj = find_record
8
+ if @obj.draft? && @obj.update(status: :sent)
9
+ render_success("request_for_quotations.success.sent", data: @obj)
10
+ else
11
+ render_error("request_for_quotations.errors.send_failed")
12
+ end
13
+ end
14
+
15
+ def close
16
+ @obj = find_record
17
+ if (@obj.sent? || @obj.responded?) && @obj.update(status: :closed)
18
+ render_success("request_for_quotations.success.closed", data: @obj)
19
+ else
20
+ render_error("request_for_quotations.errors.close_failed")
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def model_params
27
+ params.require(:request_for_quotation).permit(
28
+ :user_id, :selected_quotation_id, :status, :notes
29
+ )
30
+ end
31
+
32
+ def eager_loaded_associations
33
+ [ :user, :selected_quotation, :rfq_items, :quotations ]
34
+ end
35
+
36
+ def default_serializer_includes
37
+ {
38
+ index: [ :user ],
39
+ show: [ :user, :selected_quotation, :rfq_items, :quotations ],
40
+ create: [ :user ],
41
+ update: [ :user ]
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -12,6 +12,15 @@ module Dscf
12
12
 
13
13
  scope :root_categories, -> { where(parent_id: nil) }
14
14
 
15
+ # Ransack configuration for secure filtering
16
+ def self.ransackable_attributes(_auth_object = nil)
17
+ %w[id name description parent_id created_at updated_at]
18
+ end
19
+
20
+ def self.ransackable_associations(_auth_object = nil)
21
+ %w[parent subcategories products]
22
+ end
23
+
15
24
  def root?
16
25
  parent_id.nil?
17
26
  end
@@ -0,0 +1,116 @@
1
+ module Dscf::Marketplace
2
+ class DeliveryOrder < ApplicationRecord
3
+ enum :status, {assigned: 0, picked_up: 1, in_transit: 2, delivered: 3, failed: 4}
4
+ enum :vehicle_type, {motorcycle: 0, car: 1, van: 2, truck: 3}
5
+
6
+ belongs_to :driver, class_name: "Dscf::Core::User"
7
+ belongs_to :pickup_address, class_name: "Dscf::Core::Address"
8
+ belongs_to :delivery_vehicle, optional: true
9
+ has_many :delivery_order_items, dependent: :destroy
10
+ has_many :marketplace_orders, class_name: "Dscf::Marketplace::Order", dependent: :nullify
11
+
12
+ validates :driver_id, presence: true
13
+ validates :pickup_address_id, presence: true
14
+ validates :status, presence: true
15
+ validates :vehicle_type, presence: true
16
+ validates :estimated_delivery_price, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
17
+ validates :actual_delivery_price, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
18
+
19
+ before_save :calculate_actual_delivery_price, if: :status_changed?
20
+
21
+ # Ransack configuration for secure filtering
22
+ def self.ransackable_attributes(_auth_object = nil)
23
+ %w[id driver_id pickup_address_id delivery_vehicle_id status vehicle_type estimated_delivery_price actual_delivery_price estimated_delivery_time actual_delivery_time delivery_notes created_at updated_at]
24
+ end
25
+
26
+ def self.ransackable_associations(_auth_object = nil)
27
+ %w[driver pickup_address delivery_vehicle delivery_order_items marketplace_orders]
28
+ end
29
+
30
+ def pickup!
31
+ return false unless assigned?
32
+ update!(status: :picked_up, actual_delivery_time: Time.current)
33
+ true
34
+ end
35
+
36
+ def start_delivery!
37
+ return false unless picked_up?
38
+ update!(status: :in_transit)
39
+ true
40
+ end
41
+
42
+ def complete_delivery!
43
+ return false unless in_transit?
44
+ update!(status: :delivered, actual_delivery_time: Time.current)
45
+ update_associated_orders
46
+ true
47
+ end
48
+
49
+ def mark_failed!(reason = nil)
50
+ update!(status: :failed, delivery_notes: reason)
51
+ true
52
+ end
53
+
54
+ def total_items
55
+ delivery_order_items.sum(:quantity)
56
+ end
57
+
58
+ def total_orders
59
+ marketplace_orders.count
60
+ end
61
+
62
+ def delivery_duration
63
+ return nil unless actual_delivery_time && estimated_delivery_time
64
+ (actual_delivery_time - estimated_delivery_time).to_i
65
+ end
66
+
67
+ def on_time?
68
+ return nil unless actual_delivery_time && estimated_delivery_time
69
+ actual_delivery_time <= estimated_delivery_time
70
+ end
71
+
72
+ # Handoff tracking methods
73
+ def handoff_completion_status
74
+ items = delivery_order_items
75
+ {
76
+ total_items: items.count,
77
+ confirmed_items: items.count(&:receiver_confirmed?),
78
+ pending_confirmation: items.count { |item| item.delivered? && !item.receiver_confirmed? },
79
+ issues_reported: items.count(&:requires_resolution?),
80
+ completion_percentage: items.present? ? (items.count(&:receiver_confirmed?).to_f / items.count * 100).round(2) : 0
81
+ }
82
+ end
83
+
84
+ def all_handoffs_confirmed?
85
+ delivery_order_items.all?(&:receiver_confirmed?)
86
+ end
87
+
88
+ def items_requiring_attention
89
+ delivery_order_items.select(&:requires_resolution?)
90
+ end
91
+
92
+ def generate_handoff_summary_report
93
+ {
94
+ delivery_order_id: id,
95
+ driver: driver&.name,
96
+ vehicle: vehicle_type,
97
+ total_destinations: delivery_order_items.distinct.pluck(:dropoff_address_id).count,
98
+ handoff_status: handoff_completion_status,
99
+ issues: items_requiring_attention.map(&:comprehensive_damage_report),
100
+ generated_at: Time.current
101
+ }
102
+ end
103
+
104
+ private
105
+
106
+ def calculate_actual_delivery_price
107
+ self.actual_delivery_price ||= estimated_delivery_price
108
+ end
109
+
110
+ def update_associated_orders
111
+ marketplace_orders.each do |order|
112
+ order.update!(status: :completed)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,201 @@
1
+ module Dscf::Marketplace
2
+ class DeliveryOrderItem < ApplicationRecord
3
+ enum :status, {pending: 0, picked_up: 1, delivered: 2, receiver_confirmed: 3, damaged: 4, missing: 5, disputed: 6}
4
+
5
+ belongs_to :delivery_order
6
+ belongs_to :order_item, class_name: "Dscf::Marketplace::OrderItem"
7
+ belongs_to :pickup_address, class_name: "Dscf::Core::Address"
8
+ belongs_to :dropoff_address, class_name: "Dscf::Core::Address"
9
+
10
+ validates :delivery_order_id, presence: true
11
+ validates :order_item_id, presence: true
12
+ validates :quantity, presence: true, numericality: {greater_than: 0}
13
+ validates :status, presence: true
14
+ validates :pickup_verified_quantity, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
15
+ validates :dropoff_verified_quantity, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
16
+ validates :damaged_quantity, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
17
+ validates :missing_quantity, numericality: {greater_than_or_equal_to: 0}, allow_nil: true
18
+ validates :receiver_confirmed_by, presence: true, if: :receiver_action_taken?
19
+ validates :receiver_confirmed_at, presence: true, if: :receiver_action_taken?
20
+
21
+ before_save :calculate_verification_totals
22
+
23
+ # Ransack configuration for secure filtering
24
+ def self.ransackable_attributes(_auth_object = nil)
25
+ %w[id delivery_order_id order_item_id pickup_address_id dropoff_address_id quantity status pickup_verified_quantity dropoff_verified_quantity damaged_quantity missing_quantity receiver_confirmed_by receiver_confirmed_at receiver_notes handoff_checklist_completed created_at updated_at]
26
+ end
27
+
28
+ def self.ransackable_associations(_auth_object = nil)
29
+ %w[delivery_order order_item pickup_address dropoff_address]
30
+ end
31
+
32
+ def pending?
33
+ status == "pending"
34
+ end
35
+
36
+ def picked_up?
37
+ status == "picked_up"
38
+ end
39
+
40
+ def delivered?
41
+ status == "delivered"
42
+ end
43
+
44
+ def damaged?
45
+ status == "damaged"
46
+ end
47
+
48
+ def missing?
49
+ status == "missing"
50
+ end
51
+
52
+ def receiver_confirmed?
53
+ status == "receiver_confirmed"
54
+ end
55
+
56
+ def disputed?
57
+ status == "disputed"
58
+ end
59
+
60
+ def pickup!
61
+ return false unless pending?
62
+ update!(status: :picked_up, pickup_verified_quantity: quantity)
63
+ true
64
+ end
65
+
66
+ def deliver!
67
+ return false unless picked_up?
68
+ update!(status: :delivered, dropoff_verified_quantity: pickup_verified_quantity.to_f - (damaged_quantity || 0) - (missing_quantity || 0))
69
+ true
70
+ end
71
+
72
+ # Handoff workflow methods
73
+ def receiver_confirm!(confirmed_quantity, notes = nil, confirmed_by = nil)
74
+ return false unless delivered? || picked_up?
75
+
76
+ update!(
77
+ status: :receiver_confirmed,
78
+ dropoff_verified_quantity: confirmed_quantity,
79
+ receiver_notes: notes,
80
+ receiver_confirmed_at: Time.current,
81
+ receiver_confirmed_by: confirmed_by,
82
+ handoff_checklist_completed: true
83
+ )
84
+ notify_receiver_confirmation
85
+ true
86
+ end
87
+
88
+ def report_issue!(issue_type, quantity, description, reported_by)
89
+ return false unless delivered? || picked_up?
90
+
91
+ update!(
92
+ status: issue_type.to_sym, # :damaged or :missing
93
+ damaged_quantity: ((issue_type == "damaged") ? quantity : 0),
94
+ missing_quantity: ((issue_type == "missing") ? quantity : 0),
95
+ receiver_notes: description,
96
+ receiver_confirmed_at: Time.current,
97
+ receiver_confirmed_by: reported_by,
98
+ handoff_checklist_completed: true
99
+ )
100
+ notify_platform_of_issue
101
+ true
102
+ end
103
+
104
+ def dispute_delivery!(reason, disputed_by)
105
+ return false unless delivered? || receiver_confirmed?
106
+
107
+ update!(
108
+ status: :disputed,
109
+ receiver_notes: reason,
110
+ receiver_confirmed_at: Time.current,
111
+ receiver_confirmed_by: disputed_by
112
+ )
113
+ create_dispute_record
114
+ true
115
+ end
116
+
117
+ def mark_damaged!(quantity)
118
+ update!(status: :damaged, damaged_quantity: quantity)
119
+ true
120
+ end
121
+
122
+ def mark_missing!(quantity)
123
+ update!(status: :missing, missing_quantity: quantity)
124
+ true
125
+ end
126
+
127
+ def verification_complete?
128
+ pickup_verified_quantity.present? && dropoff_verified_quantity.present?
129
+ end
130
+
131
+ def has_discrepancy?
132
+ return false unless verification_complete?
133
+ pickup_verified_quantity != dropoff_verified_quantity
134
+ end
135
+
136
+ def discrepancy_amount
137
+ return 0 unless has_discrepancy?
138
+ (pickup_verified_quantity - dropoff_verified_quantity).abs
139
+ end
140
+
141
+ def product_name
142
+ order_item&.product_name
143
+ end
144
+
145
+ def unit_name
146
+ order_item&.unit_name
147
+ end
148
+
149
+ # Loss/Damage Reporting Methods
150
+ def comprehensive_damage_report
151
+ {
152
+ item_id: id,
153
+ product_name: order_item&.product&.name,
154
+ expected_quantity: quantity,
155
+ received_quantity: dropoff_verified_quantity,
156
+ damaged_quantity: damaged_quantity,
157
+ missing_quantity: missing_quantity,
158
+ total_discrepancy: discrepancy_amount,
159
+ issue_description: receiver_notes,
160
+ reported_at: receiver_confirmed_at,
161
+ reported_by: receiver_confirmed_by,
162
+ requires_resolution: requires_resolution?
163
+ }
164
+ end
165
+
166
+ def requires_resolution?
167
+ has_discrepancy? || damaged? || missing? || disputed?
168
+ end
169
+
170
+ private
171
+
172
+ def receiver_action_taken?
173
+ receiver_confirmed? || damaged? || missing? || disputed?
174
+ end
175
+
176
+ def notify_receiver_confirmation
177
+ # Placeholder for notification logic
178
+ # This will be implemented when we add notification system
179
+ Rails.logger.info("Receiver confirmation completed for delivery order item #{id}")
180
+ end
181
+
182
+ def notify_platform_of_issue
183
+ # Placeholder for platform notification
184
+ # This will be implemented when we add notification system
185
+ Rails.logger.info("Issue reported for delivery order item #{id}: #{status}")
186
+ end
187
+
188
+ def create_dispute_record
189
+ # Placeholder for dispute record creation
190
+ # This will be implemented when we add dispute management
191
+ Rails.logger.info("Dispute created for delivery order item #{id}")
192
+ end
193
+
194
+ def calculate_verification_totals
195
+ if pickup_verified_quantity.present? && damaged_quantity.present? && missing_quantity.present?
196
+ expected_dropoff = pickup_verified_quantity - damaged_quantity - missing_quantity
197
+ self.dropoff_verified_quantity ||= expected_dropoff
198
+ end
199
+ end
200
+ end
201
+ end