dscf-marketplace 0.3.94 → 0.3.96

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: 000d8116a19e742c018a144b6cde46aa1a3221b95735cb78794baa9981ed5228
4
- data.tar.gz: 41b84e6e4b189dbe622fecc89e8bd4aa33a605b586a82ee7baa416b92c03baec
3
+ metadata.gz: 995d1749e7bc2caebbdfcaaef9ba08ca94d8ae3b6b71d7241c4d979ebdf8668c
4
+ data.tar.gz: e8725566a7aa2b5ea1d8b5b1dc78f464e87fdf2a9b8a0788918dec215b5b2fb4
5
5
  SHA512:
6
- metadata.gz: 6e24ef4567ecf8373aa62fd33243f71bf9e102826dacbf9f7e9e03f66fab1c44770a71ef8e291ca42392254662d1f7c44646ba46405af12d499a354912b0a42f
7
- data.tar.gz: d435066b0c124ee9a3ab0dedc1b037353f3433029dbafcf7bb38640c2c2dcbc4e43415a6d1cf323e890166edbfb9567c56f314c21289f043ccfd69d3548f4302
6
+ metadata.gz: 64a8101ea7155a3918dcceaef775a87b24d0534b8db003c439a676956374bd9afa7e71ea66ace1be00fde9d5eecb1656741e8048819515df75823cb0ec91aece
7
+ data.tar.gz: 9c28c9fe4b743de98be18fd3d1d01471e571e8a83b792042e84080a04bb5c0912e0637d398876520f5fe6082ba2ea1a008032903460afe8994af4da48b1d1d19
@@ -159,6 +159,54 @@ module Dscf
159
159
  end
160
160
  end
161
161
 
162
+ def my_driver_deliveries
163
+ # Verify user has driver role
164
+ unless Dscf::Marketplace::RoleService.user_has_driver_role?(current_user)
165
+ render_error("delivery_orders.errors.access_denied", message: "Only drivers can access this endpoint")
166
+ return
167
+ end
168
+
169
+ # Get deliveries where user is the driver
170
+ delivery_orders = @clazz.where(driver: current_user)
171
+
172
+ # Apply Ransack filtering if q params present
173
+ if params[:q].present?
174
+ # Filter out any parameters that might reference JSON fields
175
+ filtered_params = params[:q].reject { |key, _| key.to_s.include?("optimized_route") }
176
+ if filtered_params.present?
177
+ delivery_orders = delivery_orders.ransack(filtered_params).result
178
+ end
179
+ end
180
+
181
+ # Apply eager loading if defined
182
+ delivery_orders = delivery_orders.includes(eager_loaded_associations) if eager_loaded_associations.present?
183
+
184
+ # Apply pagination if requested
185
+ if params[:page]
186
+ total_count = delivery_orders.count
187
+ delivery_orders = delivery_orders.then(&paginate)
188
+ total_pages = (total_count.to_f / per_page).ceil
189
+ count = delivery_orders.is_a?(Array) ? delivery_orders.length : delivery_orders.count
190
+
191
+ pagination_meta = {
192
+ current_page: page_no,
193
+ per_page: per_page,
194
+ count: count,
195
+ total_count: total_count,
196
+ total_pages: total_pages,
197
+ links: pagination_links(total_pages)
198
+ }
199
+ end
200
+
201
+ # Add serializer includes
202
+ includes = serializer_includes_for_action(:index)
203
+ options = {}
204
+ options[:include] = includes if includes.present?
205
+ options[:pagination] = pagination_meta if params[:page]
206
+
207
+ render_success(data: delivery_orders, serializer_options: options)
208
+ end
209
+
162
210
  private
163
211
 
164
212
  def find_record
@@ -24,7 +24,31 @@ module Dscf
24
24
 
25
25
  def accept
26
26
  @obj = find_record
27
- if @obj.accept!
27
+
28
+ fulfillment_type = params[:fulfillment_type]
29
+ dropoff_address_id = params[:dropoff_address_id]
30
+
31
+ # Validate fulfillment_type
32
+ unless fulfillment_type.present? && Dscf::Marketplace::Order.fulfillment_types.keys.include?(fulfillment_type)
33
+ render_error("Invalid or missing fulfillment_type", status: :unprocessable_entity)
34
+ return
35
+ end
36
+
37
+ dropoff_address = nil
38
+ if dropoff_address_id.present?
39
+ dropoff_address = Dscf::Core::Address.find_by(id: dropoff_address_id) if defined?(Dscf::Core::Address)
40
+ unless dropoff_address && dropoff_address.user_id == current_user.id
41
+ render_error("Invalid dropoff_address_id", status: :unprocessable_entity)
42
+ return
43
+ end
44
+ end
45
+
46
+ if fulfillment_type == "delivery" && dropoff_address.nil?
47
+ render_error("dropoff_address_id required for delivery", status: :unprocessable_entity)
48
+ return
49
+ end
50
+
51
+ if @obj.accept!(fulfillment_type: fulfillment_type, dropoff_address: dropoff_address)
28
52
  render_success("quotations.success.accepted", data: @obj)
29
53
  else
30
54
  render_error("quotations.errors.accept_failed")
@@ -19,6 +19,7 @@ module Dscf
19
19
  validate :valid_until_after_delivery_date
20
20
  validate :delivery_date_not_in_past
21
21
  validate :valid_until_not_in_past
22
+ validate :total_price_required_for_operations
22
23
  scope :by_business, ->(business_id) { where(business_id: business_id) }
23
24
  scope :valid_quotations, -> { where("valid_until > ?", Time.current) }
24
25
  scope :expired_quotations, -> { where("valid_until <= ?", Time.current) }
@@ -69,7 +70,7 @@ module Dscf
69
70
  update_columns(status: "expired") if valid_until <= Time.current
70
71
  end
71
72
 
72
- def accept!
73
+ def accept!(options = {})
73
74
  return false unless sent?
74
75
  return false if within_validity_period? == false
75
76
 
@@ -80,17 +81,11 @@ module Dscf
80
81
  request_for_quotation.update!(status: "selected", selected_quotation: self)
81
82
 
82
83
  # Create order from accepted quotation
83
- create_order_from_quotation
84
+ create_order_from_quotation(options)
84
85
 
85
86
  true
86
87
  end
87
88
 
88
- def create_order_from_quotation
89
- return if order.present?
90
-
91
- Order.create_from_quotation(self)
92
- end
93
-
94
89
  def reject!
95
90
  return false unless sent?
96
91
 
@@ -122,16 +117,21 @@ module Dscf
122
117
  quotation_items.exists? && quotation_items.all? { |item| item.unit_price.present? }
123
118
  end
124
119
 
125
- def create_order_from_quotation
120
+ def create_order_from_quotation(options = {})
126
121
  return if order.present?
127
122
 
123
+ fulfillment_type = options[:fulfillment_type] || "self_pickup"
124
+ dropoff_address = options[:dropoff_address]
125
+
128
126
  order = Dscf::Marketplace::Order.create!(
129
127
  order_type: :rfq_based,
130
128
  status: :pending,
129
+ fulfillment_type: fulfillment_type,
131
130
  quotation: self,
132
131
  user: request_for_quotation.user, # Keep for backward compatibility
133
132
  ordered_by: request_for_quotation.user,
134
133
  ordered_to: business,
134
+ dropoff_address: dropoff_address,
135
135
  total_amount: total_price
136
136
  )
137
137
 
@@ -170,7 +170,9 @@ module Dscf
170
170
  private
171
171
 
172
172
  def calculate_total_price
173
- self.total_price = quotation_items.sum(&:subtotal).to_f
173
+ # Reload association to ensure we have the latest data
174
+ quotation_items.reload
175
+ self.total_price = quotation_items.sum { |item| item.subtotal || 0 }.to_f
174
176
  end
175
177
 
176
178
  def valid_until_after_delivery_date
@@ -196,6 +198,17 @@ module Dscf
196
198
  errors.add(:valid_until, "cannot be in the past")
197
199
  end
198
200
  end
201
+
202
+ def total_price_required_for_operations
203
+ return if total_price.present?
204
+ return if draft? # Allow null total_price for draft quotations
205
+ return if quotation_items.empty? # Allow null if no items
206
+
207
+ # Require total_price for sent, accepted, rejected, or expired quotations
208
+ if sent? || accepted? || rejected? || expired?
209
+ errors.add(:total_price, "must be calculated before quotation can be processed")
210
+ end
211
+ end
199
212
  end
200
213
  end
201
214
  end
@@ -15,6 +15,8 @@ module Dscf
15
15
  # validate :unit_compatible_with_product
16
16
  validate :subtotal_matches_calculation, if: :should_validate_subtotal?
17
17
  before_save :calculate_subtotal, if: :should_recalculate_subtotal?
18
+ after_save :update_quotation_total_price
19
+ after_destroy :update_quotation_total_price
18
20
 
19
21
  scope :priced, -> { where.not(unit_price: nil) }
20
22
  scope :unpriced, -> { where(unit_price: nil) }
@@ -86,6 +88,10 @@ module Dscf
86
88
  self.subtotal = (unit_price * quantity).round(2)
87
89
  end
88
90
 
91
+ def update_quotation_total_price
92
+ quotation.update_total_price! if quotation.present?
93
+ end
94
+
89
95
  # Calculate subtotal automatically if not set
90
96
  def calculated_subtotal
91
97
  return subtotal if subtotal.present?
@@ -22,6 +22,7 @@ module Dscf
22
22
 
23
23
  if quotation.save
24
24
  create_quotation_items(quotation, params[:quotation_items_attributes] || [])
25
+ quotation.update_total_price!
25
26
  rfq.update(status: :responded) if rfq.sent?
26
27
  {success: true, quotation: quotation}
27
28
  else
@@ -288,6 +288,7 @@ en:
288
288
  orders_not_found: "Some orders were not found"
289
289
  orders_conversion_failed: "Failed to convert orders to delivery"
290
290
  convert_orders_failed: "Unexpected error during order conversion"
291
+ access_denied: "Access denied"
291
292
 
292
293
  delivery_order_item:
293
294
  success:
data/config/routes.rb CHANGED
@@ -105,6 +105,7 @@ Dscf::Marketplace::Engine.routes.draw do
105
105
  post "accept"
106
106
  end
107
107
  collection do
108
+ get "my_driver_deliveries"
108
109
  get "filter"
109
110
  post "convert_orders"
110
111
  end
@@ -0,0 +1,11 @@
1
+ class BackfillQuotationTotalPrices < ActiveRecord::Migration[8.0]
2
+ def up
3
+ # Backfill total_price for existing quotations
4
+ Dscf::Marketplace::Quotation.find_each do |quotation|
5
+ quotation.update_total_price!
6
+ end
7
+ end
8
+
9
+ def down
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.3.94".freeze
3
+ VERSION = "0.3.96".freeze
4
4
  end
5
5
  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.3.94
4
+ version: 0.3.96
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-19 00:00:00.000000000 Z
10
+ date: 2025-09-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -503,6 +503,7 @@ files:
503
503
  - db/migrate/20250909092700_add_notes_to_dscf_marketplace_quotation_items.rb
504
504
  - db/migrate/20250916083536_make_driver_id_nullable_in_delivery_orders.rb
505
505
  - db/migrate/20250917110618_add_dropoff_address_to_dscf_marketplace_orders.rb
506
+ - db/migrate/20250920105329_backfill_quotation_total_prices.rb
506
507
  - lib/dscf/marketplace.rb
507
508
  - lib/dscf/marketplace/engine.rb
508
509
  - lib/dscf/marketplace/version.rb