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