synergy_inventory_management 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +88 -0
- data/Rakefile +1 -0
- data/Versionfile +3 -0
- data/app/assets/images/admin/icons/camera.png +0 -0
- data/app/assets/stylesheets/admin/synergy_imi.css +33 -0
- data/app/controllers/spree/admin/products_controller_decorator.rb +55 -0
- data/app/models/spree/product_decorator.rb +22 -0
- data/app/models/spree/taxon_decorator.rb +14 -0
- data/app/overrides/sim_admin_product_sub_tabs.rb +5 -0
- data/app/overrides/sim_new_product_taxon_field.rb +5 -0
- data/app/views/spree/admin/products/_js_head.erb +297 -0
- data/app/views/spree/admin/products/_taxon.erb +8 -0
- data/app/views/spree/admin/products/edit_multiple.html.erb +115 -0
- data/app/views/spree/admin/products/update.js.erb +2 -0
- data/config/locales/en.yml +25 -0
- data/config/locales/ru.yml +25 -0
- data/config/routes.rb +9 -0
- data/lib/generators/synergy_inventory_management/install/install_generator.rb +9 -0
- data/lib/synergy_inventory_management.rb +20 -0
- data/synergy_inventory_management.gemspec +27 -0
- metadata +82 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Synergy Inventory Management Interface (IMI)
|
2
|
+
============================================
|
3
|
+
|
4
|
+
An interface where administrator can manage products easily on one page and quickly perform the common products actions.
|
5
|
+
|
6
|
+
|
7
|
+
Installation
|
8
|
+
------------
|
9
|
+
|
10
|
+
Install synergy_inventory_management from git:
|
11
|
+
|
12
|
+
gem 'synergy_inventory_management', :git => 'git@github.com:secoint/synergy_inventory_management.git'
|
13
|
+
|
14
|
+
Bundle up with:
|
15
|
+
|
16
|
+
bundle install
|
17
|
+
|
18
|
+
Run the rake task that adds the assets to your project:
|
19
|
+
|
20
|
+
rails g synergy_inventory_management:install
|
21
|
+
|
22
|
+
Now you should be able to boot up your server with:
|
23
|
+
|
24
|
+
rails s
|
25
|
+
|
26
|
+
Enjoy!
|
27
|
+
|
28
|
+
|
29
|
+
Compatibility
|
30
|
+
-------------
|
31
|
+
|
32
|
+
Current version (0.2) compatible with Spree/Synergy 0.60.
|
33
|
+
|
34
|
+
|
35
|
+
Usage
|
36
|
+
-----
|
37
|
+
|
38
|
+
You can access the interface in Products -> Inventory management menu.
|
39
|
+
|
40
|
+
From the left there is a tree of taxonomies, where admin can manage nodes just like he does it in /admin/taxonomies now (create, move, rename etc.). After each taxon there is a number of products, having this taxon. By clicking on a taxon or taxonomy admin can see products having taxons in a selected node.
|
41
|
+
|
42
|
+
Products are shown in Products list. Products list contains the following columns:
|
43
|
+
|
44
|
+
* Selecting checkbox (helps to select a set of products for group processing).
|
45
|
+
|
46
|
+
* SKU
|
47
|
+
|
48
|
+
* Product name (also a link to edit product)
|
49
|
+
|
50
|
+
* Main price
|
51
|
+
|
52
|
+
* Indicator if a product has image
|
53
|
+
|
54
|
+
* Quick actions
|
55
|
+
|
56
|
+
Products list have pagination and "products per page" selector (10/50/100/All).
|
57
|
+
|
58
|
+
Each product has the following quick actions (from left to right):
|
59
|
+
|
60
|
+
* Indicator if the product is shown in a shop. If an indicator is dark, the product is hidden. By clicking on a dark indicator product becomes available from now. Light indicator tells that the product is active; admin can hide the product from now by clicking it.
|
61
|
+
|
62
|
+
* Preview the product on site (opens in a new tab).
|
63
|
+
|
64
|
+
* Edit product.
|
65
|
+
|
66
|
+
* Clone product.
|
67
|
+
|
68
|
+
* Delete product.
|
69
|
+
|
70
|
+
Above the Products list there a button "New product in this category". This button opens "new product" dialog with pre-defined taxon (current selected taxon).
|
71
|
+
|
72
|
+
Quick group products processing controls from the right perform just the most common actions with selected products:
|
73
|
+
|
74
|
+
* Publish from (select date).
|
75
|
+
|
76
|
+
* Quick hide from now.
|
77
|
+
|
78
|
+
* Add a taxon to selected products (select taxon from combo-box).
|
79
|
+
|
80
|
+
* Delete selected products.
|
81
|
+
|
82
|
+
|
83
|
+
Any questions?
|
84
|
+
--------------
|
85
|
+
|
86
|
+
Feel free to ask: synergy@secoint.ru
|
87
|
+
|
88
|
+
Russian-speaking community: http://groups.google.com/group/synergy-user
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Versionfile
ADDED
Binary file
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#synergy_imi #taxonomy_tree {
|
2
|
+
overflow: auto;
|
3
|
+
width: 100%;
|
4
|
+
}
|
5
|
+
|
6
|
+
#synergy_imi #products_actions{
|
7
|
+
margin-bottom: 20px;
|
8
|
+
}
|
9
|
+
|
10
|
+
#synergy_imi #current_path{
|
11
|
+
margin-bottom: 15px;
|
12
|
+
}
|
13
|
+
|
14
|
+
#synergy_imi .pagination {
|
15
|
+
padding-top: 0;
|
16
|
+
float: right;
|
17
|
+
}
|
18
|
+
|
19
|
+
#synergy_imi #per_page_form {
|
20
|
+
float: left;
|
21
|
+
}
|
22
|
+
|
23
|
+
#synergy_imi table.index td.actions {
|
24
|
+
white-space: nowrap;
|
25
|
+
}
|
26
|
+
|
27
|
+
#synergy_imi table.index td.has_image {
|
28
|
+
text-align: center;
|
29
|
+
}
|
30
|
+
|
31
|
+
#synergy_imi .actions a {
|
32
|
+
margin: 0 0 0 5px;
|
33
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Spree
|
2
|
+
Admin::ProductsController.class_eval do
|
3
|
+
skip_before_filter :load_resource, :only => [:edit_multiple, :update_multiple, :destroy_multiple]
|
4
|
+
before_filter :load_multiple, :only => [:update_multiple, :destroy_multiple]
|
5
|
+
|
6
|
+
|
7
|
+
def edit_multiple
|
8
|
+
session[:im_per_page] = params[:per_page].to_i if !params[:per_page].nil?
|
9
|
+
@taxon = params[:id].present? ? Taxon.find(params[:id]) : Taxon.root
|
10
|
+
product_ids = @taxon.present? ? @taxon.self_and_descendants.map { |tax| tax.product_ids }.flatten.uniq : []
|
11
|
+
|
12
|
+
params[:q] ||= {}
|
13
|
+
params[:q][:deleted_at_null] = '1' if params[:q][:deleted_at_null].nil?
|
14
|
+
params[:q][:s] ||= 'name asc'
|
15
|
+
|
16
|
+
@search = Product.where(:id => product_ids).ransack(params[:q])
|
17
|
+
# pagination_options = {:per_page => (session[:im_per_page] || 10), :page => params[:page]}
|
18
|
+
@collection = @search.result.group_by_products_id.page(params[:page]).per(session[:im_per_page] || 10)
|
19
|
+
|
20
|
+
respond_with(@collection) do |format|
|
21
|
+
format.html
|
22
|
+
format.json { render :json => json_data }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_multiple
|
27
|
+
@collection.each { |pr| pr.update_attributes!(params[:product]) }
|
28
|
+
flash[:notice] = I18n.t('sim.products_successfully_updated')
|
29
|
+
respond_with(@collection) do |format|
|
30
|
+
format.html { redirect_to admin_edit_multiple_products_url(:id => params[:id]) }
|
31
|
+
format.json { render :json => json_data }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def destroy_multiple
|
36
|
+
authorize! :destroy, Product
|
37
|
+
@collection.each do |pr|
|
38
|
+
pr.update_attributes!(:deleted_at => Time.zone.now)
|
39
|
+
pr.variants.each { |v| v.update_attributes!(:deleted_at => Time.zone.now) }
|
40
|
+
end
|
41
|
+
flash[:notice] = I18n.t('sim.products_successfully_deleted')
|
42
|
+
respond_with(@collection) do |format|
|
43
|
+
format.html { redirect_to admin_edit_multiple_products_url(:id => params[:id]) }
|
44
|
+
format.json { render :json => json_data }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_multiple
|
49
|
+
@collection = Product.where(:id => params[:product_ids])
|
50
|
+
end
|
51
|
+
|
52
|
+
private :load_multiple
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Spree
|
2
|
+
Product.class_eval do
|
3
|
+
attr_accessible :add_taxon
|
4
|
+
attr_accessor :add_taxon
|
5
|
+
after_save :add_taxon_save
|
6
|
+
|
7
|
+
def active?
|
8
|
+
self.deleted_at.nil? and available?
|
9
|
+
end
|
10
|
+
|
11
|
+
def available?
|
12
|
+
available_on.nil? ? false : available_on < Time.zone.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_taxon_save
|
16
|
+
return true unless add_taxon.present?
|
17
|
+
taxon = Taxon.find(add_taxon)
|
18
|
+
self.taxons << taxon if !self.taxons.include? taxon
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Spree
|
2
|
+
Taxon.class_eval do
|
3
|
+
after_save :update_children_taxonomy, :if => :taxonomy_id_changed?
|
4
|
+
|
5
|
+
def not_deleted_products_count
|
6
|
+
self.self_and_descendants.map { |tax| tax.products.select(:id).not_deleted }.flatten.uniq.size
|
7
|
+
end
|
8
|
+
|
9
|
+
def update_children_taxonomy
|
10
|
+
self.children.each { |ch| ch.update_attributes(:taxonomy_id => self.taxonomy.id) }
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Deface::Override.new(
|
2
|
+
:virtual_path => "spree/admin/shared/_product_sub_menu",
|
3
|
+
:insert_bottom => "[data-hook='admin_product_sub_tabs']",
|
4
|
+
:text => "<li class='<%= 'selected' if params[:action].to_s == 'edit_multiple' %>'><%= link_to I18n.t('sim.edit_multiple_products'), admin_edit_multiple_products_start_path %></li>",
|
5
|
+
:name => "sim_admin_product_sub_tabs")
|
@@ -0,0 +1,297 @@
|
|
1
|
+
<% content_for :head do %>
|
2
|
+
<%#= javascript_include_tag 'jquery.cookie.js', 'jsTree/jquery.jstree.js' %>
|
3
|
+
<%= stylesheet_link_tag 'admin/synergy_imi' %>
|
4
|
+
<script type="text/javascript">
|
5
|
+
|
6
|
+
// var taxonomy_id = '3';
|
7
|
+
var loading = '<%= escape_javascript t('loading') %>';
|
8
|
+
var new_taxon = '<%=escape_javascript t('new_taxon') %>';
|
9
|
+
var server_error = '<%=escape_javascript t('server_error') %>';
|
10
|
+
var taxonomy_tree_error = '<%=escape_javascript t('taxonomy_tree_error') %>';
|
11
|
+
var is_cut = false;
|
12
|
+
var last_rollback = null;
|
13
|
+
|
14
|
+
var handle_ajax_error = function(XMLHttpRequest, textStatus, errorThrown) {
|
15
|
+
jQuery.jstree.rollback(last_rollback);
|
16
|
+
jQuery("#ajax_error").show().html("<strong>" + server_error + "</strong><br/>" + taxonomy_tree_error);
|
17
|
+
};
|
18
|
+
|
19
|
+
var node_taxonomy = function (node) {
|
20
|
+
return node.attr('data-taxonomy') || node.parents('li[rel="root"]').attr('data-taxonomy');
|
21
|
+
}
|
22
|
+
|
23
|
+
var handle_move = function(e, data) {
|
24
|
+
last_rollback = data.rlbk;
|
25
|
+
var position = data.rslt.cp;
|
26
|
+
var node = data.rslt.o;
|
27
|
+
var new_parent = data.rslt.np;
|
28
|
+
var old_parent = data.rslt.op;
|
29
|
+
|
30
|
+
jQuery.ajax({
|
31
|
+
type: "POST",
|
32
|
+
dataType: "json",
|
33
|
+
//url: "/admin/taxonomies/" + node.attr('data-taxonomy') + "/taxons/" + node.attr("id"),
|
34
|
+
url: "/admin/taxonomies/" + node_taxonomy(old_parent) + "/taxons/" + node.attr("id"),
|
35
|
+
data: ({_method: "put", "taxon[taxonomy_id]": node_taxonomy(new_parent),"taxon[parent_id]": new_parent.attr("id"), "taxon[position]": position, authenticity_token: AUTH_TOKEN}),
|
36
|
+
error: handle_ajax_error
|
37
|
+
});
|
38
|
+
|
39
|
+
return true
|
40
|
+
};
|
41
|
+
|
42
|
+
var handle_create = function(e, data) {
|
43
|
+
last_rollback = data.rlbk;
|
44
|
+
var node = data.rslt.obj;
|
45
|
+
var name = data.rslt.name;
|
46
|
+
var position = data.rslt.position;
|
47
|
+
var new_parent = data.rslt.parent;
|
48
|
+
|
49
|
+
jQuery.ajax({
|
50
|
+
type: "POST",
|
51
|
+
dataType: "json",
|
52
|
+
url: "/admin/taxonomies/" + node_taxonomy(new_parent) + "/taxons/",
|
53
|
+
data: ({"taxon[name]": name, "taxon[parent_id]": new_parent.attr("id"), "taxon[position]": position, authenticity_token: AUTH_TOKEN}),
|
54
|
+
error: handle_ajax_error,
|
55
|
+
success: function(data, result) {
|
56
|
+
node.attr('id', data.taxon.id);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
};
|
60
|
+
|
61
|
+
var handle_rename = function(e, data) {
|
62
|
+
last_rollback = data.rlbk;
|
63
|
+
var node = data.rslt.obj;
|
64
|
+
var name = data.rslt.new_name;
|
65
|
+
|
66
|
+
jQuery.ajax({
|
67
|
+
type: "POST",
|
68
|
+
dataType: "json",
|
69
|
+
url: "/admin/taxonomies/" + node_taxonomy(node) + "/taxons/" + node.attr("id"),
|
70
|
+
data: ({_method: "put", "taxon[name]": name, authenticity_token: AUTH_TOKEN}),
|
71
|
+
error: handle_ajax_error
|
72
|
+
});
|
73
|
+
};
|
74
|
+
|
75
|
+
var handle_delete = function(e, data) {
|
76
|
+
last_rollback = data.rlbk;
|
77
|
+
var node = data.rslt.obj;
|
78
|
+
|
79
|
+
|
80
|
+
jConfirm('Are you sure you want to delete this taxon?', 'Confirm Taxon Deletion', function(r) {
|
81
|
+
if (r) {
|
82
|
+
jQuery.ajax({
|
83
|
+
type: "POST",
|
84
|
+
dataType: "json",
|
85
|
+
url: "/admin/taxonomies/0/taxons/" + node.attr("id"),
|
86
|
+
data: ({_method: "delete", authenticity_token: AUTH_TOKEN}),
|
87
|
+
error: handle_ajax_error
|
88
|
+
});
|
89
|
+
} else {
|
90
|
+
jQuery.jstree.rollback(last_rollback);
|
91
|
+
last_rollback = null;
|
92
|
+
}
|
93
|
+
});
|
94
|
+
};
|
95
|
+
|
96
|
+
|
97
|
+
var handle_select = function (event, data) {
|
98
|
+
data.rslt.obj.parents('.jstree-closed').each(function () {
|
99
|
+
data.inst.open_node(this);
|
100
|
+
});
|
101
|
+
};
|
102
|
+
|
103
|
+
jQuery(document).ready(function() {
|
104
|
+
|
105
|
+
|
106
|
+
var conf = {
|
107
|
+
"ui" : { "initially_select" : [ "<%= current_taxon.id %>" ] },
|
108
|
+
"core" : { "initially_open" : [ "<%= current_taxon.id %>" ] },
|
109
|
+
"themes" : {
|
110
|
+
"theme" : "apple",
|
111
|
+
"url" : "/assets/jquery.jstree/themes/apple/style.css"
|
112
|
+
},
|
113
|
+
"strings" : {
|
114
|
+
"new_node" : new_taxon,
|
115
|
+
"loading" : loading + "..."
|
116
|
+
},
|
117
|
+
"crrm" : {
|
118
|
+
"move" : {
|
119
|
+
"check_move" : function (m) {
|
120
|
+
var position = m.cp;
|
121
|
+
var node = m.o;
|
122
|
+
var new_parent = m.np;
|
123
|
+
|
124
|
+
if (!new_parent) return false; //no parent
|
125
|
+
if (node.attr("rel") == "root") return false; //can't drag root
|
126
|
+
if (new_parent.attr("id") == "taxonomy_tree") return false;
|
127
|
+
return true;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
},
|
131
|
+
"contextmenu" : {
|
132
|
+
"items" : function(obj) {
|
133
|
+
var id_of_node = obj.attr("id");
|
134
|
+
var type_of_node = obj.attr("rel");
|
135
|
+
var menu = {};
|
136
|
+
if (type_of_node == "root") {
|
137
|
+
menu = {
|
138
|
+
"create" : {
|
139
|
+
"label" : "Create",
|
140
|
+
"action" : function (obj) {
|
141
|
+
this.create(obj);
|
142
|
+
}
|
143
|
+
},
|
144
|
+
"paste" : {
|
145
|
+
"separator_before" : true,
|
146
|
+
"label" : "Paste",
|
147
|
+
"action" : function (obj) {
|
148
|
+
is_cut = false;
|
149
|
+
this.paste(obj);
|
150
|
+
},
|
151
|
+
"_disabled" : is_cut == false
|
152
|
+
},
|
153
|
+
"edit" : {
|
154
|
+
"separator_before" : true,
|
155
|
+
"label" : "Edit",
|
156
|
+
"action" : function (obj) {
|
157
|
+
window.location = "/admin/taxonomies/" + node_taxonomy(obj) + "/taxons/" + obj.attr("id") + "/edit/";
|
158
|
+
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
} else {
|
163
|
+
menu = {
|
164
|
+
"create" : {
|
165
|
+
"label" : "Create",
|
166
|
+
"action" : function (obj) {
|
167
|
+
this.create(obj);
|
168
|
+
}
|
169
|
+
},
|
170
|
+
"rename" : {
|
171
|
+
"label" : "Rename",
|
172
|
+
"action" : function (obj) {
|
173
|
+
this.rename(obj);
|
174
|
+
}
|
175
|
+
},
|
176
|
+
"remove" : {
|
177
|
+
"label" : "Remove",
|
178
|
+
"action" : function (obj) {
|
179
|
+
this.remove(obj);
|
180
|
+
}
|
181
|
+
},
|
182
|
+
"cut" : {
|
183
|
+
"separator_before" : true,
|
184
|
+
"label" : "Cut",
|
185
|
+
"action" : function (obj) {
|
186
|
+
is_cut = true;
|
187
|
+
this.cut(obj);
|
188
|
+
}
|
189
|
+
},
|
190
|
+
"paste" : {
|
191
|
+
"label" : "Paste",
|
192
|
+
"action" : function (obj) {
|
193
|
+
is_cut = false;
|
194
|
+
this.paste(obj);
|
195
|
+
},
|
196
|
+
"_disabled" : is_cut == false
|
197
|
+
},
|
198
|
+
"edit" : {
|
199
|
+
"separator_before" : true,
|
200
|
+
"label" : "Edit",
|
201
|
+
"action" : function (obj) {
|
202
|
+
window.location = "/admin/taxonomies/" + node_taxonomy(obj) + "/taxons/" + obj.attr("id") + "/edit/";
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
return menu;
|
208
|
+
}
|
209
|
+
},
|
210
|
+
|
211
|
+
"plugins" : [ "themes", "json_data", "html_data", "dnd", "crrm", "contextmenu", "ui"]
|
212
|
+
};
|
213
|
+
|
214
|
+
|
215
|
+
$('#taxonomy_tree').jstree().delegate("a", "click", function(event, data) {
|
216
|
+
event.preventDefault();
|
217
|
+
$('#progress').show();
|
218
|
+
window.location.href = this.href
|
219
|
+
});
|
220
|
+
|
221
|
+
jQuery("#taxonomy_tree").jstree(conf)
|
222
|
+
.bind("select_node.jstree", handle_select)
|
223
|
+
.bind("move_node.jstree", handle_move)
|
224
|
+
.bind("remove.jstree", handle_delete)
|
225
|
+
.bind("create.jstree", handle_create)
|
226
|
+
.bind("rename.jstree", handle_rename);
|
227
|
+
|
228
|
+
|
229
|
+
jQuery(document).keypress(function(e) {
|
230
|
+
if (e.keyCode == 13) {
|
231
|
+
e.preventDefault();
|
232
|
+
}
|
233
|
+
});
|
234
|
+
|
235
|
+
$('#check-all').change(function(e) {
|
236
|
+
if (this.checked) {
|
237
|
+
$('.product-select').each(function() {
|
238
|
+
this.checked = true;
|
239
|
+
});
|
240
|
+
} else {
|
241
|
+
$('.product-select').each(function() {
|
242
|
+
this.checked = false;
|
243
|
+
});
|
244
|
+
}
|
245
|
+
});
|
246
|
+
|
247
|
+
$('#products_actions button').click(function() {
|
248
|
+
|
249
|
+
var count = $(".product-select:checked").length;
|
250
|
+
if (!count) {
|
251
|
+
alert('<%= t('sim.select_products') %>');
|
252
|
+
return false;
|
253
|
+
}
|
254
|
+
|
255
|
+
$('.product-select').clone().appendTo('#product_ids_form');
|
256
|
+
switch ($("input[name='products_action']:checked").val()) {
|
257
|
+
case 'publish_from':
|
258
|
+
$('#product_available_on').clone().appendTo('#product_ids_form');
|
259
|
+
break;
|
260
|
+
case 'hide':
|
261
|
+
$('#product_hide').clone().appendTo('#product_ids_form');
|
262
|
+
break;
|
263
|
+
case 'add_taxon':
|
264
|
+
$('<input>').attr({
|
265
|
+
type: 'hidden',
|
266
|
+
name: 'product[add_taxon]',
|
267
|
+
value: $('#product_add_taxon').val()
|
268
|
+
}).appendTo('#product_ids_form');
|
269
|
+
break;
|
270
|
+
case 'delete':
|
271
|
+
var del = confirm("<%= t('sim.confirm_destroy') %>");
|
272
|
+
if (!del) {
|
273
|
+
return false
|
274
|
+
}
|
275
|
+
$('#product_ids_form input[name=_method]').val('delete');
|
276
|
+
break;
|
277
|
+
default:
|
278
|
+
alert('<%= t('sim.select_action') %>');
|
279
|
+
return false;
|
280
|
+
}
|
281
|
+
|
282
|
+
$('#actions button').attr("disabled", "true");
|
283
|
+
$('#product_ids_form').submit();
|
284
|
+
});
|
285
|
+
|
286
|
+
$('.change-availability').live('click', function() {
|
287
|
+
$(this).parents('form').submit();
|
288
|
+
});
|
289
|
+
|
290
|
+
$('#per_page').change(function() {
|
291
|
+
$('#per_page_form').submit();
|
292
|
+
});
|
293
|
+
});
|
294
|
+
|
295
|
+
</script>
|
296
|
+
|
297
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<li id="<%= taxon.id %>" <%= "rel='root' data-taxonomy='#{taxon.taxonomy_id}'".html_safe if taxon.root? %>>
|
2
|
+
<%= link_to taxon.name, admin_edit_multiple_products_path(taxon) %>
|
3
|
+
<span>(<%= taxon.not_deleted_products_count %>)</span>
|
4
|
+
<% if taxon.children.any? %>
|
5
|
+
<ul><%= render :partial => 'taxon', :collection => taxon.children, :locals => {:current_taxon => current_taxon} %></ul>
|
6
|
+
<% end %>
|
7
|
+
</li>
|
8
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
<%= render :partial => 'spree/admin/shared/product_sub_menu' %>
|
2
|
+
|
3
|
+
<% content_for :page_title do %>
|
4
|
+
<%= t("sim.edit_multiple_products") %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<% content_for :page_actions do %>
|
8
|
+
<li id="new_product_link">
|
9
|
+
<%= button_link_to t("sim.new_product_here"), new_object_url("product[add_taxon]" => @taxon.id), {"data-update" => :new_product, :icon => 'icon-plus', :id => 'admin_new_product'} %>
|
10
|
+
</li>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<div id="synergy_imi">
|
14
|
+
|
15
|
+
<div id="new_product"></div>
|
16
|
+
<p id="ajax_error" class="errorExplanation" style="display:none;"></p>
|
17
|
+
<% if @taxon.present? %>
|
18
|
+
|
19
|
+
<%= render :partial => 'js_head', :locals => {:current_taxon => @taxon} %>
|
20
|
+
|
21
|
+
<% content_for :sidebar do %>
|
22
|
+
<div class="sidebar-title">
|
23
|
+
<span><%= t("sim.catalog") %></span>
|
24
|
+
</div>
|
25
|
+
<div id='taxonomy_tree' style=" overflow: auto">
|
26
|
+
<ul><%= render :partial => "taxon", :collection => Spree::Taxon.roots, :locals => {:current_taxon => @taxon} %></ul>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<div class="twelve columns">
|
31
|
+
<fieldset id="products_actions">
|
32
|
+
<legend><%= t("sim.with_selected") %>:</legend>
|
33
|
+
<div class="four columns">
|
34
|
+
<p><label><%= radio_button_tag 'products_action', 'publish_from' %> <%= t("sim.publish_from") %></label>
|
35
|
+
<%= text_field 'product', :available_on, :style => 'width:100px', :class => 'datepicker' %></p>
|
36
|
+
|
37
|
+
<p><label><%= radio_button_tag 'products_action', 'hide' %> <%= t("sim.hide_from_now") %></label></p>
|
38
|
+
<%= hidden_field 'product', :available_on, :id => 'product_hide' %>
|
39
|
+
</div>
|
40
|
+
<div class="four columns">
|
41
|
+
<p><label><%= radio_button_tag 'products_action', 'add_taxon' %> <%= t("sim.add_taxon") %></label>
|
42
|
+
<% options_for_taxon = [] %>
|
43
|
+
<% Spree::Taxon.each_with_level(Spree::Taxon.roots.map{ |root|root.self_and_descendants}.flatten) { |tax, level| options_for_taxon << "<option value='#{tax.id}'>#{'-' * level}#{tax.name}</option>" } %>
|
44
|
+
<%= select 'product', :add_taxon, raw(options_for_taxon.join) %></p>
|
45
|
+
|
46
|
+
<p><label><%= radio_button_tag 'products_action', 'delete' %> <%= t("sim.delete") %></label></p>
|
47
|
+
</div>
|
48
|
+
<div class="two columns">
|
49
|
+
<%= button t("sim.go") %>
|
50
|
+
</div>
|
51
|
+
</fieldset>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
<div class="clear"></div>
|
55
|
+
|
56
|
+
<div id="products_list" class="twelve columns">
|
57
|
+
<div id="current_path"><%= @taxon.self_and_ancestors.map {|tax| link_to tax.name, admin_edit_multiple_products_path(tax) }.join(" > ").html_safe %></div>
|
58
|
+
|
59
|
+
<table class="index" id="listing_products">
|
60
|
+
<thead>
|
61
|
+
<tr>
|
62
|
+
<th><%= check_box_tag 'check-all', 1, false, :id => 'check-all' %></th>
|
63
|
+
<th><%= t("sku") %></th>
|
64
|
+
<th><%= sort_link @search, :name, t("name") %></th>
|
65
|
+
<th><%= sort_link @search, :master_default_price_amount, t("master_price") %></th>
|
66
|
+
<th><%= t("sim.photo") %></th>
|
67
|
+
<th class="actions"></th>
|
68
|
+
</tr>
|
69
|
+
</thead>
|
70
|
+
<tbody>
|
71
|
+
<% @collection.each do |product| %>
|
72
|
+
<tr id="<%= dom_id product %>">
|
73
|
+
<td><%= check_box_tag 'product_ids[]', product.id, false, :class => 'product-select' %></td>
|
74
|
+
<td><%= product.sku %></td>
|
75
|
+
<td><%= product.name %></td>
|
76
|
+
<td><%= product.price %></td>
|
77
|
+
<td class="has_image"><%= product.images.any? ? image_tag('admin/icons/camera.png', :title => t("sim.has_photo")) : '' %></td>
|
78
|
+
<td class="actions">
|
79
|
+
<%= form_for product, :url => admin_product_path(product), :remote => true, :html => {:style => 'display:inline'} do |f| %>
|
80
|
+
<% if product.active? %>
|
81
|
+
<%= link_to ' ', 'javascript:void(0);', :class => 'change-availability icon-check no-text', :title => t('sim.published_hide') %>
|
82
|
+
<% else %>
|
83
|
+
<%= link_to ' ', 'javascript:void(0);', :class => 'change-availability icon-check-empty no-text', :title => t('sim.unpublished_show') %>
|
84
|
+
<% end %>
|
85
|
+
<%= f.hidden_field :available_on, :class => 'product_available_on', :value => (product.active? ? '' : Time.zone.now.strftime('%Y/%m/%d')) %>
|
86
|
+
<%- end %>
|
87
|
+
<% unless product.deleted? %>
|
88
|
+
<%= link_to_edit product, :no_text => true %>
|
89
|
+
<%= link_to_clone product, :no_text => true %>
|
90
|
+
<%= link_to_delete product, :no_text => true %>
|
91
|
+
<%= link_to_with_icon 'icon-search', t("preview"), product, :no_text => true, :target => "_blank" %>
|
92
|
+
<% end %>
|
93
|
+
</td>
|
94
|
+
</tr>
|
95
|
+
<% end %>
|
96
|
+
</tbody>
|
97
|
+
</table>
|
98
|
+
<%= form_tag admin_edit_multiple_products_path(@taxon), :id => 'product_ids_form', :method => :put, :style => "display:none" do %>
|
99
|
+
<% end %>
|
100
|
+
<%= paginate(@collection, :previous_label => "« #{t('previous')}", :next_label => "#{t('next')} »") %>
|
101
|
+
<%= form_tag '', :method => :get, :id => 'per_page_form' do %>
|
102
|
+
<%= label_tag 'per_page', t('sim.per_page') %>
|
103
|
+
<%= select_tag 'per_page', options_for_select([["10"], ["50"], ["100"], [t(:all), "10000"]], session[:im_per_page].to_s) %>
|
104
|
+
<% end %>
|
105
|
+
</div>
|
106
|
+
<% else %>
|
107
|
+
<p><%= t('sim.requires_at_least_one_taxon') %></p>
|
108
|
+
<% end %>
|
109
|
+
</div>
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
@@ -0,0 +1,2 @@
|
|
1
|
+
$('#product_<%= @object.id%> form a').replaceWith($("<%= escape_javascript @object.active? ? link_to(' ', 'javascript:void(0);', :class => 'change-availability icon-check no-text', :title => t('sim.published_hide')) : link_to(' ', 'javascript:void(0);', :class => 'change-availability icon-check-empty no-text', :title => t('sim.unpublished_show')) %>"));
|
2
|
+
$('#product_<%= @object.id%> .product_available_on').val('<%= @object.active? ? '' : Time.zone.now.strftime('%Y/%m/%d') %>');
|
@@ -0,0 +1,25 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
sim:
|
4
|
+
edit_multiple_products: "Inventory management"
|
5
|
+
with_selected: "With selected"
|
6
|
+
catalog: "Catalog"
|
7
|
+
publish_from: "Publish from"
|
8
|
+
hide_from_now: "Hide from now"
|
9
|
+
add_taxon: "Add taxon"
|
10
|
+
delete: "Delete"
|
11
|
+
go: "Go"
|
12
|
+
per_page: "Per page"
|
13
|
+
photo: "Photo"
|
14
|
+
has_photo: "This product has photo"
|
15
|
+
products_successfully_updated: "Products successfully updated"
|
16
|
+
products_successfully_deleted: "Products successfully deleted"
|
17
|
+
requires_at_least_one_taxon: "Requires at least one taxon"
|
18
|
+
select_action: "Select action"
|
19
|
+
select_products: "Select products"
|
20
|
+
new_product_here: "New product in this category"
|
21
|
+
confirm_destroy: "Do you really want to delete chosen products?"
|
22
|
+
products_actions: "Actions"
|
23
|
+
published_hide: "Published (hide on click)"
|
24
|
+
unpublished_show: "Unpublished (show by click)"
|
25
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
---
|
2
|
+
ru:
|
3
|
+
sim:
|
4
|
+
edit_multiple_products: "Управление номенклатурой"
|
5
|
+
with_selected: "С выбранными"
|
6
|
+
catalog: "Каталог"
|
7
|
+
publish_from: "Опубликовать с"
|
8
|
+
hide_from_now: "Скрыть с этого момента"
|
9
|
+
add_taxon: "Добавить таксон"
|
10
|
+
delete: "Удалить"
|
11
|
+
go: "Применить"
|
12
|
+
per_page: "Количество на странице"
|
13
|
+
photo: "Фото"
|
14
|
+
has_photo: "Этот товар имеет изображение"
|
15
|
+
products_successfully_updated: "Товары успешно обновлены"
|
16
|
+
products_successfully_deleted: "Товары успешно удалены"
|
17
|
+
requires_at_least_one_taxon: "Необходим хотя бы один таксон"
|
18
|
+
select_action: "Выберите действие"
|
19
|
+
select_products: "Выберите товары"
|
20
|
+
new_product_here: "Новый товар в этой категории"
|
21
|
+
confirm_destroy: "Вы действительно хотите удалить выбранные товары?"
|
22
|
+
products_actions: "Действия"
|
23
|
+
published_hide: "Опубликовано (по нажатию - скрыть)"
|
24
|
+
unpublished_show: "Не опубликовано (по нажатию - опубликовать)"
|
25
|
+
|
data/config/routes.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Spree::Core::Engine.routes.draw do
|
2
|
+
namespace :admin do
|
3
|
+
match 'edit_multiple_products' => "products#edit_multiple", :as => :edit_multiple_products_start, :via => :get
|
4
|
+
match 'edit_multiple_products/:id' => "products#edit_multiple", :as => :edit_multiple_products, :via => :get
|
5
|
+
match 'edit_multiple_products/:id' => "products#update_multiple", :via => :put
|
6
|
+
match 'edit_multiple_products/:id' => "products#destroy_multiple", :via => :delete
|
7
|
+
match 'switch_product_availability/:id' => "products#switch_availability", :via => :put, :as => :switch_product_availability
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module SynergyInventoryManagement
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
def add_stylesheets
|
5
|
+
inject_into_file "app/assets/stylesheets/admin/all.css", " *= require admin/synergy_imi\n", :before => /\*\//, :verbose => true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spree_core'
|
2
|
+
|
3
|
+
module SynergyInventoryManagement
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
|
6
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
7
|
+
|
8
|
+
initializer :assets do |config|
|
9
|
+
Rails.application.config.assets.precompile += %w( admin/synergy_imi.css )
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.activate
|
13
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
|
14
|
+
Rails.env.production? ? require(c) : load(c)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
config.to_prepare &method(:activate).to_proc
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
#require "synergy_inventory_management/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.name = "synergy_inventory_management"
|
8
|
+
s.version = '1.3.0'
|
9
|
+
s.authors = 'Sergey Chazov (Service & Consulting)'
|
10
|
+
s.email = 'service@secoint.ru'
|
11
|
+
s.homepage = "https://github.com/secoint/synergy_inventory_management"
|
12
|
+
s.summary = 'Manage inventory'
|
13
|
+
s.description = 'Manage inventory'
|
14
|
+
|
15
|
+
s.rubyforge_project = "synergy_inventory_management"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.required_ruby_version = '>= 1.8.7'
|
22
|
+
s.add_dependency('spree_core', '>= 1.3.0')
|
23
|
+
|
24
|
+
# specify any dependencies here; for example:
|
25
|
+
# s.add_development_dependency "rspec"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: synergy_inventory_management
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sergey Chazov (Service & Consulting)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: spree_core
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.3.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.3.0
|
30
|
+
description: Manage inventory
|
31
|
+
email: service@secoint.ru
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- Gemfile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- Versionfile
|
41
|
+
- app/assets/images/admin/icons/camera.png
|
42
|
+
- app/assets/stylesheets/admin/synergy_imi.css
|
43
|
+
- app/controllers/spree/admin/products_controller_decorator.rb
|
44
|
+
- app/models/spree/product_decorator.rb
|
45
|
+
- app/models/spree/taxon_decorator.rb
|
46
|
+
- app/overrides/sim_admin_product_sub_tabs.rb
|
47
|
+
- app/overrides/sim_new_product_taxon_field.rb
|
48
|
+
- app/views/spree/admin/products/_js_head.erb
|
49
|
+
- app/views/spree/admin/products/_taxon.erb
|
50
|
+
- app/views/spree/admin/products/edit_multiple.html.erb
|
51
|
+
- app/views/spree/admin/products/update.js.erb
|
52
|
+
- config/locales/en.yml
|
53
|
+
- config/locales/ru.yml
|
54
|
+
- config/routes.rb
|
55
|
+
- lib/generators/synergy_inventory_management/install/install_generator.rb
|
56
|
+
- lib/synergy_inventory_management.rb
|
57
|
+
- synergy_inventory_management.gemspec
|
58
|
+
homepage: https://github.com/secoint/synergy_inventory_management
|
59
|
+
licenses: []
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.7
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project: synergy_inventory_management
|
78
|
+
rubygems_version: 1.8.24
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Manage inventory
|
82
|
+
test_files: []
|