dscf-marketplace 0.2.92 → 0.2.94
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 +3 -4
- data/app/controllers/dscf/marketplace/quotations_controller.rb +0 -1
- data/app/controllers/dscf/marketplace/request_for_quotations_controller.rb +12 -1
- data/app/models/dscf/marketplace/order.rb +1 -1
- data/app/models/dscf/marketplace/order_item.rb +1 -1
- data/app/models/dscf/marketplace/quotation.rb +18 -13
- data/app/models/dscf/marketplace/quotation_item.rb +2 -2
- data/app/models/dscf/marketplace/request_for_quotation.rb +1 -1
- data/app/models/dscf/marketplace/rfq_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 -3
- data/app/controllers/concerns/dscf/marketplace/nested_creatable.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3d9e2b9c6d302c05aba856c7add2d572e6c48c763b29022c165c32fb0ae6d39
|
4
|
+
data.tar.gz: c1a4ed98de21d1ae80ce116f9db768e24e43baa4785896c9bcbb62bfcfb604e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f88c9ecdd32d85066805cb205d3eb7e28df0f540d27ab6c9ecdfada565b3b068f3bb9ded7bdaae8c3d7f73581c4684633579da25b606c8460cf068c0075a26b3
|
7
|
+
data.tar.gz: ae10f6ddcdb722a28ecf61d00152984cbf20523bbf23fc33c385ea5378e1e22935304c636ff18e86e7f6fec2f33704def9a92087b43b33a71c809d5728660a57
|
@@ -2,7 +2,6 @@ module Dscf
|
|
2
2
|
module Marketplace
|
3
3
|
class OrdersController < ApplicationController
|
4
4
|
include Dscf::Core::Common
|
5
|
-
include Dscf::Marketplace::NestedCreatable
|
6
5
|
|
7
6
|
def confirm
|
8
7
|
@obj = find_record
|
@@ -61,10 +60,10 @@ module Dscf
|
|
61
60
|
|
62
61
|
def default_serializer_includes
|
63
62
|
{
|
64
|
-
index: [ :user, :ordered_by, :ordered_to, :quotation, order_items: [:product] ],
|
63
|
+
index: [ :user, :ordered_by, :ordered_to, :quotation, order_items: [ :product ] ],
|
65
64
|
show: [ :user, :ordered_by, :ordered_to, :quotation, :listing, :delivery_order, :order_items ],
|
66
|
-
create: [ :user, :ordered_by, :ordered_to, :quotation, :listing ],
|
67
|
-
update: [ :user, :ordered_by, :ordered_to, :quotation, :listing ]
|
65
|
+
create: [ :user, :ordered_by, :ordered_to, :quotation, :listing, :order_items ],
|
66
|
+
update: [ :user, :ordered_by, :ordered_to, :quotation, :listing, :order_items ]
|
68
67
|
}
|
69
68
|
end
|
70
69
|
end
|
@@ -2,7 +2,6 @@ module Dscf
|
|
2
2
|
module Marketplace
|
3
3
|
class RequestForQuotationsController < ApplicationController
|
4
4
|
include Dscf::Core::Common
|
5
|
-
include Dscf::Marketplace::NestedCreatable
|
6
5
|
|
7
6
|
def send_rfq
|
8
7
|
@obj = find_record
|
@@ -34,6 +33,18 @@ module Dscf
|
|
34
33
|
render_success("request_for_quotations.success.index", data: rfqs, serializer_options: options)
|
35
34
|
end
|
36
35
|
|
36
|
+
def respond
|
37
|
+
@rfq = Dscf::Marketplace::RequestForQuotation.find(params[:id])
|
38
|
+
service = RfqResponseService.new(current_user)
|
39
|
+
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 ]))
|
40
|
+
|
41
|
+
if result[:success]
|
42
|
+
render_success("request_for_quotations.success.responded", data: result[:quotation], serializer_options: {include: [ :business, quotation_items: [ :product, :unit, :rfq_item ] ]})
|
43
|
+
else
|
44
|
+
render_error("request_for_quotations.errors.respond_failed", errors: result[:errors])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
37
48
|
private
|
38
49
|
|
39
50
|
def model_params
|
@@ -10,7 +10,7 @@ module Dscf::Marketplace
|
|
10
10
|
belongs_to :ordered_by, class_name: "Dscf::Core::User"
|
11
11
|
belongs_to :ordered_to, class_name: "Dscf::Core::Business"
|
12
12
|
belongs_to :delivery_order, optional: true
|
13
|
-
has_many :order_items, dependent: :destroy
|
13
|
+
has_many :order_items, dependent: :destroy, inverse_of: :order
|
14
14
|
accepts_nested_attributes_for :order_items, allow_destroy: true
|
15
15
|
|
16
16
|
validates :order_type, presence: true
|
@@ -4,7 +4,7 @@ module Dscf::Marketplace
|
|
4
4
|
|
5
5
|
delegate :name, :description, :thumbnail_url, :images_urls, to: :product, allow_nil: true
|
6
6
|
|
7
|
-
belongs_to :order, optional: true
|
7
|
+
belongs_to :order, optional: true, inverse_of: :order_items
|
8
8
|
belongs_to :quotation_item, optional: true
|
9
9
|
belongs_to :listing, optional: true
|
10
10
|
belongs_to :product
|
@@ -4,7 +4,7 @@ module Dscf
|
|
4
4
|
belongs_to :request_for_quotation, class_name: "Dscf::Marketplace::RequestForQuotation"
|
5
5
|
belongs_to :business, class_name: "Dscf::Core::Business", optional: true if defined?(Dscf::Core)
|
6
6
|
|
7
|
-
has_many :quotation_items, class_name: "Dscf::Marketplace::QuotationItem", dependent: :destroy
|
7
|
+
has_many :quotation_items, class_name: "Dscf::Marketplace::QuotationItem", dependent: :destroy, inverse_of: :quotation
|
8
8
|
has_one :order, class_name: "Dscf::Marketplace::Order", dependent: :destroy
|
9
9
|
accepts_nested_attributes_for :quotation_items, allow_destroy: true
|
10
10
|
|
@@ -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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Dscf
|
2
2
|
module Marketplace
|
3
3
|
class QuotationItem < ApplicationRecord
|
4
|
-
belongs_to :quotation, class_name: "Dscf::Marketplace::Quotation"
|
4
|
+
belongs_to :quotation, class_name: "Dscf::Marketplace::Quotation", inverse_of: :quotation_items
|
5
5
|
belongs_to :rfq_item, class_name: "Dscf::Marketplace::RfqItem"
|
6
6
|
belongs_to :product, class_name: "Dscf::Marketplace::Product"
|
7
7
|
belongs_to :unit, class_name: "Dscf::Marketplace::Unit"
|
@@ -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)
|
@@ -4,7 +4,7 @@ module Dscf
|
|
4
4
|
belongs_to :user, class_name: "Dscf::Core::User", optional: true if defined?(Dscf::Core)
|
5
5
|
belongs_to :selected_quotation, class_name: "Dscf::Marketplace::Quotation", optional: true
|
6
6
|
|
7
|
-
has_many :rfq_items, class_name: "Dscf::Marketplace::RfqItem", dependent: :destroy
|
7
|
+
has_many :rfq_items, class_name: "Dscf::Marketplace::RfqItem", dependent: :destroy, inverse_of: :request_for_quotation
|
8
8
|
has_many :quotations, class_name: "Dscf::Marketplace::Quotation", dependent: :destroy
|
9
9
|
accepts_nested_attributes_for :rfq_items, allow_destroy: true
|
10
10
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Dscf
|
2
2
|
module Marketplace
|
3
3
|
class RfqItem < ApplicationRecord
|
4
|
-
belongs_to :request_for_quotation, class_name: "Dscf::Marketplace::RequestForQuotation"
|
4
|
+
belongs_to :request_for_quotation, class_name: "Dscf::Marketplace::RequestForQuotation", inverse_of: :rfq_items
|
5
5
|
belongs_to :product, class_name: "Dscf::Marketplace::Product"
|
6
6
|
belongs_to :unit, class_name: "Dscf::Marketplace::Unit"
|
7
7
|
|
@@ -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.94
|
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-12 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -416,7 +416,6 @@ extra_rdoc_files: []
|
|
416
416
|
files:
|
417
417
|
- MIT-LICENSE
|
418
418
|
- Rakefile
|
419
|
-
- app/controllers/concerns/dscf/marketplace/nested_creatable.rb
|
420
419
|
- app/controllers/dscf/marketplace/application_controller.rb
|
421
420
|
- app/controllers/dscf/marketplace/businesses_controller.rb
|
422
421
|
- app/controllers/dscf/marketplace/categories_controller.rb
|
@@ -470,6 +469,7 @@ files:
|
|
470
469
|
- app/serializers/dscf/marketplace/unit_conversion_serializer.rb
|
471
470
|
- app/serializers/dscf/marketplace/unit_serializer.rb
|
472
471
|
- app/services/dscf/marketplace/my_resource_service.rb
|
472
|
+
- app/services/dscf/marketplace/rfq_response_service.rb
|
473
473
|
- config/environments/production.rb
|
474
474
|
- config/locales/en.yml
|
475
475
|
- config/routes.rb
|
@@ -497,6 +497,7 @@ files:
|
|
497
497
|
- db/migrate/20250901080134_add_fulfillment_type_to_orders.rb
|
498
498
|
- db/migrate/20250903061154_add_ordered_fields_to_orders.rb
|
499
499
|
- db/migrate/20250903061306_populate_ordered_fields_for_existing_orders.rb
|
500
|
+
- db/migrate/20250909092700_add_notes_to_dscf_marketplace_quotation_items.rb
|
500
501
|
- lib/dscf/marketplace.rb
|
501
502
|
- lib/dscf/marketplace/engine.rb
|
502
503
|
- lib/dscf/marketplace/version.rb
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module Dscf::Marketplace::NestedCreatable
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
included do
|
5
|
-
# Override the create method from Dscf::Core::Common
|
6
|
-
def create
|
7
|
-
obj = @clazz.new(model_params)
|
8
|
-
assign_current_user(obj) if respond_to?(:assign_current_user)
|
9
|
-
|
10
|
-
if obj.save
|
11
|
-
obj = @clazz.includes(eager_loaded_associations).find(obj.id) if eager_loaded_associations.present?
|
12
|
-
|
13
|
-
includes = serializer_includes_for_action(:create)
|
14
|
-
options = {include: includes} if includes.present?
|
15
|
-
|
16
|
-
# Get success message from locales
|
17
|
-
model_name = @clazz.name.demodulize.underscore
|
18
|
-
message_key = "#{model_name}.success.create"
|
19
|
-
message = I18n.t(message_key, default: "#{model_name.titleize} created successfully")
|
20
|
-
|
21
|
-
# Include associated items in response for orders
|
22
|
-
if model_name == "order" && obj.respond_to?(:order_items)
|
23
|
-
render_success(message, data: obj, order_items: obj.order_items, serializer_options: options, status: :created)
|
24
|
-
else
|
25
|
-
render_success(message, data: obj, serializer_options: options, status: :created)
|
26
|
-
end
|
27
|
-
else
|
28
|
-
Rails.logger.error("#{model_name} save failed: #{obj.errors.full_messages}")
|
29
|
-
|
30
|
-
# Get error message from locales
|
31
|
-
error_key = "#{model_name}.errors.create"
|
32
|
-
error_message = I18n.t(error_key, default: "Failed to create #{model_name}")
|
33
|
-
|
34
|
-
render_error(error_message, status: :unprocessable_entity)
|
35
|
-
end
|
36
|
-
rescue => e
|
37
|
-
render_error(e.message)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def assign_current_user(obj)
|
44
|
-
obj.user = current_user if obj.respond_to?(:user=)
|
45
|
-
obj.ordered_by = current_user if obj.respond_to?(:ordered_by=)
|
46
|
-
end
|
47
|
-
end
|