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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v1/product_types_controller.rb +140 -0
  3. data/app/controllers/erp_products/shared/product_features_controller.rb +46 -0
  4. data/app/models/extensions/biz_txn_acct_root.rb +5 -0
  5. data/app/models/product_feature.rb +117 -0
  6. data/app/models/product_feature_applicability.rb +17 -0
  7. data/app/models/product_feature_interaction.rb +21 -0
  8. data/app/models/product_feature_interaction_type.rb +17 -0
  9. data/app/models/product_feature_type.rb +70 -0
  10. data/app/models/product_feature_type_product_feature_value.rb +16 -0
  11. data/app/models/product_feature_value.rb +42 -0
  12. data/app/models/product_instance.rb +4 -0
  13. data/app/models/product_offer.rb +25 -3
  14. data/app/models/product_type.rb +185 -21
  15. data/app/models/product_type_pty_role.rb +2 -0
  16. data/app/models/simple_product_offer.rb +3 -1
  17. data/config/routes.rb +15 -4
  18. data/db/data_migrations/20141222195727_add_product_type_view_types.rb +24 -0
  19. data/db/migrate/20080805000040_base_products.rb +284 -136
  20. data/db/migrate/20150304211841_add_shipping_cost_to_product_types.rb +7 -0
  21. data/db/migrate/20150507075057_add_dimensions_to_product_types.rb +12 -0
  22. data/db/migrate/20150622150625_add_taxable_to_product_types.rb +5 -0
  23. data/db/migrate/20150711194216_update_product_feature_interactions.rb +21 -0
  24. data/db/migrate/20151216235328_add_biz_txn_acct_root_to_products.rb +15 -0
  25. data/db/migrate/20160310163055_add_created_by_updated_by_to_erp_products.rb +43 -0
  26. data/lib/erp_products/engine.rb +0 -4
  27. data/lib/erp_products/extensions/active_record/acts_as_product_offer.rb +7 -4
  28. data/lib/erp_products/version.rb +1 -1
  29. metadata +24 -14
  30. data/app/controllers/erp_products/shared/product_types_controller.rb +0 -96
  31. data/app/views/erp_products/shared/product_types/show_details.html.erb +0 -8
  32. data/db/migrate/20080805000041_base_products_indexes.rb +0 -61
  33. data/db/migrate/20130131204335_add_product_instances_nested_set_indexes.rb +0 -9
  34. data/db/migrate/20130131204336_add_type_column_to_product_types.rb +0 -9
  35. data/db/migrate/20140124185720_add_product_party_roles.rb +0 -17
  36. 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: cd9373780450ef58a539f607556ee88ff417272e
4
- data.tar.gz: de281f11a15e17f9b5cc0914d31b104f67b8f5c9
3
+ metadata.gz: eb72e4e09cd0dec844882476e0cae80ecfb365f3
4
+ data.tar.gz: 30c5dde2888fd427d1cf601f5a1bab66bf9bafdc
5
5
  SHA512:
6
- metadata.gz: f86542e4a62072f0ac42a9def7addcaf76b592c5e8c2192e64671917d052c9bd54a6e90cf4cc71386a4d3c2efe25affa4d702c7204db8e73edda27f8752ea682
7
- data.tar.gz: 40763f3e853e92126be1fa46ecefe6b8fe5024c9452d1679d86c0d9f9eb293b3575245d9c29cfe7f7e87374b678e4bffe19cf5891ecbae55acfdf95f5a46fbd7
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,5 @@
1
+ BizTxnAcctRoot.class_eval do
2
+
3
+ has_many :product_types
4
+
5
+ end
@@ -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