dscf-marketplace 0.6.3 → 0.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87c9e26ee1a03d60a892a9959d1beefd2287039c433c173bf896ea0e9a44a409
4
- data.tar.gz: 6da6652ed712f8e1d5c617e61ff95c72c181148fbb219f800da503dc12a4f645
3
+ metadata.gz: 594b8a0c6b3e5d673cc49e5b9fed9944ed8741a9e060878b7db0faf18b425316
4
+ data.tar.gz: 3a4ae57c850ebc4f616def389b9b66d3d1aebe75eed35a0920fac405bcd34789
5
5
  SHA512:
6
- metadata.gz: d39c8853dc117c49f980f458e4b25d6e9666f6a51ce6894d5078bb27133569fbf0cf3abd84ce76f977d4df6a6b81762073e77e0edb2acfac694eca3c53cfcc79
7
- data.tar.gz: 92d80800e80f81ea590c8c4cd4c2ca0d64e43540c2f7d12a7796e6891d359cd74e25b5266fcbadd7f3511a6a5aa723a88cee64c0706b305f5c0a71b2199cd139
6
+ metadata.gz: 25512776a17bd4984353716c8020a13d3ce77b510c68cc7b8b139bc19f4c3b3145ee55cd61a788c1a21e96f3d121cd5dddfe12a62f2359a01d1a5b3d008400d1
7
+ data.tar.gz: 2b0e4ed3a9fc273e0cfee298f8fcd59f714494dd85cbf818d5ba9dd6ff124270ee200724e53be170f1af5fb9f0ffc71dffa97cdfc680aee10a1b1926c617a82d
@@ -68,6 +68,11 @@ module Dscf
68
68
  render_success(data: delivery_orders, serializer_options: options)
69
69
  end
70
70
 
71
+ def summary
72
+ @delivery_order = find_record
73
+ render_success(data: @delivery_order.generate_handoff_summary_report)
74
+ end
75
+
71
76
  def pickup
72
77
  @obj = find_record
73
78
  if @obj.pickup!
@@ -0,0 +1,57 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class DeliveryStopsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ def index
7
+ super do
8
+ if params[:delivery_order_id]
9
+ stops = DeliveryStop.where(delivery_order_id: params[:delivery_order_id])
10
+ options = { include: [:dropoff_address, :delivery_order_items] }
11
+ [stops, options]
12
+ else
13
+ # Fallback or standard index if needed, though mostly used nested
14
+ [DeliveryStop.all, {}]
15
+ end
16
+ end
17
+ end
18
+
19
+ def verify
20
+ @delivery_stop = DeliveryStop.find(params[:id])
21
+ if @delivery_stop.verify!(verified_by: current_user.id)
22
+ render_success(
23
+ data: @delivery_stop,
24
+ serializer_options: { include: [:dropoff_address, :delivery_order_items] }
25
+ )
26
+ else
27
+ render_error(
28
+ errors: @delivery_stop.errors.full_messages,
29
+ status: :unprocessable_entity
30
+ )
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def model_params
37
+ params.require(:delivery_stop).permit(:notes, :status)
38
+ end
39
+
40
+ def eager_loaded_associations
41
+ [:dropoff_address, :delivery_order_items]
42
+ end
43
+
44
+ def allowed_order_columns
45
+ %w[id status sequence_number created_at updated_at]
46
+ end
47
+
48
+ def default_serializer_includes
49
+ {
50
+ index: [:dropoff_address, :delivery_order_items],
51
+ show: [:dropoff_address, :delivery_order_items],
52
+ verify: [:dropoff_address, :delivery_order_items]
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -7,6 +7,7 @@ module Dscf::Marketplace
7
7
  belongs_to :pickup_address, class_name: "Dscf::Core::Address"
8
8
  belongs_to :delivery_vehicle, optional: true
9
9
  has_many :delivery_order_items, dependent: :destroy
10
+ has_many :delivery_stops, class_name: "Dscf::Marketplace::DeliveryStop", dependent: :destroy
10
11
  has_many :marketplace_orders, class_name: "Dscf::Marketplace::Order", dependent: :nullify
11
12
 
12
13
  validates :driver_id, presence: true, unless: :draft?
@@ -100,7 +101,6 @@ module Dscf::Marketplace
100
101
  actual_delivery_time <= estimated_delivery_time
101
102
  end
102
103
 
103
- # Handoff tracking methods
104
104
  def handoff_completion_status
105
105
  items = delivery_order_items
106
106
  {
@@ -123,15 +123,40 @@ module Dscf::Marketplace
123
123
  def generate_handoff_summary_report
124
124
  {
125
125
  delivery_order_id: id,
126
- driver: driver&.name,
126
+ driver: [driver&.user_profile&.first_name, driver&.user_profile&.last_name].compact.join(" "),
127
127
  vehicle: vehicle_type,
128
- total_destinations: delivery_order_items.distinct.pluck(:dropoff_address_id).count,
129
- handoff_status: handoff_completion_status,
130
- issues: items_requiring_attention.map(&:comprehensive_damage_report),
128
+
129
+ # Financials & Metrics
130
+ delivery_price: actual_delivery_price || estimated_delivery_price,
131
+ time_taken: formatted_delivery_duration,
132
+ distance_covered: 0, # Placeholder until distance tracking is implemented
133
+
134
+ stops: delivery_stops.order(:sequence_number).map do |stop|
135
+ {
136
+ id: stop.id,
137
+ address: [stop.dropoff_address&.city, stop.dropoff_address&.sub_city].compact.join(", ").presence || "Unknown Address",
138
+ city: stop.dropoff_address&.city,
139
+ status: stop.status&.titleize
140
+ }
141
+ end,
142
+
131
143
  generated_at: Time.current
132
144
  }
133
145
  end
134
146
 
147
+ def formatted_delivery_duration
148
+ return "N/A" unless delivery_duration
149
+
150
+ hours = delivery_duration / 3600
151
+ minutes = (delivery_duration % 3600) / 60
152
+
153
+ if hours > 0
154
+ "#{hours}h #{minutes}m"
155
+ else
156
+ "#{minutes}m"
157
+ end
158
+ end
159
+
135
160
  private
136
161
 
137
162
  def calculate_actual_delivery_price
@@ -3,6 +3,7 @@ module Dscf::Marketplace
3
3
  enum :status, {pending: 0, picked_up: 1, delivered: 2, receiver_confirmed: 3, damaged: 4, missing: 5, disputed: 6, wrong_item: 7, missing: 8}
4
4
 
5
5
  belongs_to :delivery_order
6
+ belongs_to :delivery_stop, class_name: "Dscf::Marketplace::DeliveryStop", optional: true
6
7
  belongs_to :order_item, class_name: "Dscf::Marketplace::OrderItem"
7
8
  belongs_to :pickup_address, class_name: "Dscf::Core::Address"
8
9
  belongs_to :dropoff_address, class_name: "Dscf::Core::Address"
@@ -71,7 +72,7 @@ module Dscf::Marketplace
71
72
 
72
73
  # Handoff workflow methods
73
74
  def receiver_confirm!(confirmed_quantity, notes = nil, confirmed_by = nil)
74
- return false unless picked_up? || pending?
75
+ return false unless picked_up? || pending? || delivered?
75
76
 
76
77
  update!(
77
78
  status: :receiver_confirmed,
@@ -0,0 +1,47 @@
1
+ module Dscf::Marketplace
2
+ class DeliveryStop < ApplicationRecord
3
+ enum :status, { pending: 0, arrived: 1, completed: 2, failed: 3 }, default: :pending
4
+
5
+ belongs_to :delivery_order, class_name: "Dscf::Marketplace::DeliveryOrder"
6
+ belongs_to :dropoff_address, class_name: "Dscf::Core::Address"
7
+ has_many :delivery_order_items, class_name: "Dscf::Marketplace::DeliveryOrderItem", dependent: :nullify
8
+
9
+ validates :delivery_order, presence: true
10
+ validates :dropoff_address, presence: true
11
+ validates :status, presence: true
12
+ validates :sequence_number, numericality: { greater_than: 0 }, allow_nil: true
13
+
14
+ def self.ransackable_attributes(auth_object = nil)
15
+ %w[id status sequence_number notes created_at updated_at]
16
+ end
17
+
18
+ def self.ransackable_associations(auth_object = nil)
19
+ %w[delivery_order dropoff_address delivery_order_items]
20
+ end
21
+
22
+ def verify!(verified_by: nil)
23
+ return false unless pending? || arrived?
24
+
25
+ update!(
26
+ status: :completed
27
+ )
28
+
29
+ # Auto-confirm items
30
+ delivery_order_items.each do |item|
31
+ item.receiver_confirm!(item.quantity, "Auto-confirmed via stop verification", verified_by)
32
+ end
33
+
34
+ check_for_completion
35
+
36
+ true
37
+ end
38
+
39
+ private
40
+
41
+ def check_for_completion
42
+ if delivery_order.delivery_stops.all?(&:completed?)
43
+ delivery_order.complete_delivery!
44
+ end
45
+ end
46
+ end
47
+ end
@@ -12,6 +12,7 @@ module Dscf
12
12
  belongs_to :delivery_vehicle
13
13
  has_many :delivery_order_items
14
14
  has_many :marketplace_orders
15
+ has_many :delivery_stops
15
16
  end
16
17
  end
17
18
  end
@@ -0,0 +1,10 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class DeliveryStopSerializer < ActiveModel::Serializer
4
+ attributes :id, :status, :sequence_number, :notes, :created_at, :updated_at
5
+
6
+ belongs_to :dropoff_address
7
+ has_many :delivery_order_items
8
+ end
9
+ end
10
+ end
@@ -9,16 +9,14 @@ module Dscf::Marketplace
9
9
  def assign_driver(delivery_order, driver)
10
10
  raise ArgumentError, "Delivery order is required" unless delivery_order
11
11
  raise ArgumentError, "Driver is required" unless driver
12
-
13
- unless delivery_order.draft?
14
- raise InvalidStatusTransitionError, "Can only assign driver to draft delivery orders"
15
- end
12
+
13
+ validate_assignment(delivery_order)
16
14
 
17
15
  delivery_order.driver_id = driver.id
18
16
  delivery_order.status = :assigned
19
17
 
20
18
  unless delivery_order.save
21
- raise AssignmentError, "Failed to assign driver: #{delivery_order.errors.full_messages.join(", ")}"
19
+ raise AssignmentError, "Failed to assign driver: #{delivery_order.errors.full_messages.join(', ')}"
22
20
  end
23
21
 
24
22
  delivery_order
@@ -35,8 +33,11 @@ module Dscf::Marketplace
35
33
  # Create delivery order in a transaction
36
34
  DeliveryOrder.transaction do
37
35
  delivery_order = create_delivery_order(vehicle_type, pickup_address, delivery_notes)
38
- create_delivery_order_items(delivery_order, orders, pickup_address)
36
+ create_delivery_stops_and_items(delivery_order, orders, pickup_address)
39
37
  associate_orders_with_delivery(delivery_order, orders)
38
+
39
+ # Optimize route - Removed as per requirement
40
+ # RouteOptimizationService.new.optimize_route(delivery_order)
40
41
 
41
42
  delivery_order
42
43
  end
@@ -60,6 +61,12 @@ module Dscf::Marketplace
60
61
  end
61
62
  end
62
63
 
64
+ def validate_assignment(delivery_order)
65
+ unless delivery_order.draft?
66
+ raise InvalidStatusTransitionError, "Can only assign driver to draft delivery orders"
67
+ end
68
+ end
69
+
63
70
  def create_delivery_order(vehicle_type, pickup_address, delivery_notes)
64
71
  DeliveryOrder.create!(
65
72
  vehicle_type: vehicle_type,
@@ -69,20 +76,31 @@ module Dscf::Marketplace
69
76
  )
70
77
  end
71
78
 
72
- def create_delivery_order_items(delivery_order, orders, pickup_address)
73
- orders.each do |order|
74
- order.order_items.each do |order_item|
75
- # Use the order's dropoff address for delivery orders
76
- dropoff_address = order.dropoff_address
77
-
78
- DeliveryOrderItem.create!(
79
- delivery_order: delivery_order,
80
- order_item: order_item,
81
- quantity: order_item.quantity,
82
- pickup_address: pickup_address,
83
- dropoff_address: dropoff_address,
84
- status: :pending
85
- )
79
+ def create_delivery_stops_and_items(delivery_order, orders, pickup_address)
80
+ # Group orders by dropoff address to create stops
81
+ orders_by_address = orders.group_by(&:dropoff_address_id)
82
+
83
+ orders_by_address.each do |dropoff_address_id, address_orders|
84
+ # Create stop
85
+ stop = DeliveryStop.create!(
86
+ delivery_order: delivery_order,
87
+ dropoff_address_id: dropoff_address_id,
88
+ status: :pending
89
+ )
90
+
91
+ # Create items for this stop
92
+ address_orders.each do |order|
93
+ order.order_items.reload.each do |order_item|
94
+ DeliveryOrderItem.create!(
95
+ delivery_order: delivery_order,
96
+ delivery_stop: stop,
97
+ order_item: order_item,
98
+ quantity: order_item.quantity,
99
+ pickup_address: pickup_address,
100
+ dropoff_address_id: dropoff_address_id,
101
+ status: :pending
102
+ )
103
+ end
86
104
  end
87
105
  end
88
106
  end
@@ -0,0 +1,17 @@
1
+ module Dscf::Marketplace
2
+ class DisputeService
3
+ def raise_dispute(delivery_stop, reason, reported_by)
4
+ return false unless delivery_stop
5
+
6
+ delivery_stop.update!(status: :failed, notes: reason)
7
+
8
+ delivery_stop.delivery_order_items.each do |item|
9
+ item.dispute_delivery!(reason, reported_by)
10
+ end
11
+
12
+ DeliveryNotificationService.new.notify_dispute(delivery_stop)
13
+
14
+ true
15
+ end
16
+ end
17
+ end
data/config/routes.rb CHANGED
@@ -90,12 +90,14 @@ Dscf::Marketplace::Engine.routes.draw do
90
90
  # Delivery System
91
91
  resources :delivery_orders do
92
92
  resources :delivery_order_items, only: [ :index ]
93
+ resources :delivery_stops, only: [ :index ]
93
94
  member do
94
95
  post "pickup"
95
96
  post "start_delivery"
96
97
  post "complete_delivery"
97
98
  post "mark_failed"
98
99
  post "accept"
100
+ get "summary"
99
101
  end
100
102
  collection do
101
103
  get "my_driver_deliveries"
@@ -104,6 +106,12 @@ Dscf::Marketplace::Engine.routes.draw do
104
106
  end
105
107
  end
106
108
 
109
+ resources :delivery_stops, only: [ :show ] do
110
+ member do
111
+ post "verify"
112
+ end
113
+ end
114
+
107
115
  resources :delivery_order_items, only: [ :index, :show, :update, :create ] do
108
116
  member do
109
117
  post "receiver_confirm"
@@ -0,0 +1,13 @@
1
+ class CreateDscfMarketplaceDeliveryStops < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :dscf_marketplace_delivery_stops do |t|
4
+ t.references :delivery_order, null: false, foreign_key: { to_table: :dscf_marketplace_delivery_orders }
5
+ t.references :dropoff_address, null: false, foreign_key: { to_table: :dscf_core_addresses }
6
+ t.integer :status, default: 0
7
+ t.integer :sequence_number
8
+ t.text :notes
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class AddDeliveryStopToDeliveryOrderItems < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_reference :dscf_marketplace_delivery_order_items, :delivery_stop, null: true, foreign_key: { to_table: :dscf_marketplace_delivery_stops }
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.6.3".freeze
3
+ VERSION = "0.6.4".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :dscf_marketplace_delivery_stop, class: 'Dscf::Marketplace::DeliveryStop' do
3
+ association :delivery_order, factory: :dscf_marketplace_delivery_order
4
+ association :dropoff_address, factory: :dscf_core_address
5
+ status { :pending }
6
+ sequence_number { 1 }
7
+ end
8
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-marketplace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-11-28 00:00:00.000000000 Z
10
+ date: 2025-12-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -420,6 +420,7 @@ files:
420
420
  - app/controllers/dscf/marketplace/categories_controller.rb
421
421
  - app/controllers/dscf/marketplace/delivery_order_items_controller.rb
422
422
  - app/controllers/dscf/marketplace/delivery_orders_controller.rb
423
+ - app/controllers/dscf/marketplace/delivery_stops_controller.rb
423
424
  - app/controllers/dscf/marketplace/delivery_vehicles_controller.rb
424
425
  - app/controllers/dscf/marketplace/listings_controller.rb
425
426
  - app/controllers/dscf/marketplace/order_items_controller.rb
@@ -438,6 +439,7 @@ files:
438
439
  - app/models/dscf/marketplace/category.rb
439
440
  - app/models/dscf/marketplace/delivery_order.rb
440
441
  - app/models/dscf/marketplace/delivery_order_item.rb
442
+ - app/models/dscf/marketplace/delivery_stop.rb
441
443
  - app/models/dscf/marketplace/delivery_vehicle.rb
442
444
  - app/models/dscf/marketplace/listing.rb
443
445
  - app/models/dscf/marketplace/order.rb
@@ -453,6 +455,7 @@ files:
453
455
  - app/serializers/dscf/marketplace/category_serializer.rb
454
456
  - app/serializers/dscf/marketplace/delivery_order_item_serializer.rb
455
457
  - app/serializers/dscf/marketplace/delivery_order_serializer.rb
458
+ - app/serializers/dscf/marketplace/delivery_stop_serializer.rb
456
459
  - app/serializers/dscf/marketplace/delivery_vehicle_serializer.rb
457
460
  - app/serializers/dscf/marketplace/document_serializer.rb
458
461
  - app/serializers/dscf/marketplace/listing_serializer.rb
@@ -468,6 +471,7 @@ files:
468
471
  - app/serializers/dscf/marketplace/unit_serializer.rb
469
472
  - app/serializers/dscf/marketplace/user_serializer.rb
470
473
  - app/services/dscf/marketplace/delivery_order_service.rb
474
+ - app/services/dscf/marketplace/dispute_service.rb
471
475
  - app/services/dscf/marketplace/my_resource_service.rb
472
476
  - app/services/dscf/marketplace/rfq_response_service.rb
473
477
  - app/services/dscf/marketplace/role_service.rb
@@ -504,6 +508,8 @@ files:
504
508
  - db/migrate/20250917110618_add_dropoff_address_to_dscf_marketplace_orders.rb
505
509
  - db/migrate/20251006055550_add_payment_method_to_dscf_marketplace_orders.rb
506
510
  - db/migrate/20251006055948_change_default_payment_method_on_orders.rb
511
+ - db/migrate/20251130143731_create_dscf_marketplace_delivery_stops.rb
512
+ - db/migrate/20251130144500_add_delivery_stop_to_delivery_order_items.rb
507
513
  - db/seeds.rb
508
514
  - lib/dscf/marketplace.rb
509
515
  - lib/dscf/marketplace/engine.rb
@@ -518,6 +524,7 @@ files:
518
524
  - spec/factories/dscf/marketplace/categories.rb
519
525
  - spec/factories/dscf/marketplace/delivery_order_items.rb
520
526
  - spec/factories/dscf/marketplace/delivery_orders.rb
527
+ - spec/factories/dscf/marketplace/delivery_stops.rb
521
528
  - spec/factories/dscf/marketplace/delivery_vehicles.rb
522
529
  - spec/factories/dscf/marketplace/listings.rb
523
530
  - spec/factories/dscf/marketplace/order_items.rb