Commit 53d8ceb4d43536a7311c19bc15d4ed5911f5b88a
Exists in
master
and in
22 other branches
Merge branch 'merge-requests/248' into redemoinho
Conflicts: lib/noosfero/plugin/settings.rb test/unit/plugin_settings_test.rb
Showing
37 changed files
with
690 additions
and
117 deletions
Show diff stats
app/controllers/public/catalog_controller.rb
| 1 | class CatalogController < PublicController | 1 | class CatalogController < PublicController |
| 2 | needs_profile | 2 | needs_profile |
| 3 | + no_design_blocks | ||
| 3 | 4 | ||
| 4 | before_filter :check_enterprise_and_environment | 5 | before_filter :check_enterprise_and_environment |
| 5 | 6 | ||
| 6 | def index | 7 | def index |
| 7 | - @products = @profile.products.paginate(:order => 'name asc', :per_page => 9, :page => params[:page]) | 8 | + @category = params[:level] ? ProductCategory.find(params[:level]) : nil |
| 9 | + @products = @profile.products.from_category(@category).paginate(:order => 'available desc, highlighted desc, name asc', :per_page => 9, :page => params[:page]) | ||
| 10 | + @categories = ProductCategory.on_level(params[:level]) | ||
| 8 | end | 11 | end |
| 9 | 12 | ||
| 10 | protected | 13 | protected |
app/helpers/catalog_helper.rb
| @@ -3,4 +3,28 @@ module CatalogHelper | @@ -3,4 +3,28 @@ module CatalogHelper | ||
| 3 | include DisplayHelper | 3 | include DisplayHelper |
| 4 | include ManageProductsHelper | 4 | include ManageProductsHelper |
| 5 | 5 | ||
| 6 | + def breadcrumb(category) | ||
| 7 | + start = link_to(_('Start'), {:action => 'index'}) | ||
| 8 | + ancestors = category.ancestors.map { |c| link_to(c.name, {:action => 'index', :level => c.id}) }.reverse | ||
| 9 | + current_level = content_tag('strong', category.name) | ||
| 10 | + all_items = [start] + ancestors + [current_level] | ||
| 11 | + content_tag('div', all_items.join(' → '), :id => 'breadcrumb') | ||
| 12 | + end | ||
| 13 | + | ||
| 14 | + def category_link(category, sub = false) | ||
| 15 | + count = profile.products.from_category(category).count | ||
| 16 | + name = truncate(category.name, :length => 22 - count.to_s.size) | ||
| 17 | + link_name = sub ? name : content_tag('strong', name) | ||
| 18 | + link = link_to(link_name, {:action => 'index', :level => category.id}, :title => category.name) | ||
| 19 | + content_tag('li', "#{link} (#{count})") if count > 0 | ||
| 20 | + end | ||
| 21 | + | ||
| 22 | + def category_sub_links(category) | ||
| 23 | + sub_categories = [] | ||
| 24 | + category.children.each do |sub_category| | ||
| 25 | + sub_categories << category_link(sub_category, true) | ||
| 26 | + end | ||
| 27 | + content_tag('ul', sub_categories) if sub_categories.size > 1 | ||
| 28 | + end | ||
| 29 | + | ||
| 6 | end | 30 | end |
app/helpers/display_helper.rb
| @@ -8,6 +8,14 @@ module DisplayHelper | @@ -8,6 +8,14 @@ module DisplayHelper | ||
| 8 | opts | 8 | opts |
| 9 | end | 9 | end |
| 10 | 10 | ||
| 11 | + def themed_path(file) | ||
| 12 | + if File.exists?(File.join(Rails.root, 'public', theme_path, file)) | ||
| 13 | + File.join(theme_path, file) | ||
| 14 | + else | ||
| 15 | + file | ||
| 16 | + end | ||
| 17 | + end | ||
| 18 | + | ||
| 11 | def image_link_to_product(product, opts={}) | 19 | def image_link_to_product(product, opts={}) |
| 12 | return _('No product') unless product | 20 | return _('No product') unless product |
| 13 | target = product_path(product) | 21 | target = product_path(product) |
app/models/category.rb
| @@ -13,6 +13,16 @@ class Category < ActiveRecord::Base | @@ -13,6 +13,16 @@ class Category < ActiveRecord::Base | ||
| 13 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} | 13 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | + named_scope :on_level, lambda { |parent| {:conditions => {:parent_id => parent}} } | ||
| 17 | + | ||
| 18 | + named_scope :sub_categories, lambda { |category| | ||
| 19 | + {:conditions => ['categories.path LIKE ? AND categories.id != ?', "%#{category.slug}%", category.id]} | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + named_scope :sub_tree, lambda { |category| | ||
| 23 | + {:conditions => ['categories.path LIKE ?', "%#{category.slug}%"]} | ||
| 24 | + } | ||
| 25 | + | ||
| 16 | acts_as_filesystem | 26 | acts_as_filesystem |
| 17 | 27 | ||
| 18 | has_many :article_categorizations, :dependent => :destroy | 28 | has_many :article_categorizations, :dependent => :destroy |
app/models/organization.rb
| @@ -78,6 +78,8 @@ class Organization < Profile | @@ -78,6 +78,8 @@ class Organization < Profile | ||
| 78 | country | 78 | country |
| 79 | tag_list | 79 | tag_list |
| 80 | template_id | 80 | template_id |
| 81 | + district | ||
| 82 | + address_reference | ||
| 81 | ] | 83 | ] |
| 82 | 84 | ||
| 83 | def self.fields | 85 | def self.fields |
| @@ -96,8 +98,8 @@ class Organization < Profile | @@ -96,8 +98,8 @@ class Organization < Profile | ||
| 96 | [] | 98 | [] |
| 97 | end | 99 | end |
| 98 | 100 | ||
| 99 | - N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Tag list') | ||
| 100 | - settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information | 101 | + N_('Display name'); N_('Description'); N_('Contact person'); N_('Contact email'); N_('Acronym'); N_('Foundation year'); N_('Legal form'); N_('Economic activity'); N_('Management information'); N_('Tag list'); N_('District'); N_('Address reference') |
| 102 | + settings_items :display_name, :description, :contact_person, :contact_email, :acronym, :foundation_year, :legal_form, :economic_activity, :management_information, :district, :address_reference | ||
| 101 | 103 | ||
| 102 | validates_format_of :foundation_year, :with => Noosfero::Constants::INTEGER_FORMAT | 104 | validates_format_of :foundation_year, :with => Noosfero::Constants::INTEGER_FORMAT |
| 103 | validates_format_of :contact_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda { |org| !org.contact_email.blank? }) | 105 | validates_format_of :contact_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda { |org| !org.contact_email.blank? }) |
app/models/person.rb
| @@ -147,6 +147,9 @@ class Person < Profile | @@ -147,6 +147,9 @@ class Person < Profile | ||
| 147 | contact_phone | 147 | contact_phone |
| 148 | contact_information | 148 | contact_information |
| 149 | description | 149 | description |
| 150 | + image | ||
| 151 | + district | ||
| 152 | + address_reference | ||
| 150 | ] | 153 | ] |
| 151 | 154 | ||
| 152 | validates_multiparameter_assignments | 155 | validates_multiparameter_assignments |
| @@ -201,8 +204,8 @@ class Person < Profile | @@ -201,8 +204,8 @@ class Person < Profile | ||
| 201 | N_('Education'); N_('Custom education'); N_('Custom area of study'); | 204 | N_('Education'); N_('Custom education'); N_('Custom area of study'); |
| 202 | settings_items :formation, :custom_formation, :custom_area_of_study | 205 | settings_items :formation, :custom_formation, :custom_area_of_study |
| 203 | 206 | ||
| 204 | - N_('Contact information'); N_('City'); N_('State'); N_('Country'); N_('Sex'); N_('Zip code') | ||
| 205 | - settings_items :photo, :contact_information, :sex, :city, :state, :country, :zip_code | 207 | + N_('Contact information'); N_('City'); N_('State'); N_('Country'); N_('Sex'); N_('Zip code'); N_('District'); N_('Address reference') |
| 208 | + settings_items :photo, :contact_information, :sex, :city, :state, :country, :zip_code, :district, :address_reference | ||
| 206 | 209 | ||
| 207 | extend SetProfileRegionFromCityState::ClassMethods | 210 | extend SetProfileRegionFromCityState::ClassMethods |
| 208 | set_profile_region_from_city_state | 211 | set_profile_region_from_city_state |
app/models/product.rb
| @@ -23,6 +23,10 @@ class Product < ActiveRecord::Base | @@ -23,6 +23,10 @@ class Product < ActiveRecord::Base | ||
| 23 | 23 | ||
| 24 | named_scope :more_recent, :order => "created_at DESC" | 24 | named_scope :more_recent, :order => "created_at DESC" |
| 25 | 25 | ||
| 26 | + named_scope :from_category, lambda { |category| | ||
| 27 | + {:joins => :product_category, :conditions => ['categories.path LIKE ?', "%#{category.slug}%"]} if category | ||
| 28 | + } | ||
| 29 | + | ||
| 26 | after_update :save_image | 30 | after_update :save_image |
| 27 | 31 | ||
| 28 | def lat | 32 | def lat |
app/models/profile.rb
| @@ -141,6 +141,10 @@ class Profile < ActiveRecord::Base | @@ -141,6 +141,10 @@ class Profile < ActiveRecord::Base | ||
| 141 | 141 | ||
| 142 | acts_as_having_settings :field => :data | 142 | acts_as_having_settings :field => :data |
| 143 | 143 | ||
| 144 | + def settings | ||
| 145 | + data | ||
| 146 | + end | ||
| 147 | + | ||
| 144 | settings_items :redirect_l10n, :type => :boolean, :default => false | 148 | settings_items :redirect_l10n, :type => :boolean, :default => false |
| 145 | settings_items :public_content, :type => :boolean, :default => true | 149 | settings_items :public_content, :type => :boolean, :default => true |
| 146 | settings_items :description | 150 | settings_items :description |
| @@ -229,7 +233,7 @@ class Profile < ActiveRecord::Base | @@ -229,7 +233,7 @@ class Profile < ActiveRecord::Base | ||
| 229 | if myregion | 233 | if myregion |
| 230 | myregion.hierarchy.reverse.first(2).map(&:name).join(separator) | 234 | myregion.hierarchy.reverse.first(2).map(&:name).join(separator) |
| 231 | else | 235 | else |
| 232 | - %w[address city state country_name zip_code ].map {|item| (self.respond_to?(item) && !self.send(item).blank?) ? self.send(item) : nil }.compact.join(separator) | 236 | + %w[address district city state country_name zip_code ].map {|item| (self.respond_to?(item) && !self.send(item).blank?) ? self.send(item) : nil }.compact.join(separator) |
| 233 | end | 237 | end |
| 234 | end | 238 | end |
| 235 | 239 | ||
| @@ -694,7 +698,7 @@ private :generate_url, :url_options | @@ -694,7 +698,7 @@ private :generate_url, :url_options | ||
| 694 | def custom_footer_expanded | 698 | def custom_footer_expanded |
| 695 | footer = custom_footer | 699 | footer = custom_footer |
| 696 | if footer | 700 | if footer |
| 697 | - %w[contact_person contact_email contact_phone location address economic_activity city state country zip_code].each do |att| | 701 | + %w[contact_person contact_email contact_phone location address district address_reference economic_activity city state country zip_code].each do |att| |
| 698 | if self.respond_to?(att) && footer.match(/\{[^{]*#{att}\}/) | 702 | if self.respond_to?(att) && footer.match(/\{[^{]*#{att}\}/) |
| 699 | if !self.send(att).nil? && !self.send(att).blank? | 703 | if !self.send(att).nil? && !self.send(att).blank? |
| 700 | footer = footer.gsub(/\{([^{]*)#{att}\}/, '\1' + self.send(att)) | 704 | footer = footer.gsub(/\{([^{]*)#{att}\}/, '\1' + self.send(att)) |
app/views/catalog/index.rhtml
| 1 | <% extra_content = [] %> | 1 | <% extra_content = [] %> |
| 2 | <% extra_content_list = [] %> | 2 | <% extra_content_list = [] %> |
| 3 | 3 | ||
| 4 | -<ul id="product-list"> | ||
| 5 | - <li><h1><%= _('Products/Services') %></h1></li> | 4 | +<h1><%= _('Products/Services') %></h1> |
| 6 | 5 | ||
| 6 | +<%= breadcrumb(@category) if params[:level] %> | ||
| 7 | + | ||
| 8 | +<div class='l-sidebar-left-bar'> | ||
| 9 | + <ul> | ||
| 10 | + <% if @categories.size > 0 %> | ||
| 11 | + <% @categories.each do |category| %> | ||
| 12 | + <%= category_link(category) %> | ||
| 13 | + <%= category_sub_links(category) %> | ||
| 14 | + <% end %> | ||
| 15 | + <% else %> | ||
| 16 | + <%= content_tag('li', _('There are no sub-categories for %s') % @category.name, :style => 'color: #555753; padding-bottom: 0.5em;') %> | ||
| 17 | + <% end %> | ||
| 18 | + </ul> | ||
| 19 | +</div> | ||
| 20 | + | ||
| 21 | +<ul id="product-list" class="l-sidebar-left-content"> | ||
| 7 | <% @products.each do |product| %> | 22 | <% @products.each do |product| %> |
| 8 | <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %> | 23 | <% extra_content = @plugins.dispatch(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %> |
| 9 | <% extra_content_list = @plugins.dispatch(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %> | 24 | <% extra_content_list = @plugins.dispatch(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %> |
| 10 | 25 | ||
| 11 | - <li class="product <%= "not-available" unless product.available %>"> | 26 | + <% status = [] %> |
| 27 | + <% status << 'not-available' if !product.available %> | ||
| 28 | + <% status << 'highlighted' if product.highlighted %> | ||
| 29 | + | ||
| 30 | + <li class="product <%= status.join(' ') %>"> | ||
| 12 | <ul> | 31 | <ul> |
| 13 | <li class="product-image-link"> | 32 | <li class="product-image-link"> |
| 33 | + <% if product.highlighted? %> | ||
| 34 | + <%= link_to image_tag(themed_path('/images/star.png'), :class => 'star', :alt => _('Highlighted product')), product_path(product) %> | ||
| 35 | + <% end %> | ||
| 14 | <% if product.image %> | 36 | <% if product.image %> |
| 15 | <div class="zoomable-image"> | 37 | <div class="zoomable-image"> |
| 16 | <%= link_to_product product, :class => 'product-big', :style => "background-image: url(#{product.default_image(:big)})" %> | 38 | <%= link_to_product product, :class => 'product-big', :style => "background-image: url(#{product.default_image(:big)})" %> |
app/views/profile_editor/_person_form.rhtml
| @@ -21,6 +21,8 @@ | @@ -21,6 +21,8 @@ | ||
| 21 | <%= optional_field(@person, 'city', f.text_field(:city, :rel => _('City'))) %> | 21 | <%= optional_field(@person, 'city', f.text_field(:city, :rel => _('City'))) %> |
| 22 | <%= optional_field(@person, 'zip_code', labelled_form_field(_('ZIP code'), text_field(:profile_data, :zip_code, :rel => _('ZIP code')))) %> | 22 | <%= optional_field(@person, 'zip_code', labelled_form_field(_('ZIP code'), text_field(:profile_data, :zip_code, :rel => _('ZIP code')))) %> |
| 23 | <%= optional_field(@person, 'address', labelled_form_field(_('Address (street and number)'), text_field(:profile_data, :address, :rel => _('Address')))) %> | 23 | <%= optional_field(@person, 'address', labelled_form_field(_('Address (street and number)'), text_field(:profile_data, :address, :rel => _('Address')))) %> |
| 24 | +<%= optional_field(profile, 'address_reference', labelled_form_field(_('Address reference'), text_field(object_name, :address_reference, :rel => _('Address reference')))) %> | ||
| 25 | +<%= optional_field(@person, 'district', labelled_form_field(_('District'), text_field(:profile_data, :district, :rel => _('District')))) %> | ||
| 24 | 26 | ||
| 25 | <% optional_field(@person, 'schooling') do %> | 27 | <% optional_field(@person, 'schooling') do %> |
| 26 | <div class="formfieldline"> | 28 | <div class="formfieldline"> |
app/views/search/_product.rhtml
| 1 | <% extra_content = @plugins.dispatch(:asset_product_extras, product, product.enterprise).collect { |content| instance_eval(&content) } %> | 1 | <% extra_content = @plugins.dispatch(:asset_product_extras, product, product.enterprise).collect { |content| instance_eval(&content) } %> |
| 2 | <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%> | 2 | <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%> |
| 3 | 3 | ||
| 4 | -<li class="search-product-item"> | 4 | +<li class="search-product-item <%= 'highlighted' if product.highlighted? %>"> |
| 5 | 5 | ||
| 6 | <div class="search-product-item-first-column"> | 6 | <div class="search-product-item-first-column"> |
| 7 | <%= render :partial => 'search/image', :object => product %> | 7 | <%= render :partial => 'search/image', :object => product %> |
app/views/shared/_organization_custom_fields.rhtml
| @@ -10,6 +10,8 @@ | @@ -10,6 +10,8 @@ | ||
| 10 | <%= optional_field(profile, 'economic_activity', f.text_field(:economic_activity)) %> | 10 | <%= optional_field(profile, 'economic_activity', f.text_field(:economic_activity)) %> |
| 11 | <%= optional_field(profile, 'management_information', f.text_area(:management_information, :rows => 5)) %> | 11 | <%= optional_field(profile, 'management_information', f.text_area(:management_information, :rows => 5)) %> |
| 12 | <%= optional_field(profile, 'address', labelled_form_field(_('Address (street and number)'), text_field(object_name, :address))) %> | 12 | <%= optional_field(profile, 'address', labelled_form_field(_('Address (street and number)'), text_field(object_name, :address))) %> |
| 13 | +<%= optional_field(profile, 'address_reference', labelled_form_field(_('Address reference'), text_field(object_name, :address_reference))) %> | ||
| 14 | +<%= optional_field(profile, 'district', labelled_form_field(_('District'), text_field(object_name, :district))) %> | ||
| 13 | <%= optional_field(profile, 'zip_code', labelled_form_field(_('ZIP code'), text_field(object_name, :zip_code))) %> | 15 | <%= optional_field(profile, 'zip_code', labelled_form_field(_('ZIP code'), text_field(object_name, :zip_code))) %> |
| 14 | <%= optional_field(profile, 'city', f.text_field(:city)) %> | 16 | <%= optional_field(profile, 'city', f.text_field(:city)) %> |
| 15 | <%= optional_field(profile, 'state', f.text_field(:state)) %> | 17 | <%= optional_field(profile, 'state', f.text_field(:state)) %> |
lib/noosfero/plugin/settings.rb
| 1 | class Noosfero::Plugin::Settings | 1 | class Noosfero::Plugin::Settings |
| 2 | 2 | ||
| 3 | - def initialize(environment, plugin, attributes = nil) | ||
| 4 | - @environment = environment | 3 | + def initialize(base, plugin, attributes = nil) |
| 4 | + @base = base | ||
| 5 | @plugin = plugin | 5 | @plugin = plugin |
| 6 | attributes ||= {} | 6 | attributes ||= {} |
| 7 | attributes.each do |k,v| | 7 | attributes.each do |k,v| |
| @@ -10,7 +10,7 @@ class Noosfero::Plugin::Settings | @@ -10,7 +10,7 @@ class Noosfero::Plugin::Settings | ||
| 10 | end | 10 | end |
| 11 | 11 | ||
| 12 | def settings | 12 | def settings |
| 13 | - @environment.settings["#{@plugin.public_name}_plugin".to_sym] ||= {} | 13 | + @base.settings["#{@plugin.public_name}_plugin".to_sym] ||= {} |
| 14 | end | 14 | end |
| 15 | 15 | ||
| 16 | def method_missing(method, *args, &block) | 16 | def method_missing(method, *args, &block) |
| @@ -38,7 +38,11 @@ class Noosfero::Plugin::Settings | @@ -38,7 +38,11 @@ class Noosfero::Plugin::Settings | ||
| 38 | end | 38 | end |
| 39 | 39 | ||
| 40 | def save! | 40 | def save! |
| 41 | +<<<<<<< HEAD | ||
| 41 | @environment.save! | 42 | @environment.save! |
| 43 | +======= | ||
| 44 | + @base.save! | ||
| 45 | +>>>>>>> merge-requests/248 | ||
| 42 | end | 46 | end |
| 43 | 47 | ||
| 44 | end | 48 | end |
plugins/shopping_cart/controllers/shopping_cart_plugin_myprofile_controller.rb
| @@ -4,9 +4,12 @@ class ShoppingCartPluginMyprofileController < MyProfileController | @@ -4,9 +4,12 @@ class ShoppingCartPluginMyprofileController < MyProfileController | ||
| 4 | append_view_path File.join(File.dirname(__FILE__) + '/../views') | 4 | append_view_path File.join(File.dirname(__FILE__) + '/../views') |
| 5 | 5 | ||
| 6 | def edit | 6 | def edit |
| 7 | + params[:settings] = treat_cart_options(params[:settings]) | ||
| 8 | + | ||
| 9 | + @settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin, params[:settings]) | ||
| 7 | if request.post? | 10 | if request.post? |
| 8 | begin | 11 | begin |
| 9 | - profile.update_attributes!(params[:profile_attr]) | 12 | + @settings.save! |
| 10 | session[:notice] = _('Option updated successfully.') | 13 | session[:notice] = _('Option updated successfully.') |
| 11 | rescue Exception => exception | 14 | rescue Exception => exception |
| 12 | session[:notice] = _('Option wasn\'t updated successfully.') | 15 | session[:notice] = _('Option wasn\'t updated successfully.') |
| @@ -46,4 +49,25 @@ class ShoppingCartPluginMyprofileController < MyProfileController | @@ -46,4 +49,25 @@ class ShoppingCartPluginMyprofileController < MyProfileController | ||
| 46 | order.save! | 49 | order.save! |
| 47 | redirect_to :action => 'reports', :from => params[:context_from], :to => params[:context_to], :filter_status => params[:context_status] | 50 | redirect_to :action => 'reports', :from => params[:context_from], :to => params[:context_to], :filter_status => params[:context_status] |
| 48 | end | 51 | end |
| 52 | + | ||
| 53 | + private | ||
| 54 | + | ||
| 55 | + def treat_cart_options(settings) | ||
| 56 | + return if settings.blank? | ||
| 57 | + settings[:enabled] = settings[:enabled] == '1' | ||
| 58 | + settings[:delivery] = settings[:delivery] == '1' | ||
| 59 | + settings[:free_delivery_price] = settings[:free_delivery_price].blank? ? nil : settings[:free_delivery_price].to_d | ||
| 60 | + settings[:delivery_options] = treat_delivery_options(settings[:delivery_options]) | ||
| 61 | + settings | ||
| 62 | + end | ||
| 63 | + | ||
| 64 | + def treat_delivery_options(params) | ||
| 65 | + result = {} | ||
| 66 | + params[:options].size.times do |counter| | ||
| 67 | + if params[:options][counter].present? && params[:prices][counter].present? | ||
| 68 | + result[params[:options][counter]] = params[:prices][counter] | ||
| 69 | + end | ||
| 70 | + end | ||
| 71 | + result | ||
| 72 | + end | ||
| 49 | end | 73 | end |
plugins/shopping_cart/controllers/shopping_cart_plugin_profile_controller.rb
| @@ -85,14 +85,15 @@ class ShoppingCartPluginProfileController < ProfileController | @@ -85,14 +85,15 @@ class ShoppingCartPluginProfileController < ProfileController | ||
| 85 | 85 | ||
| 86 | def buy | 86 | def buy |
| 87 | @environment = profile.environment | 87 | @environment = profile.environment |
| 88 | + @settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin) | ||
| 88 | render :layout => false | 89 | render :layout => false |
| 89 | end | 90 | end |
| 90 | 91 | ||
| 91 | def send_request | 92 | def send_request |
| 92 | register_order(params[:customer], session[:cart][:items]) | 93 | register_order(params[:customer], session[:cart][:items]) |
| 93 | begin | 94 | begin |
| 94 | - ShoppingCartPlugin::Mailer.deliver_customer_notification(params[:customer], profile, session[:cart][:items]) | ||
| 95 | - ShoppingCartPlugin::Mailer.deliver_supplier_notification(params[:customer], profile, session[:cart][:items]) | 95 | + ShoppingCartPlugin::Mailer.deliver_customer_notification(params[:customer], profile, session[:cart][:items], params[:delivery_option]) |
| 96 | + ShoppingCartPlugin::Mailer.deliver_supplier_notification(params[:customer], profile, session[:cart][:items], params[:delivery_option]) | ||
| 96 | render :text => { | 97 | render :text => { |
| 97 | :ok => true, | 98 | :ok => true, |
| 98 | :message => _('Request sent successfully. Check your email.'), | 99 | :message => _('Request sent successfully. Check your email.'), |
| @@ -151,6 +152,24 @@ class ShoppingCartPluginProfileController < ProfileController | @@ -151,6 +152,24 @@ class ShoppingCartPluginProfileController < ProfileController | ||
| 151 | end | 152 | end |
| 152 | end | 153 | end |
| 153 | 154 | ||
| 155 | + def update_delivery_option | ||
| 156 | + settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin) | ||
| 157 | + delivery_price = settings.delivery_options[params[:delivery_option]] | ||
| 158 | + delivery = Product.new(:name => params[:delivery_option], :price => delivery_price) | ||
| 159 | + delivery.save(false) | ||
| 160 | + items = session[:cart][:items].clone | ||
| 161 | + items[delivery.id] = 1 | ||
| 162 | + total_price = get_total_on_currency(items, environment) | ||
| 163 | + delivery.destroy | ||
| 164 | + render :text => { | ||
| 165 | + :ok => true, | ||
| 166 | + :delivery_price => float_to_currency_cart(delivery_price, environment), | ||
| 167 | + :total_price => total_price, | ||
| 168 | + :message => _('Delivery option updated.'), | ||
| 169 | + :error => {:code => 0} | ||
| 170 | + }.to_json | ||
| 171 | + end | ||
| 172 | + | ||
| 154 | private | 173 | private |
| 155 | 174 | ||
| 156 | def validate_same_enterprise | 175 | def validate_same_enterprise |
plugins/shopping_cart/db/migrate/20121022190819_move_fields_included_on_profiles_table_to_settings.rb
0 → 100644
| @@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
| 1 | +class MoveFieldsIncludedOnProfilesTableToSettings < ActiveRecord::Migration | ||
| 2 | + def self.up | ||
| 3 | + Profile.find_each do |profile| | ||
| 4 | + settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin) | ||
| 5 | + settings.enabled = profile.shopping_cart | ||
| 6 | + settings.delivery = profile.shopping_cart_delivery | ||
| 7 | + settings.delivery_price = profile.shopping_cart_delivery_price | ||
| 8 | + settings.save! | ||
| 9 | + end | ||
| 10 | + | ||
| 11 | + remove_column :profiles, :shopping_cart | ||
| 12 | + remove_column :profiles, :shopping_cart_delivery | ||
| 13 | + remove_column :profiles, :shopping_cart_delivery_price | ||
| 14 | + end | ||
| 15 | + | ||
| 16 | + def self.down | ||
| 17 | + say "This migration can not be reverted!" | ||
| 18 | + end | ||
| 19 | +end |
plugins/shopping_cart/lib/shopping_cart_plugin.rb
| @@ -3,16 +3,35 @@ require_dependency 'shopping_cart_plugin/ext/person' | @@ -3,16 +3,35 @@ require_dependency 'shopping_cart_plugin/ext/person' | ||
| 3 | 3 | ||
| 4 | class ShoppingCartPlugin < Noosfero::Plugin | 4 | class ShoppingCartPlugin < Noosfero::Plugin |
| 5 | 5 | ||
| 6 | - def self.plugin_name | 6 | + class << self |
| 7 | + def plugin_name | ||
| 7 | "Shopping Basket" | 8 | "Shopping Basket" |
| 8 | - end | 9 | + end |
| 10 | + | ||
| 11 | + def plugin_description | ||
| 12 | + _("A shopping basket feature for enterprises") | ||
| 13 | + end | ||
| 14 | + | ||
| 15 | + def enabled_default_setting | ||
| 16 | + true | ||
| 17 | + end | ||
| 9 | 18 | ||
| 10 | - def self.plugin_description | ||
| 11 | - _("A shopping basket feature for enterprises") | 19 | + def delivery_default_setting |
| 20 | + false | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def delivery_price_default_setting | ||
| 24 | + 0 | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | + def delivery_options_default_setting | ||
| 28 | + {} | ||
| 29 | + end | ||
| 12 | end | 30 | end |
| 13 | 31 | ||
| 14 | def add_to_cart_button(item, enterprise = context.profile) | 32 | def add_to_cart_button(item, enterprise = context.profile) |
| 15 | - if enterprise.shopping_cart && item.available | 33 | + settings = Noosfero::Plugin::Settings.new(enterprise, ShoppingCartPlugin) |
| 34 | + if settings.enabled && item.available | ||
| 16 | lambda { | 35 | lambda { |
| 17 | link_to(_('Add to basket'), "add:#{item.name}", | 36 | link_to(_('Add to basket'), "add:#{item.name}", |
| 18 | :class => 'cart-add-item', | 37 | :class => 'cart-add-item', |
| @@ -39,11 +58,12 @@ class ShoppingCartPlugin < Noosfero::Plugin | @@ -39,11 +58,12 @@ class ShoppingCartPlugin < Noosfero::Plugin | ||
| 39 | end | 58 | end |
| 40 | 59 | ||
| 41 | def control_panel_buttons | 60 | def control_panel_buttons |
| 61 | + settings = Noosfero::Plugin::Settings.new(context.profile, ShoppingCartPlugin) | ||
| 42 | buttons = [] | 62 | buttons = [] |
| 43 | if context.profile.enterprise? | 63 | if context.profile.enterprise? |
| 44 | buttons << { :title => _('Shopping basket'), :icon => 'shopping-cart-icon', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'edit'} } | 64 | buttons << { :title => _('Shopping basket'), :icon => 'shopping-cart-icon', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'edit'} } |
| 45 | end | 65 | end |
| 46 | - if context.profile.enterprise? && context.profile.shopping_cart | 66 | + if context.profile.enterprise? && settings.enabled |
| 47 | buttons << { :title => _('Purchase reports'), :icon => 'shopping-cart-purchase-report', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'reports'} } | 67 | buttons << { :title => _('Purchase reports'), :icon => 'shopping-cart-purchase-report', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'reports'} } |
| 48 | end | 68 | end |
| 49 | 69 |
plugins/shopping_cart/lib/shopping_cart_plugin/cart_helper.rb
| @@ -7,46 +7,69 @@ module ShoppingCartPlugin::CartHelper | @@ -7,46 +7,69 @@ module ShoppingCartPlugin::CartHelper | ||
| 7 | product.discount ? product.price_with_discount : product.price | 7 | product.discount ? product.price_with_discount : product.price |
| 8 | end | 8 | end |
| 9 | 9 | ||
| 10 | - def get_price(product, environment) | ||
| 11 | - float_to_currency_cart(sell_price(product), environment) | 10 | + def get_price(product, environment, quantity=1) |
| 11 | + float_to_currency_cart(price_with_quantity(product,quantity), environment) | ||
| 12 | end | 12 | end |
| 13 | 13 | ||
| 14 | - def get_total(items, environment) | ||
| 15 | - float_to_currency_cart(items.map { |id, quantity| sell_price(Product.find(id)) * quantity}.sum, environment) | 14 | + def price_with_quantity(product, quantity=1) |
| 15 | + quantity = 1 if !quantity.kind_of?(Numeric) | ||
| 16 | + sell_price(product)*quantity | ||
| 16 | end | 17 | end |
| 17 | 18 | ||
| 18 | - def items_table(items, profile, by_mail = false) | 19 | + def get_total(items) |
| 20 | + items.map { |id, quantity| price_with_quantity(Product.find(id),quantity)}.sum | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def get_total_on_currency(items, environment) | ||
| 24 | + float_to_currency_cart(get_total(items), environment) | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | + def items_table(items, profile, delivery_option = nil, by_mail = false) | ||
| 19 | environment = profile.environment | 28 | environment = profile.environment |
| 29 | + settings = Noosfero::Plugin::Settings.new(profile, ShoppingCartPlugin) | ||
| 20 | items = items.to_a | 30 | items = items.to_a |
| 21 | - if profile.shopping_cart_delivery | ||
| 22 | - delivery = Product.create!(:name => _('Delivery'), :price => profile.shopping_cart_delivery_price, :product_category => ProductCategory.last) | ||
| 23 | - items << [delivery.id, 1] | ||
| 24 | - end | ||
| 25 | 31 | ||
| 26 | quantity_opts = { :class => 'cart-table-quantity' } | 32 | quantity_opts = { :class => 'cart-table-quantity' } |
| 27 | quantity_opts.merge!({:align => 'center'}) if by_mail | 33 | quantity_opts.merge!({:align => 'center'}) if by_mail |
| 28 | price_opts = {:class => 'cart-table-price'} | 34 | price_opts = {:class => 'cart-table-price'} |
| 29 | price_opts.merge!({:align => 'right'}) if by_mail | 35 | price_opts.merge!({:align => 'right'}) if by_mail |
| 36 | + items.sort! {|a, b| Product.find(a.first).name <=> Product.find(b.first).name} | ||
| 37 | + | ||
| 38 | + if settings.delivery | ||
| 39 | + if settings.free_delivery_price && get_total(items) >= settings.free_delivery_price | ||
| 40 | + delivery = Product.new(:name => _('Free delivery'), :price => 0) | ||
| 41 | + else | ||
| 42 | + delivery = Product.new(:name => delivery_option || _('Delivery'), :price => settings.delivery_options[delivery_option]) | ||
| 43 | + end | ||
| 44 | + delivery.save(false) | ||
| 45 | + items << [delivery.id, ''] | ||
| 46 | + end | ||
| 30 | 47 | ||
| 31 | table = '<table id="cart-items-table" cellpadding="2" cellspacing="0" | 48 | table = '<table id="cart-items-table" cellpadding="2" cellspacing="0" |
| 32 | border="'+(by_mail ? '1' : '0')+'" | 49 | border="'+(by_mail ? '1' : '0')+'" |
| 33 | style="'+(by_mail ? 'border-collapse:collapse' : '')+'">' + | 50 | style="'+(by_mail ? 'border-collapse:collapse' : '')+'">' + |
| 34 | content_tag('tr', | 51 | content_tag('tr', |
| 35 | - content_tag('th', _('Item name')) + | ||
| 36 | - content_tag('th', by_mail ? ' # ' : '#') + | ||
| 37 | - content_tag('th', _('Price')) | 52 | + content_tag('th', _('Item name')) + |
| 53 | + content_tag('th', by_mail ? ' # ' : '#') + | ||
| 54 | + content_tag('th', _('Price')) | ||
| 38 | ) + | 55 | ) + |
| 39 | items.map do |id, quantity| | 56 | items.map do |id, quantity| |
| 40 | product = Product.find(id) | 57 | product = Product.find(id) |
| 58 | + name_opts = {} | ||
| 59 | + is_delivery = quantity.kind_of?(String) | ||
| 60 | + if is_delivery | ||
| 61 | + price_opts.merge!({:id => 'delivery-price'}) | ||
| 62 | + name_opts.merge!({:id => 'delivery-name'}) | ||
| 63 | + end | ||
| 41 | content_tag('tr', | 64 | content_tag('tr', |
| 42 | - content_tag('td', product.name) + | ||
| 43 | - content_tag('td', quantity, quantity_opts ) + | ||
| 44 | - content_tag('td', get_price(product, environment), price_opts ) | ||
| 45 | - ) | 65 | + content_tag('td', product.name, name_opts) + |
| 66 | + content_tag('td', quantity, quantity_opts ) + | ||
| 67 | + content_tag('td', get_price(product, environment, quantity), price_opts) | ||
| 68 | + ) | ||
| 46 | end.join("\n") | 69 | end.join("\n") |
| 47 | 70 | ||
| 48 | - total = get_total(items, environment) | ||
| 49 | - delivery.destroy if profile.shopping_cart_delivery | 71 | + total = get_total_on_currency(items, environment) |
| 72 | + delivery.destroy if settings.delivery | ||
| 50 | 73 | ||
| 51 | table + | 74 | table + |
| 52 | content_tag('th', _('Total:'), :colspan => 2, :class => 'cart-table-total-label') + | 75 | content_tag('th', _('Total:'), :colspan => 2, :class => 'cart-table-total-label') + |
| @@ -55,6 +78,14 @@ module ShoppingCartPlugin::CartHelper | @@ -55,6 +78,14 @@ module ShoppingCartPlugin::CartHelper | ||
| 55 | end | 78 | end |
| 56 | 79 | ||
| 57 | def float_to_currency_cart(value, environment) | 80 | def float_to_currency_cart(value, environment) |
| 58 | - number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :precision => 2, :format => "%u %n") | 81 | + number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :precision => 2, :format => "%u%n") |
| 82 | + end | ||
| 83 | + | ||
| 84 | + def select_delivery_options(options, environment) | ||
| 85 | + result = options.map do |option, price| | ||
| 86 | + ["#{option} (#{float_to_currency_cart(price, environment)})", option] | ||
| 87 | + end | ||
| 88 | + result << ["#{_('Delivery')} (#{float_to_currency_cart(0, environment)})", 'delivery'] if result.empty? | ||
| 89 | + result | ||
| 59 | end | 90 | end |
| 60 | end | 91 | end |
plugins/shopping_cart/lib/shopping_cart_plugin/mailer.rb
| 1 | class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase | 1 | class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase |
| 2 | 2 | ||
| 3 | - def customer_notification(customer, supplier, items) | 3 | + def customer_notification(customer, supplier, items, delivery_option) |
| 4 | domain = supplier.hostname || supplier.environment.default_hostname | 4 | domain = supplier.hostname || supplier.environment.default_hostname |
| 5 | recipients customer[:email] | 5 | recipients customer[:email] |
| 6 | from 'no-reply@' + domain | 6 | from 'no-reply@' + domain |
| @@ -10,10 +10,11 @@ class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase | @@ -10,10 +10,11 @@ class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase | ||
| 10 | body :customer => customer, | 10 | body :customer => customer, |
| 11 | :supplier => supplier, | 11 | :supplier => supplier, |
| 12 | :items => items, | 12 | :items => items, |
| 13 | - :environment => supplier.environment | 13 | + :environment => supplier.environment, |
| 14 | + :delivery_option => delivery_option | ||
| 14 | end | 15 | end |
| 15 | 16 | ||
| 16 | - def supplier_notification(customer, supplier, items) | 17 | + def supplier_notification(customer, supplier, items, delivery_option) |
| 17 | domain = supplier.environment.default_hostname | 18 | domain = supplier.environment.default_hostname |
| 18 | recipients supplier.contact_email | 19 | recipients supplier.contact_email |
| 19 | from 'no-reply@' + domain | 20 | from 'no-reply@' + domain |
| @@ -23,6 +24,7 @@ class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase | @@ -23,6 +24,7 @@ class ShoppingCartPlugin::Mailer < Noosfero::Plugin::MailerBase | ||
| 23 | body :customer => customer, | 24 | body :customer => customer, |
| 24 | :supplier => supplier, | 25 | :supplier => supplier, |
| 25 | :items => items, | 26 | :items => items, |
| 26 | - :environment => supplier.environment | 27 | + :environment => supplier.environment, |
| 28 | + :delivery_option => delivery_option | ||
| 27 | end | 29 | end |
| 28 | end | 30 | end |
| @@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
| 1 | +jQuery(document).ready(function(){ | ||
| 2 | + jQuery("#cart-request-form").validate({ | ||
| 3 | + submitHandler: function(form) { | ||
| 4 | + jQuery(form).find('input.submit').attr('disabled', true); | ||
| 5 | + jQuery('#cboxLoadingOverlay').show().addClass('loading'); | ||
| 6 | + jQuery('#cboxLoadingGraphic').show().addClass('loading'); | ||
| 7 | + } | ||
| 8 | + }); | ||
| 9 | +}); | ||
| 10 | + | ||
| 11 | +jQuery('#delivery_option').change(function(){ | ||
| 12 | + jQuery('#cboxLoadingGraphic').show(); | ||
| 13 | + me = this; | ||
| 14 | + enterprise = jQuery(me).attr('data-profile-identifier'); | ||
| 15 | + option = jQuery(me).val(); | ||
| 16 | + jQuery.ajax({ | ||
| 17 | + url: '/profile/'+ enterprise +'/plugin/shopping_cart/update_delivery_option', | ||
| 18 | + dataType: "json", | ||
| 19 | + data: 'delivery_option='+option, | ||
| 20 | + success: function(data, st, ajax) { | ||
| 21 | + jQuery('#delivery-price').text(data.delivery_price); | ||
| 22 | + jQuery('.cart-table-total-value').text(data.total_price); | ||
| 23 | + jQuery('#delivery-name').text(option); | ||
| 24 | + jQuery('#cboxLoadingGraphic').hide(); | ||
| 25 | + }, | ||
| 26 | + error: function(ajax, st, errorThrown) { | ||
| 27 | + alert('Update delivery option - HTTP '+st+': '+errorThrown); | ||
| 28 | + }, | ||
| 29 | + }); | ||
| 30 | +}); | ||
| 31 | + | ||
| 32 | +jQuery('#customer_payment').change(function(){ | ||
| 33 | + jQuery(this).closest('.formfieldline').next().slideToggle('fast'); | ||
| 34 | +}); |
plugins/shopping_cart/public/cart.js
| @@ -14,7 +14,7 @@ function Cart(config) { | @@ -14,7 +14,7 @@ function Cart(config) { | ||
| 14 | this.enterprise = config.enterprise; | 14 | this.enterprise = config.enterprise; |
| 15 | me = this; | 15 | me = this; |
| 16 | $.ajax({ | 16 | $.ajax({ |
| 17 | - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/visibility', | 17 | + url: '/profile/'+ this.enterprise +'/plugin/shopping_cart/visibility', |
| 18 | dataType: 'json', | 18 | dataType: 'json', |
| 19 | success: function(data, status, ajax){ | 19 | success: function(data, status, ajax){ |
| 20 | me.visible = /^true$/i.test(data); | 20 | me.visible = /^true$/i.test(data); |
| @@ -25,7 +25,7 @@ function Cart(config) { | @@ -25,7 +25,7 @@ function Cart(config) { | ||
| 25 | alert('Visibility - HTTP '+status+': '+errorThrown); | 25 | alert('Visibility - HTTP '+status+': '+errorThrown); |
| 26 | } | 26 | } |
| 27 | }); | 27 | }); |
| 28 | - $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugins/shopping_cart/buy'}); | 28 | + $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugin/shopping_cart/buy'}); |
| 29 | } | 29 | } |
| 30 | } | 30 | } |
| 31 | 31 | ||
| @@ -34,7 +34,7 @@ function Cart(config) { | @@ -34,7 +34,7 @@ function Cart(config) { | ||
| 34 | Cart.prototype.listProducts = function() { | 34 | Cart.prototype.listProducts = function() { |
| 35 | var me = this; | 35 | var me = this; |
| 36 | $.ajax({ | 36 | $.ajax({ |
| 37 | - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/list', | 37 | + url: '/profile/'+ this.enterprise +'/plugin/shopping_cart/list', |
| 38 | dataType: 'json', | 38 | dataType: 'json', |
| 39 | success: function(data, ststus, ajax){ | 39 | success: function(data, ststus, ajax){ |
| 40 | if ( !data.ok ) alert(data.error.message); | 40 | if ( !data.ok ) alert(data.error.message); |
| @@ -100,7 +100,7 @@ function Cart(config) { | @@ -100,7 +100,7 @@ function Cart(config) { | ||
| 100 | var me = this; | 100 | var me = this; |
| 101 | if( quantity == NaN ) return input.value = input.lastValue; | 101 | if( quantity == NaN ) return input.value = input.lastValue; |
| 102 | $.ajax({ | 102 | $.ajax({ |
| 103 | - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/update_quantity/'+ itemId +'?quantity='+ quantity, | 103 | + url: '/profile/'+ this.enterprise +'/plugin/shopping_cart/update_quantity/'+ itemId +'?quantity='+ quantity, |
| 104 | dataType: 'json', | 104 | dataType: 'json', |
| 105 | success: function(data, status, ajax){ | 105 | success: function(data, status, ajax){ |
| 106 | if ( !data.ok ) { | 106 | if ( !data.ok ) { |
| @@ -150,12 +150,12 @@ function Cart(config) { | @@ -150,12 +150,12 @@ function Cart(config) { | ||
| 150 | Cart.prototype.addItem = function(enterprise, itemId, callback) { | 150 | Cart.prototype.addItem = function(enterprise, itemId, callback) { |
| 151 | if(!this.enterprise) { | 151 | if(!this.enterprise) { |
| 152 | this.enterprise = enterprise; | 152 | this.enterprise = enterprise; |
| 153 | - $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugins/shopping_cart/buy'}); | 153 | + $(".cart-buy", this.cartElem).colorbox({href: '/profile/' + this.enterprise + '/plugin/shopping_cart/buy'}); |
| 154 | // $(this.cartElem).show(); | 154 | // $(this.cartElem).show(); |
| 155 | } | 155 | } |
| 156 | var me = this; | 156 | var me = this; |
| 157 | $.ajax({ | 157 | $.ajax({ |
| 158 | - url: '/profile/'+ enterprise +'/plugins/shopping_cart/add/'+ itemId, | 158 | + url: '/profile/'+ enterprise +'/plugin/shopping_cart/add/'+ itemId, |
| 159 | dataType: 'json', | 159 | dataType: 'json', |
| 160 | success: function(data, status, ajax){ | 160 | success: function(data, status, ajax){ |
| 161 | if ( !data.ok ) alert(data.error.message); | 161 | if ( !data.ok ) alert(data.error.message); |
| @@ -178,7 +178,7 @@ function Cart(config) { | @@ -178,7 +178,7 @@ function Cart(config) { | ||
| 178 | if ($("li", this.itemsBox).size() < 2) return this.clean(); | 178 | if ($("li", this.itemsBox).size() < 2) return this.clean(); |
| 179 | var me = this; | 179 | var me = this; |
| 180 | $.ajax({ | 180 | $.ajax({ |
| 181 | - url: '/profile/'+ enterprise +'/plugins/shopping_cart/remove/'+ itemId, | 181 | + url: '/profile/'+ enterprise +'/plugin/shopping_cart/remove/'+ itemId, |
| 182 | dataType: 'json', | 182 | dataType: 'json', |
| 183 | success: function(data, status, ajax){ | 183 | success: function(data, status, ajax){ |
| 184 | if ( !data.ok ) alert(data.error.message); | 184 | if ( !data.ok ) alert(data.error.message); |
| @@ -200,7 +200,7 @@ function Cart(config) { | @@ -200,7 +200,7 @@ function Cart(config) { | ||
| 200 | 200 | ||
| 201 | Cart.prototype.show = function() { | 201 | Cart.prototype.show = function() { |
| 202 | $.ajax({ | 202 | $.ajax({ |
| 203 | - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/show', | 203 | + url: '/profile/'+ this.enterprise +'/plugin/shopping_cart/show', |
| 204 | dataType: 'json', | 204 | dataType: 'json', |
| 205 | cache: false, | 205 | cache: false, |
| 206 | error: function(ajax, status, errorThrown) { | 206 | error: function(ajax, status, errorThrown) { |
| @@ -215,7 +215,7 @@ function Cart(config) { | @@ -215,7 +215,7 @@ function Cart(config) { | ||
| 215 | } | 215 | } |
| 216 | Cart.prototype.hide = function() { | 216 | Cart.prototype.hide = function() { |
| 217 | $.ajax({ | 217 | $.ajax({ |
| 218 | - url: '/profile/'+ this.enterprise +'/plugins/shopping_cart/hide', | 218 | + url: '/profile/'+ this.enterprise +'/plugin/shopping_cart/hide', |
| 219 | dataType: 'json', | 219 | dataType: 'json', |
| 220 | cache: false, | 220 | cache: false, |
| 221 | error: function(ajax, status, errorThrown) { | 221 | error: function(ajax, status, errorThrown) { |
| @@ -252,7 +252,7 @@ function Cart(config) { | @@ -252,7 +252,7 @@ function Cart(config) { | ||
| 252 | Cart.prototype.clean = function() { | 252 | Cart.prototype.clean = function() { |
| 253 | var me = this; | 253 | var me = this; |
| 254 | $.ajax({ | 254 | $.ajax({ |
| 255 | - url: '/profile/'+ me.enterprise +'/plugins/shopping_cart/clean', | 255 | + url: '/profile/'+ me.enterprise +'/plugin/shopping_cart/clean', |
| 256 | dataType: 'json', | 256 | dataType: 'json', |
| 257 | success: function(data, status, ajax){ | 257 | success: function(data, status, ajax){ |
| 258 | if ( !data.ok ) alert(data.error.message); | 258 | if ( !data.ok ) alert(data.error.message); |
| @@ -284,7 +284,7 @@ function Cart(config) { | @@ -284,7 +284,7 @@ function Cart(config) { | ||
| 284 | var me = this; | 284 | var me = this; |
| 285 | $.ajax({ | 285 | $.ajax({ |
| 286 | type: 'POST', | 286 | type: 'POST', |
| 287 | - url: '/profile/'+ me.enterprise +'/plugins/shopping_cart/send_request', | 287 | + url: '/profile/'+ me.enterprise +'/plugin/shopping_cart/send_request', |
| 288 | data: params, | 288 | data: params, |
| 289 | dataType: 'json', | 289 | dataType: 'json', |
| 290 | success: function(data, status, ajax){ | 290 | success: function(data, status, ajax){ |
| @@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
| 1 | +jQuery('#settings_delivery').click(function(){ | ||
| 2 | + jQuery('#delivery_settings').toggle('fast'); | ||
| 3 | +}); | ||
| 4 | + | ||
| 5 | +jQuery('#add-new-option').click(function(){ | ||
| 6 | + new_option = jQuery('#empty-option').clone(); | ||
| 7 | + new_option.removeAttr('id'); | ||
| 8 | + jQuery('#add-new-option-row').before(new_option); | ||
| 9 | + new_option.show(); | ||
| 10 | + return false; | ||
| 11 | +}); | ||
| 12 | + | ||
| 13 | +jQuery('.remove-option').live('click', function(){ | ||
| 14 | + jQuery(this).closest('tr').remove(); | ||
| 15 | + return false; | ||
| 16 | +}); |
plugins/shopping_cart/test/functional/shopping_cart_plugin_myprofile_controller_test.rb
| @@ -13,46 +13,42 @@ class ShoppingCartPluginMyprofileControllerTest < ActionController::TestCase | @@ -13,46 +13,42 @@ class ShoppingCartPluginMyprofileControllerTest < ActionController::TestCase | ||
| 13 | attr_reader :enterprise | 13 | attr_reader :enterprise |
| 14 | 14 | ||
| 15 | should 'be able to enable shopping cart' do | 15 | should 'be able to enable shopping cart' do |
| 16 | - enterprise.shopping_cart = false | ||
| 17 | - enterprise.save | ||
| 18 | - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart => '1'} | ||
| 19 | - enterprise.reload | 16 | + settings.enabled = false |
| 17 | + settings.save! | ||
| 18 | + post :edit, :profile => enterprise.identifier, :settings => {:enabled => '1'} | ||
| 20 | 19 | ||
| 21 | - assert enterprise.shopping_cart | 20 | + assert settings.enabled |
| 22 | end | 21 | end |
| 23 | 22 | ||
| 24 | should 'be able to disable shopping cart' do | 23 | should 'be able to disable shopping cart' do |
| 25 | - enterprise.shopping_cart = true | ||
| 26 | - enterprise.save | ||
| 27 | - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart => '0'} | ||
| 28 | - enterprise.reload | 24 | + settings.enabled = true |
| 25 | + settings.save! | ||
| 26 | + post :edit, :profile => enterprise.identifier, :settings => {:enabled => '0'} | ||
| 29 | 27 | ||
| 30 | - assert !enterprise.shopping_cart | 28 | + assert !settings.enabled |
| 31 | end | 29 | end |
| 32 | 30 | ||
| 33 | should 'be able to enable shopping cart delivery' do | 31 | should 'be able to enable shopping cart delivery' do |
| 34 | - enterprise.shopping_cart_delivery = false | ||
| 35 | - enterprise.save | ||
| 36 | - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery => '1'} | ||
| 37 | - enterprise.reload | 32 | + settings.delivery = false |
| 33 | + settings.save! | ||
| 34 | + post :edit, :profile => enterprise.identifier, :settings => {:delivery => '1'} | ||
| 38 | 35 | ||
| 39 | - assert enterprise.shopping_cart_delivery | 36 | + assert settings.delivery |
| 40 | end | 37 | end |
| 41 | 38 | ||
| 42 | should 'be able to disable shopping cart delivery' do | 39 | should 'be able to disable shopping cart delivery' do |
| 43 | - enterprise.shopping_cart_delivery = true | ||
| 44 | - enterprise.save | ||
| 45 | - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery => '0'} | ||
| 46 | - enterprise.reload | 40 | + settings.delivery = true |
| 41 | + settings.save! | ||
| 42 | + post :edit, :profile => enterprise.identifier, :settings => {:delivery => '0'} | ||
| 47 | 43 | ||
| 48 | - assert !enterprise.shopping_cart_delivery | 44 | + assert !settings.delivery |
| 49 | end | 45 | end |
| 50 | 46 | ||
| 51 | should 'be able to choose the delivery price' do | 47 | should 'be able to choose the delivery price' do |
| 52 | price = 4.35 | 48 | price = 4.35 |
| 53 | - post :edit, :profile => enterprise.identifier, :profile_attr => {:shopping_cart_delivery_price => price} | ||
| 54 | - enterprise.reload | ||
| 55 | - assert enterprise.shopping_cart_delivery_price == price | 49 | + post :edit, :profile => enterprise.identifier, :settings => {:delivery_price => price} |
| 50 | + | ||
| 51 | + assert settings.delivery_price == price | ||
| 56 | end | 52 | end |
| 57 | 53 | ||
| 58 | should 'filter the reports correctly' do | 54 | should 'filter the reports correctly' do |
| @@ -112,4 +108,11 @@ class ShoppingCartPluginMyprofileControllerTest < ActionController::TestCase | @@ -112,4 +108,11 @@ class ShoppingCartPluginMyprofileControllerTest < ActionController::TestCase | ||
| 112 | po.reload | 108 | po.reload |
| 113 | assert_equal ShoppingCartPlugin::PurchaseOrder::Status::CONFIRMED, po.status | 109 | assert_equal ShoppingCartPlugin::PurchaseOrder::Status::CONFIRMED, po.status |
| 114 | end | 110 | end |
| 111 | + | ||
| 112 | + private | ||
| 113 | + | ||
| 114 | + def settings | ||
| 115 | + @enterprise.reload | ||
| 116 | + Noosfero::Plugin::Settings.new(@enterprise, ShoppingCartPlugin) | ||
| 117 | + end | ||
| 115 | end | 118 | end |
plugins/shopping_cart/views/shopping_cart_plugin/mailer/customer_notification.html.erb
| @@ -17,25 +17,35 @@ | @@ -17,25 +17,35 @@ | ||
| 17 | <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li> | 17 | <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li> |
| 18 | <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li> | 18 | <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li> |
| 19 | <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li> | 19 | <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li> |
| 20 | - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %> | 20 | + <li><b><%= _('Payment') %>: </b><%= @customer[:payment] == 'money' ? _('Money') : _('Check') %></li> |
| 21 | + <% if @customer[:payment] == 'money' %> | ||
| 22 | + <li><b><%= _('Change') %>: </b><%= @customer[:change] %></li> | ||
| 23 | + <% end %> | ||
| 24 | + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %> | ||
| 21 | <li><b><%= _('Address') %>:</b> | 25 | <li><b><%= _('Address') %>:</b> |
| 22 | <% end %> | 26 | <% end %> |
| 23 | <% if !@customer[:address].blank? %> | 27 | <% if !@customer[:address].blank? %> |
| 24 | <%= @customer[:address] %><br \> | 28 | <%= @customer[:address] %><br \> |
| 25 | <% end %> | 29 | <% end %> |
| 30 | + <% if !@customer[:district].blank? %> | ||
| 31 | + <%= @customer[:district] %><br \> | ||
| 32 | + <% end %> | ||
| 26 | <% if !@customer[:city].blank? %> | 33 | <% if !@customer[:city].blank? %> |
| 27 | <%= @customer[:city] %><br \> | 34 | <%= @customer[:city] %><br \> |
| 28 | <% end %> | 35 | <% end %> |
| 29 | <% if !@customer[:zip_code].blank? %> | 36 | <% if !@customer[:zip_code].blank? %> |
| 30 | <%= @customer[:zip_code] %> | 37 | <%= @customer[:zip_code] %> |
| 31 | <% end %> | 38 | <% end %> |
| 32 | - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %> | 39 | + <% if !@customer[:address_reference].blank? %> |
| 40 | + <%= @customer[:address_reference] %><br \> | ||
| 41 | + <% end %> | ||
| 42 | + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %> | ||
| 33 | </li> | 43 | </li> |
| 34 | <% end %> | 44 | <% end %> |
| 35 | </ul> | 45 | </ul> |
| 36 | 46 | ||
| 37 | <p><%=_('Here are the products you bought:')%></p> | 47 | <p><%=_('Here are the products you bought:')%></p> |
| 38 | - <%= items_table(@items, @supplier, true) %> | 48 | + <%= items_table(@items, @supplier, @delivery_option, true) %> |
| 39 | 49 | ||
| 40 | <p> | 50 | <p> |
| 41 | --<br/> | 51 | --<br/> |
plugins/shopping_cart/views/shopping_cart_plugin/mailer/supplier_notification.html.erb
| @@ -15,25 +15,35 @@ | @@ -15,25 +15,35 @@ | ||
| 15 | <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li> | 15 | <li><b><%= _('Full name') %>: </b><%= @customer[:name] %></li> |
| 16 | <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li> | 16 | <li><b><%= _('Email') %>: </b><%= @customer[:email] %></li> |
| 17 | <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li> | 17 | <li><b><%= _('Phone number') %>: </b><%= @customer[:contact_phone] %></li> |
| 18 | - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %> | 18 | + <li><b><%= _('Payment') %>: </b><%= @customer[:payment] == 'money' ? _('Money') : _('Check') %></li> |
| 19 | + <% if @customer[:payment] == 'money' %> | ||
| 20 | + <li><b><%= _('Change') %>: </b><%= @customer[:change] %></li> | ||
| 21 | + <% end %> | ||
| 22 | + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %> | ||
| 19 | <li><b><%= _('Address') %>:</b> | 23 | <li><b><%= _('Address') %>:</b> |
| 20 | <% end %> | 24 | <% end %> |
| 21 | <% if !@customer[:address].blank? %> | 25 | <% if !@customer[:address].blank? %> |
| 22 | <%= @customer[:address] %><br \> | 26 | <%= @customer[:address] %><br \> |
| 23 | <% end %> | 27 | <% end %> |
| 28 | + <% if !@customer[:district].blank? %> | ||
| 29 | + <%= @customer[:district] %><br \> | ||
| 30 | + <% end %> | ||
| 24 | <% if !@customer[:city].blank? %> | 31 | <% if !@customer[:city].blank? %> |
| 25 | <%= @customer[:city] %><br \> | 32 | <%= @customer[:city] %><br \> |
| 26 | <% end %> | 33 | <% end %> |
| 27 | <% if !@customer[:zip_code].blank? %> | 34 | <% if !@customer[:zip_code].blank? %> |
| 28 | <%= @customer[:zip_code] %> | 35 | <%= @customer[:zip_code] %> |
| 29 | <% end %> | 36 | <% end %> |
| 30 | - <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? %> | 37 | + <% if !@customer[:address_reference].blank? %> |
| 38 | + <%= @customer[:address_reference] %><br \> | ||
| 39 | + <% end %> | ||
| 40 | + <% if !@customer[:address].blank? || !@customer[:city].blank? || !@customer[:zip_code].blank? || !@customer[:district].blank? || !@customer[:address_reference].blank? %> | ||
| 31 | </li> | 41 | </li> |
| 32 | <% end %> | 42 | <% end %> |
| 33 | </ul> | 43 | </ul> |
| 34 | 44 | ||
| 35 | <p><%=_('And here are the items bought by this customer:')%></p> | 45 | <p><%=_('And here are the items bought by this customer:')%></p> |
| 36 | - <%= items_table(@items, @supplier, true) %> | 46 | + <%= items_table(@items, @supplier, @delivery_option, true) %> |
| 37 | 47 | ||
| 38 | <p> | 48 | <p> |
| 39 | --<br/> | 49 | --<br/> |
plugins/shopping_cart/views/shopping_cart_plugin_myprofile/edit.html.erb
| 1 | <h1> <%= _('Basket options') %> </h1> | 1 | <h1> <%= _('Basket options') %> </h1> |
| 2 | 2 | ||
| 3 | -<% form_for(:profile_attr, profile, :url => {:action => 'edit'}, :html => {:method => 'post'}) do |f| %> | ||
| 4 | - <%= labelled_form_field(_('Enabled?'), f.check_box(:shopping_cart)) %> | ||
| 5 | - <%= labelled_form_field(_('Delivery?'), f.check_box(:shopping_cart_delivery)) %> | ||
| 6 | - <%= labelled_form_field(_('Delivery price:'), f.text_field(:shopping_cart_delivery_price)) %> | 3 | +<% form_for(:settings, @settings, :url => {:action => 'edit'}, :html => {:method => 'post'}) do |f| %> |
| 4 | + <%= labelled_form_field(_('Enabled?'), f.check_box(:enabled)) %> | ||
| 5 | + <%= labelled_form_field(_('Delivery?'), f.check_box(:delivery)) %> | ||
| 6 | + <% display_delivery_settings = @settings.delivery ? 'auto' : 'none' %> | ||
| 7 | + <fieldset id='delivery_settings' style="display: <%= display_delivery_settings %>"><legend><%=_('Delivery')%></legend> | ||
| 8 | + <table> | ||
| 9 | + <tr> | ||
| 10 | + <th><%= _('Option') %></th> | ||
| 11 | + <th><%= _('Price') %></th> | ||
| 12 | + <th> </th> | ||
| 13 | + </tr> | ||
| 14 | + <% @settings.delivery_options.each do |option, price| %> | ||
| 15 | + <tr> | ||
| 16 | + <td><%= text_field_tag('settings[delivery_options][options][]', option, :style => 'width: 100%') %></td> | ||
| 17 | + <td><%= text_field_tag('settings[delivery_options][prices][]', price, :style => 'width: 100%') %></td> | ||
| 18 | + <td><%= button_without_text(:close, _('Remove option'), '', :class => 'remove-option') %></td> | ||
| 19 | + </tr> | ||
| 20 | + <% end %> | ||
| 21 | + <tr> | ||
| 22 | + <td><%= text_field_tag('settings[delivery_options][options][]', nil, :style => 'width: 100%') %></td> | ||
| 23 | + <td><%= text_field_tag('settings[delivery_options][prices][]', nil, :style => 'width: 100%') %></td> | ||
| 24 | + <td><%= button_without_text(:close, _('Remove option'), '', :class => 'remove-option') %></td> | ||
| 25 | + </tr> | ||
| 26 | + <tr id='add-new-option-row'> | ||
| 27 | + <td colspan='3' style='background-color: #EEE; text-align: center'><%= link_to(_('ADD NEW OPTION'), '', :id => 'add-new-option') %></td> | ||
| 28 | + </tr> | ||
| 29 | + <tr id="empty-option" style='display: none'> | ||
| 30 | + <td><%= text_field_tag('settings[delivery_options][options][]', nil, :style => 'width: 100%') %></td> | ||
| 31 | + <td><%= text_field_tag('settings[delivery_options][prices][]', nil, :style => 'width: 100%') %></td> | ||
| 32 | + <td><%= button_without_text(:close, _('Remove option'), '', :class => 'remove-option') %></td> | ||
| 33 | + </tr> | ||
| 34 | + </table> | ||
| 35 | + | ||
| 36 | + <%= labelled_form_field(_('Free delivery price:'), f.text_field(:free_delivery_price)) %> | ||
| 37 | + <%= content_tag('small', _('Empty stands for no free delivery price.')) %> | ||
| 38 | + </fieldset> | ||
| 7 | <br style='clear: both'/> | 39 | <br style='clear: both'/> |
| 8 | <br style='clear: both'/> | 40 | <br style='clear: both'/> |
| 9 | <div> | 41 | <div> |
| @@ -11,3 +43,5 @@ | @@ -11,3 +43,5 @@ | ||
| 11 | <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %> | 43 | <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %> |
| 12 | </div> | 44 | </div> |
| 13 | <% end%> | 45 | <% end%> |
| 46 | + | ||
| 47 | +<%= javascript_include_tag '../plugins/shopping_cart/edit' %> |
plugins/shopping_cart/views/shopping_cart_plugin_profile/buy.html.erb
| @@ -6,30 +6,26 @@ | @@ -6,30 +6,26 @@ | ||
| 6 | <%= labelled_form_field('* ' + _("Name"), f.text_field(:name, :class => 'required') ) %> | 6 | <%= labelled_form_field('* ' + _("Name"), f.text_field(:name, :class => 'required') ) %> |
| 7 | <%= labelled_form_field('* ' + _("Email"), f.text_field(:email, :class => 'required email') ) %> | 7 | <%= labelled_form_field('* ' + _("Email"), f.text_field(:email, :class => 'required email') ) %> |
| 8 | <%= labelled_form_field('* ' + _("Contact phone"), f.text_field(:contact_phone, :class => 'required') ) %> | 8 | <%= labelled_form_field('* ' + _("Contact phone"), f.text_field(:contact_phone, :class => 'required') ) %> |
| 9 | + <%= labelled_form_field(_('Delivery option'), select_tag(:delivery_option, options_for_select(select_delivery_options(@settings.delivery_options, environment)), 'data-profile-identifier' => profile.identifier)) unless !@settings.delivery || (@settings.free_delivery_price && get_total(session[:cart][:items]) >= @settings.free_delivery_price) %> | ||
| 10 | + <%= labelled_form_field(_('Payment'), select_tag('customer[payment]', options_for_select([[_("Money"), :money],[_('Check'), :check]]))) %> | ||
| 11 | + <%= labelled_form_field(_('Change'), text_field_tag('customer[change]')) %> | ||
| 9 | </div> | 12 | </div> |
| 10 | - <fieldset><legend><%=_('Delivery Address')%></legend> | ||
| 11 | - <%= labelled_form_field(_('Address (street and number)'), f.text_field(:address)) %> | ||
| 12 | - <%= labelled_form_field( _("City"), f.text_field(:city)) %> | ||
| 13 | - <%= labelled_form_field(_('ZIP code'), f.text_field(:zip_code)) %> | ||
| 14 | - </fieldset> | 13 | + <% if @settings.delivery %> |
| 14 | + <fieldset><legend><%=_('Delivery Address')%></legend> | ||
| 15 | + <%= labelled_form_field(_('Address (street and number)'), f.text_field(:address)) %> | ||
| 16 | + <%= labelled_form_field(_('Address reference'), f.text_field(:address_reference)) %> | ||
| 17 | + <%= labelled_form_field(_('District'), f.text_field(:district)) %> | ||
| 18 | + <%= labelled_form_field( _("City"), f.text_field(:city)) %> | ||
| 19 | + <%= labelled_form_field(_('ZIP code'), f.text_field(:zip_code)) %> | ||
| 20 | + </fieldset> | ||
| 21 | + <% end %> | ||
| 15 | <div id="cart-form-actions"> | 22 | <div id="cart-form-actions"> |
| 16 | <%= submit_button(:send, _('Send buy request')) %> | 23 | <%= submit_button(:send, _('Send buy request')) %> |
| 17 | </div> | 24 | </div> |
| 18 | <% end %> | 25 | <% end %> |
| 19 | - <%= items_table(session[:cart][:items], profile) %> | 26 | + <% delivery_option = @settings.delivery_options.first && @settings.delivery_options.first.first %> |
| 27 | + <%= items_table(session[:cart][:items], profile, delivery_option) %> | ||
| 20 | <%= link_to '', '#', :onclick => "Cart.colorbox_close(this);", :class => 'cart-box-close icon-cancel' %> | 28 | <%= link_to '', '#', :onclick => "Cart.colorbox_close(this);", :class => 'cart-box-close icon-cancel' %> |
| 21 | </div> | 29 | </div> |
| 22 | 30 | ||
| 23 | -<script type="text/javascript"> | ||
| 24 | -//<![CDATA[ | ||
| 25 | - jQuery(document).ready(function(){ | ||
| 26 | - jQuery("#cart-request-form").validate({ | ||
| 27 | - submitHandler: function(form) { | ||
| 28 | - jQuery(form).find('input.submit').attr('disabled', true); | ||
| 29 | - jQuery('#cboxLoadingOverlay').show().addClass('loading'); | ||
| 30 | - jQuery('#cboxLoadingGraphic').show().addClass('loading'); | ||
| 31 | - } | ||
| 32 | - }); | ||
| 33 | - }); | ||
| 34 | -//]]> | ||
| 35 | -</script> | 31 | +<%= javascript_include_tag '../plugins/shopping_cart/buy' %> |
1.75 KB
3.58 KB
public/stylesheets/application.css
| @@ -2677,6 +2677,40 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation | @@ -2677,6 +2677,40 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation | ||
| 2677 | .msie #product-list h3 { | 2677 | .msie #product-list h3 { |
| 2678 | margin-top: -15px; | 2678 | margin-top: -15px; |
| 2679 | } | 2679 | } |
| 2680 | + | ||
| 2681 | +.l-sidebar-left-bar ul, | ||
| 2682 | +.l-sidebar-right-bar ul { | ||
| 2683 | + list-style-type: none; | ||
| 2684 | + margin-left: 0; | ||
| 2685 | + padding: 1em 1em 0.5em 1em; | ||
| 2686 | +} | ||
| 2687 | + | ||
| 2688 | +.l-sidebar-left-bar ul ul, | ||
| 2689 | +.l-sidebar-right-bar ul ul { | ||
| 2690 | + padding-top: 0; | ||
| 2691 | +} | ||
| 2692 | + | ||
| 2693 | +.l-sidebar-left-bar ul{ | ||
| 2694 | + background-color: #eeeeec; | ||
| 2695 | + border-radius: 5px; | ||
| 2696 | +} | ||
| 2697 | + | ||
| 2698 | +.l-sidebar-left-bar a, | ||
| 2699 | +.l-sidebar-right-bar a { | ||
| 2700 | + text-decoration: none; | ||
| 2701 | +} | ||
| 2702 | + | ||
| 2703 | +.l-sidebar-left-bar a:hover, | ||
| 2704 | +.l-sidebar-right-bar a:hover { | ||
| 2705 | + text-decoration: underline; | ||
| 2706 | +} | ||
| 2707 | + | ||
| 2708 | +.l-sidebar-left-bar li, | ||
| 2709 | +.l-sidebar-right-bar li { | ||
| 2710 | + margin: 0; | ||
| 2711 | + padding: 0; | ||
| 2712 | +} | ||
| 2713 | + | ||
| 2680 | /* * * Show Product * * * * * * * * * * * * */ | 2714 | /* * * Show Product * * * * * * * * * * * * */ |
| 2681 | 2715 | ||
| 2682 | .controller-catalog #show_product .product-pic { | 2716 | .controller-catalog #show_product .product-pic { |
| @@ -6066,6 +6100,35 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { | @@ -6066,6 +6100,35 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { | ||
| 6066 | line-height: 1.5; | 6100 | line-height: 1.5; |
| 6067 | } | 6101 | } |
| 6068 | 6102 | ||
| 6103 | +/* Sidebar Layout */ | ||
| 6104 | + | ||
| 6105 | +.l-sidebar-left-bar { | ||
| 6106 | + float: left; | ||
| 6107 | + width: 20%; | ||
| 6108 | +} | ||
| 6109 | + | ||
| 6110 | +.l-sidebar-left-content { | ||
| 6111 | + float: right; | ||
| 6112 | + width: 78%; | ||
| 6113 | +} | ||
| 6114 | + | ||
| 6115 | +.l-sidebar-right-bar { | ||
| 6116 | + float: right; | ||
| 6117 | + width: 20%; | ||
| 6118 | +} | ||
| 6119 | + | ||
| 6120 | +.l-sidebar-right-content { | ||
| 6121 | + float: left; | ||
| 6122 | + width: 78%; | ||
| 6123 | +} | ||
| 6124 | + | ||
| 6125 | +/* Breadcrumb */ | ||
| 6126 | + | ||
| 6127 | +#breadcrumb { | ||
| 6128 | + font-size: 16px; | ||
| 6129 | + margin: 15px 0; | ||
| 6130 | +} | ||
| 6131 | + | ||
| 6069 | .controller-profile_editor #profile-data { | 6132 | .controller-profile_editor #profile-data { |
| 6070 | display: table; | 6133 | display: table; |
| 6071 | width: auto; | 6134 | width: auto; |
| @@ -6114,3 +6177,10 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { | @@ -6114,3 +6177,10 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { | ||
| 6114 | width: 100px; | 6177 | width: 100px; |
| 6115 | text-align: center; | 6178 | text-align: center; |
| 6116 | } | 6179 | } |
| 6180 | + | ||
| 6181 | +#product-list .highlighted img.star { | ||
| 6182 | + position: absolute; | ||
| 6183 | + top: 2px; | ||
| 6184 | + right: 2px; | ||
| 6185 | + z-index: 10; | ||
| 6186 | +} |
test/functional/catalog_controller_test.rb
| @@ -109,4 +109,71 @@ class CatalogControllerTest < ActionController::TestCase | @@ -109,4 +109,71 @@ class CatalogControllerTest < ActionController::TestCase | ||
| 109 | assert_tag :tag => 'span', :content => 'This is Plugin2 speaking!', :attributes => {:id => 'plugin2'} | 109 | assert_tag :tag => 'span', :content => 'This is Plugin2 speaking!', :attributes => {:id => 'plugin2'} |
| 110 | end | 110 | end |
| 111 | 111 | ||
| 112 | + should 'get categories of the right level' do | ||
| 113 | + pc1 = ProductCategory.create!(:name => "PC1", :environment => @enterprise.environment) | ||
| 114 | + pc2 = ProductCategory.create!(:name => "PC2", :environment => @enterprise.environment, :parent_id => pc1.id) | ||
| 115 | + pc3 = ProductCategory.create!(:name => "PC3", :environment => @enterprise.environment, :parent_id => pc1.id) | ||
| 116 | + pc4 = ProductCategory.create!(:name => "PC4", :environment => @enterprise.environment, :parent_id => pc2.id) | ||
| 117 | + p1 = fast_create(Product, :product_category_id => pc1.id, :enterprise_id => @enterprise.id) | ||
| 118 | + p2 = fast_create(Product, :product_category_id => pc2.id, :enterprise_id => @enterprise.id) | ||
| 119 | + p3 = fast_create(Product, :product_category_id => pc3.id, :enterprise_id => @enterprise.id) | ||
| 120 | + p4 = fast_create(Product, :product_category_id => pc4.id, :enterprise_id => @enterprise.id) | ||
| 121 | + | ||
| 122 | + get :index, :profile => @enterprise.identifier, :level => pc1.id | ||
| 123 | + | ||
| 124 | + assert_not_includes assigns(:categories), pc1 | ||
| 125 | + assert_includes assigns(:categories), pc2 | ||
| 126 | + assert_includes assigns(:categories), pc3 | ||
| 127 | + assert_not_includes assigns(:categories), pc4 | ||
| 128 | + end | ||
| 129 | + | ||
| 130 | + should 'filter products based on level selected' do | ||
| 131 | + pc1 = ProductCategory.create!(:name => "PC1", :environment => @enterprise.environment) | ||
| 132 | + pc2 = ProductCategory.create!(:name => "PC2", :environment => @enterprise.environment, :parent_id => pc1.id) | ||
| 133 | + pc3 = ProductCategory.create!(:name => "PC3", :environment => @enterprise.environment, :parent_id => pc1.id) | ||
| 134 | + pc4 = ProductCategory.create!(:name => "PC4", :environment => @enterprise.environment, :parent_id => pc2.id) | ||
| 135 | + p1 = fast_create(Product, :product_category_id => pc1.id, :enterprise_id => @enterprise.id) | ||
| 136 | + p2 = fast_create(Product, :product_category_id => pc2.id, :enterprise_id => @enterprise.id) | ||
| 137 | + p3 = fast_create(Product, :product_category_id => pc3.id, :enterprise_id => @enterprise.id) | ||
| 138 | + p4 = fast_create(Product, :product_category_id => pc4.id, :enterprise_id => @enterprise.id) | ||
| 139 | + | ||
| 140 | + get :index, :profile => @enterprise.identifier, :level => pc2.id | ||
| 141 | + | ||
| 142 | + assert_not_includes assigns(:products), p1 | ||
| 143 | + assert_includes assigns(:products), p2 | ||
| 144 | + assert_not_includes assigns(:products), p3 | ||
| 145 | + assert_includes assigns(:products), p4 | ||
| 146 | + end | ||
| 147 | + | ||
| 148 | + should 'get products ordered by availability, highlighted and then name' do | ||
| 149 | + p1 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Zebra', :available => true, :highlighted => true) | ||
| 150 | + p2 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Car', :available => true) | ||
| 151 | + p3 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Panda', :available => true) | ||
| 152 | + p4 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Pen', :available => false, :highlighted => true) | ||
| 153 | + p5 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Ball', :available => false) | ||
| 154 | + p6 = fast_create(Product, :enterprise_id => @enterprise.id, :name => 'Medal', :available => false) | ||
| 155 | + | ||
| 156 | + get :index, :profile => @enterprise.identifier | ||
| 157 | + | ||
| 158 | + assert_equal [p1,p2,p3,p4,p5,p6], assigns(:products) | ||
| 159 | + end | ||
| 160 | + | ||
| 161 | + should 'add highlighted CSS class around a highlighted product' do | ||
| 162 | + prod = @enterprise.products.create!(:name => 'Highlighted Product', :product_category => @product_category, :highlighted => true) | ||
| 163 | + get :index, :profile => @enterprise.identifier | ||
| 164 | + assert_tag :tag => 'li', :attributes => { :class => 'product highlighted' }, :content => /Highlighted Product/ | ||
| 165 | + end | ||
| 166 | + | ||
| 167 | + should 'do not add highlighted CSS class around an ordinary product' do | ||
| 168 | + prod = @enterprise.products.create!(:name => 'Ordinary Product', :product_category => @product_category, :highlighted => false) | ||
| 169 | + get :index, :profile => @enterprise.identifier | ||
| 170 | + assert_no_tag :tag => 'li', :attributes => { :class => 'product highlighted' }, :content => /Ordinary Product/ | ||
| 171 | + end | ||
| 172 | + | ||
| 173 | + should 'display star image in highlighted product' do | ||
| 174 | + prod = @enterprise.products.create!(:name => 'The Eyes Are The Light', :product_category => @product_category, :highlighted => true) | ||
| 175 | + get :index, :profile => @enterprise.identifier | ||
| 176 | + assert_tag :tag => 'img', :attributes => { :class => 'star', :src => /star.png/ } | ||
| 177 | + end | ||
| 178 | + | ||
| 112 | end | 179 | end |
test/functional/search_controller_test.rb
| @@ -926,6 +926,20 @@ class SearchControllerTest < ActionController::TestCase | @@ -926,6 +926,20 @@ class SearchControllerTest < ActionController::TestCase | ||
| 926 | end | 926 | end |
| 927 | end | 927 | end |
| 928 | 928 | ||
| 929 | + should 'add highlighted CSS class around a highlighted product' do | ||
| 930 | + enterprise = fast_create(Enterprise) | ||
| 931 | + product = Product.create!(:name => 'Enter Sandman', :enterprise_id => enterprise.id, :product_category_id => @product_category.id, :highlighted => true) | ||
| 932 | + get :products | ||
| 933 | + assert_tag :tag => 'li', :attributes => { :class => 'search-product-item highlighted' }, :content => /Enter Sandman/ | ||
| 934 | + end | ||
| 935 | + | ||
| 936 | + should 'do not add highlighted CSS class around an ordinary product' do | ||
| 937 | + enterprise = fast_create(Enterprise) | ||
| 938 | + product = Product.create!(:name => 'Holier Than Thou', :enterprise_id => enterprise.id, :product_category_id => @product_category.id, :highlighted => false) | ||
| 939 | + get :products | ||
| 940 | + assert_no_tag :tag => 'li', :attributes => { :class => 'search-product-item highlighted' }, :content => /Holier Than Thou/ | ||
| 941 | + end | ||
| 942 | + | ||
| 929 | ################################################################## | 943 | ################################################################## |
| 930 | ################################################################## | 944 | ################################################################## |
| 931 | 945 |
test/unit/category_test.rb
| @@ -552,4 +552,62 @@ class CategoryTest < ActiveSupport::TestCase | @@ -552,4 +552,62 @@ class CategoryTest < ActiveSupport::TestCase | ||
| 552 | cat.save! | 552 | cat.save! |
| 553 | end | 553 | end |
| 554 | 554 | ||
| 555 | + should 'return categories of a level' do | ||
| 556 | + c1 = fast_create(Category) | ||
| 557 | + c2 = fast_create(Category) | ||
| 558 | + c3 = fast_create(Category, :parent_id => c1) | ||
| 559 | + c4 = fast_create(Category, :parent_id => c1) | ||
| 560 | + c5 = fast_create(Category, :parent_id => c2) | ||
| 561 | + c6 = fast_create(Category, :parent_id => c3) | ||
| 562 | + | ||
| 563 | + assert_includes Category.on_level(nil), c1 | ||
| 564 | + assert_includes Category.on_level(nil), c2 | ||
| 565 | + assert_includes Category.on_level(c1), c3 | ||
| 566 | + assert_includes Category.on_level(c1), c4 | ||
| 567 | + assert_includes Category.on_level(c2), c5 | ||
| 568 | + assert_includes Category.on_level(c3), c6 | ||
| 569 | + end | ||
| 570 | + | ||
| 571 | + should 'on level named_scope must be able to receive parent or parent_id' do | ||
| 572 | + parent = fast_create(Category) | ||
| 573 | + category = fast_create(Category, :parent_id => parent) | ||
| 574 | + | ||
| 575 | + assert_includes Category.on_level(parent), category | ||
| 576 | + assert_includes Category.on_level(parent.id), category | ||
| 577 | + end | ||
| 578 | + | ||
| 579 | + should 'list category sub-categories' do | ||
| 580 | + c1 = Category.create!(:name => 'Category 1', :environment => Environment.default) | ||
| 581 | + c2 = Category.create!(:name => 'Category 2', :environment => Environment.default) | ||
| 582 | + c3 = Category.create!(:name => 'Category 3', :environment => Environment.default, :parent_id => c1) | ||
| 583 | + c4 = Category.create!(:name => 'Category 4', :environment => Environment.default, :parent_id => c1) | ||
| 584 | + c5 = Category.create!(:name => 'Category 5', :environment => Environment.default, :parent_id => c3) | ||
| 585 | + | ||
| 586 | + sub_categories = Category.sub_categories(c1) | ||
| 587 | + | ||
| 588 | + assert ActiveRecord::NamedScope::Scope, sub_categories.class | ||
| 589 | + assert_not_includes sub_categories, c1 | ||
| 590 | + assert_not_includes sub_categories, c2 | ||
| 591 | + assert_includes sub_categories, c3 | ||
| 592 | + assert_includes sub_categories, c4 | ||
| 593 | + assert_includes sub_categories, c5 | ||
| 594 | + end | ||
| 595 | + | ||
| 596 | + should 'list category sub-tree' do | ||
| 597 | + c1 = Category.create!(:name => 'Category 1', :environment => Environment.default) | ||
| 598 | + c2 = Category.create!(:name => 'Category 2', :environment => Environment.default) | ||
| 599 | + c3 = Category.create!(:name => 'Category 3', :environment => Environment.default, :parent_id => c1) | ||
| 600 | + c4 = Category.create!(:name => 'Category 4', :environment => Environment.default, :parent_id => c1) | ||
| 601 | + c5 = Category.create!(:name => 'Category 5', :environment => Environment.default, :parent_id => c3) | ||
| 602 | + | ||
| 603 | + sub_tree = Category.sub_tree(c1) | ||
| 604 | + | ||
| 605 | + assert ActiveRecord::NamedScope::Scope, sub_tree.class | ||
| 606 | + assert_includes sub_tree, c1 | ||
| 607 | + assert_not_includes sub_tree, c2 | ||
| 608 | + assert_includes sub_tree, c3 | ||
| 609 | + assert_includes sub_tree, c4 | ||
| 610 | + assert_includes sub_tree, c5 | ||
| 611 | + end | ||
| 612 | + | ||
| 555 | end | 613 | end |
test/unit/display_helper_test.rb
| @@ -44,4 +44,14 @@ class DisplayHelperTest < ActiveSupport::TestCase | @@ -44,4 +44,14 @@ class DisplayHelperTest < ActiveSupport::TestCase | ||
| 44 | assert_equal 'go to <a href="http://www.noosfero.org" onclick="return confirm(\'Are you sure you want to visit this web site?\')" rel="nofolow" target="_blank">www.​noos​fero​.org</a> yeah!', html | 44 | assert_equal 'go to <a href="http://www.noosfero.org" onclick="return confirm(\'Are you sure you want to visit this web site?\')" rel="nofolow" target="_blank">www.​noos​fero​.org</a> yeah!', html |
| 45 | end | 45 | end |
| 46 | 46 | ||
| 47 | + should 'return path to file under theme dir if theme has that file' do | ||
| 48 | + stubs(:theme_path).returns('/designs/themes/noosfero') | ||
| 49 | + assert_equal '/designs/themes/noosfero/images/rails.png', themed_path('/images/rails.png') | ||
| 50 | + end | ||
| 51 | + | ||
| 52 | + should 'return path to file under public dir if theme hasnt that file' do | ||
| 53 | + stubs(:theme_path).returns('/designs/themes/noosfero') | ||
| 54 | + assert_equal '/images/invalid-file.png', themed_path('/images/invalid-file.png') | ||
| 55 | + end | ||
| 56 | + | ||
| 47 | end | 57 | end |
test/unit/person_test.rb
| @@ -64,7 +64,7 @@ class PersonTest < ActiveSupport::TestCase | @@ -64,7 +64,7 @@ class PersonTest < ActiveSupport::TestCase | ||
| 64 | 64 | ||
| 65 | should "have person info fields" do | 65 | should "have person info fields" do |
| 66 | p = Person.new(:environment => Environment.default) | 66 | p = Person.new(:environment => Environment.default) |
| 67 | - [ :name, :photo, :contact_information, :birth_date, :sex, :address, :city, :state, :country, :zip_code, :image ].each do |i| | 67 | + [ :name, :photo, :contact_information, :birth_date, :sex, :address, :city, :state, :country, :zip_code, :image, :district, :address_reference ].each do |i| |
| 68 | assert_respond_to p, i | 68 | assert_respond_to p, i |
| 69 | end | 69 | end |
| 70 | end | 70 | end |
test/unit/plugin_settings_test.rb
| @@ -10,29 +10,41 @@ class PluginSettingsTest < ActiveSupport::TestCase | @@ -10,29 +10,41 @@ class PluginSettingsTest < ActiveSupport::TestCase | ||
| 10 | 10 | ||
| 11 | def setup | 11 | def setup |
| 12 | @environment = Environment.new | 12 | @environment = Environment.new |
| 13 | + @profile = Profile.new | ||
| 13 | @plugin = SolarSystemPlugin | 14 | @plugin = SolarSystemPlugin |
| 14 | - @settings = Noosfero::Plugin::Settings.new(@environment, @plugin) | ||
| 15 | end | 15 | end |
| 16 | 16 | ||
| 17 | - attr_accessor :environment, :plugin, :settings | 17 | + attr_accessor :environment, :profile, :plugin, :settings |
| 18 | 18 | ||
| 19 | - should 'store setttings in environment' do | 19 | + should 'store setttings on any model that offers settings' do |
| 20 | + base = environment | ||
| 21 | + settings = Noosfero::Plugin::Settings.new(base, plugin) | ||
| 20 | settings.star = 'sun' | 22 | settings.star = 'sun' |
| 21 | settings.planets = 8 | 23 | settings.planets = 8 |
| 22 | - assert_equal 'sun', environment.settings[:solar_system_plugin][:star] | ||
| 23 | - assert_equal 8, environment.settings[:solar_system_plugin][:planets] | 24 | + assert_equal 'sun', base.settings[:solar_system_plugin][:star] |
| 25 | + assert_equal 8, base.settings[:solar_system_plugin][:planets] | ||
| 26 | + assert_equal 'sun', settings.star | ||
| 27 | + assert_equal 8, settings.planets | ||
| 28 | + | ||
| 29 | + base = profile | ||
| 30 | + settings = Noosfero::Plugin::Settings.new(base, plugin) | ||
| 31 | + settings.star = 'sun' | ||
| 32 | + settings.planets = 8 | ||
| 33 | + assert_equal 'sun', base.settings[:solar_system_plugin][:star] | ||
| 34 | + assert_equal 8, base.settings[:solar_system_plugin][:planets] | ||
| 24 | assert_equal 'sun', settings.star | 35 | assert_equal 'sun', settings.star |
| 25 | assert_equal 8, settings.planets | 36 | assert_equal 8, settings.planets |
| 26 | end | 37 | end |
| 27 | 38 | ||
| 28 | - should 'save environment on save' do | 39 | + should 'save base on save' do |
| 29 | environment.expects(:save!) | 40 | environment.expects(:save!) |
| 41 | + settings = Noosfero::Plugin::Settings.new(environment, plugin) | ||
| 30 | settings.save! | 42 | settings.save! |
| 31 | end | 43 | end |
| 32 | 44 | ||
| 33 | should 'use default value defined on the plugin class' do | 45 | should 'use default value defined on the plugin class' do |
| 46 | + settings = Noosfero::Plugin::Settings.new(profile, plugin) | ||
| 34 | assert_equal 42, settings.secret | 47 | assert_equal 42, settings.secret |
| 35 | end | 48 | end |
| 36 | 49 | ||
| 37 | end | 50 | end |
| 38 | - |
test/unit/product_test.rb
| @@ -772,4 +772,40 @@ class ProductTest < ActiveSupport::TestCase | @@ -772,4 +772,40 @@ class ProductTest < ActiveSupport::TestCase | ||
| 772 | assert_equal [prod3, prod2, prod1], Product.more_recent | 772 | assert_equal [prod3, prod2, prod1], Product.more_recent |
| 773 | end | 773 | end |
| 774 | 774 | ||
| 775 | + should 'return products from a category' do | ||
| 776 | + pc1 = ProductCategory.create!(:name => 'PC1', :environment => Environment.default) | ||
| 777 | + pc2 = ProductCategory.create!(:name => 'PC2', :environment => Environment.default) | ||
| 778 | + pc3 = ProductCategory.create!(:name => 'PC3', :environment => Environment.default, :parent => pc1) | ||
| 779 | + p1 = fast_create(Product, :product_category_id => pc1) | ||
| 780 | + p2 = fast_create(Product, :product_category_id => pc1) | ||
| 781 | + p3 = fast_create(Product, :product_category_id => pc2) | ||
| 782 | + p4 = fast_create(Product, :product_category_id => pc3) | ||
| 783 | + | ||
| 784 | + products = Product.from_category(pc1) | ||
| 785 | + | ||
| 786 | + assert_includes products, p1 | ||
| 787 | + assert_includes products, p2 | ||
| 788 | + assert_not_includes products, p3 | ||
| 789 | + assert_includes products, p4 | ||
| 790 | + end | ||
| 791 | + | ||
| 792 | + should 'not crash if nil is passed to from_category' do | ||
| 793 | + assert_nothing_raised do | ||
| 794 | + Product.from_category(nil) | ||
| 795 | + end | ||
| 796 | + end | ||
| 797 | + | ||
| 798 | + should 'return from_category scope untouched if passed nil' do | ||
| 799 | + enterprise = fast_create(Enterprise) | ||
| 800 | + p1 = fast_create(Product, :enterprise_id => enterprise.id) | ||
| 801 | + p2 = fast_create(Product, :enterprise_id => enterprise.id) | ||
| 802 | + p3 = fast_create(Product, :enterprise_id => enterprise.id) | ||
| 803 | + | ||
| 804 | + products = enterprise.products.from_category(nil) | ||
| 805 | + | ||
| 806 | + assert_includes products, p1 | ||
| 807 | + assert_includes products, p2 | ||
| 808 | + assert_includes products, p3 | ||
| 809 | + end | ||
| 810 | + | ||
| 775 | end | 811 | end |