erp_products 4.0.0 → 4.2.0
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/api/v1/product_types_controller.rb +140 -0
- data/app/controllers/erp_products/shared/product_features_controller.rb +46 -0
- data/app/models/extensions/biz_txn_acct_root.rb +5 -0
- data/app/models/product_feature.rb +117 -0
- data/app/models/product_feature_applicability.rb +17 -0
- data/app/models/product_feature_interaction.rb +21 -0
- data/app/models/product_feature_interaction_type.rb +17 -0
- data/app/models/product_feature_type.rb +70 -0
- data/app/models/product_feature_type_product_feature_value.rb +16 -0
- data/app/models/product_feature_value.rb +42 -0
- data/app/models/product_instance.rb +4 -0
- data/app/models/product_offer.rb +25 -3
- data/app/models/product_type.rb +185 -21
- data/app/models/product_type_pty_role.rb +2 -0
- data/app/models/simple_product_offer.rb +3 -1
- data/config/routes.rb +15 -4
- data/db/data_migrations/20141222195727_add_product_type_view_types.rb +24 -0
- data/db/migrate/20080805000040_base_products.rb +284 -136
- data/db/migrate/20150304211841_add_shipping_cost_to_product_types.rb +7 -0
- data/db/migrate/20150507075057_add_dimensions_to_product_types.rb +12 -0
- data/db/migrate/20150622150625_add_taxable_to_product_types.rb +5 -0
- data/db/migrate/20150711194216_update_product_feature_interactions.rb +21 -0
- data/db/migrate/20151216235328_add_biz_txn_acct_root_to_products.rb +15 -0
- data/db/migrate/20160310163055_add_created_by_updated_by_to_erp_products.rb +43 -0
- data/lib/erp_products/engine.rb +0 -4
- data/lib/erp_products/extensions/active_record/acts_as_product_offer.rb +7 -4
- data/lib/erp_products/version.rb +1 -1
- metadata +24 -14
- data/app/controllers/erp_products/shared/product_types_controller.rb +0 -96
- data/app/views/erp_products/shared/product_types/show_details.html.erb +0 -8
- data/db/migrate/20080805000041_base_products_indexes.rb +0 -61
- data/db/migrate/20130131204335_add_product_instances_nested_set_indexes.rb +0 -9
- data/db/migrate/20130131204336_add_type_column_to_product_types.rb +0 -9
- data/db/migrate/20140124185720_add_product_party_roles.rb +0 -17
- data/db/migrate/20140130211433_add_sku_comment_uom_to_product_type.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb72e4e09cd0dec844882476e0cae80ecfb365f3
|
4
|
+
data.tar.gz: 30c5dde2888fd427d1cf601f5a1bab66bf9bafdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7257bfbed6ee33d738a5f9618a2a626bca9fe598fcfb86db4c215a0f39a2381df16ff9fe6199f41b4bdacf17693f0d213e4e0e99735cee97bc73258d18f40bcd
|
7
|
+
data.tar.gz: f2d0e65653701d0580ca1bf63648ed9cc5a13beceeb4473275fd8bd22a62bb5e393c9d42dbc28e8a085ceda1a71f6a3b7cea4c83a2a5d092dd6b5533f5c67fc3
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Api
|
2
|
+
module V1
|
3
|
+
class ProductTypesController < BaseController
|
4
|
+
|
5
|
+
def index
|
6
|
+
sort = nil
|
7
|
+
dir = nil
|
8
|
+
limit = nil
|
9
|
+
start = nil
|
10
|
+
|
11
|
+
unless params[:sort].blank?
|
12
|
+
sort_hash = params[:sort].blank? ? {} : Hash.symbolize_keys(JSON.parse(params[:sort]).first)
|
13
|
+
sort = sort_hash[:property] || 'description'
|
14
|
+
dir = sort_hash[:direction] || 'ASC'
|
15
|
+
limit = params[:limit] || 25
|
16
|
+
start = params[:start] || 0
|
17
|
+
end
|
18
|
+
|
19
|
+
query_filter = params[:query_filter].blank? ? {} : JSON.parse(params[:query_filter]).symbolize_keys
|
20
|
+
|
21
|
+
# hook method to apply any scopes passed via parameters to this api
|
22
|
+
product_types = ProductType.apply_filters(query_filter)
|
23
|
+
|
24
|
+
# scope by dba_organizations if there are no parties passed as filters
|
25
|
+
unless query_filter[:parties]
|
26
|
+
dba_organizations = [current_user.party.dba_organization]
|
27
|
+
dba_organizations = dba_organizations.concat(current_user.party.dba_organization.child_dba_organizations)
|
28
|
+
product_types = product_types.scope_by_dba_organization(dba_organizations)
|
29
|
+
end
|
30
|
+
|
31
|
+
if sort and dir
|
32
|
+
product_types = product_types.order("#{sort} #{dir}")
|
33
|
+
end
|
34
|
+
|
35
|
+
total_count = product_types.count
|
36
|
+
|
37
|
+
if start and limit
|
38
|
+
product_types = product_types.offset(start).limit(limit)
|
39
|
+
end
|
40
|
+
|
41
|
+
render :json => {success: true,
|
42
|
+
total_count: total_count,
|
43
|
+
product_types: product_types.collect { |product_type| product_type.to_data_hash }}
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def show
|
48
|
+
product_type = ProductType.find(params[:id])
|
49
|
+
|
50
|
+
render :json => {success: true,
|
51
|
+
product_type: product_type.to_data_hash}
|
52
|
+
end
|
53
|
+
|
54
|
+
def create
|
55
|
+
begin
|
56
|
+
ActiveRecord::Base.transaction do
|
57
|
+
product_type = ProductType.new
|
58
|
+
product_type.description = params[:description]
|
59
|
+
product_type.sku = params[:sku]
|
60
|
+
product_type.unit_of_measurement_id = params[:unit_of_measurement]
|
61
|
+
product_type.comment = params[:comment]
|
62
|
+
|
63
|
+
product_type.created_by_party = current_user.party
|
64
|
+
|
65
|
+
product_type.save!
|
66
|
+
|
67
|
+
#
|
68
|
+
# For scoping by party, add party_id and role_type 'vendor' to product_party_roles table. However may want to override controller elsewhere
|
69
|
+
# so that default is no scoping in erp_products engine
|
70
|
+
#
|
71
|
+
party_role = params[:party_role]
|
72
|
+
party_id = params[:party_id]
|
73
|
+
unless party_role.blank? or party_id.blank?
|
74
|
+
product_type_party_role = ProductTypePtyRole.new
|
75
|
+
product_type_party_role.product_type = product_type
|
76
|
+
product_type_party_role.party_id = party_id
|
77
|
+
product_type_party_role.role_type = RoleType.iid(party_role)
|
78
|
+
product_type_party_role.save
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
render :json => {success: true,
|
83
|
+
product_type: product_type.to_data_hash}
|
84
|
+
|
85
|
+
rescue ActiveRecord::RecordInvalid => invalid
|
86
|
+
|
87
|
+
render :json => {success: false, message: invalid.record.errors.messages}
|
88
|
+
|
89
|
+
rescue => ex
|
90
|
+
Rails.logger.error ex.message
|
91
|
+
Rails.logger.error ex.backtrace.join("\n")
|
92
|
+
|
93
|
+
# email error
|
94
|
+
ExceptionNotifier.notify_exception(ex) if defined? ExceptionNotifier
|
95
|
+
|
96
|
+
render :json => {success: false, message: 'Could not create product type'}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def update
|
101
|
+
begin
|
102
|
+
ActiveRecord::Base.transaction do
|
103
|
+
product_type = ProductType.find(params[:id])
|
104
|
+
|
105
|
+
product_type.description = params[:description]
|
106
|
+
product_type.sku = params[:sku]
|
107
|
+
product_type.unit_of_measurement_id = params[:unit_of_measurement]
|
108
|
+
product_type.comment = params[:comment]
|
109
|
+
|
110
|
+
product_type.updated_by_party = current_user.party
|
111
|
+
|
112
|
+
product_type.save!
|
113
|
+
|
114
|
+
render :json => {success: true,
|
115
|
+
product_type: product_type.to_data_hash}
|
116
|
+
end
|
117
|
+
rescue ActiveRecord::RecordInvalid => invalid
|
118
|
+
|
119
|
+
render :json => {success: false, message: invalid.record.errors.messages}
|
120
|
+
|
121
|
+
rescue => ex
|
122
|
+
Rails.logger.error ex.message
|
123
|
+
Rails.logger.error ex.backtrace.join("\n")
|
124
|
+
|
125
|
+
# email error
|
126
|
+
ExceptionNotifier.notify_exception(ex) if defined? ExceptionNotifier
|
127
|
+
|
128
|
+
render :json => {success: false, message: 'Could not update product type'}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def destroy
|
133
|
+
ProductType.find(params[:id]).destroy
|
134
|
+
|
135
|
+
render :json => {:success => true}
|
136
|
+
end
|
137
|
+
|
138
|
+
end # ProductTypesController
|
139
|
+
end # V1
|
140
|
+
end # Api
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ErpProducts
|
2
|
+
module Shared
|
3
|
+
class ProductFeaturesController < ActionController::Base
|
4
|
+
|
5
|
+
def index
|
6
|
+
product_features = []
|
7
|
+
if params[:product_features]
|
8
|
+
params[:product_features].each do |product_feature|
|
9
|
+
found = ProductFeature.where(product_features: {product_feature_type_id: product_feature['type_id'].to_i, product_feature_value_id: product_feature['value_id'].to_i}).first
|
10
|
+
product_features << found if found
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
feature_type_arr = ProductFeature.get_feature_types(product_features)
|
15
|
+
render :json => {results: feature_type_arr}
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_values
|
19
|
+
if params[:product_feature_type_id].present?
|
20
|
+
product_feature_type = ProductFeatureType.find(params[:product_feature_type_id])
|
21
|
+
|
22
|
+
if params[:product_features].present?
|
23
|
+
product_features = []
|
24
|
+
product_feature_params = params[:product_features].to_a.flatten.delete_if { |o| !o.is_a? Hash }
|
25
|
+
product_feature_params.each do |product_feature_hash|
|
26
|
+
product_features << ProductFeature.where('product_feature_type_id = ? and product_feature_value_id = ?',
|
27
|
+
product_feature_hash['product_feature_type_id'],
|
28
|
+
product_feature_hash['product_feature_value_id']).first
|
29
|
+
end
|
30
|
+
|
31
|
+
value_ids = ProductFeature.get_values(product_feature_type, product_features)
|
32
|
+
else
|
33
|
+
value_ids = ProductFeature.get_values(product_feature_type)
|
34
|
+
end
|
35
|
+
|
36
|
+
else
|
37
|
+
value_ids = []
|
38
|
+
end
|
39
|
+
|
40
|
+
render :json => {results: value_ids.map { |id| ProductFeatureValue.find(id) }}
|
41
|
+
end
|
42
|
+
|
43
|
+
end # ErpProducts
|
44
|
+
end # Shared
|
45
|
+
end # ProductFeaturesController
|
46
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# create_table :product_features do |t|
|
2
|
+
# t.references :product_feature_type
|
3
|
+
# t.references :product_feature_value
|
4
|
+
#
|
5
|
+
# t.timestamps
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# add_index :product_features, :product_feature_type_id, :name => 'prod_feature_type_idx'
|
9
|
+
# add_index :product_features, :product_feature_value_id, :name => 'prod_feature_value_idx'
|
10
|
+
|
11
|
+
class ProductFeature < ActiveRecord::Base
|
12
|
+
attr_protected :created_at, :updated_at
|
13
|
+
|
14
|
+
tracks_created_by_updated_by
|
15
|
+
|
16
|
+
belongs_to :product_feature_type
|
17
|
+
belongs_to :product_feature_value
|
18
|
+
has_many :product_feature_applicabilities, dependent: :destroy
|
19
|
+
|
20
|
+
after_destroy :destroy_interactions
|
21
|
+
|
22
|
+
def self.find_or_create(product_feature_type, product_feature_value)
|
23
|
+
product_feature = ProductFeature.where(product_feature_type_id: product_feature_type.id,
|
24
|
+
product_feature_value_id: product_feature_value.id).first
|
25
|
+
|
26
|
+
unless product_feature
|
27
|
+
product_feature = ProductFeature.create(product_feature_type: product_feature_type,
|
28
|
+
product_feature_value: product_feature_value)
|
29
|
+
end
|
30
|
+
|
31
|
+
product_feature
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.get_feature_types(product_features)
|
35
|
+
array = []
|
36
|
+
already_filtered_product_features = if product_features
|
37
|
+
product_features.map { |pf| pf.product_feature_type }
|
38
|
+
else
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
ProductFeatureType.each_with_level(ProductFeatureType.root.self_and_descendants) do |o, level|
|
42
|
+
if !already_filtered_product_features.include?(o) && level != 0
|
43
|
+
array << {feature_type: o, parent_id: o.parent_id, level: level}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
block_given? ? yield(array) : array
|
48
|
+
end
|
49
|
+
|
50
|
+
def feature_of_records
|
51
|
+
product_feature_applicabilities.map { |o| o.feature_of_record_type.constantize.find(o.feature_of_record_id) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.get_values(feature_type, product_features=[])
|
55
|
+
feature_value_ids = feature_type.product_feature_values.order('description').pluck(:id)
|
56
|
+
valid_feature_value_ids = feature_value_ids.dup
|
57
|
+
|
58
|
+
# if there are a product features passed then it is being scoped by that product feature and
|
59
|
+
# we only what valid interactions
|
60
|
+
unless product_features.empty?
|
61
|
+
product_features.each do |product_feature|
|
62
|
+
|
63
|
+
# check each possible feature type / feature value combination for the given feature_type
|
64
|
+
feature_value_ids.each do |value_id|
|
65
|
+
|
66
|
+
# Is there a product feature to support this feature type / feature value combination?
|
67
|
+
test_product_feature = ProductFeature.where(product_features: {product_feature_type_id: feature_type.id, product_feature_value_id: value_id}).last
|
68
|
+
|
69
|
+
valid_feature_value_ids.delete_at(valid_feature_value_ids.index(value_id)) unless test_product_feature
|
70
|
+
next unless test_product_feature
|
71
|
+
|
72
|
+
unless test_product_feature.find_interactions(:invalid).empty?
|
73
|
+
if test_product_feature.find_interactions(:invalid).where('product_feature_to_id = ?', product_feature.id).count == 1
|
74
|
+
index = valid_feature_value_ids.index(value_id)
|
75
|
+
if index
|
76
|
+
valid_feature_value_ids.delete_at(index)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end # feature_value_ids loop
|
82
|
+
end # product_features loop
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
valid_feature_value_ids.uniq
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Product Feature Interactions
|
91
|
+
#
|
92
|
+
def interactions
|
93
|
+
ProductFeatureInteraction.where('product_feature_from_id = ? or product_feature_to_id = ?', self.id, self.id)
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_interactions
|
97
|
+
ProductFeatureInteraction.where('product_feature_to_id = ?', self.id)
|
98
|
+
end
|
99
|
+
|
100
|
+
def from_interactions
|
101
|
+
ProductFeatureInteraction.where('product_feature_from_id = ?', self.id)
|
102
|
+
end
|
103
|
+
|
104
|
+
# find product feature interactions by ProductFeatureInteractionsType
|
105
|
+
def find_interactions(type)
|
106
|
+
# look up type if iid is passed
|
107
|
+
type = ProductFeatureInteractionType.iid(type.to_s)
|
108
|
+
|
109
|
+
self.from_interactions.where('product_feature_interaction_type_id' => type.id)
|
110
|
+
end
|
111
|
+
|
112
|
+
# destroy all interactions this ProductFeature is part of
|
113
|
+
def destroy_interactions
|
114
|
+
self.interactions.destroy_all
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# create_table :product_feature_applicabilities do |t|
|
2
|
+
# t.references :feature_of_record, :polymorphic => true
|
3
|
+
# t.references :product_feature
|
4
|
+
#
|
5
|
+
# t.boolean :is_mandatory
|
6
|
+
#
|
7
|
+
# t.timestamps
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# add_index :product_feature_applicabilities, [:feature_of_record_type, :feature_of_record_id], :name => 'prod_feature_record_idx'
|
11
|
+
|
12
|
+
class ProductFeatureApplicability < ActiveRecord::Base
|
13
|
+
attr_protected :created_at, :updated_at
|
14
|
+
|
15
|
+
belongs_to :feature_of_record, polymorphic: true
|
16
|
+
belongs_to :product_feature
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# create_table :product_feature_interactions do |t|
|
2
|
+
# t.references :product_feature
|
3
|
+
# t.references :interacted_product_feature
|
4
|
+
# t.references :product_feature_interaction_type
|
5
|
+
#
|
6
|
+
# t.timestamps
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# add_index :product_feature_interactions, :product_feature_id, :name => 'prod_feature_int_feature_idx'
|
10
|
+
# add_index :product_feature_interactions, :interacted_product_feature_id, :name => 'prod_feature_int_interacted_feature_idx'
|
11
|
+
# add_index :product_feature_interactions, :product_feature_interaction_type_id, :name => 'prod_feature_int_interacted_feature_type_idx'
|
12
|
+
|
13
|
+
class ProductFeatureInteraction < ActiveRecord::Base
|
14
|
+
attr_protected :created_at, :updated_at
|
15
|
+
|
16
|
+
belongs_to :product_feature_from, class_name: "ProductFeature"
|
17
|
+
belongs_to :product_feature_to, class_name: "ProductFeature"
|
18
|
+
belongs_to :product_feature_interaction_type
|
19
|
+
|
20
|
+
is_json :custom_fields
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# create_table :product_feature_interaction_types do |t|
|
2
|
+
# t.string :internal_identifier
|
3
|
+
# t.string :description
|
4
|
+
#
|
5
|
+
# t.timestamps
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# add_index :product_feature_interaction_types, :internal_identifier, name: 'product_ft_int_types_iid_idx'
|
9
|
+
|
10
|
+
class ProductFeatureInteractionType < ActiveRecord::Base
|
11
|
+
attr_protected :created_at, :updated_at
|
12
|
+
|
13
|
+
acts_as_erp_type
|
14
|
+
|
15
|
+
has_many :product_feature_interactions, dependent: :destroy
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# create_table :product_feature_types do |t|
|
2
|
+
#
|
3
|
+
# t.integer :parent_id
|
4
|
+
# t.integer :lft
|
5
|
+
# t.integer :rgt
|
6
|
+
#
|
7
|
+
# t.string :description
|
8
|
+
# t.string :internal_identifier
|
9
|
+
# t.string :external_identifier
|
10
|
+
# t.string :external_id
|
11
|
+
#
|
12
|
+
# t.timestamps
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# add_index :product_feature_types, :internal_identifier, name: 'product_ft_types_iid_idx'
|
16
|
+
# add_index :product_feature_types, [:rgt, :lft, :parent_id], name: 'product_ft_types_nested_set_idx'
|
17
|
+
|
18
|
+
class ProductFeatureType < ActiveRecord::Base
|
19
|
+
attr_protected :created_at, :updated_at
|
20
|
+
|
21
|
+
tracks_created_by_updated_by
|
22
|
+
|
23
|
+
has_many :product_feature_type_product_feature_values, dependent: :destroy
|
24
|
+
has_many :product_feature_values, through: :product_feature_type_product_feature_values
|
25
|
+
|
26
|
+
has_many :product_features, dependent: :destroy
|
27
|
+
|
28
|
+
acts_as_nested_set
|
29
|
+
include ErpTechSvcs::Utils::DefaultNestedSetMethods # acts_as_nested_set
|
30
|
+
|
31
|
+
def self.iid(description)
|
32
|
+
self.where("lower(description) = ?", description.downcase).first
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_record_representation(root = ProductFeatureType.root)
|
36
|
+
# returns a string of category descriptions like
|
37
|
+
# 'main_category > sub_category n > ... > this category instance'
|
38
|
+
if root?
|
39
|
+
description
|
40
|
+
else
|
41
|
+
crawl_up_from(self, root).split('///').reverse.join(' > ')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_representation
|
46
|
+
# returns a string that consists of 1) a number of dashes equal to
|
47
|
+
# the category's level and 2) the category's description attr
|
48
|
+
rep = ''
|
49
|
+
level.times {rep << '- '}
|
50
|
+
rep << description
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.to_all_representation(root = ProductFeatureType.root)
|
54
|
+
# returns an array of hashes which represent all categories in nested set order,
|
55
|
+
# each of which consists of the category's id and representation
|
56
|
+
container_arr = []
|
57
|
+
each_with_level(root.self_and_descendants) do |o, level|
|
58
|
+
container_arr << {:id => o.id, :description => o.to_representation}
|
59
|
+
end
|
60
|
+
container_arr
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def crawl_up_from(product_feature_type, to_product_feature_type = ProductFeatureType.root)
|
66
|
+
# returns a string that is a '///'-separated list of categories
|
67
|
+
# from child category to root
|
68
|
+
"#{product_feature_type.description}///#{crawl_up_from(product_feature_type.parent, to_product_feature_type) if product_feature_type != to_product_feature_type}"
|
69
|
+
end
|
70
|
+
end
|