erp_products 3.0.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.
- data/GPL-3-LICENSE +674 -0
- data/README.rdoc +2 -0
- data/Rakefile +30 -0
- data/app/assets/javascripts/erp_products/application.js +9 -0
- data/app/assets/stylesheets/erp_products/application.css +7 -0
- data/app/controllers/erp_products/erp_app/desktop/product_manager/base_controller.rb +233 -0
- data/app/helpers/erp_products/application_helper.rb +4 -0
- data/app/models/prod_availability_status_type.rb +5 -0
- data/app/models/prod_instance_reln.rb +14 -0
- data/app/models/prod_instance_reln_type.rb +4 -0
- data/app/models/prod_instance_role_type.rb +4 -0
- data/app/models/prod_type_reln.rb +15 -0
- data/app/models/prod_type_reln_type.rb +4 -0
- data/app/models/prod_type_role_type.rb +4 -0
- data/app/models/product_instance.rb +21 -0
- data/app/models/product_offer.rb +11 -0
- data/app/models/product_package.rb +7 -0
- data/app/models/product_type.rb +37 -0
- data/app/models/simple_product_offer.rb +4 -0
- data/app/views/layouts/erp_products/application.html.erb +14 -0
- data/config/routes.rb +4 -0
- data/db/data_migrations/20110324010232_product_role_types.rb +23 -0
- data/db/data_migrations/20110527160807_add_default_prod_avail_types.rb +26 -0
- data/db/data_migrations/20110728201730_create_desktop_app_product_manager.rb +25 -0
- data/db/migrate/20080805000040_base_products.rb +232 -0
- data/db/migrate/20080805000041_base_products_indexes.rb +58 -0
- data/lib/erp_products/engine.rb +16 -0
- data/lib/erp_products/extensions/active_record/acts_as_product_instance.rb +50 -0
- data/lib/erp_products/extensions/active_record/acts_as_product_offer.rb +52 -0
- data/lib/erp_products/extensions/active_record/acts_as_product_type.rb +59 -0
- data/lib/erp_products/extensions.rb +3 -0
- data/lib/erp_products/version.rb +3 -0
- data/lib/erp_products.rb +5 -0
- data/lib/tasks/erp_products_tasks.rake +4 -0
- data/public/javascripts/erp_app/desktop/applications/product_manager/module.js +799 -0
- data/public/javascripts/erp_app/desktop/applications/product_manager/product_list_panel.js +141 -0
- data/public/stylesheets/erp_app/desktop/applications/product_manager/style.css +71 -0
- metadata +119 -0
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'ErpProducts'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require "rspec/core/rake_task"
|
29
|
+
RSpec::Core::RakeTask.new(:spec)
|
30
|
+
task :default => :spec
|
@@ -0,0 +1,9 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into including all the files listed below.
|
2
|
+
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
3
|
+
// be included in the compiled file accessible from http://example.com/assets/application.js
|
4
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
5
|
+
// the compiled file.
|
6
|
+
//
|
7
|
+
//= require jquery
|
8
|
+
//= require jquery_ujs
|
9
|
+
//= require_tree .
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll automatically include all the stylesheets available in this directory
|
3
|
+
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
|
4
|
+
* the top of the compiled file, but it's generally better to create a new file per style scope.
|
5
|
+
*= require_self
|
6
|
+
*= require_tree .
|
7
|
+
*/
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module ErpProducts
|
2
|
+
module ErpApp
|
3
|
+
module Desktop
|
4
|
+
module ProductManager
|
5
|
+
class BaseController < ::ErpApp::Desktop::BaseController
|
6
|
+
def index
|
7
|
+
products = ProductType.all.collect do |product_type|
|
8
|
+
{
|
9
|
+
:id => product_type.id,
|
10
|
+
:title => product_type.description,
|
11
|
+
:imageUrl => product_type.images.empty? ? '/images/img_blank.png' : product_type.images.first.data.url,
|
12
|
+
:price => product_type.get_current_simple_amount_with_currency.nil? ? 'no price set' : product_type.get_current_simple_amount_with_currency,
|
13
|
+
:available => product_type.inventory_entries.first.number_available,
|
14
|
+
:sold => product_type.inventory_entries.first.number_sold,
|
15
|
+
:sku => product_type.inventory_entries.first.sku.nil? ? '' : product_type.inventory_entries.first.sku
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
render :json => {:products => products}
|
20
|
+
end
|
21
|
+
|
22
|
+
def show
|
23
|
+
ProductType.class_eval do
|
24
|
+
def long_description
|
25
|
+
self.descriptions.find_by_internal_identifier('long_description').description
|
26
|
+
end
|
27
|
+
end
|
28
|
+
product_type = ProductType.find(params[:id])
|
29
|
+
render :json => {:id => product_type.id, :description => product_type.long_description, :title => product_type.description}
|
30
|
+
end
|
31
|
+
|
32
|
+
def update
|
33
|
+
product_type = ProductType.find(params[:id])
|
34
|
+
product_type.description = params[:title]
|
35
|
+
descriptive_asset = product_type.find_description_by_iid('long_description')
|
36
|
+
descriptive_asset.description = params[:description]
|
37
|
+
descriptive_asset.save
|
38
|
+
|
39
|
+
render :json => product_type.save ? {:success => true} : {:success => false}
|
40
|
+
end
|
41
|
+
|
42
|
+
def new
|
43
|
+
result = {}
|
44
|
+
|
45
|
+
title = params[:title]
|
46
|
+
description = params[:description]
|
47
|
+
|
48
|
+
product_type = ProductType.new(
|
49
|
+
:description => title
|
50
|
+
)
|
51
|
+
|
52
|
+
product_type.descriptions << DescriptiveAsset.create(
|
53
|
+
:description => description,
|
54
|
+
:internal_identifier => 'long_description'
|
55
|
+
)
|
56
|
+
|
57
|
+
if product_type.save
|
58
|
+
#create inventory
|
59
|
+
inventory_entry = InventoryEntry.new(
|
60
|
+
:product_type => product_type,
|
61
|
+
:number_available => 0,
|
62
|
+
:number_sold => 0,
|
63
|
+
:description => product_type.description
|
64
|
+
)
|
65
|
+
|
66
|
+
if inventory_entry.save
|
67
|
+
result[:success] = true
|
68
|
+
result[:id] = product_type.id
|
69
|
+
else
|
70
|
+
product_type.destroy
|
71
|
+
result[:success] = false
|
72
|
+
end
|
73
|
+
|
74
|
+
else
|
75
|
+
result[:success] = false
|
76
|
+
end
|
77
|
+
|
78
|
+
render :json => result
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete
|
82
|
+
render :json => (ProductType.find(params[:id]).destroy) ? {:success => true} : {:success => false}
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
#Images
|
87
|
+
#
|
88
|
+
|
89
|
+
def images
|
90
|
+
data = {:images => []}
|
91
|
+
product_type = ProductType.find(params[:id])
|
92
|
+
|
93
|
+
product_type.images.each do |image|
|
94
|
+
data[:images] << {:id => image.id, :name => image.name, :shortName => image.name, :url => image.data.url}
|
95
|
+
end
|
96
|
+
|
97
|
+
render :json => data
|
98
|
+
end
|
99
|
+
|
100
|
+
def new_image
|
101
|
+
result = {}
|
102
|
+
|
103
|
+
begin
|
104
|
+
name = request.env['HTTP_X_FILE_NAME'].blank? ? params[:file_data].original_filename : request.env['HTTP_X_FILE_NAME']
|
105
|
+
data = request.env['HTTP_X_FILE_NAME'].blank? ? params[:file_data] : request.raw_post
|
106
|
+
|
107
|
+
product_type = ProductType.find(params[:product_type_id])
|
108
|
+
#build path
|
109
|
+
path = File.join(product_type.images_path,name)
|
110
|
+
|
111
|
+
product_type.add_file(data, path)
|
112
|
+
result = {:success => true}
|
113
|
+
rescue Exception=>ex
|
114
|
+
logger.error ex.message
|
115
|
+
logger.error ex.backtrace.join("\n")
|
116
|
+
result = {:success => false, :error => "Error uploading #{name}"}
|
117
|
+
end
|
118
|
+
|
119
|
+
#file uploader wants this inline
|
120
|
+
render :inline => result.to_json
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_image
|
124
|
+
render :json => (FileAsset.find(params[:id]).destroy) ? {:success => true} : {:success => false}
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
#Prices
|
129
|
+
#
|
130
|
+
|
131
|
+
def currencies
|
132
|
+
Currency.include_root_in_json = false
|
133
|
+
render :inline => "{currencies:#{Currency.all.to_json(:only => [:id, :internal_identifier])}}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def prices
|
137
|
+
result = {:prices => []}
|
138
|
+
|
139
|
+
product_type = ProductType.find(params[:id])
|
140
|
+
product_type.pricing_plans.each do |pricing_plan|
|
141
|
+
result[:prices] << {
|
142
|
+
:pricing_plan_id => pricing_plan.id,
|
143
|
+
:price => pricing_plan.money_amount,
|
144
|
+
:currency => pricing_plan.currency.id,
|
145
|
+
:currency_display => pricing_plan.currency.internal_identifier,
|
146
|
+
:from_date => pricing_plan.from_date,
|
147
|
+
:thru_date => pricing_plan.thru_date,
|
148
|
+
:description => pricing_plan.description,
|
149
|
+
:comments => pricing_plan.comments
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
render :json => result
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
#pricing uses one form for new models and updates. So we use one action
|
158
|
+
def new_and_update_price
|
159
|
+
result = {}
|
160
|
+
|
161
|
+
if params[:pricing_plan_id].blank?
|
162
|
+
pricing_plan = PricingPlan.new(
|
163
|
+
:money_amount => params[:price],
|
164
|
+
:comments => params[:comments],
|
165
|
+
:currency => Currency.find(params[:currency]),
|
166
|
+
:from_date => Date.strptime(params[:from_date], '%m/%d/%Y').to_date,
|
167
|
+
:thru_date => Date.strptime(params[:thru_date], '%m/%d/%Y').to_date,
|
168
|
+
:description => params[:description],
|
169
|
+
:is_simple_amount => true
|
170
|
+
)
|
171
|
+
|
172
|
+
if pricing_plan.save
|
173
|
+
product_type = ProductType.find(params[:product_type_id])
|
174
|
+
product_type.pricing_plans << pricing_plan
|
175
|
+
if product_type.save
|
176
|
+
result[:success] = true
|
177
|
+
else
|
178
|
+
pricing_plan.destroy
|
179
|
+
result[:success] = false
|
180
|
+
end
|
181
|
+
else
|
182
|
+
result[:success] = false
|
183
|
+
end
|
184
|
+
else
|
185
|
+
pricing_plan = PricingPlan.find(params[:pricing_plan_id])
|
186
|
+
pricing_plan.money_amount = params[:price]
|
187
|
+
pricing_plan.currency = Currency.find(params[:currency])
|
188
|
+
pricing_plan.from_date = Date.strptime(params[:from_date], '%m/%d/%Y').to_date
|
189
|
+
pricing_plan.thru_date = Date.strptime(params[:thru_date], '%m/%d/%Y').to_date
|
190
|
+
pricing_plan.description = params[:description]
|
191
|
+
pricing_plan.comments = params[:comments]
|
192
|
+
|
193
|
+
if pricing_plan.save
|
194
|
+
result[:success] = true
|
195
|
+
else
|
196
|
+
result[:success] = false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
render :json => result
|
201
|
+
end
|
202
|
+
|
203
|
+
def delete_price
|
204
|
+
render :json => (PricingPlan.find(params[:id]).destroy) ? {:success => true} : {:success => false}
|
205
|
+
end
|
206
|
+
|
207
|
+
#
|
208
|
+
#Inventory
|
209
|
+
#
|
210
|
+
|
211
|
+
def inventory
|
212
|
+
result = {}
|
213
|
+
|
214
|
+
inventory_entry = InventoryEntry.find_by_product_type_id(params[:id])
|
215
|
+
result[:number_available] = inventory_entry.number_available
|
216
|
+
result[:sku] = inventory_entry.sku
|
217
|
+
|
218
|
+
render :json => result
|
219
|
+
end
|
220
|
+
|
221
|
+
def update_inventory
|
222
|
+
inventory_entry = InventoryEntry.find_by_product_type_id(params[:product_type_id])
|
223
|
+
inventory_entry.sku = params[:sku]
|
224
|
+
inventory_entry.number_available = params[:number_available]
|
225
|
+
|
226
|
+
render :json => (inventory_entry.save) ? {:success => true} : {:success => false}
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ProdInstanceReln < ActiveRecord::Base
|
2
|
+
|
3
|
+
belongs_to :prod_instance_from, :class_name => "ProductInstance", :foreign_key => "prod_instance_id_from"
|
4
|
+
belongs_to :prod_instance_to, :class_name => "ProductInstance", :foreign_key => "prod_instance_id_to"
|
5
|
+
|
6
|
+
belongs_to :from_role, :class_name => "ProdInstanceRoleType", :foreign_key => "role_type_id_from"
|
7
|
+
belongs_to :to_role, :class_name => "ProdInstanceRoleType", :foreign_key => "role_type_id_to"
|
8
|
+
|
9
|
+
belongs_to :prod_instance_reln_type
|
10
|
+
|
11
|
+
alias :from_item :prod_instance_from
|
12
|
+
alias :to_item :prod_instance_to
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ProdTypeReln < ActiveRecord::Base
|
2
|
+
|
3
|
+
belongs_to :prod_type_from, :class_name => "ProductType", :foreign_key => "prod_type_id_from"
|
4
|
+
belongs_to :prod_type_to, :class_name => "ProductType", :foreign_key => "prod_type_id_to"
|
5
|
+
|
6
|
+
belongs_to :from_role, :class_name => "ProdTypeRoleType", :foreign_key => "role_type_id_from"
|
7
|
+
belongs_to :to_role, :class_name => "ProdTypeRoleType", :foreign_key => "role_type_id_to"
|
8
|
+
|
9
|
+
belongs_to :prod_type_reln_type
|
10
|
+
|
11
|
+
alias :from_item :prod_type_from
|
12
|
+
alias :to_item :prod_type_to
|
13
|
+
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class ProductInstance < ActiveRecord::Base
|
2
|
+
acts_as_nested_set
|
3
|
+
include ErpTechSvcs::Utils::DefaultNestedSetMethods
|
4
|
+
acts_as_priceable
|
5
|
+
|
6
|
+
belongs_to :product_type
|
7
|
+
belongs_to :product_instance_status_type
|
8
|
+
belongs_to :product_instance_record, :polymorphic => true
|
9
|
+
belongs_to :prod_availability_status_type
|
10
|
+
|
11
|
+
alias :status :product_instance_status_type
|
12
|
+
|
13
|
+
def prod_instance_relns_to
|
14
|
+
ProdInstanceReln.where('prod_instance_id_to = ?',id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def prod_instance_relns_from
|
18
|
+
ProdInstanceReln.where('prod_instance_id_from = ?',id)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ProductType < ActiveRecord::Base
|
2
|
+
acts_as_nested_set
|
3
|
+
include ErpTechSvcs::Utils::DefaultNestedSetMethods
|
4
|
+
|
5
|
+
acts_as_priceable
|
6
|
+
has_file_assets
|
7
|
+
is_describable
|
8
|
+
|
9
|
+
belongs_to :product_type_record, :polymorphic => true
|
10
|
+
has_one :product_instance
|
11
|
+
|
12
|
+
def prod_type_relns_to
|
13
|
+
ProdTypeReln.where('prod_type_id_to = ?',id)
|
14
|
+
end
|
15
|
+
|
16
|
+
def prod_type_relns_from
|
17
|
+
ProdTypeReln.where('prod_type_id_from = ?',id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_label
|
21
|
+
"#{description}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{description}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.count_by_status(product_type, prod_availability_status_type)
|
29
|
+
ProductInstance.count("product_type_id = #{product_type.id} and prod_availability_status_type_id = #{prod_availability_status_type.id}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def images_path
|
33
|
+
file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
|
34
|
+
File.join(file_support.root,'products/images',"#{self.description.underscore}_#{self.id}")
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>ErpProducts</title>
|
5
|
+
<%= stylesheet_link_tag "erp_products/application" %>
|
6
|
+
<%= javascript_include_tag "erp_products/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class ProductRoleTypes
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
ProdTypeRelnType.create(:internal_identifier => 'product_type_package_reln', :description => 'Product Type Package Relationship')
|
5
|
+
ProdTypeRoleType.create(:internal_identifier => 'product_type_package', :description => 'Product Type Package')
|
6
|
+
ProdTypeRoleType.create(:internal_identifier => 'packaged_product_type', :description => 'Packaged Product Type')
|
7
|
+
|
8
|
+
ProdInstanceRelnType.create(:internal_identifier => 'product_instance_package_reln', :description => 'Product Instance Package Relantionship')
|
9
|
+
ProdInstanceRoleType.create(:internal_identifier => 'product_instance_package', :description => 'Product Instance Package')
|
10
|
+
ProdInstanceRoleType.create(:internal_identifier => 'packaged_product_instance', :description => 'Packaged Product Instance')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
['product_type_package_reln','product_type_package','packaged_product_type'].each do |iid|
|
15
|
+
ProdTypeRelnType.iid(iid).destroy
|
16
|
+
end
|
17
|
+
|
18
|
+
['product_instance_package_reln','product_instance_package','packaged_product_instance'].each do |iid|
|
19
|
+
ProdInstanceRoleType.iid(iid).destroy
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class AddDefaultProdAvailTypes
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
ProdAvailabilityStatusType.create(
|
5
|
+
:description => 'Available',
|
6
|
+
:internal_identifier => 'available'
|
7
|
+
)
|
8
|
+
|
9
|
+
ProdAvailabilityStatusType.create(
|
10
|
+
:description => 'Sold',
|
11
|
+
:internal_identifier => 'sold'
|
12
|
+
)
|
13
|
+
|
14
|
+
ProdAvailabilityStatusType.create(
|
15
|
+
:description => 'Shipped',
|
16
|
+
:internal_identifier => 'shipped'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.down
|
21
|
+
%w(available sold shipped).each do |iid|
|
22
|
+
ProdAvailabilityStatusType.find_by_internal_identifier(iid).destroy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class CreateDesktopAppProductManager
|
2
|
+
def self.up
|
3
|
+
app = DesktopApplication.create(
|
4
|
+
:description => 'Products',
|
5
|
+
:icon => 'icon-product',
|
6
|
+
:javascript_class_name => 'Compass.ErpApp.Desktop.Applications.ProductManager',
|
7
|
+
:internal_identifier => 'product_manager',
|
8
|
+
:shortcut_id => 'product_manager-win'
|
9
|
+
)
|
10
|
+
|
11
|
+
pt1 = PreferenceType.iid('desktop_shortcut')
|
12
|
+
pt1.preferenced_records << app
|
13
|
+
pt1.save
|
14
|
+
|
15
|
+
pt2 = PreferenceType.iid('autoload_application')
|
16
|
+
pt2.preferenced_records << app
|
17
|
+
pt2.save
|
18
|
+
|
19
|
+
app.save
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
DesktopApplication.destroy_all(:conditions => ['internal_identifier = ?','hello_world'])
|
24
|
+
end
|
25
|
+
end
|