dscf-marketplace 0.2.92 → 0.2.93
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/orders_controller.rb +1 -1
- data/app/controllers/dscf/marketplace/request_for_quotations_controller.rb +12 -0
- data/app/models/dscf/marketplace/quotation.rb +17 -12
- data/app/models/dscf/marketplace/quotation_item.rb +1 -1
- data/app/services/dscf/marketplace/rfq_response_service.rb +55 -0
- data/config/locales/en.yml +59 -3
- data/config/routes.rb +1 -0
- data/db/migrate/20250909092700_add_notes_to_dscf_marketplace_quotation_items.rb +5 -0
- data/lib/dscf/marketplace/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40d81c92e7033ba2d583bca456a9fa0114795f713c118e92c1b70d2141c1c3ac
|
4
|
+
data.tar.gz: e8f5cca804a50156aff124176f482980628a069a3961c295d0480c395607fbc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75ba3df20836f6e89c20b2a35a01822f132f13fec48c2604807e96ad5b75f675bc6cbf0f72d65acbf40492e4d03d80d8e28532ac1941d6cde80779f4611e208b
|
7
|
+
data.tar.gz: 1e3620e360ec311517da1ff4955960a301523b0375801b236b9c3f518dc70ddf97daa7bd576068f9955eed2658f72619ffad90cbca02a13dcccf5e62ffa85fbd
|
@@ -61,7 +61,7 @@ module Dscf
|
|
61
61
|
|
62
62
|
def default_serializer_includes
|
63
63
|
{
|
64
|
-
index: [ :user, :ordered_by, :ordered_to, :quotation, order_items: [:product] ],
|
64
|
+
index: [ :user, :ordered_by, :ordered_to, :quotation, order_items: [ :product ] ],
|
65
65
|
show: [ :user, :ordered_by, :ordered_to, :quotation, :listing, :delivery_order, :order_items ],
|
66
66
|
create: [ :user, :ordered_by, :ordered_to, :quotation, :listing ],
|
67
67
|
update: [ :user, :ordered_by, :ordered_to, :quotation, :listing ]
|
@@ -34,6 +34,18 @@ module Dscf
|
|
34
34
|
render_success("request_for_quotations.success.index", data: rfqs, serializer_options: options)
|
35
35
|
end
|
36
36
|
|
37
|
+
def respond
|
38
|
+
@rfq = Dscf::Marketplace::RequestForQuotation.find(params[:id])
|
39
|
+
service = RfqResponseService.new(current_user)
|
40
|
+
result = service.respond_to_rfq(@rfq, params.permit(:notes, :valid_until, :delivery_date, quotation_items_attributes: [ :rfq_item_id, :unit_id, :quantity, :unit_price, :notes ]))
|
41
|
+
|
42
|
+
if result[:success]
|
43
|
+
render_success("request_for_quotations.success.responded", data: result[:quotation], serializer_options: {include: [ :business, quotation_items: [ :product, :unit, :rfq_item ] ]})
|
44
|
+
else
|
45
|
+
render_error("request_for_quotations.errors.respond_failed", errors: result[:errors])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
37
49
|
private
|
38
50
|
|
39
51
|
def model_params
|
@@ -61,18 +61,6 @@ module Dscf
|
|
61
61
|
status == "draft"
|
62
62
|
end
|
63
63
|
|
64
|
-
def sent?
|
65
|
-
status == "sent"
|
66
|
-
end
|
67
|
-
|
68
|
-
def accepted?
|
69
|
-
status == "accepted"
|
70
|
-
end
|
71
|
-
|
72
|
-
def rejected?
|
73
|
-
status == "rejected"
|
74
|
-
end
|
75
|
-
|
76
64
|
def within_validity_period?
|
77
65
|
valid_until > Time.current && !expired?
|
78
66
|
end
|
@@ -162,6 +150,23 @@ module Dscf
|
|
162
150
|
order
|
163
151
|
end
|
164
152
|
|
153
|
+
def build_from_rfq_items(items_attributes)
|
154
|
+
items_attributes.each do |item_attrs|
|
155
|
+
rfq_item = request_for_quotation.rfq_items.find_by(id: item_attrs[:rfq_item_id])
|
156
|
+
next unless rfq_item
|
157
|
+
|
158
|
+
quotation_items.build(
|
159
|
+
rfq_item: rfq_item,
|
160
|
+
product: rfq_item.product,
|
161
|
+
unit: item_attrs[:unit_id] ? Dscf::Marketplace::Unit.find(item_attrs[:unit_id]) : rfq_item.unit,
|
162
|
+
quantity: [ item_attrs[:quantity].to_i, rfq_item.quantity ].min,
|
163
|
+
unit_price: item_attrs[:unit_price].to_f,
|
164
|
+
notes: item_attrs[:notes] || ""
|
165
|
+
)
|
166
|
+
end
|
167
|
+
valid? # To trigger validations
|
168
|
+
end
|
169
|
+
|
165
170
|
private
|
166
171
|
|
167
172
|
def calculate_total_price
|
@@ -25,7 +25,7 @@ module Dscf
|
|
25
25
|
|
26
26
|
# Ransack configuration for secure filtering
|
27
27
|
def self.ransackable_attributes(_auth_object = nil)
|
28
|
-
%w[id quotation_id rfq_item_id product_id unit_id quantity unit_price subtotal created_at updated_at]
|
28
|
+
%w[id quotation_id rfq_item_id product_id unit_id quantity unit_price subtotal notes created_at updated_at]
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.ransackable_associations(_auth_object = nil)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dscf
|
2
|
+
module Marketplace
|
3
|
+
class RfqResponseService
|
4
|
+
def initialize(current_user)
|
5
|
+
@current_user = current_user
|
6
|
+
end
|
7
|
+
|
8
|
+
def respond_to_rfq(rfq, params = {})
|
9
|
+
return {success: false, errors: [ "RFQ not found or invalid" ]} unless rfq.present? && rfq.sent?
|
10
|
+
|
11
|
+
business = Dscf::Core::Business.find_by(user: @current_user)
|
12
|
+
return {success: false, errors: [ "No business associated with user" ]} unless business.present?
|
13
|
+
|
14
|
+
quotation = Dscf::Marketplace::Quotation.new(
|
15
|
+
request_for_quotation: rfq,
|
16
|
+
business: business,
|
17
|
+
status: :draft,
|
18
|
+
notes: params[:notes] || "",
|
19
|
+
valid_until: params[:valid_until] || 7.days.from_now,
|
20
|
+
delivery_date: params[:delivery_date] || (params[:valid_until] || 7.days.from_now).to_date
|
21
|
+
)
|
22
|
+
|
23
|
+
if quotation.save
|
24
|
+
create_quotation_items(quotation, params[:quotation_items_attributes] || [])
|
25
|
+
rfq.update(status: :responded) if rfq.sent?
|
26
|
+
{success: true, quotation: quotation}
|
27
|
+
else
|
28
|
+
{success: false, errors: quotation.errors.full_messages}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_quotation_items(quotation, items_attributes)
|
35
|
+
items_attributes.each do |item_attrs|
|
36
|
+
rfq_item = quotation.request_for_quotation.rfq_items.find_by(id: item_attrs[:rfq_item_id])
|
37
|
+
next unless rfq_item
|
38
|
+
|
39
|
+
unit_id = item_attrs[:unit_id] || rfq_item.unit_id
|
40
|
+
unit = Dscf::Marketplace::Unit.find_by(id: unit_id)
|
41
|
+
|
42
|
+
quotation.quotation_items.build(
|
43
|
+
rfq_item: rfq_item,
|
44
|
+
product: rfq_item.product,
|
45
|
+
unit: unit,
|
46
|
+
quantity: item_attrs[:quantity] || rfq_item.quantity,
|
47
|
+
unit_price: item_attrs[:unit_price],
|
48
|
+
notes: item_attrs[:notes] || ""
|
49
|
+
)
|
50
|
+
end
|
51
|
+
quotation.save
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/config/locales/en.yml
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
# DSCF Marketplace Locale File
|
2
|
-
#
|
3
|
-
#
|
1
|
+
# Updated DSCF Marketplace Locale File
|
2
|
+
# Added missing keys for request_for_quotations (plural) to match controller usage
|
3
|
+
# Ensures full error messages are displayed instead of "Translation missing"
|
4
|
+
# Pattern: {model_name}.{success|errors}.{action_name} - using plural for controller consistency
|
4
5
|
|
5
6
|
en:
|
6
7
|
# Core Models
|
@@ -106,6 +107,28 @@ en:
|
|
106
107
|
update: "Failed to update listing"
|
107
108
|
destroy: "Failed to delete listing"
|
108
109
|
|
110
|
+
# RFQ Models (Added plural keys to match controller)
|
111
|
+
request_for_quotations:
|
112
|
+
success:
|
113
|
+
index: "RFQs retrieved successfully"
|
114
|
+
show: "RFQ details retrieved successfully"
|
115
|
+
create: "RFQ created successfully"
|
116
|
+
update: "RFQ updated successfully"
|
117
|
+
destroy: "RFQ deleted successfully"
|
118
|
+
sent: "RFQ sent successfully"
|
119
|
+
closed: "RFQ closed successfully"
|
120
|
+
responded: "RFQ response sent successfully"
|
121
|
+
errors:
|
122
|
+
index: "Failed to retrieve RFQs"
|
123
|
+
show: "Failed to retrieve RFQ details"
|
124
|
+
create_failed: "Failed to create RFQ" # Key fix: Matches render_error("request_for_quotations.errors.create_failed")
|
125
|
+
update: "Failed to update RFQ"
|
126
|
+
destroy: "Failed to delete RFQ"
|
127
|
+
send_failed: "Failed to send RFQ"
|
128
|
+
close_failed: "Failed to close RFQ"
|
129
|
+
respond_failed: "Failed to respond to RFQ"
|
130
|
+
|
131
|
+
# Singular fallback for consistency
|
109
132
|
request_for_quotation:
|
110
133
|
success:
|
111
134
|
index: "RFQs retrieved successfully"
|
@@ -115,6 +138,7 @@ en:
|
|
115
138
|
destroy: "RFQ deleted successfully"
|
116
139
|
sent: "RFQ sent successfully"
|
117
140
|
closed: "RFQ closed successfully"
|
141
|
+
responded: "RFQ response sent successfully"
|
118
142
|
errors:
|
119
143
|
index: "Failed to retrieve RFQs"
|
120
144
|
show: "Failed to retrieve RFQ details"
|
@@ -123,6 +147,7 @@ en:
|
|
123
147
|
destroy: "Failed to delete RFQ"
|
124
148
|
sent: "Failed to send RFQ"
|
125
149
|
closed: "Failed to close RFQ"
|
150
|
+
responded: "Failed to respond to RFQ"
|
126
151
|
|
127
152
|
rfq_item:
|
128
153
|
success:
|
@@ -351,3 +376,34 @@ en:
|
|
351
376
|
operation_failed: "Operation failed"
|
352
377
|
record_not_found: "Record not found"
|
353
378
|
supplier_not_found: "Supplier not found"
|
379
|
+
# Added for common validation errors to show full messages
|
380
|
+
activerecord:
|
381
|
+
errors:
|
382
|
+
messages:
|
383
|
+
record_invalid: "Validation failed: %{errors}"
|
384
|
+
inclusion: "is not included in the list"
|
385
|
+
confirmation: "doesn't match %{attribute}"
|
386
|
+
accepted: "must be accepted"
|
387
|
+
empty: "can't be empty"
|
388
|
+
blank: "can't be blank"
|
389
|
+
too_short: "is too short (minimum is %{count} characters)"
|
390
|
+
too_long: "is too long (maximum is %{count} characters)"
|
391
|
+
invalid: "is invalid"
|
392
|
+
not_a_number: "is not a number"
|
393
|
+
not_an_integer: "is not an integer"
|
394
|
+
greater_than: "must be greater than %{count}"
|
395
|
+
greater_than_or_equal_to: "must be greater than or equal to %{count}"
|
396
|
+
equal_to: "must be equal to %{count}"
|
397
|
+
less_than: "must be less than %{count}"
|
398
|
+
less_than_or_equal_to: "must be less than or equal to %{count}"
|
399
|
+
other_than: "must be other than %{count}"
|
400
|
+
excluded: "should not be %{object}"
|
401
|
+
taken: "has already been taken"
|
402
|
+
unique: "has already been taken"
|
403
|
+
uniqueness: "has already been taken"
|
404
|
+
present: "must be present"
|
405
|
+
absence: "must be blank"
|
406
|
+
url: "is not a valid URL"
|
407
|
+
email: "is not a valid email"
|
408
|
+
format: "is invalid"
|
409
|
+
required: "can't be blank"
|
data/config/routes.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.93
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Asrat
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-09-
|
10
|
+
date: 2025-09-11 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -470,6 +470,7 @@ files:
|
|
470
470
|
- app/serializers/dscf/marketplace/unit_conversion_serializer.rb
|
471
471
|
- app/serializers/dscf/marketplace/unit_serializer.rb
|
472
472
|
- app/services/dscf/marketplace/my_resource_service.rb
|
473
|
+
- app/services/dscf/marketplace/rfq_response_service.rb
|
473
474
|
- config/environments/production.rb
|
474
475
|
- config/locales/en.yml
|
475
476
|
- config/routes.rb
|
@@ -497,6 +498,7 @@ files:
|
|
497
498
|
- db/migrate/20250901080134_add_fulfillment_type_to_orders.rb
|
498
499
|
- db/migrate/20250903061154_add_ordered_fields_to_orders.rb
|
499
500
|
- db/migrate/20250903061306_populate_ordered_fields_for_existing_orders.rb
|
501
|
+
- db/migrate/20250909092700_add_notes_to_dscf_marketplace_quotation_items.rb
|
500
502
|
- lib/dscf/marketplace.rb
|
501
503
|
- lib/dscf/marketplace/engine.rb
|
502
504
|
- lib/dscf/marketplace/version.rb
|