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.
- checksums.yaml +4 -4
- data/app/controllers/dscf/marketplace/application_controller.rb +2 -0
- data/app/controllers/dscf/marketplace/categories_controller.rb +26 -0
- data/app/controllers/dscf/marketplace/listings_controller.rb +55 -0
- data/app/controllers/dscf/marketplace/orders_controller.rb +58 -0
- data/app/controllers/dscf/marketplace/products_controller.rb +30 -0
- data/app/controllers/dscf/marketplace/quotations_controller.rb +56 -0
- data/app/controllers/dscf/marketplace/request_for_quotations_controller.rb +46 -0
- data/app/models/dscf/marketplace/category.rb +9 -0
- data/app/models/dscf/marketplace/delivery_order.rb +116 -0
- data/app/models/dscf/marketplace/delivery_order_item.rb +201 -0
- data/app/models/dscf/marketplace/delivery_vehicle.rb +70 -0
- data/app/models/dscf/marketplace/listing.rb +9 -0
- data/app/models/dscf/marketplace/order.rb +35 -0
- data/app/models/dscf/marketplace/order_item.rb +17 -0
- data/app/models/dscf/marketplace/product.rb +9 -0
- data/app/models/dscf/marketplace/quotation.rb +9 -0
- data/app/models/dscf/marketplace/quotation_item.rb +10 -3
- data/app/models/dscf/marketplace/request_for_quotation.rb +9 -0
- data/app/models/dscf/marketplace/rfq_item.rb +9 -0
- data/app/models/dscf/marketplace/supplier_product.rb +9 -0
- data/app/models/dscf/marketplace/unit.rb +9 -0
- data/app/models/dscf/marketplace/unit_conversion.rb +9 -0
- data/app/serializers/dscf/marketplace/category_serializer.rb +11 -0
- data/app/serializers/dscf/marketplace/listing_serializer.rb +13 -0
- data/app/serializers/dscf/marketplace/order_serializer.rb +15 -0
- data/app/serializers/dscf/marketplace/product_serializer.rb +13 -0
- data/app/serializers/dscf/marketplace/quotation_serializer.rb +14 -0
- data/app/serializers/dscf/marketplace/request_for_quotation_serializer.rb +13 -0
- data/config/locales/en.yml +254 -0
- data/config/routes.rb +85 -0
- data/db/migrate/20250831110907_create_dscf_marketplace_delivery_orders.rb +21 -0
- data/db/migrate/20250831110921_create_dscf_marketplace_delivery_order_items.rb +27 -0
- data/db/migrate/20250831110933_create_dscf_marketplace_delivery_vehicles.rb +17 -0
- data/db/migrate/20250901074509_add_delivery_handoff_fields_to_delivery_order_items.rb +16 -0
- data/db/migrate/20250901074746_add_delivery_order_to_orders.rb +7 -0
- data/db/migrate/20250901075511_add_delivery_vehicle_to_delivery_orders.rb +7 -0
- data/db/migrate/20250901080134_add_fulfillment_type_to_orders.rb +6 -0
- data/lib/dscf/marketplace/version.rb +1 -1
- data/spec/factories/dscf/core/addresses.rb +15 -0
- data/spec/factories/dscf/marketplace/delivery_order_items.rb +46 -0
- data/spec/factories/dscf/marketplace/delivery_orders.rb +14 -0
- data/spec/factories/dscf/marketplace/delivery_vehicles.rb +37 -0
- data/spec/factories/dscf/marketplace/order_items.rb +1 -1
- data/spec/factories/dscf/marketplace/orders.rb +10 -1
- metadata +29 -3
- data/README.md +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5af5705a5f167b0e7f9f34ae086a62f92528d0634e53ca1b50e1ddd0894934b
|
4
|
+
data.tar.gz: c53e6675625b7a1225ed8cdaaf977e940325ff9d9fe934f84a47517b22b82302
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69d7b1dc54ca8c0df0177243a8a9c2b9e410348b7aaf53464890c07f90c2e13a0c9313805992db5e7f80962c771cde51ab7889046fb93a7ad7c4d07c532880ad
|
7
|
+
data.tar.gz: c00490bed736cc44c717edc3cbb64de8f2ebac369fd6f1ec98ecf24f44d4c00d07b52aeda5062d04663ecd079948890d0fe160721c2f2295272790b588bde568
|
@@ -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
|