erp_products 4.0.0 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|