synergy 0.50.0.rc1

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