synergy_inventory_management 1.3.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/.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: []
|