synergy 0.50.0.rc1

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.
Files changed (43) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE.txt +674 -0
  3. data/README.md +53 -0
  4. data/Rakefile +76 -0
  5. data/app/controllers/admin/products_controller_decorator.rb +64 -0
  6. data/app/controllers/checkout_controller_decorator.rb +10 -0
  7. data/app/models/calculator/cash_on_delivery.rb +14 -0
  8. data/app/models/taxon_decorator.rb +3 -0
  9. data/app/views/admin/overview/index.html.erb +170 -0
  10. data/app/views/admin/products/index.html.erb +94 -0
  11. data/app/views/shared/_filters.html.erb +0 -0
  12. data/config/initializers/secoint.rb +1 -0
  13. data/config/locales/affiliate_ru.yml +12 -0
  14. data/config/locales/devise.ru.yml +48 -0
  15. data/config/locales/email_to_friend_ru.yml +22 -0
  16. data/config/locales/reviews_ru.yml +59 -0
  17. data/config/locales/robokassa_ru.yml +14 -0
  18. data/config/locales/ru.yml +65 -0
  19. data/config/locales/wishlist_ru.yml +27 -0
  20. data/config/routes.rb +4 -0
  21. data/db/default/countries.yml +1583 -0
  22. data/db/default/roles.yml +7 -0
  23. data/db/default/states.yml +418 -0
  24. data/db/default/zone_members.yml +7 -0
  25. data/db/default/zones.yml +8 -0
  26. data/db/migrate/20090311090247_make_unicode_friendly.rb +48 -0
  27. data/db/migrate/20090827180351_add_secondname_to_addresses.rb +9 -0
  28. data/db/sample/calculators.yml +16 -0
  29. data/db/sample/preferences.yml +20 -0
  30. data/db/sample/shipping_categories.yml +3 -0
  31. data/db/sample/shipping_methods.yml +9 -0
  32. data/lib/synergy.rb +48 -0
  33. data/lib/synergy_hooks.rb +8 -0
  34. data/lib/tasks/install.rake +30 -0
  35. data/lib/tasks/synergy.rake +1 -0
  36. data/public/images/admin/icons/help.png +0 -0
  37. data/public/javascripts/admin/inline_help.js +24 -0
  38. data/public/javascripts/admin/jquery.simpletip-1.3.1.pack.js +14 -0
  39. data/public/stylesheets/admin/inline_help.css +21 -0
  40. data/public/stylesheets/admin/synergy.css +8 -0
  41. data/spec/spec_helper.rb +30 -0
  42. data/synergy.gemspec +30 -0
  43. metadata +261 -0
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ Synergy
2
+ =======
3
+
4
+ Решение для создания российских интернет-магазинов, основанное на Spree.
5
+
6
+
7
+ Установка
8
+ =========
9
+
10
+ 1. Установите Rails 3.0.5
11
+
12
+ gem install rails -v 3.0.5
13
+
14
+ 1. Создайте новое приложение
15
+
16
+ rails new synergy_app -GJTq
17
+
18
+ 1. Настройте подключение к базе данных (рекомендуется MySQL с адаптером mysql2)
19
+ 1. Добавьте в Gemfile следующие строки:
20
+
21
+ gem 'mysql2', '0.2.7'
22
+ gem 'spree', '~> 0.50.2'
23
+ gem 'spree_i18n', :git => 'git://github.com/spree/spree_i18n.git'
24
+ gem 'spree_static_content'
25
+ gem 'spree_editor'
26
+ gem 'spree_online_support'
27
+ gem 'spree_robokassa'
28
+ gem 'spree_yandex_market'
29
+ gem 'spree_address_book'
30
+ gem 'spree_dynamic_sitemaps'
31
+ gem 'synergy', :git => 'git://github.com/secoint/synergy.git'
32
+
33
+ 1. Выполните следующие команды:
34
+
35
+ bundle install
36
+ rails g spree:site -f
37
+ rake spree:install
38
+ rake synergy:install
39
+ rake db:migrate
40
+ rake db:seed db:sample
41
+ rails s
42
+
43
+
44
+ Обновление
45
+ ==========
46
+
47
+ bundle update
48
+ rake synergy:install
49
+ rake db:migrate
50
+ rails s
51
+
52
+
53
+ Copyright (c) 2011 Roman Smirnov, released under the GNU GPL v3
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+
7
+ gemfile = File.expand_path('../spec/test_app/Gemfile', __FILE__)
8
+ if File.exists?(gemfile) && (%w(spec cucumber).include?(ARGV.first.to_s) || ARGV.size == 0)
9
+ require 'bundler'
10
+ ENV['BUNDLE_GEMFILE'] = gemfile
11
+ Bundler.setup
12
+
13
+ require 'rspec'
14
+ require 'rspec/core/rake_task'
15
+ RSpec::Core::RakeTask.new
16
+
17
+ require 'cucumber/rake/task'
18
+ Cucumber::Rake::Task.new do |t|
19
+ t.cucumber_opts = %w{--format progress}
20
+ end
21
+ end
22
+
23
+ desc "Default Task"
24
+ task :default => [:spec, :cucumber ]
25
+
26
+ spec = eval(File.read('synergy.gemspec'))
27
+
28
+ Rake::GemPackageTask.new(spec) do |p|
29
+ p.gem_spec = spec
30
+ end
31
+
32
+ desc "Release to gemcutter"
33
+ task :release => :package do
34
+ require 'rake/gemcutter'
35
+ Rake::Gemcutter::Tasks.new(spec).define
36
+ Rake::Task['gem:push'].invoke
37
+ end
38
+
39
+ desc "Default Task"
40
+ task :default => [ :spec ]
41
+
42
+ desc "Regenerates a rails 3 app for testing"
43
+ task :test_app do
44
+ require '../spree/lib/generators/spree/test_app_generator'
45
+ class SynergyTestAppGenerator < Spree::Generators::TestAppGenerator
46
+
47
+ def install_gems
48
+ inside "test_app" do
49
+ run 'rake spree:install'
50
+ run 'rake synergy:install'
51
+ end
52
+ end
53
+
54
+ def migrate_db
55
+ run_migrations
56
+ end
57
+
58
+ protected
59
+ def full_path_for_local_gems
60
+ <<-gems
61
+ gem 'spree', :path => \'#{File.join(File.dirname(__FILE__), "../spree")}\'
62
+ gem 'synergy', :path => \'#{File.dirname(__FILE__)}\'
63
+ gems
64
+ end
65
+
66
+ end
67
+ SynergyTestAppGenerator.start
68
+ end
69
+
70
+ namespace :test_app do
71
+ desc 'Rebuild test and cucumber databases'
72
+ task :rebuild_dbs do
73
+ system("cd spec/test_app && rake db:drop db:migrate RAILS_ENV=test && rake db:drop db:migrate RAILS_ENV=cucumber")
74
+ end
75
+ end
76
+
@@ -0,0 +1,64 @@
1
+ # coding: utf-8
2
+ Admin::ProductsController.class_eval do
3
+ require 'nokogiri'
4
+ require 'open-uri'
5
+
6
+ def import_from_yandex_market
7
+ if params[:model_id].to_i > 0
8
+ market_url = "http://market.yandex.ru/model.xml?modelid=#{params[:model_id]}"
9
+ else
10
+ market_url = params[:model_id]
11
+ end
12
+ taxonomy = Taxonomy.find_or_create_by_name("Категории")
13
+ begin
14
+ doc = Nokogiri::HTML(open(market_url))
15
+
16
+ nbsp = [194.chr, 160.chr].join
17
+
18
+ properties = {}
19
+ doc.css('#main-spec-cont tr').each do |tr|
20
+ key = tr.children[0].content.strip
21
+ next if key == nbsp
22
+ properties[key] = tr.children[1].content.strip
23
+ end
24
+
25
+ taxon_name = doc.css(".b-breadcrumbs a").last.content
26
+ price = doc.css(".price span").first.content.gsub(nbsp, '')
27
+ image_urls = doc.css("#model-pictures a").map{|link| link['href'] }
28
+ name = doc.css("#global-model-name").first.content
29
+
30
+ p = Product.new(:name => name, :price => price)
31
+ p.available_on = Time.now if params[:available]
32
+ if p.valid?
33
+ taxon = Taxon.find_by_name(taxon_name)
34
+ taxon ||= Taxon.create(:name => taxon_name, :taxonomy_id => taxonomy.id, :parent_id => taxonomy.root.id)
35
+ p.taxons << taxon
36
+ properties.each do |prop_name, value|
37
+ full_prop_name = [taxon_name, prop_name].join(': ')
38
+ property = Property.find_by_name(full_prop_name)
39
+ property ||= Property.create(:name => full_prop_name, :presentation => prop_name)
40
+ p.product_properties << ProductProperty.new(:property_id => property.id, :value => value)
41
+ end
42
+
43
+ image_urls.each do |url|
44
+ p.images << Image.new(:attachment => download_remote_image(url))
45
+ end
46
+ if p.save
47
+ flash[:notice] = "Данные о товаре \"#{p.name}\" успешно скачены с Яндекс.Маркет"
48
+ end
49
+ end
50
+ rescue
51
+ flash[:error] = "Данные о товаре не удалось взять с Яндекс.Маркет, проверьте ID товара и наличие полной информации о нём по ссылке #{market_url}"
52
+ end
53
+ redirect_to admin_products_path
54
+ end
55
+
56
+ private
57
+
58
+ def download_remote_image(image_url)
59
+ io = open(URI.parse(image_url))
60
+ def io.original_filename; [base_uri.path.split('/').last, '.jpg'].join; end
61
+ io.original_filename.blank? ? nil : io
62
+ rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
63
+ end
64
+ end
@@ -0,0 +1,10 @@
1
+ CheckoutController.class_eval do
2
+ private
3
+
4
+ def after_payment
5
+ if @order.shipping_method.calculator.is_a?(Calculator::CashOnDelivery)
6
+ @order.complete_without_payment!
7
+ after_complete
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ class Calculator::CashOnDelivery < Calculator
2
+ def self.description
3
+ I18n.t("cash_on_delivery")
4
+ end
5
+
6
+ def self.register
7
+ super
8
+ ShippingMethod.register_calculator(self)
9
+ end
10
+
11
+ def compute(object)
12
+ 0
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ Taxon.class_eval do
2
+ before_update :set_permalink
3
+ end
@@ -0,0 +1,170 @@
1
+ <h1><%= t("overview") %></h1>
2
+
3
+ <%= hook :admin_dashboard do %>
4
+ <% if @show_dashboard %>
5
+ <div class="dashboard">
6
+ <div class="dashboard_left">
7
+ <%= hook :admin_dashboard_left do %>
8
+ <div class="dashboard_small_wrapper">
9
+ <h2><%= t('best_selling_products') %></h2>
10
+ <div id="best_selling_products" style="width:50%;height:170px;float:left"></div>
11
+ <div id="pie_legend">
12
+ <% @best_selling_variants.each_with_index do |v,i| %>
13
+ <span style="background-color:<%= @pie_colors[i] %>">&nbsp;</span>
14
+ <label><%= truncate v[0], :length => 27 %></label>
15
+ <div><%= number_with_delimiter v[1] %> <%= t(:units) %></div>
16
+ <% end %>
17
+ </div>
18
+
19
+ <h2><%= t('top_grossing_products') %></h2>
20
+ <div id="top_grossing_products" style="width:50%;height:170px;float:left"></div>
21
+ <div id="pie_legend">
22
+ <% @top_grossing_variants.each_with_index do |v,i| %>
23
+ <span style="background-color:<%= @pie_colors[i] %>">&nbsp;</span>
24
+ <label><%= truncate v[0], :length => 27 %></label>
25
+ <div><%= number_to_currency v[1], :precision => 0 %></div>
26
+ <% end %>
27
+ </div>
28
+
29
+ <h2><%= t('best_selling_taxons') %></h2>
30
+ <div id="best_selling_taxons" style="width:50%;height:170px;float:left"></div>
31
+ <div id="pie_legend">
32
+ <% @best_selling_taxons.each_with_index do |t,i| %>
33
+ <span style="background-color:<%= @pie_colors[i] %>">&nbsp;</span>
34
+ <label><%= truncate t[0], :length => 27 %></label>
35
+ <div><%= number_with_delimiter t[1] %> <%= t(:units) %></div>
36
+ <% end %>
37
+ </div>
38
+
39
+ </div>
40
+ </div>
41
+ <% end %>
42
+ <%= hook :admin_dashboard_center do %>
43
+ <div class="dashboard_main">
44
+ <div class="dashboard_main_wrapper">
45
+ <h2 id="order_by_day_title"><%= t('orders') %> <%= t('count') %> <%= t('by_day') %> (<%= t('last_7_days') %>)</h2>
46
+
47
+ <table id="order_totals">
48
+ <tr>
49
+ <td style="width:23%;">
50
+ <label id="orders_total"><%= number_with_delimiter @orders_total.to_i %>&nbsp;<%= t("number.currency.format.unit") %></label>
51
+ <span><%= t('orders') %> (<%= t('amount') %>)</span>
52
+ </td>
53
+ <td class="spacer">|</td>
54
+ <td style="width:23%;">
55
+ <label id="orders_line_total"><%= number_with_delimiter @orders_line_total.to_i %>&nbsp;<%= t("number.currency.format.unit") %></label>
56
+ <span><%== t('items') %> (<%= t('amount') %>)</span>
57
+ </td>
58
+ <td class="spacer">|</td>
59
+ <td style="width:23%;">
60
+ <label id="orders_adjustment_total"><%= number_with_delimiter @orders_adjustment_total.to_i %>&nbsp;<%= t("number.currency.format.unit") %></label>
61
+ <span><%= t('adjustments') %></span>
62
+ </td>
63
+ <td class="spacer">|</td>
64
+ <td style="width:22%">
65
+ <label id="orders_adjustment_total"><%= number_with_delimiter @orders_credit_total.to_i %>&nbsp;<%= t("number.currency.format.unit") %></label>
66
+ <span><%= t('credits') %></span>
67
+ </td>
68
+ </tr>
69
+ </table>
70
+
71
+ <div id="orders_by_day" style="width:100%;height:240px;"></div>
72
+
73
+ <div id="orders_by_day_options">
74
+ <label><%= t('range') %>:</label>
75
+ <%= select_tag "orders_by_day_reports", options_for_select([[t('last_7_days'), "7_days"], [t('last_14_days'), "14_days"], [t('this_month'), "this_month"],
76
+ [t('last_month'), "last_month"], [t('this_year'), "this_year"], [t('last_year'), "last_year"] ], "7_days") %>
77
+ <label><%= t('value') %>:</label>
78
+ <%= select_tag "orders_by_day_value", options_for_select({t('count') => 'Count', t('value') => 'Value'}, 'Count') %>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <% end %>
83
+ <%= hook :admin_dashboard_right do %>
84
+ <div class="dashboard_right">
85
+ <h2><%= t('last_5_orders') %></h2>
86
+ <table>
87
+ <thead>
88
+ <tr>
89
+ <th><%= t('user') %></th>
90
+ <th class="text-right"><%== t('items') %></th>
91
+ <th class="text-right"><%= t('total') %></th>
92
+ </tr>
93
+ </thead>
94
+ <% @last_five_orders.each do |order| %>
95
+ <tr>
96
+ <td><%= truncate order[0], :length => 18 %></td>
97
+ <td class="text-right"><%= order[1] %></td>
98
+ <td class="text-right"><%= number_to_currency order[2] %></td>
99
+ </tr>
100
+ <% end %>
101
+ </table>
102
+
103
+ <h2><%= t('5_biggest_spenders') %></h2>
104
+ <table>
105
+ <thead>
106
+ <tr>
107
+ <th><%= t('email') %></th>
108
+ <th class="text-right"><%= t('ord_qty') %></th>
109
+ <th class="text-right"><%= t('ord_total') %></th>
110
+ </tr>
111
+ </thead>
112
+ <% @biggest_spenders.each do |order| %>
113
+ <tr>
114
+ <td><%= truncate order[0], :length => 18 %></td>
115
+ <td class="text-right"><%= order[1] %></td>
116
+ <td class="text-right"><%= number_to_currency order[2] %></td>
117
+ </tr>
118
+ <% end %>
119
+ </table>
120
+
121
+ <h2><%= t('out_of_stock_products') %></h2>
122
+ <table>
123
+ <thead>
124
+ <tr>
125
+ <th><%= t('name') %></th>
126
+ </tr>
127
+ </thead>
128
+ <% @out_of_stock_products.each do |product| %>
129
+ <tr>
130
+ <td><%= product.name %></td>
131
+ </tr>
132
+ <% end %>
133
+ </table>
134
+ </div>
135
+ </div>
136
+ <% end %>
137
+ <p style="clear:both;">&nbsp;</p>
138
+ <% else %>
139
+ <%= hook :admin_dashboard_welcome do %>
140
+ <%= raw t('overview_welcome') %>
141
+ <% end %>
142
+ <% end %>
143
+ <% end %>
144
+
145
+ <% content_for :head do %>
146
+ <%= hook :admin_dashboard_javascript do %>
147
+ <% if @show_dashboard %>
148
+ <script type="text/javascript">
149
+ var orders_by_day_points = [[<%= raw @orders_by_day.map { |day| "[\"#{day[0]}\",#{day[1]}]" }.join(",") %>]];
150
+ var best_selling_variants_points = [<%= raw @best_selling_variants.map { |v| "[\"#{v[0]}\",#{v[1]}]" }.join(",") %>];
151
+ var top_grossing_variants_points = [<%= raw @top_grossing_variants.map { |v| "[\"#{v[0]}\",#{v[1]}]" }.join(",") %>];
152
+ var best_selling_taxons_points = [<%= raw @best_selling_taxons.map { |t| "[\"#{t[0]}\",#{t[1]}]" }.join(",") %>];
153
+
154
+ var orders = "<%= t(:orders) %>";
155
+ var by_day = "<%= t(:by_day) %>";
156
+
157
+ var pie_colors = [<%= raw @pie_colors.map{|c| "'#{c}'"}.join(",") %>];
158
+ </script>
159
+
160
+ <%= javascript_include_tag 'jqPlot/jquery.jqplot.min.js', 'jqPlot/plugins/jqplot.dateAxisRenderer.min.js', 'jqPlot/plugins/jqplot.highlighter.min.js',
161
+ 'jqPlot/plugins/jqplot.canvasAxisTickRenderer.min.js', 'jqPlot/plugins/jqplot.canvasTextRenderer.min.js', 'jqPlot/plugins/jqplot.canvasAxisLabelRenderer.min.js',
162
+ 'jqPlot/plugins/jqplot.pieRenderer.min.js', 'dashboard.js' %>
163
+ <!--[if IE]><%= javascript_include_tag 'jqPlot/excanvas.min.js' %><![endif]-->
164
+ <% end %>
165
+ <% end %>
166
+
167
+ <%= hook :admin_dashboard_stylesheet do %>
168
+ <%= stylesheet_link_tag 'admin/dashboard.css' %>
169
+ <% end %>
170
+ <% end %>
@@ -0,0 +1,94 @@
1
+ <%= render :partial => 'admin/shared/product_sub_menu' %>
2
+
3
+ <div class='toolbar'>
4
+ <ul class='actions'>
5
+ <li id="new_product_link">
6
+ <%= button_link_to t("new_product"), new_object_url, {:remote => true, :icon => 'add', :id => 'admin_new_product'} %>
7
+ </li>
8
+ </ul>
9
+ <br class='clear' />
10
+ </div>
11
+
12
+ <h1><%= "#{t("actions.listing")} #{t("products")}" %></h1>
13
+
14
+ <div id="new_product"></div>
15
+
16
+ <table class="index" id='listing_products'>
17
+ <tr>
18
+ <%= hook :admin_products_index_headers do %>
19
+ <th><%= t("sku") %></th>
20
+ <th><%= sort_link @search,:name, t("name"), {}, {:title => 'admin_products_listing_name_title'} %></th>
21
+ <th><%= sort_link @search,:master_price, t("master_price") %></th>
22
+ <% end %>
23
+ <th>
24
+ <%= hook :admin_products_index_header_actions %>
25
+ </th>
26
+ </tr>
27
+ <% @collection.each do |product| %>
28
+ <tr <%= 'style="color:red;"' if product.deleted? %> id="<%= dom_id product %>">
29
+ <%- locals = {:product => product} %>
30
+ <%= hook :admin_products_index_rows, locals do %>
31
+ <td><%= product.sku %></td>
32
+ <td><%= product.name %></td>
33
+ <td><%= product.price %></td>
34
+ <% end %>
35
+ <td class="actions">
36
+ <%= hook :admin_products_index_row_actions, locals do %>
37
+ <%= link_to_edit product, :class => 'edit' unless product.deleted? %>
38
+ &nbsp;
39
+ <%= link_to_clone product, :class => 'clone' %>
40
+ &nbsp;
41
+ <%= link_to_delete product unless product.deleted? %>
42
+ <% end %>
43
+ </td>
44
+ </tr>
45
+ <% end %>
46
+ </table>
47
+ <%= will_paginate(:previous_label => "&#171; #{t('previous')}", :next_label => "#{t('next')} &#187;") %>
48
+
49
+ <% content_for :sidebar do %>
50
+
51
+ <%= form_for [:admin, @search] do |f| %>
52
+ <div class="box">
53
+ <h3><%= t(:search) %></h3>
54
+
55
+ <%- locals = {:f => f} %>
56
+ <%= hook :admin_products_index_search, locals do %>
57
+ <p>
58
+ <%= f.label :name_contains, t("name") %><br />
59
+ <%= f.text_field :name_contains, :size => 15 %>
60
+ </p>
61
+ <p>
62
+ <%= f.label :variants_including_master_sku_contains, t("sku") %><br />
63
+ <%= f.text_field :variants_including_master_sku_contains, :size => 15 %>
64
+ </p>
65
+ <p>
66
+ <%= f.label :deleted_at_is_null, t("show_deleted") %><br />
67
+ <%= f.check_box :deleted_at_is_null, {:checked => params[:search][:deleted_at_is_null].blank?}, "", "1" %>
68
+ </p>
69
+ <% end %>
70
+
71
+ <%= hook :admin_products_index_search_buttons, locals do %>
72
+ <p class="form-buttons">
73
+ <%= button t("search") %>
74
+ </p>
75
+ <% end %>
76
+ </div>
77
+ <% end %>
78
+
79
+ <%= form_tag import_from_yandex_market_path, :method => :get do %>
80
+ <div class="box">
81
+ <h3>Импорт с Яндекс.Маркет</h3>
82
+ <p>
83
+ <%= label_tag :model_id, "ID товара на ЯМ" %><br />
84
+ <%= text_field_tag :model_id %>
85
+ </p>
86
+ <p>
87
+ <%= check_box_tag :available %>&nbsp;<%= label_tag :available, "сделать доступным" %>
88
+ </p>
89
+ <p class="form-buttons">
90
+ <%= button "Импорт" %>
91
+ </p>
92
+ </div>
93
+ <% end %>
94
+ <% end %>