Commit 2ae57e721398479198b0b97a9bdae1802672cf30

Authored by Antonio Terceiro
2 parents 8665815d 8d889f08

Merge branch 'master' into rails-2.3.5

Conflicts:
	features/step_definitions/noosfero_steps.rb
Showing 78 changed files with 2512 additions and 374 deletions   Show diff stats
app/controllers/my_profile/manage_products_controller.rb
@@ -111,6 +111,36 @@ class ManageProductsController < ApplicationController @@ -111,6 +111,36 @@ class ManageProductsController < ApplicationController
111 end 111 end
112 end 112 end
113 113
  114 + def manage_product_details
  115 + @product = @profile.products.find(params[:id])
  116 + if request.post?
  117 + @product.update_price_details(params[:price_details]) if params[:price_details]
  118 + render :partial => 'display_price_details'
  119 + else
  120 + render :partial => 'manage_product_details'
  121 + end
  122 + end
  123 +
  124 + def remove_price_detail
  125 + @product = @profile.products.find(params[:product])
  126 + @price_detail = @product.price_details.find(params[:id])
  127 + @product = @price_detail.product
  128 + if request.post?
  129 + @price_detail.destroy
  130 + render :nothing => true
  131 + end
  132 + end
  133 +
  134 + def display_price_composition_bar
  135 + @product = @profile.products.find(params[:id])
  136 + render :partial => 'price_composition_bar'
  137 + end
  138 +
  139 + def display_inputs_cost
  140 + @product = @profile.products.find(params[:id])
  141 + render :inline => "<%= float_to_currency(@product.inputs_cost) %>"
  142 + end
  143 +
114 def destroy 144 def destroy
115 @product = @profile.products.find(params[:id]) 145 @product = @profile.products.find(params[:id])
116 if @product.destroy 146 if @product.destroy
@@ -167,4 +197,18 @@ class ManageProductsController &lt; ApplicationController @@ -167,4 +197,18 @@ class ManageProductsController &lt; ApplicationController
167 end 197 end
168 end 198 end
169 199
  200 + def create_production_cost
  201 + cost = @profile.production_costs.create(:name => params[:id])
  202 + if cost.valid?
  203 + cost.save
  204 + render :text => {:name => cost.name,
  205 + :id => cost.id,
  206 + :ok => true
  207 + }.to_json
  208 + else
  209 + render :text => {:ok => false,
  210 + :error_msg => _(cost.errors['name']) % {:fn => _('Name')}
  211 + }.to_json
  212 + end
  213 + end
170 end 214 end
app/controllers/public/catalog_controller.rb
@@ -4,10 +4,11 @@ class CatalogController &lt; PublicController @@ -4,10 +4,11 @@ class CatalogController &lt; PublicController
4 before_filter :check_enterprise_and_environment 4 before_filter :check_enterprise_and_environment
5 5
6 def index 6 def index
7 - @products = @profile.products.paginate(:per_page => 10, :page => params[:page]) 7 + @products = @profile.products.paginate(:order => 'name asc', :per_page => 9, :page => params[:page])
8 end 8 end
9 9
10 protected 10 protected
  11 +
11 def check_enterprise_and_environment 12 def check_enterprise_and_environment
12 unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises') 13 unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises')
13 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index' 14 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index'
app/helpers/application_helper.rb
@@ -1254,25 +1254,27 @@ module ApplicationHelper @@ -1254,25 +1254,27 @@ module ApplicationHelper
1254 task.information[:message] % values 1254 task.information[:message] % values
1255 end 1255 end
1256 1256
  1257 + def add_zoom_to_article_images
  1258 + add_zoom_to_images if environment.enabled?(:show_zoom_button_on_article_images)
  1259 + end
  1260 +
1257 def add_zoom_to_images 1261 def add_zoom_to_images
1258 - if environment.enabled?(:show_zoom_button_on_article_images)  
1259 - stylesheet_link_tag('fancybox') +  
1260 - javascript_include_tag('jquery.fancybox-1.3.4.pack') +  
1261 - javascript_tag("jQuery(function($) {  
1262 - $(window).load( function() {  
1263 - $('#article .article-body img').each( function(index) {  
1264 - var original = original_image_dimensions($(this).attr('src'));  
1265 - if ($(this).width() < original['width'] || $(this).height() < original['height']) {  
1266 - $(this).wrap('<div class=\"zoomable-image\" />');  
1267 - $(this).parent('.zoomable-image').attr('style', $(this).attr('style'));  
1268 - $(this).attr('style', '');  
1269 - $(this).after(\'<a href=\"' + $(this).attr('src') + '\" class=\"zoomify-image\"><span class=\"zoomify-text\">%s</span></a>');  
1270 - }  
1271 - });  
1272 - $('.zoomify-image').fancybox(); 1262 + stylesheet_link_tag('fancybox') +
  1263 + javascript_include_tag('jquery.fancybox-1.3.4.pack') +
  1264 + javascript_tag("jQuery(function($) {
  1265 + $(window).load( function() {
  1266 + $('#article .article-body img').each( function(index) {
  1267 + var original = original_image_dimensions($(this).attr('src'));
  1268 + if ($(this).width() < original['width'] || $(this).height() < original['height']) {
  1269 + $(this).wrap('<div class=\"zoomable-image\" />');
  1270 + $(this).parent('.zoomable-image').attr('style', $(this).attr('style'));
  1271 + $(this).attr('style', '');
  1272 + $(this).after(\'<a href=\"' + $(this).attr('src') + '\" class=\"zoomify-image\"><span class=\"zoomify-text\">%s</span></a>');
  1273 + }
1273 }); 1274 });
1274 - });" % _('Zoom in'))  
1275 - end 1275 + $('.zoomify-image').fancybox();
  1276 + });
  1277 + });" % _('Zoom in'))
1276 end 1278 end
1277 1279
1278 def render_dialog_error_messages(instance_name) 1280 def render_dialog_error_messages(instance_name)
app/helpers/catalog_helper.rb
1 module CatalogHelper 1 module CatalogHelper
2 2
3 -include DisplayHelper  
4 -include ManageProductsHelper 3 + include DisplayHelper
  4 + include ManageProductsHelper
5 5
6 - def display_products_list(profile, products)  
7 - data = ''  
8 - extra_content = []  
9 - extra_content_list = []  
10 - products.each { |product|  
11 - extra_content = @plugins.map(:catalog_item_extras, product).collect { |content| instance_eval(&content) } if @plugins  
12 - extra_content_list = @plugins.map(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } if @plugins  
13 - data << content_tag('li',  
14 - link_to_product(product, :class => 'product-pic', :style => 'background-image:url(%s)' % product.default_image(:portrait) ) +  
15 - content_tag('h3', link_to_product(product)) +  
16 - content_tag('ul',  
17 - (product.price ? content_tag('li', _('Price: %s') % ( "%.2f" % product.price), :class => 'product_price') : '') +  
18 - content_tag('li', product_category_name(profile, product.product_category), :class => 'product_category') +  
19 - extra_content_list.map { |content| content_tag('li', content)}.join("\n")  
20 - ) +  
21 - (product.description ? content_tag('div',  
22 - txt2html(product.description),  
23 - :class => 'description') : tag('br',  
24 - :style => 'clear:both')) +  
25 - extra_content.join("\n"),  
26 - :class => 'product')  
27 - }  
28 - content_tag('h1', _('Products/Services')) + content_tag('ul', data, :id => 'product_list')  
29 - end  
30 -  
31 - private  
32 -  
33 - def product_category_name(profile, product_category)  
34 - if profile.enabled?  
35 - link_to_product_category(product_category)  
36 - else  
37 - product_category ? product_category.full_name(' &rarr; ') : _('Uncategorized product')  
38 - end  
39 - end  
40 end 6 end
app/helpers/display_helper.rb
@@ -8,6 +8,20 @@ module DisplayHelper @@ -8,6 +8,20 @@ module DisplayHelper
8 opts 8 opts
9 end 9 end
10 10
  11 + def image_link_to_product(product, opts={})
  12 + return _('No product') unless product
  13 + target = product_path(product)
  14 + link_to image_tag(product.default_image(:big), :alt => product.name),
  15 + target,
  16 + opts
  17 + end
  18 +
  19 + def price_span(price, options = {})
  20 + content_tag 'span',
  21 + number_to_currency(price, :unit => environment.currency_unit, :delimiter => environment.currency_delimiter, :separator => environment.currency_separator),
  22 + options
  23 + end
  24 +
11 def product_path(product) 25 def product_path(product)
12 product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url 26 product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
13 end 27 end
app/helpers/manage_products_helper.rb
@@ -271,4 +271,23 @@ module ManageProductsHelper @@ -271,4 +271,23 @@ module ManageProductsHelper
271 return input_amount_used if input.unit.blank? 271 return input_amount_used if input.unit.blank?
272 n_('1 %{singular_unit}', '%{num} %{plural_unit}', input.amount_used.to_f) % { :num => input_amount_used, :singular_unit => content_tag('span', input.unit.singular, :class => 'input-unit'), :plural_unit => content_tag('span', input.unit.plural, :class => 'input-unit') } 272 n_('1 %{singular_unit}', '%{num} %{plural_unit}', input.amount_used.to_f) % { :num => input_amount_used, :singular_unit => content_tag('span', input.unit.singular, :class => 'input-unit'), :plural_unit => content_tag('span', input.unit.plural, :class => 'input-unit') }
273 end 273 end
  274 +
  275 + def select_production_cost(product,selected=nil)
  276 + url = url_for( :controller => 'manage_products', :action => 'create_production_cost' )
  277 + prompt_msg = _('Insert the name of the new cost:')
  278 + error_msg = _('Something went wrong. Please, try again')
  279 + select_tag('price_details[][production_cost_id]',
  280 + '<option value="" disabled="disabled">' + _('Select...') + '</option>' +
  281 + options_for_select(product.available_production_costs.map {|item| [truncate(item.name, 10, '...'), item.id]} + [[_('Other cost'), '']], selected),
  282 + {:class => 'production-cost-selection',
  283 + :onchange => "productionCostTypeChange(this, '#{url}', '#{prompt_msg}', '#{error_msg}')"})
  284 + end
  285 +
  286 + def price_composition_progressbar_text(product, args = {})
  287 + currency = environment.currency_unit
  288 + production_cost = args[:production_cost_value] || product.formatted_value(:total_production_cost)
  289 + product_price = args[:product_price] || product.formatted_value(:price)
  290 +
  291 + _("%{currency} %{production_cost} of %{currency} %{product_price}") % {:currency => currency, :production_cost => content_tag('span', production_cost, :class => 'production_cost'), :product_price => content_tag('span', product_price, :class => 'product_price')}
  292 + end
274 end 293 end
app/models/enterprise.rb
@@ -6,6 +6,7 @@ class Enterprise &lt; Organization @@ -6,6 +6,7 @@ class Enterprise &lt; Organization
6 6
7 has_many :products, :dependent => :destroy, :order => 'name ASC' 7 has_many :products, :dependent => :destroy, :order => 'name ASC'
8 has_many :inputs, :through => :products 8 has_many :inputs, :through => :products
  9 + has_many :production_costs, :as => :owner
9 10
10 has_and_belongs_to_many :fans, :class_name => 'Person', :join_table => 'favorite_enteprises_people' 11 has_and_belongs_to_many :fans, :class_name => 'Person', :join_table => 'favorite_enteprises_people'
11 12
app/models/enterprise_homepage.rb
@@ -12,18 +12,13 @@ class EnterpriseHomepage &lt; Article @@ -12,18 +12,13 @@ class EnterpriseHomepage &lt; Article
12 profile.nil? ? _('Homepage') : profile.name 12 profile.nil? ? _('Homepage') : profile.name
13 end 13 end
14 14
15 - # FIXME isn't this too much including just to be able to generate some HTML?  
16 - include ActionView::Helpers::TagHelper  
17 - include ActionView::Helpers::UrlHelper  
18 - include ActionController::UrlWriter  
19 - include ActionView::Helpers::AssetTagHelper  
20 - include EnterpriseHomepageHelper  
21 - include CatalogHelper  
22 -  
23 - def to_html(options ={})  
24 - products = self.profile.products  
25 - display_profile_info(self.profile) + content_tag('div', self.body || '') +  
26 - (self.profile.environment.enabled?('disable_products_for_enterprises') ? '' : display_products_list(self.profile, products)) 15 + def to_html(options = {})
  16 + enterprise_homepage = self
  17 + lambda do
  18 + extend EnterpriseHomepageHelper
  19 + @products = profile.products.paginate(:order => 'id asc', :per_page => 9, :page => 1)
  20 + render :partial => 'content_viewer/enterprise_homepage', :object => enterprise_homepage
  21 + end
27 end 22 end
28 23
29 def can_display_hits? 24 def can_display_hits?
app/models/environment.rb
@@ -174,6 +174,7 @@ class Environment &lt; ActiveRecord::Base @@ -174,6 +174,7 @@ class Environment &lt; ActiveRecord::Base
174 acts_as_accessible 174 acts_as_accessible
175 175
176 has_many :units, :order => 'position' 176 has_many :units, :order => 'position'
  177 + has_many :production_costs, :as => :owner
177 178
178 def superior_intances 179 def superior_intances
179 [self, nil] 180 [self, nil]
app/models/image.rb
@@ -9,15 +9,15 @@ class Image &lt; ActiveRecord::Base @@ -9,15 +9,15 @@ class Image &lt; ActiveRecord::Base
9 has_attachment :content_type => :image, 9 has_attachment :content_type => :image,
10 :storage => :file_system, 10 :storage => :file_system,
11 :path_prefix => 'public/image_uploads', 11 :path_prefix => 'public/image_uploads',
12 - :resize_to => '320x200>', 12 + :resize_to => '800x600>',
13 :thumbnails => { :big => '150x150', 13 :thumbnails => { :big => '150x150',
14 :thumb => '100x100', 14 :thumb => '100x100',
15 :portrait => '64x64', 15 :portrait => '64x64',
16 :minor => '50x50', 16 :minor => '50x50',
17 :icon => '20x20!' }, 17 :icon => '20x20!' },
18 - :max_size => 500.kilobytes # remember to update validate message below 18 + :max_size => 5.megabytes # remember to update validate message below
19 19
20 - validates_attachment :size => N_("%{fn} of uploaded file was larger than the maximum size of 500.0 KB") 20 + validates_attachment :size => N_("%{fn} of uploaded file was larger than the maximum size of 5.0 MB")
21 21
22 delay_attachment_fu_thumbnails 22 delay_attachment_fu_thumbnails
23 23
app/models/input.rb
@@ -45,6 +45,18 @@ class Input &lt; ActiveRecord::Base @@ -45,6 +45,18 @@ class Input &lt; ActiveRecord::Base
45 %w[price_per_unit amount_used].each do |field| 45 %w[price_per_unit amount_used].each do |field|
46 return true unless self.send(field).blank? 46 return true unless self.send(field).blank?
47 end 47 end
48 - return false 48 + false
  49 + end
  50 +
  51 + def has_all_price_details?
  52 + %w[price_per_unit unit amount_used].each do |field|
  53 + return false if self.send(field).blank?
  54 + end
  55 + true
  56 + end
  57 +
  58 + def cost
  59 + return 0 if self.amount_used.blank? || self.price_per_unit.blank?
  60 + self.amount_used * self.price_per_unit
49 end 61 end
50 end 62 end
app/models/price_detail.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class PriceDetail < ActiveRecord::Base
  2 +
  3 + belongs_to :product
  4 + validates_presence_of :product_id
  5 +
  6 + belongs_to :production_cost
  7 + validates_presence_of :production_cost_id
  8 + validates_uniqueness_of :production_cost_id, :scope => :product_id
  9 +
  10 + def price
  11 + self[:price] || 0
  12 + end
  13 +
  14 + include FloatHelper
  15 + def price=(value)
  16 + if value.is_a?(String)
  17 + super(decimal_to_float(value))
  18 + else
  19 + super(value)
  20 + end
  21 + end
  22 +
  23 + def formatted_value(value)
  24 + ("%.2f" % self[value]).to_s.gsub('.', product.enterprise.environment.currency_separator) if self[value]
  25 + end
  26 +
  27 +end
app/models/product.rb
@@ -5,6 +5,8 @@ class Product &lt; ActiveRecord::Base @@ -5,6 +5,8 @@ class Product &lt; ActiveRecord::Base
5 has_many :product_qualifiers 5 has_many :product_qualifiers
6 has_many :qualifiers, :through => :product_qualifiers 6 has_many :qualifiers, :through => :product_qualifiers
7 has_many :inputs, :dependent => :destroy, :order => 'position' 7 has_many :inputs, :dependent => :destroy, :order => 'position'
  8 + has_many :price_details, :dependent => :destroy
  9 + has_many :production_costs, :through => :price_details
8 10
9 validates_uniqueness_of :name, :scope => :enterprise_id, :allow_nil => true 11 validates_uniqueness_of :name, :scope => :enterprise_id, :allow_nil => true
10 validates_presence_of :product_category_id 12 validates_presence_of :product_category_id
@@ -101,12 +103,13 @@ class Product &lt; ActiveRecord::Base @@ -101,12 +103,13 @@ class Product &lt; ActiveRecord::Base
101 enterprise.public_profile 103 enterprise.public_profile
102 end 104 end
103 105
104 - def formatted_value(value)  
105 - ("%.2f" % self[value]).to_s.gsub('.', enterprise.environment.currency_separator) if self[value] 106 + def formatted_value(method)
  107 + value = self[method] || self.send(method)
  108 + ("%.2f" % value).to_s.gsub('.', enterprise.environment.currency_separator) if value
106 end 109 end
107 110
108 def price_with_discount 111 def price_with_discount
109 - price - discount if discount 112 + discount ? (price - discount) : price
110 end 113 end
111 114
112 def price=(value) 115 def price=(value)
@@ -125,6 +128,23 @@ class Product &lt; ActiveRecord::Base @@ -125,6 +128,23 @@ class Product &lt; ActiveRecord::Base
125 end 128 end
126 end 129 end
127 130
  131 + # Note: will probably be completely overhauled for AI1413
  132 + def inputs_prices?
  133 + return false if self.inputs.count <= 0
  134 + self.inputs.each do |input|
  135 + return false if input.has_price_details? == false
  136 + end
  137 + true
  138 + end
  139 +
  140 + def any_inputs_details?
  141 + return false if self.inputs.count <= 0
  142 + self.inputs.each do |input|
  143 + return true if input.has_all_price_details? == true
  144 + end
  145 + false
  146 + end
  147 +
128 def has_basic_info? 148 def has_basic_info?
129 %w[unit price discount].each do |field| 149 %w[unit price discount].each do |field|
130 return true if !self.send(field).blank? 150 return true if !self.send(field).blank?
@@ -153,4 +173,43 @@ class Product &lt; ActiveRecord::Base @@ -153,4 +173,43 @@ class Product &lt; ActiveRecord::Base
153 true 173 true
154 end 174 end
155 175
  176 + def inputs_cost
  177 + return 0 if inputs.empty?
  178 + inputs.map(&:cost).inject { |sum,price| sum + price }
  179 + end
  180 +
  181 + def total_production_cost
  182 + return inputs_cost if price_details.empty?
  183 + inputs_cost + price_details.map(&:price).inject { |sum,price| sum + price }
  184 + end
  185 +
  186 + def price_described?
  187 + return false if price.nil?
  188 + (price - total_production_cost).zero?
  189 + end
  190 +
  191 + def update_price_details(price_details)
  192 + self.price_details.destroy_all
  193 + price_details.each do |price_detail|
  194 + self.price_details.create(price_detail)
  195 + end
  196 + end
  197 +
  198 + def price_description_percentage
  199 + total_production_cost * 100 / price
  200 + end
  201 +
  202 + def available_production_costs
  203 + self.enterprise.environment.production_costs + self.enterprise.production_costs
  204 + end
  205 +
  206 + include ActionController::UrlWriter
  207 + def price_composition_bar_display_url
  208 + url_for({:host => enterprise.default_hostname, :controller => 'manage_products', :action => 'display_price_composition_bar', :profile => enterprise.identifier, :id => self.id }.merge(Noosfero.url_options))
  209 + end
  210 +
  211 + def inputs_cost_update_url
  212 + url_for({:host => enterprise.default_hostname, :controller => 'manage_products', :action => 'display_inputs_cost', :profile => enterprise.identifier, :id => self.id }.merge(Noosfero.url_options))
  213 + end
  214 +
156 end 215 end
app/models/production_cost.rb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +class ProductionCost < ActiveRecord::Base
  2 +
  3 + belongs_to :owner, :polymorphic => true
  4 + validates_presence_of :owner
  5 + validates_presence_of :name
  6 + validates_length_of :name, :maximum => 30, :allow_blank => true
  7 + validates_uniqueness_of :name, :scope => [:owner_id, :owner_type]
  8 +end
app/views/catalog/index.rhtml
1 -<%= display_products_list @profile, @products %> 1 +<% extra_content = [] %>
  2 +<% extra_content_list = [] %>
2 3
3 -<%= pagination_links @products %> 4 +<ul id="product-list">
  5 + <li><h1><%= _('Products/Services') %></h1></li>
  6 +
  7 + <% @products.each do |product| %>
  8 + <% extra_content = @plugins.map(:catalog_item_extras, product).collect { |content| instance_eval(&content) } %>
  9 + <% extra_content_list = @plugins.map(:catalog_list_item_extras, product).collect { |content| instance_eval(&content) } %>
  10 +
  11 + <li class="product <%= "not-available" unless product.available %>">
  12 + <ul>
  13 + <li class="product-image-link">
  14 + <% if product.image %>
  15 + <div class="zoomable-image">
  16 + <%= link_to_product product, :class => 'product-big', :style => "background-image: url(#{product.default_image(:big)})" %>
  17 + <%= link_to content_tag(:span, _('Zoom in')), product.default_image(:big).gsub('_big',''), :class => 'zoomify-image' %>
  18 + </div>
  19 + <% else %>
  20 + <div class="no-image"><%= _('No image') %></div>
  21 + <% end %>
  22 + <div class="catalog-item-extras"><%= extra_content.join("\n") %></div>
  23 + </li>
  24 +
  25 + <li class="product-link"><%= link_to_product product %></li>
  26 +
  27 + <li class="product-price-line">
  28 + <% unless product.discount.blank? or product.discount == 0 %>
  29 + <span class="product-discount">
  30 + <span><%= _('from ') + price_span(product.price) %></span>
  31 + <span class="product-discount-by"><%= _('by ') %></span>
  32 + </span>
  33 + <% end %>
  34 + <% unless product.price.blank? or product.price == 0 %>
  35 + <span class="product-price">
  36 + <%= price_span product.price_with_discount, :class => "product-price #{'with-discount' unless product.discount}" %>
  37 + <span class="product-unit"><%= _(' / ') + (product.unit ? product.unit.singular : _('unit')) %></span>
  38 + </span>
  39 + <% end %>
  40 + <div style="clear: both"></div>
  41 + </li>
  42 +
  43 + <% if product.description %>
  44 + <li class="product-description expand-box">
  45 + <span id="product-description-button"><%= _('description') %></span>
  46 + <div>
  47 + <div class="arrow"></div>
  48 + <div class="content" id="product-description"><%= txt2html(product.description || '') %></div>
  49 + </div>
  50 + </li>
  51 + <% end %>
  52 +
  53 + <% if product.price_described? %>
  54 + <li class="product-price-composition expand-box">
  55 + <span id="product-price-composition-button"><%= _('price composition') %></span>
  56 + <div>
  57 + <div class="arrow"></div>
  58 + <div class="content" id="product-price-composition">
  59 + <% product.inputs.each do |i| %>
  60 + <div class="search-product-input-dots-to-price">
  61 + <div class="search-product-input-name"><%= i.product_category.name %></div>
  62 + <%= price_span i.cost, :class => 'search-product-input-price' %>
  63 + </div>
  64 + <% end %>
  65 + <% product.price_details.each do |i| %>
  66 + <div class="search-product-input-dots-to-price">
  67 + <div class="search-product-input-name"><%= i.production_cost.name %></div>
  68 + <%= price_span i.price, :class => 'search-product-input-price' %>
  69 + </div>
  70 + <% end %>
  71 + </div>
  72 + </div>
  73 + </li>
  74 + <% end %>
  75 +
  76 + <% if product.inputs.count > 0 %>
  77 + <li class="product-inputs expand-box">
  78 + <span id="inputs-button"><%= _('inputs and raw materials') %></span>
  79 + <div>
  80 + <div class="arrow"></div>
  81 + <div class="content" id="inputs-description">
  82 + <% product.inputs.each do |i| %>
  83 + <div>
  84 + <%= _('%{amount_used} %{unit} of') % {:amount_used => i.amount_used, :unit => i.unit.singular} + ' ' if i.has_all_price_details? %>
  85 + <%= i.product_category.name %>
  86 + </div>
  87 + <% end %>
  88 + </div>
  89 + </div>
  90 + </li>
  91 + <% end %>
  92 +
  93 + <% unless product.qualifiers.blank? %>
  94 + <li class="product-qualifiers">
  95 + <span><%= _('qualifiers') if product.product_qualifiers.count > 0 %></span>
  96 + <div><%= render :partial => 'shared/product/qualifiers', :locals => {:product => product} %></div>
  97 + <% end %>
  98 +
  99 + <% extra_content_list.map do |content| %>
  100 + <li class="catalog-list-item-extras"><%= content %></li>
  101 + <% end %>
  102 +
  103 + <li class="product-unavailable"><%= _('product unavailable') unless product.available %></li>
  104 + </ul>
  105 + </li>
  106 + <% end %>
  107 +</ul>
  108 +
  109 +<%= pagination_links @products, :params => {:controller => :catalog, :action => :index, :profile => profile.identifier} %>
  110 +
  111 +<%= add_zoom_to_images %>
  112 +
  113 +<br style="clear:both"/>
app/views/content_viewer/_enterprise_homepage.rhtml 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<%= display_profile_info enterprise_homepage.profile %>
  2 +<div><%= enterprise_homepage.body %></div>
  3 +<%= render :file => 'catalog/index' unless enterprise_homepage.profile.environment.enabled?('disable_products_for_enterprises') %>
app/views/content_viewer/view_page.rhtml
@@ -91,4 +91,4 @@ @@ -91,4 +91,4 @@
91 </div><!-- end class="comments" --> 91 </div><!-- end class="comments" -->
92 92
93 </div><!-- end id="article" --> 93 </div><!-- end id="article" -->
94 -<%= add_zoom_to_images %> 94 +<%= add_zoom_to_article_images %>
app/views/layouts/_javascript.rhtml
1 -<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery.cookie', 'reflection', 'add-and-join', 'jquery.tokeninput', 'report-abuse','colorbox', 'jquery-validation/jquery.validate', :cache => 'cache-general' %> 1 +<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery.cookie', 'reflection', 'add-and-join', 'jquery.tokeninput', 'report-abuse','colorbox', 'jquery-validation/jquery.validate', 'catalog', 'manage-products', :cache => 'cache-general' %>
2 <% language = FastGettext.locale %> 2 <% language = FastGettext.locale %>
3 <%= javascript_include_tag 'jquery-validation/localization/messages_'+language, 'jquery-validation/localization/methods_'+language %> 3 <%= javascript_include_tag 'jquery-validation/localization/messages_'+language, 'jquery-validation/localization/methods_'+language %>
app/views/layouts/application-ng.rhtml
@@ -11,13 +11,13 @@ @@ -11,13 +11,13 @@
11 <%= stylesheet_link_tag noosfero_stylesheets, :cache => 'cache' %> 11 <%= stylesheet_link_tag noosfero_stylesheets, :cache => 'cache' %>
12 <%= stylesheet_link_tag template_stylesheet_path %> 12 <%= stylesheet_link_tag template_stylesheet_path %>
13 <%= stylesheet_link_tag icon_theme_stylesheet_path %> 13 <%= stylesheet_link_tag icon_theme_stylesheet_path %>
14 - <%= stylesheet_link_tag theme_stylesheet_path %>  
15 <%= stylesheet_link_tag jquery_ui_theme_stylesheet_path %> 14 <%= stylesheet_link_tag jquery_ui_theme_stylesheet_path %>
16 <% @plugins.enabled_plugins.each do |plugin| %> 15 <% @plugins.enabled_plugins.each do |plugin| %>
17 <% if plugin.stylesheet? %> 16 <% if plugin.stylesheet? %>
18 <%= stylesheet_tag plugin.class.public_path('style.css'), {} %> 17 <%= stylesheet_tag plugin.class.public_path('style.css'), {} %>
19 <% end %> 18 <% end %>
20 <% end %> 19 <% end %>
  20 + <%= stylesheet_link_tag theme_stylesheet_path %>
21 21
22 <%# Add custom tags/styles/etc via content_for %> 22 <%# Add custom tags/styles/etc via content_for %>
23 <%= yield :head %> 23 <%= yield :head %>
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 <%= 30 <%=
31 @plugins.map(:head_ending).collect do |content| 31 @plugins.map(:head_ending).collect do |content|
32 content.respond_to?(:call) ? content.call : content 32 content.respond_to?(:call) ? content.call : content
33 - end.join('\n') 33 + end.join("\n")
34 %> 34 %>
35 </head> 35 </head>
36 <body class="<%= 36 <body class="<%=
@@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
45 <%= 45 <%=
46 @plugins.map(:body_beginning).collect do |content| 46 @plugins.map(:body_beginning).collect do |content|
47 content.respond_to?(:call) ? content.call : content 47 content.respond_to?(:call) ? content.call : content
48 - end.join('\n') 48 + end.join("\n")
49 %> 49 %>
50 50
51 <div id="wrap-1"> 51 <div id="wrap-1">
app/views/layouts/application.rhtml
@@ -27,11 +27,8 @@ @@ -27,11 +27,8 @@
27 <%# Add custom tags/styles/etc via content_for %> 27 <%# Add custom tags/styles/etc via content_for %>
28 <%= yield :head %> 28 <%= yield :head %>
29 <%= javascript_tag('render_all_jquery_ui_widgets()') %> 29 <%= javascript_tag('render_all_jquery_ui_widgets()') %>
30 - <script type="text/javascript">  
31 - jQuery(".numbers-only").keypress(function(event) {  
32 - return numbersonly(event, '<%= environment.currency_separator %>')  
33 - });  
34 - </script> 30 +
  31 + <%= render :partial => 'shared/numbers_only_javascript' %>
35 </head> 32 </head>
36 33
37 <body class='noosfero category<%= category_color %><%= 34 <body class='noosfero category<%= category_color %><%=
app/views/manage_products/_display_price_details.rhtml 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +<div id='display-manage-price-details'></div>
  2 +
  3 +<div id='display-price-details'>
  4 + <ul class='price-details-list'>
  5 + <li>
  6 + <div class='price-detail-name'><%= _('Inputs:') %></div>
  7 + <div class='price-detail-price inputs-cost'>
  8 + <span><%= float_to_currency(@product.inputs_cost) %></span>
  9 + </div>
  10 + </li>
  11 + <% @product.price_details.each do |price_detail| %>
  12 + <li>
  13 + <div class='price-detail-name'><%= "%s:" % price_detail.production_cost.name %></div>
  14 + <div class='price-detail-price'><%= float_to_currency(price_detail.price) %></div>
  15 + </li>
  16 + <% end %>
  17 + </ul>
  18 +</div>
app/views/manage_products/_edit_info.rhtml
@@ -49,15 +49,12 @@ @@ -49,15 +49,12 @@
49 <%= hidden_field_tag "product[qualifiers_list]" %> 49 <%= hidden_field_tag "product[qualifiers_list]" %>
50 <% end %> 50 <% end %>
51 51
  52 + <%= hidden_field_tag 'info-bar-update-url', @product.price_composition_bar_display_url, :class => 'bar-update-url' %>
  53 +
52 <% button_bar do %> 54 <% button_bar do %>
53 <%= submit_button :save, _('Save') %> 55 <%= submit_button :save, _('Save') %>
54 <%= cancel_edit_product_link(@product, 'info') %> 56 <%= cancel_edit_product_link(@product, 'info') %>
55 <% end %> 57 <% end %>
56 <% end %> 58 <% end %>
57 59
58 -<% javascript_tag do %>  
59 - jQuery(".numbers-only").keypress(function(event) {  
60 - var separator = "<%= environment.currency_separator %>"  
61 - return numbersonly(event, separator)  
62 - });  
63 -<% end %> 60 +<%= render :partial => 'shared/numbers_only_javascript' %>
app/views/manage_products/_edit_input.rhtml
1 <% form_for(@input, :url => {:controller => 'manage_products', :action => 'edit_input', :id => @input}, 1 <% form_for(@input, :url => {:controller => 'manage_products', :action => 'edit_input', :id => @input},
2 :html => {:method => 'post', :id => "edit-input-#{ @input.id }-form"}) do |f| %> 2 :html => {:method => 'post', :id => "edit-input-#{ @input.id }-form"}) do |f| %>
  3 +
  4 + <%= hidden_field_tag 'input-bar-update-url', @input.product.price_composition_bar_display_url, :class => 'bar-update-url' %>
  5 + <%= hidden_field_tag 'inputs-cost-update-url', @input.product.inputs_cost_update_url %>
  6 +
3 <table> 7 <table>
4 <tr> 8 <tr>
5 <td><%= f.label :amount_used, label_amount_used(@input), :class => 'formlabel' %></td> 9 <td><%= f.label :amount_used, label_amount_used(@input), :class => 'formlabel' %></td>
app/views/manage_products/_edit_price_details.rhtml 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +<% price_details.each do |price_detail| %>
  2 + <tr id='<%= "price-detail-#{price_detail.id}" %>'>
  3 + <td><%= select_production_cost(@product, price_detail.production_cost_id) %></td>
  4 + <td><%= labelled_form_field(environment.currency_unit, text_field_tag('price_details[][price]', price_detail.formatted_value(:price), :class => 'numbers-only price-details-price')) %></td>
  5 + <td>
  6 + <%= link_to_remote(_('Remove'),
  7 + :update => "price-detail-#{price_detail.id}",
  8 + :complete => "calculateValuesForBar();",
  9 + :confirm => _('Are you sure that you want to remove this cost?'),
  10 + :url => { :action => 'remove_price_detail', :id => price_detail, :product => @product }) %>
  11 + </tr>
  12 +<% end %>
  13 +
  14 +<%= render :partial => 'shared/numbers_only_javascript' %>
app/views/manage_products/_manage_product_details.rhtml 0 → 100644
@@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
  1 +<div id='price-composition-bar'>
  2 + <%= render :partial => 'price_composition_bar' %>
  3 +</div>
  4 +
  5 +<% form_tag({:action => 'manage_product_details'}, :method => 'post', :id => 'manage-product-details-form') do %>
  6 + <div>
  7 + <table id='display-product-price-details'>
  8 + <tr>
  9 + <td><%= _('Inputs') %></td>
  10 + <td class='inputs-cost'>
  11 + <span><%= float_to_currency(@product.inputs_cost) %></span>
  12 + </td>
  13 + <td>
  14 + <small><%= _('This value is composed by the total value of registered inputs') %></small>
  15 + </td>
  16 + </tr>
  17 + <%= render :partial => 'edit_price_details', :locals => {:price_details => @product.price_details} %>
  18 + </table>
  19 + </div>
  20 +
  21 + <%= hidden_field(:product, :inputs_cost) %>
  22 + <%= hidden_field(:product, :price) %>
  23 +
  24 + <% button_bar do %>
  25 + <%= submit_button :save, _('Save'), :disabled => '', :class => 'disabled' %>
  26 + <%= button(:cancel, _('Cancel'), '#', :class => 'cancel-price-details', 'data-confirm' => _('If you leave, you will lose all unsaved information. Are you sure you want to quit?')) %>
  27 + <%= button(:add, _('New cost'), '#', :id => 'add-new-cost') %>
  28 + <span class='loading-area'></span>
  29 + <% end %>
  30 +
  31 +<% end %>
  32 +
  33 +<div style='display:none'>
  34 + <table id='new-cost-fields'>
  35 + <tr>
  36 + <td><%= select_production_cost(@product) %></td>
  37 + <td><%= labelled_form_field(environment.currency_unit, text_field_tag('price_details[][price]', nil, :class => 'numbers-only price-details-price')) %></td>
  38 + <td><%= link_to(_('Cancel'), '#', {:class => 'cancel-new-cost'}) %></td>
  39 + </tr>
  40 + </table>
  41 +</div>
  42 +
  43 +<%= render :partial => 'shared/numbers_only_javascript' %>
app/views/manage_products/_price_composition_bar.rhtml 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +<% javascript_tag do %>
  2 + var value = <%= @product.price_description_percentage %>;
  3 + var total_cost = <%= @product.total_production_cost %>;
  4 + var price = '<%= @product.formatted_value(:price) %>';
  5 + var described = false;
  6 + var currency_format = { separator : '<%= environment.currency_separator %>', delimiter : '<%= environment.currency_delimiter %>', unit : '<%= environment.currency_unit %>' };
  7 + if (<%= @product.price_described? %>) {
  8 + var described = true;
  9 + }
  10 + priceCompositionBar(value,described,total_cost,price);
  11 +<% end %>
  12 +
  13 +<div id="price-details-info">
  14 + <div id="details-progressbar">
  15 + <div id='progressbar'></div>
  16 + <div id='progressbar-text'>
  17 + <%= price_composition_progressbar_text(@product) %>
  18 + </div>
  19 + </div>
  20 + <div id='progressbar-icon' class='ui-icon ui-icon-info' data-price-not-described-message='<%= _("The production cost of your product is not described yet. If you want to display the price composition, please add all the costs") %>' data-price-described-message='<%= _("The production cost of your product is fully described and will be displayed on your product's page") %>' data-price-described-notice='<%= _("Congratulations! Now the price is open to the public") %>'>
  21 +</div>
  22 +</div>
app/views/manage_products/_price_details_button.rhtml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<%= edit_ui_button(
  2 + _('Describe here the cost of production'),
  3 + {:action => 'manage_product_details', :id => @product.id},
  4 + :id => 'manage-product-details-button',
  5 + 'data-primary-icon' => 'ui-icon-pencil',
  6 + 'data-secondary-icon' => 'ui-icon-triangle-1-s',
  7 + :title => _('Describe details about how the price was defined')
  8 +) %>
  9 +<%= javascript_tag("render_jquery_ui_buttons('manage-product-details-button')") %>
  10 +<span class='loading-area'></span>
app/views/manage_products/show.rhtml
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 23
24 <br style='clear: both'/> 24 <br style='clear: both'/>
25 25
26 - <% unless !@allowed_user && (@product.description.blank? && @product.inputs.empty?) %> 26 + <% unless !@allowed_user && (@product.description.blank? && @product.inputs.empty? && !@product.price_described? ) %>
27 <div class='ui-tabs' id='product-<%= @product.id %>-tabs'> 27 <div class='ui-tabs' id='product-<%= @product.id %>-tabs'>
28 <ul> 28 <ul>
29 <% if !@product.description.blank? || @allowed_user %> 29 <% if !@product.description.blank? || @allowed_user %>
@@ -32,6 +32,9 @@ @@ -32,6 +32,9 @@
32 <% if !@product.inputs.empty? || @allowed_user %> 32 <% if !@product.inputs.empty? || @allowed_user %>
33 <li class='tab'><a href='#product-inputs'><%= _('Inputs and raw material') %></a></li> 33 <li class='tab'><a href='#product-inputs'><%= _('Inputs and raw material') %></a></li>
34 <% end %> 34 <% end %>
  35 + <% if @product.price_described? || @allowed_user %>
  36 + <li class='tab'><a href='#product-price-details'><%= _('Price composition') %></a></li>
  37 + <% end %>
35 </ul> 38 </ul>
36 <div id='product-description'> 39 <div id='product-description'>
37 <%= render :partial => 'manage_products/display_description' %> 40 <%= render :partial => 'manage_products/display_description' %>
@@ -39,6 +42,12 @@ @@ -39,6 +42,12 @@
39 <div id='product-inputs'> 42 <div id='product-inputs'>
40 <%= render :partial => 'manage_products/display_inputs' %> 43 <%= render :partial => 'manage_products/display_inputs' %>
41 </div> 44 </div>
  45 + <% if @product.price_described? || @allowed_user %>
  46 + <div id='product-price-details'>
  47 + <%= render :partial => 'manage_products/display_price_details' %>
  48 + <%= render :partial => 'manage_products/price_details_button' %>
  49 + </div>
  50 + <% end %>
42 </div> 51 </div>
43 <% end %> 52 <% end %>
44 53
app/views/shared/_numbers_only_javascript.rhtml 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +<% javascript_tag do %>
  2 + jQuery(".numbers-only").keypress(function(event) {
  3 + var separator = "<%= environment.currency_separator %>"
  4 + return numbersonly(event, separator)
  5 + });
  6 +<% end %>
app/views/shared/product/_qualifiers.rhtml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<% product.product_qualifiers.each do |pq| %>
  2 + <% if pq.qualifier %>
  3 + <span class="search-product-qualifier"><%= pq.qualifier.name + (pq.certifier.nil? ? _(";") : '') %></span>
  4 + <% end %>
  5 + <% if pq.certifier %>
  6 + <span class="search-product-certifier">&nbsp;<%= _('cert. ') + pq.certifier.name + _(";") %></span>
  7 + <% end %>
  8 +
  9 + <div style="clear: both"></div>
  10 +<% end %>
db/migrate/20110403184315_create_production_cost.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class CreateProductionCost < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :production_costs do |t|
  4 + t.string :name
  5 + t.references :owner, :polymorphic => true
  6 + t.timestamps
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + drop_table :production_costs
  12 + end
  13 +end
db/migrate/20110403193953_create_price_details.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class CreatePriceDetails < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :price_details do |t|
  4 + t.decimal :price, :default => 0
  5 + t.references :product
  6 + t.references :production_cost
  7 + t.timestamps
  8 + end
  9 + end
  10 +
  11 + def self.down
  12 + drop_table :price_details
  13 + end
  14 +end
@@ -323,6 +323,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do @@ -323,6 +323,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do
323 t.datetime "updated_at" 323 t.datetime "updated_at"
324 end 324 end
325 325
  326 + create_table "price_details", :force => true do |t|
  327 + t.decimal "price", :default => 0.0
  328 + t.integer "product_id"
  329 + t.integer "production_cost_id"
  330 + t.datetime "created_at"
  331 + t.datetime "updated_at"
  332 + end
  333 +
326 create_table "product_categorizations", :force => true do |t| 334 create_table "product_categorizations", :force => true do |t|
327 t.integer "category_id" 335 t.integer "category_id"
328 t.integer "product_id" 336 t.integer "product_id"
@@ -342,6 +350,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do @@ -342,6 +350,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do
342 t.datetime "updated_at" 350 t.datetime "updated_at"
343 end 351 end
344 352
  353 + create_table "production_costs", :force => true do |t|
  354 + t.string "name"
  355 + t.integer "owner_id"
  356 + t.string "owner_type"
  357 + t.datetime "created_at"
  358 + t.datetime "updated_at"
  359 + end
  360 +
345 create_table "products", :force => true do |t| 361 create_table "products", :force => true do |t|
346 t.integer "enterprise_id" 362 t.integer "enterprise_id"
347 t.integer "product_category_id" 363 t.integer "product_category_id"
features/admin_categories.feature
@@ -43,7 +43,7 @@ Feature: manage categories @@ -43,7 +43,7 @@ Feature: manage categories
43 When I follow "Show" 43 When I follow "Show"
44 Then I should see "Vegetarian" 44 Then I should see "Vegetarian"
45 And I should see "Steak" 45 And I should see "Steak"
46 - When I follow "Hide" 46 + When I follow "Hide" and sleep 1 second
47 Then I should not see "Vegetarian" 47 Then I should not see "Vegetarian"
48 And I should not see "Steak" 48 And I should not see "Steak"
49 49
features/browse_catalogs.feature 0 → 100644
@@ -0,0 +1,309 @@ @@ -0,0 +1,309 @@
  1 +Feature: browse catalogs
  2 + As a noosfero visitor
  3 + I want to browse catalogs of products
  4 +
  5 + Background:
  6 + Given the following users
  7 + | login | name |
  8 + | joaosilva | Joao Silva |
  9 + And the following enterprises
  10 + | identifier | owner | name | enabled |
  11 + | artebonito | joaosilva | Associação de Artesanato de Bonito | true |
  12 + And feature "disable_products_for_enterprises" is disabled on environment
  13 + And the following product_categories
  14 + | name |
  15 + | categ1 |
  16 + | food |
  17 + And I am on /catalog/artebonito
  18 +
  19 + Scenario: display titles
  20 + Then I should see "Associação de Artesanato de Bonito"
  21 + And I should see "Products/Services" within "#product-list"
  22 +
  23 + Scenario: display the simplest possible product
  24 + Given the following products
  25 + | owner | category |
  26 + | artebonito | categ1 |
  27 + And I am on /catalog/artebonito
  28 + Then I should see "categ1" within "li.product-link"
  29 + And I should see "No image" within ".no-image"
  30 + And I should not see "unit" within "#product-list"
  31 + And I should not see "product unavailable"
  32 + And I should not see "description"
  33 + And I should not see "qualifiers"
  34 + And I should not see "price composition"
  35 +
  36 + Scenario: display a simple product without price
  37 + Given the following products
  38 + | owner | category | name |
  39 + | artebonito | categ1 | Produto1 |
  40 + And I am on /catalog/artebonito
  41 + Then I should see "Produto1" within "li.product-link"
  42 + And I should see "No image" within ".no-image"
  43 + And I should not see "unit" within "#product-list"
  44 + And I should not see "product unavailable"
  45 + And I should not see "description"
  46 + And I should not see "qualifiers"
  47 + And I should not see "price composition"
  48 +
  49 + Scenario: display a simple product without details
  50 + Given the following products
  51 + | owner | category | name | price |
  52 + | artebonito | categ1 | Produto1 | 50.00 |
  53 + And I am on /catalog/artebonito
  54 + Then I should see "Produto1" within "li.product-link"
  55 + And I should see "50.00" within "span.product-price"
  56 + And I should see "unit" within "span.product-unit"
  57 + And I should see "No image" within ".no-image"
  58 + And I should not see "product unavailable"
  59 + And I should not see "description"
  60 + And I should not see "qualifiers"
  61 + And I should not see "price composition"
  62 +
  63 + Scenario: don't display the price when it's $0.00
  64 + Given the following products
  65 + | owner | category | name | price |
  66 + | artebonito | categ1 | Produto1 | 0.00 |
  67 + And I am on /catalog/artebonito
  68 + Then I should see "Produto1" within "li.product-link"
  69 + And I should not see "0.00"
  70 + And I should see "No image" within ".no-image"
  71 + And I should not see "product unavailable"
  72 + And I should not see "description"
  73 + And I should not see "qualifiers"
  74 + And I should not see "price composition"
  75 +
  76 + Scenario: product name links to product page
  77 + Given the following products
  78 + | owner | category | name | price |
  79 + | artebonito | categ1 | Produto1 | 50.00 |
  80 + And I am on /catalog/artebonito
  81 + When I follow "Produto1" within "li.product-link"
  82 + Then I should be taken to "Produto1" product page
  83 +
  84 + Scenario: display product with custom image
  85 + Given the following products
  86 + | owner | category | name | price | img |
  87 + | artebonito | categ1 | Agrotox | 12.34 | agrotox |
  88 + And I am on /catalog/artebonito
  89 + Then I should see "Agrotox" within "li.product-link"
  90 + And I should see "12.34" within "span.product-price"
  91 + And I should see "unit" within "span.product-unit"
  92 + And I should not see "No image"
  93 + And I should not see "product unavailable"
  94 + And I should not see "description"
  95 + And I should not see "qualifiers"
  96 + And I should not see "price composition"
  97 +
  98 + Scenario: display "zoom in" button
  99 + Given the following products
  100 + | owner | category | name | price | img |
  101 + | artebonito | categ1 | Agrotox | 12.34 | agrotox |
  102 + And I am on /catalog/artebonito
  103 + And I should not see "No image"
  104 + And I should see "Zoom in" within ".zoomify-image"
  105 +
  106 + Scenario: image links to product page
  107 + Given the following products
  108 + | owner | category | name | price | img |
  109 + | artebonito | categ1 | Agrotox | 12.34 | agrotox |
  110 + And I am on /catalog/artebonito
  111 + When I follow "Agrotox" within ".product-image-link"
  112 + Then I should be taken to "Agrotox" product page
  113 +
  114 + Scenario: display product with discount
  115 + Given the following products
  116 + | owner | category | name | price | discount | img |
  117 + | artebonito | categ1 | Semterrinha | 99.99 | 12.34 | semterrinha |
  118 + And I am on /catalog/artebonito
  119 + Then I should see "Semterrinha" within "li.product-link"
  120 + And I should see "99.99" within "span.product-discount"
  121 + And I should see "87.65" within "span.product-price"
  122 + And I should not see "No image"
  123 + And I should not see "description"
  124 + And I should not see "qualifiers"
  125 + And I should not see "price composition"
  126 +
  127 + @selenium
  128 + Scenario: display description button when needed (but not the description)
  129 + Given the following products
  130 + | owner | category | name | price | description |
  131 + | artebonito | categ1 | Produto2 | 12.34 | A small description for a product that doesn't exist. |
  132 + And I am on /catalog/artebonito
  133 + And I reload and wait for the page
  134 + Then I should see "Produto2" within "li.product-link"
  135 + And I should see "12.34" within "span.product-price"
  136 + And I should see "description" within "#product-description-button"
  137 + And the "product-description-button" should be visible
  138 + And I should see "A small description" within "#product-description"
  139 + And the "product-description" should not be visible
  140 +
  141 + @selenium
  142 + Scenario: display description when button is clicked
  143 + Given the following products
  144 + | owner | category | name | price | description |
  145 + | artebonito | categ1 | Produto3 | 12.34 | A small description for a product that doesn't exist. |
  146 + And I am on /catalog/artebonito
  147 + And I reload and wait for the page
  148 + When I click "product-description-button"
  149 + Then I should see "A small description" within "#product-description"
  150 + And the "product-description" should be visible
  151 +
  152 + @selenium
  153 + Scenario: hide description
  154 + Given the following products
  155 + | owner | category | name | price | description |
  156 + | artebonito | categ1 | Produto3 | 12.34 | A small description for a product that doesn't exist. |
  157 + And I am on /catalog/artebonito
  158 + And I reload and wait for the page
  159 + When I click "product-description-button"
  160 + Then I should see "A small description" within "#product-description"
  161 + And the "product-description" should be visible
  162 + When I click "product-list"
  163 + Then the "product-description" should not be visible
  164 +
  165 + Scenario: display unavailable product
  166 + Given the following products
  167 + | owner | category | name | price | available |
  168 + | artebonito | categ1 | Prod3 | 12.34 | false |
  169 + And I am on /catalog/artebonito
  170 + Then I should see "Prod3" within "li.not-available"
  171 + And I should see "12.34" within "li.not-available"
  172 + And I should see "product unavailable" within "li.product-unavailable"
  173 + And I should not see "qualifiers"
  174 + And I should not see "price composition"
  175 +
  176 + Scenario: display qualifiers
  177 + Given the following qualifiers
  178 + | name |
  179 + | Organic |
  180 + And the following certifiers
  181 + | name | qualifiers |
  182 + | Colivre | Organic |
  183 + And the following products
  184 + | owner | category | name | price | qualifier |
  185 + | artebonito | categ1 | Banana | 0.99 | Organic |
  186 + And I am on /catalog/artebonito
  187 + Then I should see "Banana" within "li.product-link"
  188 + And I should see "0.99" within "span.product-price"
  189 + And I should see "qualifiers" within "li.product-qualifiers"
  190 + And I should see "Organic" within "span.search-product-qualifier"
  191 + And I should not see "price composition"
  192 +
  193 + @selenium
  194 + Scenario: not display price composition button if price is not described
  195 + Given the following product
  196 + | owner | category | name | price |
  197 + | artebonito | food | Bananada | 10.00 |
  198 + And the following input
  199 + | product | category | price_per_unit | amount_used |
  200 + | Bananada | food | 0.99 | 5 |
  201 + And I am on /catalog/artebonito
  202 + And I reload and wait for the page
  203 + Then I should see "Bananada" within "li.product-link"
  204 + And I should see "10.00" within "span.product-price"
  205 + And the "#product-price-composition-button" should not be visible
  206 +
  207 + @selenium
  208 + Scenario: display price composition button (but not inputs)
  209 + Given the following product
  210 + | owner | category | name | price |
  211 + | artebonito | food | Bananada | 10.00 |
  212 + And the following input
  213 + | product | category | price_per_unit | amount_used |
  214 + | Bananada | food | 2.00 | 5 |
  215 + And I am on /catalog/artebonito
  216 + And I reload and wait for the page
  217 + Then I should see "Bananada" within "li.product-link"
  218 + And I should see "10.00" within "span.product-price"
  219 + And I should see "price composition" within "#product-price-composition-button"
  220 + And the "#product-price-composition-button" should be visible
  221 + And I should see "food" within "#product-price-composition"
  222 + And I should see "10.00" within "#product-price-composition"
  223 +
  224 + @selenium
  225 + Scenario: display price composition when button is clicked
  226 + Given the following product
  227 + | owner | category | name | price |
  228 + | artebonito | food | Bananada | 10.88 |
  229 + And the following input
  230 + | product | category | price_per_unit | amount_used |
  231 + | Bananada | food | 2.72 | 4 |
  232 + And I am on /catalog/artebonito
  233 + And I reload and wait for the page
  234 + When I click "#product-price-composition-button"
  235 + Then the "#product-price-composition" should be visible
  236 + And I should see "food" within "#product-price-composition"
  237 + And I should see "10.88" within "#product-price-composition"
  238 +
  239 + @selenium
  240 + Scenario: display inputs and raw materials button when not completely filled
  241 + Given the following product
  242 + | owner | category | name | price |
  243 + | artebonito | food | Vitamina | 17.99 |
  244 + And the following unit
  245 + | name | plural |
  246 + | Liter | Liters |
  247 + And the following input
  248 + | product | category |
  249 + | Vitamina | food |
  250 + And I am on /catalog/artebonito
  251 + And I reload and wait for the page
  252 + Then the "#inputs-button" should be visible
  253 + And I should see "inputs and raw materials" within "#inputs-button"
  254 +
  255 + @selenium
  256 + Scenario: display inputs and raw materials button
  257 + Given the following product
  258 + | owner | category | name | price |
  259 + | artebonito | food | Vitamina | 17.99 |
  260 + And the following unit
  261 + | name | plural |
  262 + | Liter | Liters |
  263 + And the following input
  264 + | product | category | price_per_unit | amount_used | unit |
  265 + | Vitamina | food | 1.45 | 7 | Liter |
  266 + And I am on /catalog/artebonito
  267 + And I reload and wait for the page
  268 + Then I should see "Vitamina" within "li.product-link"
  269 + And I should see "17.99" within "span.product-price"
  270 + And the "#inputs-button" should be visible
  271 + And I should see "inputs and raw materials" within "#inputs-button"
  272 + And the "#inputs-description" should not be visible
  273 + And I should see "7.0 Liter of food" within "#inputs-description"
  274 +
  275 + @selenium
  276 + Scenario: display inputs and raw materials description
  277 + Given the following product
  278 + | owner | category | name | price |
  279 + | artebonito | food | Vitamina | 17.99 |
  280 + And the following unit
  281 + | name | plural |
  282 + | Liter | Liters |
  283 + And the following input
  284 + | product | category | price_per_unit | amount_used | unit |
  285 + | Vitamina | food | 1.45 | 7 | Liter |
  286 + And I am on /catalog/artebonito
  287 + And I reload and wait for the page
  288 + When I click "#inputs-button"
  289 + Then the "#inputs-description" should be visible
  290 + And I should see "7.0 Liter of food" within "#inputs-description"
  291 +
  292 + @selenium
  293 + Scenario: hide inputs and raw materials
  294 + Given the following product
  295 + | owner | category | name | price |
  296 + | artebonito | food | Vitamina | 17.99 |
  297 + And the following unit
  298 + | name | plural |
  299 + | Liter | Liters |
  300 + And the following input
  301 + | product | category | price_per_unit | amount_used | unit |
  302 + | Vitamina | food | 1.45 | 7 | Liter |
  303 + And I am on /catalog/artebonito
  304 + And I reload and wait for the page
  305 + When I click "#inputs-button"
  306 + Then the "#inputs-description" should be visible
  307 + And I should see "7.0 Liter of food" within "#inputs-description"
  308 + When I click "#product-list"
  309 + Then the "#inputs-description" should not be visible
features/browse_enterprises.feature 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +Feature: browse enterprises
  2 + As a noosfero user
  3 + I want to browse enterprises
  4 +
  5 +Background:
  6 + Given the following enterprises
  7 + | identifier | name |
  8 + | shop1 | Shoes Shop |
  9 + And feature "disable_products_for_enterprises" is disabled on environment
  10 + And feature "show_balloon_with_profile_links_when_clicked" is enabled on environment
  11 +
  12 +Scenario: show all enterprises
  13 + Given the following enterprises
  14 + | identifier | name |
  15 + | shop2 | Fruits Shop |
  16 + Given I am on /assets/enterprises
  17 + Then I should see "Enterprises"
  18 + And I should see "Shoes Shop"
  19 + And I should see "Fruits Shop"
  20 +
  21 +Scenario: show profile links button
  22 + Given I am on /assets/enterprises
  23 + Then I should see "Profile links" within "a.enterprise-trigger"
  24 + And I should not see "Members"
  25 + And I should not see "Agenda"
  26 +
  27 +@selenium
  28 +Scenario: show profile links when clicked
  29 + Given I am on /assets/enterprises
  30 + When I follow "Profile links"
  31 + Then I should see "Products" within "ul.menu-submenu-list"
  32 + And I should see "Members" within "ul.menu-submenu-list"
  33 + And I should see "Agenda" within "ul.menu-submenu-list"
  34 +
  35 +@selenium
  36 +Scenario: go to catalog when click on products link
  37 + Given I am on /assets/enterprises
  38 + When I follow "Profile links"
  39 + And I follow "Products" and wait
  40 + Then I should be exactly on /catalog/shop1
features/enterprise_homepage.feature 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +# These tests were originally unit tests, but they were moved here since they are view tests. The originals have been kept just in case somebody wants to review them, but should be removed shortly.
  2 +
  3 +Feature: enterprise homepage
  4 + As a noosfero visitor
  5 + I want to browse an enterprise's homepage
  6 + In order to know more information about the enterprise
  7 +
  8 + Background:
  9 + Given the following users
  10 + | login | name |
  11 + | durdentyler | Tyler Durden |
  12 + And the following enterprises
  13 + | identifier | owner | name | contact_email | contact_phone | enabled |
  14 + | mayhem | durdentyler | Paper Street Soap Co. | queen@workerbees.org | (288) 555-0153 | true |
  15 + And the following enterprise homepage
  16 + | enterprise | name |
  17 + | mayhem | article homepage |
  18 + And the following product_category
  19 + | name |
  20 + | soap |
  21 + And the following product
  22 + | name | category | owner |
  23 + | Natural Handmade | soap | mayhem |
  24 +
  25 +
  26 + Scenario: display profile info
  27 + When I go to /mayhem/homepage
  28 + Then I should see "queen@workerbees.org"
  29 + And I should see "(288) 555-0153"
  30 +
  31 + Scenario: display products list
  32 + When I go to /mayhem/homepage
  33 + Then I should see "Natural Handmade"
  34 +
  35 + Scenario: display link to product
  36 + When I go to /mayhem/homepage
  37 + And I follow "Natural Handmade"
  38 + Then I should be taken to "Natural Handmade" product page
features/manage_product_price_details.feature 0 → 100644
@@ -0,0 +1,178 @@ @@ -0,0 +1,178 @@
  1 +Feature: manage product price details
  2 + As an enterprise owner
  3 + I want to manage the details of product's price
  4 +
  5 + Background:
  6 + Given the following users
  7 + | login | name |
  8 + | joaosilva | Joao Silva |
  9 + And the following enterprises
  10 + | identifier | owner | name | enabled |
  11 + | redemoinho | joaosilva | Rede Moinho | true |
  12 + Given the following product_category
  13 + | name |
  14 + | Music |
  15 + And the following product_categories
  16 + | name | parent |
  17 + | Rock | music |
  18 + | CD Player | music |
  19 + And the following product
  20 + | owner | category | name | price |
  21 + | redemoinho | rock | Abbey Road | 80.0 |
  22 + And feature "disable_products_for_enterprises" is disabled on environment
  23 + And the following inputs
  24 + | product | category | price_per_unit | amount_used |
  25 + | Abbey Road | Rock | 10.0 | 2 |
  26 + | Abbey Road | CD Player | 20.0 | 2 |
  27 + And the following production cost
  28 + | name | owner |
  29 + | Taxes | environment |
  30 + And I am logged in as "joaosilva"
  31 +
  32 + @selenium
  33 + Scenario: list total value of inputs as price details
  34 + Given I go to Rede Moinho's page of product Abbey Road
  35 + And I follow "Price composition"
  36 + And I follow "Describe here the cost of production"
  37 + Then I should see "Inputs"
  38 + And I should see "60.0" within ".inputs-cost"
  39 +
  40 + @selenium
  41 + Scenario: return to product after save
  42 + Given I go to Rede Moinho's page of product Abbey Road
  43 + And I follow "Price composition"
  44 + And I follow "Describe here the cost of production"
  45 + And I press "Save"
  46 + Then I should be on Rede Moinho's page of product Abbey Road
  47 +
  48 + @selenium
  49 + Scenario: add first item on price details
  50 + Given I go to Rede Moinho's page of product Abbey Road
  51 + And I follow "Price composition"
  52 + And I follow "Describe here the cost of production"
  53 + And I follow "New cost"
  54 + And I select "Taxes"
  55 + And I fill in "$" with "5.00"
  56 + And I leave the ".price-details-price" field
  57 + And I press "Save"
  58 + Then I should not see "Save"
  59 + And I should see "Describe here the cost of production"
  60 +
  61 + @selenium
  62 + Scenario: edit a production cost
  63 + Given the following production cost
  64 + | name | owner |
  65 + | Energy | environment |
  66 + When I go to Rede Moinho's page of product Abbey Road
  67 + And I follow "Price composition"
  68 + And I follow "Describe here the cost of production"
  69 + And I follow "New cost"
  70 + And I select "Taxes"
  71 + And I fill in "$" with "20.00"
  72 + And I leave the ".price-details-price" field
  73 + And I press "Save"
  74 + Then I should not see "Save"
  75 + And I should see "Taxes" within "#display-price-details"
  76 + When I follow "Describe here the cost of production"
  77 + And I select "Energy"
  78 + And I leave the ".price-details-price" field
  79 + And I press "Save"
  80 + And I should not see "Taxes" within "#display-price-details"
  81 + And I should see "Energy" within "#display-price-details"
  82 +
  83 + Scenario: not display product detail button if product does not have input
  84 + Given the following product
  85 + | owner | category | name |
  86 + | redemoinho | rock | Yellow Submarine |
  87 + And the following user
  88 + | login | name |
  89 + | mariasouza | Maria Souza |
  90 + And I am logged in as "mariasouza"
  91 + When I go to Rede Moinho's page of product Yellow Submarine
  92 + And I follow "Price composition"
  93 + Then I should not see "Describe here the cost of production"
  94 +
  95 + Scenario: not display price details if price is not fully described
  96 + Given I am not logged in
  97 + And I go to Rede Moinho's page of product Abbey Road
  98 + Then I should not see "60.0"
  99 +
  100 + @selenium
  101 + Scenario: display price details if price is fully described
  102 + Given I go to Rede Moinho's page of product Abbey Road
  103 + And I follow "Price composition"
  104 + And I follow "Describe here the cost of production"
  105 + And I follow "New cost"
  106 + And I select "Taxes"
  107 + And I fill in "$" with "20.00"
  108 + And I press "Save"
  109 + Then I should see "Inputs" within ".price-detail-name"
  110 + And I should see "60.0" within ".price-detail-price"
  111 +
  112 + @selenium
  113 + Scenario: create a new cost clicking on select
  114 + Given I go to Rede Moinho's page of product Abbey Road
  115 + And I follow "Price composition"
  116 + And I follow "Describe here the cost of production"
  117 + And I follow "New cost"
  118 + And I want to add "Energy" as cost
  119 + And I select "Other cost"
  120 + And I fill in "$" with "10.00"
  121 + And I leave the ".price-details-price" field
  122 + And I press "Save"
  123 + When I follow "Describe here the cost of production"
  124 + Then I should see "Energy" within ".production-cost-selection"
  125 +
  126 + @selenium
  127 + Scenario: add created cost on new-cost-fields
  128 + Given I go to Rede Moinho's page of product Abbey Road
  129 + And I follow "Price composition"
  130 + And I follow "Describe here the cost of production"
  131 + And I want to add "Energy" as cost
  132 + And I select "Other cost"
  133 + Then I should see "Energy" within "#new-cost-fields"
  134 +
  135 + @selenium
  136 + Scenario: remove price detail
  137 + Given the following price detail
  138 + | product | production_cost | price |
  139 + | Abbey Road | Taxes | 20.0 |
  140 + And I go to Rede Moinho's page of product Abbey Road
  141 + And I follow "Price composition"
  142 + And I follow "Describe here the cost of production"
  143 + And I should see "Taxes" within "#manage-product-details-form"
  144 + When I follow "Remove" within "#manage-product-details-form"
  145 + And I confirm
  146 + And I press "Save"
  147 + And I follow "Describe here the cost of production"
  148 + Then I should not see "Taxes" within "#manage-product-details-form"
  149 +
  150 + Scenario: display progressbar
  151 + Given I go to Rede Moinho's page of product Abbey Road
  152 + And I follow "Price composition"
  153 + And I follow "Describe here the cost of production"
  154 + Then I should see "$ 60.00 of $ 80.00" within "#progressbar-text"
  155 +
  156 + @selenium
  157 + Scenario: update value on progressbar after addition of new cost
  158 + Given I go to Rede Moinho's page of product Abbey Road
  159 + And I follow "Price composition"
  160 + And I follow "Describe here the cost of production"
  161 + Then I should see "$ 60.00 of $ 80.00" within "#progressbar-text"
  162 + And I follow "New cost"
  163 + And I fill in "$" with "10.00"
  164 + And I leave the ".price-details-price" field
  165 + Then I should see "$ 70.00 of $ 80.00" within "#progressbar-text"
  166 +
  167 + @selenium
  168 + Scenario: update value on progressbar after editing an input
  169 + Given I go to Rede Moinho's page of product Abbey Road
  170 + And I follow "Price composition"
  171 + And I follow "Describe here the cost of production"
  172 + Then I should see "$ 60.00 of $ 80.00" within "#progressbar-text"
  173 + When I follow "Inputs"
  174 + And I follow "Edit" within ".input-details"
  175 + And I fill in "Price" with "23.31"
  176 + And I press "Save"
  177 + Then I follow "Price composition"
  178 + And I should see "$ 86.62 of $ 80.00" within "#progressbar-text"
features/manage_products.feature
@@ -35,19 +35,20 @@ Feature: manage products @@ -35,19 +35,20 @@ Feature: manage products
35 | redemoinho | bicycle | Bike J | bicycle 10 | 35 | redemoinho | bicycle | Bike J | bicycle 10 |
36 | redemoinho | bicycle | Bike K | bicycle 11 | 36 | redemoinho | bicycle | Bike K | bicycle 11 |
37 When I go to /catalog/redemoinho 37 When I go to /catalog/redemoinho
38 - Then I should see "Bike A" within "#product_list"  
39 - And I should see "Bike B" within "#product_list"  
40 - And I should see "Bike C" within "#product_list"  
41 - And I should see "Bike D" within "#product_list"  
42 - And I should see "Bike E" within "#product_list"  
43 - And I should see "Bike F" within "#product_list"  
44 - And I should see "Bike G" within "#product_list"  
45 - And I should see "Bike H" within "#product_list"  
46 - And I should see "Bike I" within "#product_list"  
47 - And I should see "Bike J" within "#product_list"  
48 - And I should not see "Bike K" within "#product_list" 38 + Then I should see "Bike A" within "#product-list"
  39 + And I should see "Bike B" within "#product-list"
  40 + And I should see "Bike C" within "#product-list"
  41 + And I should see "Bike D" within "#product-list"
  42 + And I should see "Bike E" within "#product-list"
  43 + And I should see "Bike F" within "#product-list"
  44 + And I should see "Bike G" within "#product-list"
  45 + And I should see "Bike H" within "#product-list"
  46 + And I should see "Bike I" within "#product-list"
  47 + And I should not see "Bike J" within "#product-list"
  48 + And I should not see "Bike K" within "#product-list"
49 When I follow "Next" 49 When I follow "Next"
50 - Then I should see "Bike K" within "#product_list" 50 + Then I should see "Bike J" within "#product-list"
  51 + Then I should see "Bike K" within "#product-list"
51 52
52 Scenario: listing products and services 53 Scenario: listing products and services
53 Given I am logged in as "joaosilva" 54 Given I am logged in as "joaosilva"
features/profile_domain.feature
1 Feature: domain for profile 1 Feature: domain for profile
  2 +
2 As a user 3 As a user
3 I want access a profile by its own domain 4 I want access a profile by its own domain
4 5
features/step_definitions/noosfero_steps.rb
@@ -150,8 +150,16 @@ Given /^the following products?$/ do |table| @@ -150,8 +150,16 @@ Given /^the following products?$/ do |table|
150 data = item.dup 150 data = item.dup
151 owner = Enterprise[data.delete("owner")] 151 owner = Enterprise[data.delete("owner")]
152 category = Category.find_by_slug(data.delete("category").to_slug) 152 category = Category.find_by_slug(data.delete("category").to_slug)
153 - img = Image.create!(:uploaded_data => fixture_file_upload('/files/rails.png', 'image/png'))  
154 - product = Product.create!(data.merge(:enterprise => owner, :product_category => category, :image_id => img.id)) 153 + data.merge!(:enterprise => owner, :product_category => category)
  154 + if data[:img]
  155 + img = Image.create!(:uploaded_data => fixture_file_upload('/files/'+data.delete("img")+'.png', 'image/png'))
  156 + data.merge!(:image_id => img.id)
  157 + end
  158 + if data[:qualifier]
  159 + qualifier = Qualifier.find_by_name(data.delete("qualifier"))
  160 + data.merge!(:qualifiers => [qualifier])
  161 + end
  162 + product = Product.create!(data)
155 end 163 end
156 end 164 end
157 165
@@ -214,6 +222,22 @@ Given /^the following certifiers$/ do |table| @@ -214,6 +222,22 @@ Given /^the following certifiers$/ do |table|
214 end 222 end
215 end 223 end
216 224
  225 +Given /^the following production costs?$/ do |table|
  226 + table.hashes.map{|item| item.dup}.each do |item|
  227 + owner_type = item.delete('owner')
  228 + owner = owner_type == 'environment' ? Environment.default : Profile[owner_type]
  229 + ProductionCost.create!(item.merge(:owner => owner))
  230 + end
  231 +end
  232 +
  233 +Given /^the following price details?$/ do |table|
  234 + table.hashes.map{|item| item.dup}.each do |item|
  235 + product = Product.find_by_name item.delete('product')
  236 + production_cost = ProductionCost.find_by_name item.delete('production_cost')
  237 + product.price_details.create!(item.merge(:production_cost => production_cost))
  238 + end
  239 +end
  240 +
217 Given /^I am logged in as "(.+)"$/ do |username| 241 Given /^I am logged in as "(.+)"$/ do |username|
218 visit('/account/logout') 242 visit('/account/logout')
219 visit('/account/login') 243 visit('/account/login')
@@ -493,3 +517,32 @@ end @@ -493,3 +517,32 @@ end
493 When 'I log off' do 517 When 'I log off' do
494 visit '/account/logout' 518 visit '/account/logout'
495 end 519 end
  520 +
  521 +Then /^I should be taken to "([^\"]*)" product page$/ do |product_name|
  522 + product = Product.find_by_name(product_name)
  523 + path = url_for(product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product, :only_path => true))
  524 + if response.class.to_s == 'Webrat::SeleniumResponse'
  525 + URI.parse(response.selenium.get_location).path.should == path_to(path)
  526 + else
  527 + URI.parse(current_url).path.should == path_to(path)
  528 + end
  529 +end
  530 +
  531 +When /^I reload and wait for the page$/ do
  532 + response.selenium.refresh
  533 + selenium.wait_for_page
  534 +end
  535 +
  536 +Given /^the following enterprise homepages?$/ do |table|
  537 + # table is a Cucumber::Ast::Table
  538 + table.hashes.each do |item|
  539 + data = item.dup
  540 + home = EnterpriseHomepage.new(:name => data[:name])
  541 + ent = Enterprise.find_by_identifier(data[:enterprise])
  542 + ent.articles << home
  543 + end
  544 +end
  545 +
  546 +And /^I want to add "([^\"]*)" as cost$/ do |string|
  547 + selenium.answer_on_next_prompt(string)
  548 +end
features/step_definitions/selenium_steps.rb
@@ -117,3 +117,17 @@ Then /^the select for category &quot;([^\&quot;]*)&quot; should be visible$/ do |name| @@ -117,3 +117,17 @@ Then /^the select for category &quot;([^\&quot;]*)&quot; should be visible$/ do |name|
117 category = Category.find_by_name(name) 117 category = Category.find_by_name(name)
118 selenium.is_visible(string_to_element_locator("option=#{category.id}")).should be_true 118 selenium.is_visible(string_to_element_locator("option=#{category.id}")).should be_true
119 end 119 end
  120 +
  121 +When /^I follow "([^\"]*)" and sleep ([^\"]*) seconds?$/ do |link, time|
  122 + click_link(link)
  123 + sleep time.to_i
  124 +end
  125 +
  126 +When /^I follow "([^\"]*)" and wait for jquery$/ do |link|
  127 + click_link(link)
  128 + selenium.wait_for(:wait_for => :ajax, :javascript_framework => framework)
  129 +end
  130 +
  131 +When /^I leave the "([^\"]+)" field$/ do |field|
  132 + selenium.fire_event("css=#{field}", "blur")
  133 +end
features/step_definitions/webrat_steps.rb
@@ -19,6 +19,7 @@ end @@ -19,6 +19,7 @@ end
19 When /^I visit "([^\"]*)" and wait$/ do |page_name| 19 When /^I visit "([^\"]*)" and wait$/ do |page_name|
20 visit path_to(page_name) 20 visit path_to(page_name)
21 selenium.wait_for_page_to_load(10000) 21 selenium.wait_for_page_to_load(10000)
  22 +# selenium.wait_for_page
22 end 23 end
23 24
24 When /^I press "([^\"]*)"$/ do |button| 25 When /^I press "([^\"]*)"$/ do |button|
plugins/shopping_cart/controllers/shopping_cart_plugin_profile_controller.rb
@@ -118,7 +118,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController @@ -118,7 +118,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
118 session[:cart][:visibility] = true 118 session[:cart][:visibility] = true
119 render :text => { 119 render :text => {
120 :ok => true, 120 :ok => true,
121 - :message => _('Cart displayed.'), 121 + :message => _('Basket displayed.'),
122 :error => {:code => 0} 122 :error => {:code => 0}
123 }.to_json 123 }.to_json
124 rescue Exception => exception 124 rescue Exception => exception
@@ -137,7 +137,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController @@ -137,7 +137,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
137 session[:cart][:visibility] = false 137 session[:cart][:visibility] = false
138 render :text => { 138 render :text => {
139 :ok => true, 139 :ok => true,
140 - :message => _('Cart Hidden.'), 140 + :message => _('Basket hidden.'),
141 :error => {:code => 0} 141 :error => {:code => 0}
142 }.to_json 142 }.to_json
143 rescue Exception => exception 143 rescue Exception => exception
@@ -173,7 +173,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController @@ -173,7 +173,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
173 :ok => false, 173 :ok => false,
174 :error => { 174 :error => {
175 :code => 2, 175 :code => 2,
176 - :message => _("There is no cart.") 176 + :message => _("There is no basket.")
177 } 177 }
178 }.to_json 178 }.to_json
179 return false 179 return false
@@ -203,7 +203,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController @@ -203,7 +203,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
203 :ok => false, 203 :ok => false,
204 :error => { 204 :error => {
205 :code => 4, 205 :code => 4,
206 - :message => _("The cart doesn't have this product.") 206 + :message => _("The basket doesn't have this product.")
207 } 207 }
208 }.to_json 208 }.to_json
209 return false 209 return false
plugins/shopping_cart/lib/shopping_cart_plugin.rb
@@ -4,17 +4,17 @@ require_dependency &#39;shopping_cart_plugin/ext/person&#39; @@ -4,17 +4,17 @@ require_dependency &#39;shopping_cart_plugin/ext/person&#39;
4 class ShoppingCartPlugin < Noosfero::Plugin 4 class ShoppingCartPlugin < Noosfero::Plugin
5 5
6 def self.plugin_name 6 def self.plugin_name
7 - "Shopping Cart" 7 + "Shopping Basket"
8 end 8 end
9 9
10 def self.plugin_description 10 def self.plugin_description
11 - _("A shopping cart feature for enterprises") 11 + _("A shopping basket feature for enterprises")
12 end 12 end
13 13
14 def add_to_cart_button(item, enterprise = context.profile) 14 def add_to_cart_button(item, enterprise = context.profile)
15 if enterprise.shopping_cart && item.available 15 if enterprise.shopping_cart && item.available
16 lambda { 16 lambda {
17 - link_to(_('Add to cart'), "add:#{item.name}", 17 + link_to(_('Add to basket'), "add:#{item.name}",
18 :class => 'cart-add-item', 18 :class => 'cart-add-item',
19 :onclick => "Cart.addItem('#{enterprise.identifier}', #{item.id}, this); return false" 19 :onclick => "Cart.addItem('#{enterprise.identifier}', #{item.id}, this); return false"
20 ) 20 )
@@ -41,10 +41,10 @@ class ShoppingCartPlugin &lt; Noosfero::Plugin @@ -41,10 +41,10 @@ class ShoppingCartPlugin &lt; Noosfero::Plugin
41 def control_panel_buttons 41 def control_panel_buttons
42 buttons = [] 42 buttons = []
43 if context.profile.enterprise? 43 if context.profile.enterprise?
44 - buttons << { :title => 'Shopping cart', :icon => 'shopping_cart_icon', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'edit'} } 44 + buttons << { :title => _('Shopping basket'), :icon => 'shopping-cart-icon', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'edit'} }
45 end 45 end
46 if context.profile.enterprise? && context.profile.shopping_cart 46 if context.profile.enterprise? && context.profile.shopping_cart
47 - buttons << { :title => 'Purchase reports', :icon => 'shopping-cart-purchase-report', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'reports'} } 47 + buttons << { :title => _('Purchase reports'), :icon => 'shopping-cart-purchase-report', :url => {:controller => 'shopping_cart_plugin_myprofile', :action => 'reports'} }
48 end 48 end
49 49
50 buttons 50 buttons
plugins/shopping_cart/public/cart.js
@@ -62,7 +62,7 @@ function Cart(config) { @@ -62,7 +62,7 @@ function Cart(config) {
62 '<div class="item-price">' + 62 '<div class="item-price">' +
63 '<input size="1" value="'+item.quantity+'" />'+ (item.price ? '&times; '+ item.price : '') +'</div>' + 63 '<input size="1" value="'+item.quantity+'" />'+ (item.price ? '&times; '+ item.price : '') +'</div>' +
64 ' <a href="remove:'+item.name+'" onclick="Cart.removeItem(\''+this.enterprise+'\', '+item.id+'); return false"' + 64 ' <a href="remove:'+item.name+'" onclick="Cart.removeItem(\''+this.enterprise+'\', '+item.id+'); return false"' +
65 - ' class="button icon-delete"><span>remove</span></a>' 65 + ' class="button icon-remove"><span>remove</span></a>'
66 ).appendTo(li); 66 ).appendTo(li);
67 var input = $("input", li)[0]; 67 var input = $("input", li)[0];
68 input.lastValue = input.value; 68 input.lastValue = input.value;
@@ -304,6 +304,10 @@ function Cart(config) { @@ -304,6 +304,10 @@ function Cart(config) {
304 }); 304 });
305 } 305 }
306 306
  307 + Cart.colorbox_close = function() {
  308 + $.colorbox.close();
  309 + }
  310 +
307 $(function(){ 311 $(function(){
308 $('.cart-add-item').button({ icons: { primary: 'ui-icon-cart'} }) 312 $('.cart-add-item').button({ icons: { primary: 'ui-icon-cart'} })
309 }); 313 });
plugins/shopping_cart/public/images/button-icon.png 0 → 100644

827 Bytes

plugins/shopping_cart/public/images/control-panel/icon.gif 0 → 100644

351 Bytes

plugins/shopping_cart/public/images/control-panel/icon.png 0 → 100644

1.96 KB

plugins/shopping_cart/public/images/control-panel/icon.svg 0 → 100644
@@ -0,0 +1,120 @@ @@ -0,0 +1,120 @@
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<!-- Created with Inkscape (http://www.inkscape.org/) -->
  3 +
  4 +<svg
  5 + xmlns:dc="http://purl.org/dc/elements/1.1/"
  6 + xmlns:cc="http://creativecommons.org/ns#"
  7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  8 + xmlns:svg="http://www.w3.org/2000/svg"
  9 + xmlns="http://www.w3.org/2000/svg"
  10 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
  11 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
  12 + width="744.09448819"
  13 + height="1052.3622047"
  14 + id="svg3362"
  15 + version="1.1"
  16 + inkscape:version="0.48.2 r9819"
  17 + sodipodi:docname="icone.svg">
  18 + <defs
  19 + id="defs3364" />
  20 + <sodipodi:namedview
  21 + id="base"
  22 + pagecolor="#ffffff"
  23 + bordercolor="#666666"
  24 + borderopacity="1.0"
  25 + inkscape:pageopacity="0.0"
  26 + inkscape:pageshadow="2"
  27 + inkscape:zoom="0.7"
  28 + inkscape:cx="278.97361"
  29 + inkscape:cy="689.0069"
  30 + inkscape:document-units="px"
  31 + inkscape:current-layer="layer1"
  32 + showgrid="false"
  33 + inkscape:window-width="1366"
  34 + inkscape:window-height="721"
  35 + inkscape:window-x="-3"
  36 + inkscape:window-y="-3"
  37 + inkscape:window-maximized="1" />
  38 + <metadata
  39 + id="metadata3367">
  40 + <rdf:RDF>
  41 + <cc:Work
  42 + rdf:about="">
  43 + <dc:format>image/svg+xml</dc:format>
  44 + <dc:type
  45 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
  46 + <dc:title></dc:title>
  47 + </cc:Work>
  48 + </rdf:RDF>
  49 + </metadata>
  50 + <g
  51 + inkscape:label="Layer 1"
  52 + inkscape:groupmode="layer"
  53 + id="layer1">
  54 + <g
  55 + id="g5575-7"
  56 + transform="matrix(0.07006136,0,0,0.07006136,-271.48534,-209.48041)"
  57 + style="fill:#d38d5f">
  58 + <path
  59 + inkscape:connector-curvature="0"
  60 + id="rect5245-9-6"
  61 + d="m 6408.0436,5064.9271 c -11.726,-11.2912 -32.3679,-9.6218 -42.4594,3.1418 -19.6123,19.9856 -39.9641,39.4417 -59.1428,59.74 -9.518,12.5005 -6.3676,31.3823 5.826,40.9329 19.6892,19.3981 38.735,39.51 58.8119,58.4787 12.3484,9.5947 31.3846,6.505 40.8262,-5.7528 19.4803,-19.979 39.9713,-39.2016 58.856,-59.6292 9.0558,-12.4283 5.7503,-30.9531 -6.2232,-40.3134 -18.839,-18.8581 -37.6197,-37.7778 -56.4947,-56.598 z"
  62 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  63 + <path
  64 + inkscape:connector-curvature="0"
  65 + id="rect5245-3-6-2"
  66 + d="m 6271.4191,5203.1033 c -11.659,-11.815 -32.7156,-10.3098 -42.9669,2.6329 -19.7244,20.2091 -40.4303,39.6819 -59.5621,60.3374 -9.0337,12.4807 -5.8043,30.8944 6.2011,40.2912 19.927,19.5313 39.0995,40.0758 59.474,59.0098 12.4653,9.0761 30.8739,5.764 40.2082,-6.2396 19.3801,-19.7478 39.4876,-38.9701 58.4368,-59.0318 9.4652,-12.3353 6.4045,-31.4347 -5.8482,-40.9551 -18.655,-18.6738 -37.2518,-37.4088 -55.9429,-56.0448 z"
  67 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  68 + <path
  69 + inkscape:connector-curvature="0"
  70 + id="rect5245-8-6-2"
  71 + d="m 6545.5287,5202.6829 c -11.7363,-11.3217 -32.3801,-9.5656 -42.4593,3.1418 -19.5937,19.9946 -39.9139,39.3349 -59.0767,59.7178 -9.5513,12.3449 -6.4556,31.438 5.7819,40.9772 19.6941,19.3922 38.7293,39.519 58.8119,58.4788 12.4118,9.6084 31.3584,6.4621 40.8704,-5.797 19.3229,-19.7402 39.4821,-38.8781 58.3264,-58.9655 9.5617,-12.3467 6.4938,-31.475 -5.7378,-40.933 -18.8464,-18.8655 -37.6344,-37.7925 -56.5168,-56.6201 z"
  72 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  73 + <path
  74 + inkscape:connector-curvature="0"
  75 + id="rect5245-7-4-7"
  76 + d="m 6683.1903,5065.7678 c -11.6727,-11.8214 -32.6711,-10.2704 -42.9667,2.6331 -19.5831,19.9842 -39.9685,39.3886 -59.0767,59.7177 -9.5608,12.3477 -6.4907,31.4696 5.7376,40.9329 19.6964,19.4311 38.8692,39.5905 58.8782,58.5894 12.4019,9.5309 31.358,6.4013 40.8042,-5.8192 19.3581,-19.7586 39.4434,-38.8626 58.3704,-59.0098 9.5628,-12.3863 6.44,-31.439 -5.7818,-40.977 -18.6625,-18.6812 -37.2665,-37.4235 -55.9652,-56.0671 z"
  77 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  78 + <path
  79 + inkscape:connector-curvature="0"
  80 + id="rect5245-5-2-8"
  81 + d="m 6820.3665,5203.4794 c -11.6211,-11.8358 -32.7205,-10.3463 -42.9888,2.6109 -19.6161,19.9836 -39.9579,39.4428 -59.1429,59.7399 -9.5091,12.4948 -6.4432,31.3232 5.7819,40.8887 19.7364,19.4377 38.8215,39.6072 58.9443,58.6115 12.2819,9.5014 31.2571,6.4201 40.7379,-5.8413 19.38,-19.7479 39.4876,-38.9702 58.4367,-59.0319 9.212,-12.1115 6.6473,-30.6982 -5.1419,-40.247 -18.8831,-18.9025 -37.708,-37.8662 -56.6272,-56.7308 z"
  82 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  83 + <path
  84 + inkscape:connector-curvature="0"
  85 + id="rect5245-5-4-1-9"
  86 + d="m 6662,5332.625 c -15.2378,0.1612 -24.0529,14.157 -34.1146,23.4276 -15.476,15.9413 -31.8697,31.086 -46.7916,47.5099 -2.9405,4.2478 -4.8192,9.3664 -5.1563,14.4687 58.2812,-0.042 116.7292,0.083 174.9063,-0.062 -0.7632,-15.4178 -15.3649,-24.1142 -24.7148,-34.6559 -15.4837,-15.0104 -30.156,-31.1956 -46.0978,-45.5317 -5.23,-3.6558 -11.6538,-5.4746 -18.0312,-5.1562 z"
  87 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  88 + <path
  89 + inkscape:connector-curvature="0"
  90 + id="rect5245-5-4-4-4-1"
  91 + d="m 6387.75,5331.1875 c -12.8167,-0.2843 -21.9125,9.8823 -29.9201,18.4726 -16.9981,17.3803 -34.8059,34.2122 -51.3299,51.9024 -3.1649,4.3908 -5.2116,9.685 -5.5625,15.0313 58.2812,-0.042 116.7292,0.083 174.9063,-0.063 -0.723,-15.0722 -14.844,-23.7682 -24.0397,-33.9792 -15.7089,-15.2187 -30.5296,-31.6073 -46.7416,-46.1458 -5.0231,-3.5423 -11.1737,-5.3579 -17.3125,-5.2187 z"
  92 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  93 + <path
  94 + inkscape:connector-curvature="0"
  95 + id="rect5245-5-4-7-7-7"
  96 + d="m 6438.0625,5011.4375 c 0.8844,15.3847 15.3961,24.0874 24.746,34.5934 15.4791,14.996 30.0891,31.1552 46.0665,45.4691 12.3222,9.0732 30.8351,5.8023 40.1691,-6.1752 19.3539,-19.7366 39.5157,-38.9385 58.3934,-58.981 3.1742,-4.3805 5.1726,-9.6672 5.5625,-15.0313 -58.2707,0.083 -116.8752,-0.1665 -174.9375,0.125 z"
  97 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  98 + <path
  99 + inkscape:connector-curvature="0"
  100 + id="rect5245-5-4-7-4-6-5"
  101 + d="m 6701.375,5008.75 c 27.4204,26.9864 54.2733,54.7791 81.9688,81.3438 12.312,9.5662 31.425,6.3692 40.8246,-5.801 59.1737,-59.3365 118.4716,-118.5488 177.7691,-177.7616 -100.1875,34.073 -200.375,68.1459 -300.5625,102.2188 z"
  102 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  103 + <path
  104 + inkscape:connector-curvature="0"
  105 + id="rect5245-5-4-7-4-3-3-0"
  106 + d="m 6998.8438,4984.5938 c -47.3943,47.7404 -95.4006,94.9567 -142.5626,142.875 -10.1297,12.3088 -7.0115,31.8296 5.3324,41.3869 25.2451,25.1714 50.3907,50.4424 75.5426,75.7068 34.073,-100.1875 68.1459,-200.375 102.2188,-300.5625 -13.5104,13.5313 -27.0208,27.0625 -40.5312,40.5938 z"
  107 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  108 + <path
  109 + inkscape:connector-curvature="0"
  110 + id="rect5245-5-4-7-4-2-3-8"
  111 + d="m 6114.2812,5244.9062 c 26.8771,-27.2005 54.2641,-54.0149 80.875,-81.4062 10.0644,-12.3049 7.0844,-31.7597 -5.3634,-41.3871 -59.3365,-59.1737 -118.5488,-118.4717 -177.7616,-177.7691 34.0834,100.1874 68.1667,200.375 102.25,300.5624 z"
  112 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  113 + <path
  114 + inkscape:connector-curvature="0"
  115 + id="rect5245-5-4-7-4-3-5-8-2"
  116 + d="m 6090.125,4947.4375 c 47.677,47.4093 94.9671,95.3478 142.7812,142.5313 12.3096,10.0651 31.7648,7.1628 41.4222,-5.2972 25.2023,-25.2247 50.4579,-50.3961 75.7341,-75.5466 -100.1875,-34.0729 -200.375,-68.1458 -300.5625,-102.2188 13.5417,13.5105 27.0833,27.0209 40.625,40.5313 z"
  117 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  118 + </g>
  119 + </g>
  120 +</svg>
plugins/shopping_cart/public/images/control-panel/purchase-report.gif 0 → 100644

1.6 KB

plugins/shopping_cart/public/images/control-panel/purchase-report.png

2.44 KB | W: | H:

3.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
plugins/shopping_cart/public/images/control-panel/purchase-report.svg
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 height="48" 14 height="48"
15 id="svg2" 15 id="svg2"
16 version="1.1" 16 version="1.1"
17 - inkscape:version="0.47 r22583" 17 + inkscape:version="0.48.2 r9819"
18 sodipodi:docname="purchase-report.svg" 18 sodipodi:docname="purchase-report.svg"
19 inkscape:export-filename="/home/aurium/purchase-report.png" 19 inkscape:export-filename="/home/aurium/purchase-report.png"
20 inkscape:export-xdpi="90" 20 inkscape:export-xdpi="90"
@@ -352,10 +352,10 @@ @@ -352,10 +352,10 @@
352 inkscape:document-units="px" 352 inkscape:document-units="px"
353 inkscape:current-layer="layer1" 353 inkscape:current-layer="layer1"
354 showgrid="true" 354 showgrid="true"
355 - inkscape:window-width="1440"  
356 - inkscape:window-height="827"  
357 - inkscape:window-x="0"  
358 - inkscape:window-y="25" 355 + inkscape:window-width="1366"
  356 + inkscape:window-height="721"
  357 + inkscape:window-x="-3"
  358 + inkscape:window-y="-3"
359 inkscape:window-maximized="1"> 359 inkscape:window-maximized="1">
360 <inkscape:grid 360 <inkscape:grid
361 type="xygrid" 361 type="xygrid"
@@ -419,120 +419,17 @@ @@ -419,120 +419,17 @@
419 ry="0" /> 419 ry="0" />
420 <g 420 <g
421 id="g4873" 421 id="g4873"
422 - mask="url(#mask4922)">  
423 - <g  
424 - transform="translate(6,1)"  
425 - id="g4757">  
426 - <path  
427 - style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"  
428 - d="m 2.5,2.5 2,0 0,8 12,0 0,-6"  
429 - id="path2839"  
430 - transform="translate(0,1004.3622)"  
431 - sodipodi:nodetypes="ccccc" />  
432 - <path  
433 - sodipodi:nodetypes="cccccc"  
434 - id="path3619"  
435 - style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 1;stroke-dashoffset:0"  
436 - d="m 15,1012.8622 -9.5,0 m 9.5,-2 -9.5,0 m 9.5,-2 -9.5,0" />  
437 - <path  
438 - transform="translate(7.5,1000.8622)"  
439 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
440 - sodipodi:ry="1.5"  
441 - sodipodi:rx="1.5"  
442 - sodipodi:cy="16"  
443 - sodipodi:cx="7"  
444 - id="path3647"  
445 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
446 - sodipodi:type="arc" />  
447 - <path  
448 - sodipodi:type="arc"  
449 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
450 - id="path4776"  
451 - sodipodi:cx="7"  
452 - sodipodi:cy="16"  
453 - sodipodi:rx="1.5"  
454 - sodipodi:ry="1.5"  
455 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
456 - transform="translate(-1,1000.8622)" />  
457 - </g>  
458 - <g  
459 - id="g4778"  
460 - transform="translate(6,15)">  
461 - <path  
462 - sodipodi:nodetypes="ccccc"  
463 - transform="translate(0,1004.3622)"  
464 - id="path4780"  
465 - d="m 2.5,2.5 2,0 0,8 12,0 0,-6"  
466 - style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />  
467 - <path  
468 - d="m 15,1012.8622 -9.5,0 m 9.5,-2 -9.5,0 m 9.5,-2 -9.5,0"  
469 - style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 1;stroke-dashoffset:0"  
470 - id="path4782"  
471 - sodipodi:nodetypes="cccccc" />  
472 - <path  
473 - sodipodi:type="arc"  
474 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
475 - id="path4784"  
476 - sodipodi:cx="7"  
477 - sodipodi:cy="16"  
478 - sodipodi:rx="1.5"  
479 - sodipodi:ry="1.5"  
480 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
481 - transform="translate(7.5,1000.8622)" />  
482 - <path  
483 - transform="translate(-1,1000.8622)"  
484 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
485 - sodipodi:ry="1.5"  
486 - sodipodi:rx="1.5"  
487 - sodipodi:cy="16"  
488 - sodipodi:cx="7"  
489 - id="path4786"  
490 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
491 - sodipodi:type="arc" />  
492 - </g>  
493 - <g  
494 - transform="translate(6,29)"  
495 - id="g4788">  
496 - <path  
497 - style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"  
498 - d="m 2.5,2.5 2,0 0,8 12,0 0,-6"  
499 - id="path4790"  
500 - transform="translate(0,1004.3622)"  
501 - sodipodi:nodetypes="ccccc" />  
502 - <path  
503 - sodipodi:nodetypes="cccccc"  
504 - id="path4792"  
505 - style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 1;stroke-dashoffset:0"  
506 - d="m 15,1012.8622 -9.5,0 m 9.5,-2 -9.5,0 m 9.5,-2 -9.5,0" />  
507 - <path  
508 - transform="translate(7.5,1000.8622)"  
509 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
510 - sodipodi:ry="1.5"  
511 - sodipodi:rx="1.5"  
512 - sodipodi:cy="16"  
513 - sodipodi:cx="7"  
514 - id="path4794"  
515 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
516 - sodipodi:type="arc" />  
517 - <path  
518 - sodipodi:type="arc"  
519 - style="color:#000000;fill:#000000;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"  
520 - id="path4796"  
521 - sodipodi:cx="7"  
522 - sodipodi:cy="16"  
523 - sodipodi:rx="1.5"  
524 - sodipodi:ry="1.5"  
525 - d="m 8.5,16 c 0,0.828427 -0.6715729,1.5 -1.5,1.5 -0.8284271,0 -1.5,-0.671573 -1.5,-1.5 0,-0.828427 0.6715729,-1.5 1.5,-1.5 0.8284271,0 1.5,0.671573 1.5,1.5 z"  
526 - transform="translate(-1,1000.8622)" />  
527 - </g> 422 + mask="url(#mask4922)"
  423 + transform="translate(0,1.7382813e-5)">
528 <path 424 <path
529 sodipodi:nodetypes="cccccc" 425 sodipodi:nodetypes="cccccc"
530 d="m 29.5,1015.8622 11,0 m -11,-3 11,0 m -11,-3 11,0" 426 d="m 29.5,1015.8622 11,0 m -11,-3 11,0 m -11,-3 11,0"
531 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" 427 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
532 - id="path4802" /> 428 + id="path4802"
  429 + inkscape:connector-curvature="0" />
533 <path 430 <path
534 transform="translate(0,1004.3622)" 431 transform="translate(0,1004.3622)"
535 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 432 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
536 sodipodi:ry="1" 433 sodipodi:ry="1"
537 sodipodi:rx="1" 434 sodipodi:rx="1"
538 sodipodi:cy="5.5" 435 sodipodi:cy="5.5"
@@ -548,11 +445,11 @@ @@ -548,11 +445,11 @@
548 sodipodi:cy="5.5" 445 sodipodi:cy="5.5"
549 sodipodi:rx="1" 446 sodipodi:rx="1"
550 sodipodi:ry="1" 447 sodipodi:ry="1"
551 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 448 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
552 transform="translate(0,1007.3622)" /> 449 transform="translate(0,1007.3622)" />
553 <path 450 <path
554 transform="translate(0,1010.3622)" 451 transform="translate(0,1010.3622)"
555 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 452 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
556 sodipodi:ry="1" 453 sodipodi:ry="1"
557 sodipodi:rx="1" 454 sodipodi:rx="1"
558 sodipodi:cy="5.5" 455 sodipodi:cy="5.5"
@@ -564,7 +461,8 @@ @@ -564,7 +461,8 @@
564 id="path4811" 461 id="path4811"
565 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" 462 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
566 d="m 29.5,1029.8622 11,0 m -11,-3 11,0 m -11,-3 11,0" 463 d="m 29.5,1029.8622 11,0 m -11,-3 11,0 m -11,-3 11,0"
567 - sodipodi:nodetypes="cccccc" /> 464 + sodipodi:nodetypes="cccccc"
  465 + inkscape:connector-curvature="0" />
568 <path 466 <path
569 sodipodi:type="arc" 467 sodipodi:type="arc"
570 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 468 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
@@ -573,11 +471,11 @@ @@ -573,11 +471,11 @@
573 sodipodi:cy="5.5" 471 sodipodi:cy="5.5"
574 sodipodi:rx="1" 472 sodipodi:rx="1"
575 sodipodi:ry="1" 473 sodipodi:ry="1"
576 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 474 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
577 transform="translate(0,1018.3622)" /> 475 transform="translate(0,1018.3622)" />
578 <path 476 <path
579 transform="translate(0,1021.3622)" 477 transform="translate(0,1021.3622)"
580 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 478 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
581 sodipodi:ry="1" 479 sodipodi:ry="1"
582 sodipodi:rx="1" 480 sodipodi:rx="1"
583 sodipodi:cy="5.5" 481 sodipodi:cy="5.5"
@@ -593,15 +491,16 @@ @@ -593,15 +491,16 @@
593 sodipodi:cy="5.5" 491 sodipodi:cy="5.5"
594 sodipodi:rx="1" 492 sodipodi:rx="1"
595 sodipodi:ry="1" 493 sodipodi:ry="1"
596 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 494 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
597 transform="translate(0,1024.3622)" /> 495 transform="translate(0,1024.3622)" />
598 <path 496 <path
599 id="path4827" 497 id="path4827"
600 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" 498 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
601 - d="m 29.5,1038.8622 11,0 m -11,3 11,0 m -11,3 9,0" /> 499 + d="m 29.5,1038.8622 11,0 m -11,3 11,0 m -11,3 9,0"
  500 + inkscape:connector-curvature="0" />
602 <path 501 <path
603 transform="translate(0,1033.3622)" 502 transform="translate(0,1033.3622)"
604 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 503 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
605 sodipodi:ry="1" 504 sodipodi:ry="1"
606 sodipodi:rx="1" 505 sodipodi:rx="1"
607 sodipodi:cy="5.5" 506 sodipodi:cy="5.5"
@@ -617,11 +516,11 @@ @@ -617,11 +516,11 @@
617 sodipodi:cy="5.5" 516 sodipodi:cy="5.5"
618 sodipodi:rx="1" 517 sodipodi:rx="1"
619 sodipodi:ry="1" 518 sodipodi:ry="1"
620 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 519 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
621 transform="translate(0,1036.3622)" /> 520 transform="translate(0,1036.3622)" />
622 <path 521 <path
623 transform="translate(0,1039.3622)" 522 transform="translate(0,1039.3622)"
624 - d="m 27.5,5.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z" 523 + d="m 27.5,5.5 c 0,0.5522847 -0.447715,1 -1,1 -0.552285,0 -1,-0.4477153 -1,-1 0,-0.5522847 0.447715,-1 1,-1 0.552285,0 1,0.4477153 1,1 z"
625 sodipodi:ry="1" 524 sodipodi:ry="1"
626 sodipodi:rx="1" 525 sodipodi:rx="1"
627 sodipodi:cy="5.5" 526 sodipodi:cy="5.5"
@@ -629,6 +528,201 @@ @@ -629,6 +528,201 @@
629 id="path4833" 528 id="path4833"
630 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 529 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
631 sodipodi:type="arc" /> 530 sodipodi:type="arc" />
  531 + <g
  532 + id="g5575-7"
  533 + transform="matrix(0.01545762,0,0,0.01545762,-84.564216,932.96399)"
  534 + style="fill:#d38d5f">
  535 + <path
  536 + inkscape:connector-curvature="0"
  537 + id="rect5245-9-6"
  538 + d="m 6408.0436,5064.9271 c -11.726,-11.2912 -32.3679,-9.6218 -42.4594,3.1418 -19.6123,19.9856 -39.9641,39.4417 -59.1428,59.74 -9.518,12.5005 -6.3676,31.3823 5.826,40.9329 19.6892,19.3981 38.735,39.51 58.8119,58.4787 12.3484,9.5947 31.3846,6.505 40.8262,-5.7528 19.4803,-19.979 39.9713,-39.2016 58.856,-59.6292 9.0558,-12.4283 5.7503,-30.9531 -6.2232,-40.3134 -18.839,-18.8581 -37.6197,-37.7778 -56.4947,-56.598 z"
  539 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  540 + <path
  541 + inkscape:connector-curvature="0"
  542 + id="rect5245-3-6-2"
  543 + d="m 6271.4191,5203.1033 c -11.659,-11.815 -32.7156,-10.3098 -42.9669,2.6329 -19.7244,20.2091 -40.4303,39.6819 -59.5621,60.3374 -9.0337,12.4807 -5.8043,30.8944 6.2011,40.2912 19.927,19.5313 39.0995,40.0758 59.474,59.0098 12.4653,9.0761 30.8739,5.764 40.2082,-6.2396 19.3801,-19.7478 39.4876,-38.9701 58.4368,-59.0318 9.4652,-12.3353 6.4045,-31.4347 -5.8482,-40.9551 -18.655,-18.6738 -37.2518,-37.4088 -55.9429,-56.0448 z"
  544 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  545 + <path
  546 + inkscape:connector-curvature="0"
  547 + id="rect5245-8-6-2"
  548 + d="m 6545.5287,5202.6829 c -11.7363,-11.3217 -32.3801,-9.5656 -42.4593,3.1418 -19.5937,19.9946 -39.9139,39.3349 -59.0767,59.7178 -9.5513,12.3449 -6.4556,31.438 5.7819,40.9772 19.6941,19.3922 38.7293,39.519 58.8119,58.4788 12.4118,9.6084 31.3584,6.4621 40.8704,-5.797 19.3229,-19.7402 39.4821,-38.8781 58.3264,-58.9655 9.5617,-12.3467 6.4938,-31.475 -5.7378,-40.933 -18.8464,-18.8655 -37.6344,-37.7925 -56.5168,-56.6201 z"
  549 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  550 + <path
  551 + inkscape:connector-curvature="0"
  552 + id="rect5245-7-4-7"
  553 + d="m 6683.1903,5065.7678 c -11.6727,-11.8214 -32.6711,-10.2704 -42.9667,2.6331 -19.5831,19.9842 -39.9685,39.3886 -59.0767,59.7177 -9.5608,12.3477 -6.4907,31.4696 5.7376,40.9329 19.6964,19.4311 38.8692,39.5905 58.8782,58.5894 12.4019,9.5309 31.358,6.4013 40.8042,-5.8192 19.3581,-19.7586 39.4434,-38.8626 58.3704,-59.0098 9.5628,-12.3863 6.44,-31.439 -5.7818,-40.977 -18.6625,-18.6812 -37.2665,-37.4235 -55.9652,-56.0671 z"
  554 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  555 + <path
  556 + inkscape:connector-curvature="0"
  557 + id="rect5245-5-2-8"
  558 + d="m 6820.3665,5203.4794 c -11.6211,-11.8358 -32.7205,-10.3463 -42.9888,2.6109 -19.6161,19.9836 -39.9579,39.4428 -59.1429,59.7399 -9.5091,12.4948 -6.4432,31.3232 5.7819,40.8887 19.7364,19.4377 38.8215,39.6072 58.9443,58.6115 12.2819,9.5014 31.2571,6.4201 40.7379,-5.8413 19.38,-19.7479 39.4876,-38.9702 58.4367,-59.0319 9.212,-12.1115 6.6473,-30.6982 -5.1419,-40.247 -18.8831,-18.9025 -37.708,-37.8662 -56.6272,-56.7308 z"
  559 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  560 + <path
  561 + inkscape:connector-curvature="0"
  562 + id="rect5245-5-4-1-9"
  563 + d="m 6662,5332.625 c -15.2378,0.1612 -24.0529,14.157 -34.1146,23.4276 -15.476,15.9413 -31.8697,31.086 -46.7916,47.5099 -2.9405,4.2478 -4.8192,9.3664 -5.1563,14.4687 58.2812,-0.042 116.7292,0.083 174.9063,-0.062 -0.7632,-15.4178 -15.3649,-24.1142 -24.7148,-34.6559 -15.4837,-15.0104 -30.156,-31.1956 -46.0978,-45.5317 -5.23,-3.6558 -11.6538,-5.4746 -18.0312,-5.1562 z"
  564 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  565 + <path
  566 + inkscape:connector-curvature="0"
  567 + id="rect5245-5-4-4-4-1"
  568 + d="m 6387.75,5331.1875 c -12.8167,-0.2843 -21.9125,9.8823 -29.9201,18.4726 -16.9981,17.3803 -34.8059,34.2122 -51.3299,51.9024 -3.1649,4.3908 -5.2116,9.685 -5.5625,15.0313 58.2812,-0.042 116.7292,0.083 174.9063,-0.063 -0.723,-15.0722 -14.844,-23.7682 -24.0397,-33.9792 -15.7089,-15.2187 -30.5296,-31.6073 -46.7416,-46.1458 -5.0231,-3.5423 -11.1737,-5.3579 -17.3125,-5.2187 z"
  569 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  570 + <path
  571 + inkscape:connector-curvature="0"
  572 + id="rect5245-5-4-7-7-7"
  573 + d="m 6438.0625,5011.4375 c 0.8844,15.3847 15.3961,24.0874 24.746,34.5934 15.4791,14.996 30.0891,31.1552 46.0665,45.4691 12.3222,9.0732 30.8351,5.8023 40.1691,-6.1752 19.3539,-19.7366 39.5157,-38.9385 58.3934,-58.981 3.1742,-4.3805 5.1726,-9.6672 5.5625,-15.0313 -58.2707,0.083 -116.8752,-0.1665 -174.9375,0.125 z"
  574 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  575 + <path
  576 + inkscape:connector-curvature="0"
  577 + id="rect5245-5-4-7-4-6-5"
  578 + d="m 6701.375,5008.75 c 27.4204,26.9864 54.2733,54.7791 81.9688,81.3438 12.312,9.5662 31.425,6.3692 40.8246,-5.801 59.1737,-59.3365 118.4716,-118.5488 177.7691,-177.7616 -100.1875,34.073 -200.375,68.1459 -300.5625,102.2188 z"
  579 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  580 + <path
  581 + inkscape:connector-curvature="0"
  582 + id="rect5245-5-4-7-4-3-3-0"
  583 + d="m 6998.8438,4984.5938 c -47.3943,47.7404 -95.4006,94.9567 -142.5626,142.875 -10.1297,12.3088 -7.0115,31.8296 5.3324,41.3869 25.2451,25.1714 50.3907,50.4424 75.5426,75.7068 34.073,-100.1875 68.1459,-200.375 102.2188,-300.5625 -13.5104,13.5313 -27.0208,27.0625 -40.5312,40.5938 z"
  584 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  585 + <path
  586 + inkscape:connector-curvature="0"
  587 + id="rect5245-5-4-7-4-2-3-8"
  588 + d="m 6114.2812,5244.9062 c 26.8771,-27.2005 54.2641,-54.0149 80.875,-81.4062 10.0644,-12.3049 7.0844,-31.7597 -5.3634,-41.3871 -59.3365,-59.1737 -118.5488,-118.4717 -177.7616,-177.7691 34.0834,100.1874 68.1667,200.375 102.25,300.5624 z"
  589 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  590 + <path
  591 + inkscape:connector-curvature="0"
  592 + id="rect5245-5-4-7-4-3-5-8-2"
  593 + d="m 6090.125,4947.4375 c 47.677,47.4093 94.9671,95.3478 142.7812,142.5313 12.3096,10.0651 31.7648,7.1628 41.4222,-5.2972 25.2023,-25.2247 50.4579,-50.3961 75.7341,-75.5466 -100.1875,-34.0729 -200.375,-68.1458 -300.5625,-102.2188 13.5417,13.5105 27.0833,27.0209 40.625,40.5313 z"
  594 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  595 + </g>
  596 + <g
  597 + id="g5575-7-8"
  598 + transform="matrix(0.01545762,0,0,0.01545762,-84.486184,947.26528)"
  599 + style="fill:#d38d5f">
  600 + <path
  601 + inkscape:connector-curvature="0"
  602 + id="rect5245-9-6-5"
  603 + d="m 6408.0436,5064.9271 c -11.726,-11.2912 -32.3679,-9.6218 -42.4594,3.1418 -19.6123,19.9856 -39.9641,39.4417 -59.1428,59.74 -9.518,12.5005 -6.3676,31.3823 5.826,40.9329 19.6892,19.3981 38.735,39.51 58.8119,58.4787 12.3484,9.5947 31.3846,6.505 40.8262,-5.7528 19.4803,-19.979 39.9713,-39.2016 58.856,-59.6292 9.0558,-12.4283 5.7503,-30.9531 -6.2232,-40.3134 -18.839,-18.8581 -37.6197,-37.7778 -56.4947,-56.598 z"
  604 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  605 + <path
  606 + inkscape:connector-curvature="0"
  607 + id="rect5245-3-6-2-0"
  608 + d="m 6271.4191,5203.1033 c -11.659,-11.815 -32.7156,-10.3098 -42.9669,2.6329 -19.7244,20.2091 -40.4303,39.6819 -59.5621,60.3374 -9.0337,12.4807 -5.8043,30.8944 6.2011,40.2912 19.927,19.5313 39.0995,40.0758 59.474,59.0098 12.4653,9.0761 30.8739,5.764 40.2082,-6.2396 19.3801,-19.7478 39.4876,-38.9701 58.4368,-59.0318 9.4652,-12.3353 6.4045,-31.4347 -5.8482,-40.9551 -18.655,-18.6738 -37.2518,-37.4088 -55.9429,-56.0448 z"
  609 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  610 + <path
  611 + inkscape:connector-curvature="0"
  612 + id="rect5245-8-6-2-9"
  613 + d="m 6545.5287,5202.6829 c -11.7363,-11.3217 -32.3801,-9.5656 -42.4593,3.1418 -19.5937,19.9946 -39.9139,39.3349 -59.0767,59.7178 -9.5513,12.3449 -6.4556,31.438 5.7819,40.9772 19.6941,19.3922 38.7293,39.519 58.8119,58.4788 12.4118,9.6084 31.3584,6.4621 40.8704,-5.797 19.3229,-19.7402 39.4821,-38.8781 58.3264,-58.9655 9.5617,-12.3467 6.4938,-31.475 -5.7378,-40.933 -18.8464,-18.8655 -37.6344,-37.7925 -56.5168,-56.6201 z"
  614 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  615 + <path
  616 + inkscape:connector-curvature="0"
  617 + id="rect5245-7-4-7-6"
  618 + d="m 6683.1903,5065.7678 c -11.6727,-11.8214 -32.6711,-10.2704 -42.9667,2.6331 -19.5831,19.9842 -39.9685,39.3886 -59.0767,59.7177 -9.5608,12.3477 -6.4907,31.4696 5.7376,40.9329 19.6964,19.4311 38.8692,39.5905 58.8782,58.5894 12.4019,9.5309 31.358,6.4013 40.8042,-5.8192 19.3581,-19.7586 39.4434,-38.8626 58.3704,-59.0098 9.5628,-12.3863 6.44,-31.439 -5.7818,-40.977 -18.6625,-18.6812 -37.2665,-37.4235 -55.9652,-56.0671 z"
  619 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  620 + <path
  621 + inkscape:connector-curvature="0"
  622 + id="rect5245-5-2-8-3"
  623 + d="m 6820.3665,5203.4794 c -11.6211,-11.8358 -32.7205,-10.3463 -42.9888,2.6109 -19.6161,19.9836 -39.9579,39.4428 -59.1429,59.7399 -9.5091,12.4948 -6.4432,31.3232 5.7819,40.8887 19.7364,19.4377 38.8215,39.6072 58.9443,58.6115 12.2819,9.5014 31.2571,6.4201 40.7379,-5.8413 19.38,-19.7479 39.4876,-38.9702 58.4367,-59.0319 9.212,-12.1115 6.6473,-30.6982 -5.1419,-40.247 -18.8831,-18.9025 -37.708,-37.8662 -56.6272,-56.7308 z"
  624 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  625 + <path
  626 + inkscape:connector-curvature="0"
  627 + id="rect5245-5-4-1-9-8"
  628 + d="m 6662,5332.625 c -15.2378,0.1612 -24.0529,14.157 -34.1146,23.4276 -15.476,15.9413 -31.8697,31.086 -46.7916,47.5099 -2.9405,4.2478 -4.8192,9.3664 -5.1563,14.4687 58.2812,-0.042 116.7292,0.083 174.9063,-0.062 -0.7632,-15.4178 -15.3649,-24.1142 -24.7148,-34.6559 -15.4837,-15.0104 -30.156,-31.1956 -46.0978,-45.5317 -5.23,-3.6558 -11.6538,-5.4746 -18.0312,-5.1562 z"
  629 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  630 + <path
  631 + inkscape:connector-curvature="0"
  632 + id="rect5245-5-4-4-4-1-5"
  633 + d="m 6387.75,5331.1875 c -12.8167,-0.2843 -21.9125,9.8823 -29.9201,18.4726 -16.9981,17.3803 -34.8059,34.2122 -51.3299,51.9024 -3.1649,4.3908 -5.2116,9.685 -5.5625,15.0313 58.2812,-0.042 116.7292,0.083 174.9063,-0.063 -0.723,-15.0722 -14.844,-23.7682 -24.0397,-33.9792 -15.7089,-15.2187 -30.5296,-31.6073 -46.7416,-46.1458 -5.0231,-3.5423 -11.1737,-5.3579 -17.3125,-5.2187 z"
  634 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  635 + <path
  636 + inkscape:connector-curvature="0"
  637 + id="rect5245-5-4-7-7-7-6"
  638 + d="m 6438.0625,5011.4375 c 0.8844,15.3847 15.3961,24.0874 24.746,34.5934 15.4791,14.996 30.0891,31.1552 46.0665,45.4691 12.3222,9.0732 30.8351,5.8023 40.1691,-6.1752 19.3539,-19.7366 39.5157,-38.9385 58.3934,-58.981 3.1742,-4.3805 5.1726,-9.6672 5.5625,-15.0313 -58.2707,0.083 -116.8752,-0.1665 -174.9375,0.125 z"
  639 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  640 + <path
  641 + inkscape:connector-curvature="0"
  642 + id="rect5245-5-4-7-4-6-5-1"
  643 + d="m 6701.375,5008.75 c 27.4204,26.9864 54.2733,54.7791 81.9688,81.3438 12.312,9.5662 31.425,6.3692 40.8246,-5.801 59.1737,-59.3365 118.4716,-118.5488 177.7691,-177.7616 -100.1875,34.073 -200.375,68.1459 -300.5625,102.2188 z"
  644 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  645 + <path
  646 + inkscape:connector-curvature="0"
  647 + id="rect5245-5-4-7-4-3-3-0-1"
  648 + d="m 6998.8438,4984.5938 c -47.3943,47.7404 -95.4006,94.9567 -142.5626,142.875 -10.1297,12.3088 -7.0115,31.8296 5.3324,41.3869 25.2451,25.1714 50.3907,50.4424 75.5426,75.7068 34.073,-100.1875 68.1459,-200.375 102.2188,-300.5625 -13.5104,13.5313 -27.0208,27.0625 -40.5312,40.5938 z"
  649 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  650 + <path
  651 + inkscape:connector-curvature="0"
  652 + id="rect5245-5-4-7-4-2-3-8-5"
  653 + d="m 6114.2812,5244.9062 c 26.8771,-27.2005 54.2641,-54.0149 80.875,-81.4062 10.0644,-12.3049 7.0844,-31.7597 -5.3634,-41.3871 -59.3365,-59.1737 -118.5488,-118.4717 -177.7616,-177.7691 34.0834,100.1874 68.1667,200.375 102.25,300.5624 z"
  654 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  655 + <path
  656 + inkscape:connector-curvature="0"
  657 + id="rect5245-5-4-7-4-3-5-8-2-9"
  658 + d="m 6090.125,4947.4375 c 47.677,47.4093 94.9671,95.3478 142.7812,142.5313 12.3096,10.0651 31.7648,7.1628 41.4222,-5.2972 25.2023,-25.2247 50.4579,-50.3961 75.7341,-75.5466 -100.1875,-34.0729 -200.375,-68.1458 -300.5625,-102.2188 13.5417,13.5105 27.0833,27.0209 40.625,40.5313 z"
  659 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  660 + </g>
  661 + <g
  662 + id="g5575-7-8-8"
  663 + transform="matrix(0.01545762,0,0,0.01545762,-84.564216,961.96398)"
  664 + style="fill:#d38d5f">
  665 + <path
  666 + inkscape:connector-curvature="0"
  667 + id="rect5245-9-6-5-4"
  668 + d="m 6408.0436,5064.9271 c -11.726,-11.2912 -32.3679,-9.6218 -42.4594,3.1418 -19.6123,19.9856 -39.9641,39.4417 -59.1428,59.74 -9.518,12.5005 -6.3676,31.3823 5.826,40.9329 19.6892,19.3981 38.735,39.51 58.8119,58.4787 12.3484,9.5947 31.3846,6.505 40.8262,-5.7528 19.4803,-19.979 39.9713,-39.2016 58.856,-59.6292 9.0558,-12.4283 5.7503,-30.9531 -6.2232,-40.3134 -18.839,-18.8581 -37.6197,-37.7778 -56.4947,-56.598 z"
  669 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  670 + <path
  671 + inkscape:connector-curvature="0"
  672 + id="rect5245-3-6-2-0-8"
  673 + d="m 6271.4191,5203.1033 c -11.659,-11.815 -32.7156,-10.3098 -42.9669,2.6329 -19.7244,20.2091 -40.4303,39.6819 -59.5621,60.3374 -9.0337,12.4807 -5.8043,30.8944 6.2011,40.2912 19.927,19.5313 39.0995,40.0758 59.474,59.0098 12.4653,9.0761 30.8739,5.764 40.2082,-6.2396 19.3801,-19.7478 39.4876,-38.9701 58.4368,-59.0318 9.4652,-12.3353 6.4045,-31.4347 -5.8482,-40.9551 -18.655,-18.6738 -37.2518,-37.4088 -55.9429,-56.0448 z"
  674 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  675 + <path
  676 + inkscape:connector-curvature="0"
  677 + id="rect5245-8-6-2-9-1"
  678 + d="m 6545.5287,5202.6829 c -11.7363,-11.3217 -32.3801,-9.5656 -42.4593,3.1418 -19.5937,19.9946 -39.9139,39.3349 -59.0767,59.7178 -9.5513,12.3449 -6.4556,31.438 5.7819,40.9772 19.6941,19.3922 38.7293,39.519 58.8119,58.4788 12.4118,9.6084 31.3584,6.4621 40.8704,-5.797 19.3229,-19.7402 39.4821,-38.8781 58.3264,-58.9655 9.5617,-12.3467 6.4938,-31.475 -5.7378,-40.933 -18.8464,-18.8655 -37.6344,-37.7925 -56.5168,-56.6201 z"
  679 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  680 + <path
  681 + inkscape:connector-curvature="0"
  682 + id="rect5245-7-4-7-6-0"
  683 + d="m 6683.1903,5065.7678 c -11.6727,-11.8214 -32.6711,-10.2704 -42.9667,2.6331 -19.5831,19.9842 -39.9685,39.3886 -59.0767,59.7177 -9.5608,12.3477 -6.4907,31.4696 5.7376,40.9329 19.6964,19.4311 38.8692,39.5905 58.8782,58.5894 12.4019,9.5309 31.358,6.4013 40.8042,-5.8192 19.3581,-19.7586 39.4434,-38.8626 58.3704,-59.0098 9.5628,-12.3863 6.44,-31.439 -5.7818,-40.977 -18.6625,-18.6812 -37.2665,-37.4235 -55.9652,-56.0671 z"
  684 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  685 + <path
  686 + inkscape:connector-curvature="0"
  687 + id="rect5245-5-2-8-3-3"
  688 + d="m 6820.3665,5203.4794 c -11.6211,-11.8358 -32.7205,-10.3463 -42.9888,2.6109 -19.6161,19.9836 -39.9579,39.4428 -59.1429,59.7399 -9.5091,12.4948 -6.4432,31.3232 5.7819,40.8887 19.7364,19.4377 38.8215,39.6072 58.9443,58.6115 12.2819,9.5014 31.2571,6.4201 40.7379,-5.8413 19.38,-19.7479 39.4876,-38.9702 58.4367,-59.0319 9.212,-12.1115 6.6473,-30.6982 -5.1419,-40.247 -18.8831,-18.9025 -37.708,-37.8662 -56.6272,-56.7308 z"
  689 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  690 + <path
  691 + inkscape:connector-curvature="0"
  692 + id="rect5245-5-4-1-9-8-0"
  693 + d="m 6662,5332.625 c -15.2378,0.1612 -24.0529,14.157 -34.1146,23.4276 -15.476,15.9413 -31.8697,31.086 -46.7916,47.5099 -2.9405,4.2478 -4.8192,9.3664 -5.1563,14.4687 58.2812,-0.042 116.7292,0.083 174.9063,-0.062 -0.7632,-15.4178 -15.3649,-24.1142 -24.7148,-34.6559 -15.4837,-15.0104 -30.156,-31.1956 -46.0978,-45.5317 -5.23,-3.6558 -11.6538,-5.4746 -18.0312,-5.1562 z"
  694 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  695 + <path
  696 + inkscape:connector-curvature="0"
  697 + id="rect5245-5-4-4-4-1-5-4"
  698 + d="m 6387.75,5331.1875 c -12.8167,-0.2843 -21.9125,9.8823 -29.9201,18.4726 -16.9981,17.3803 -34.8059,34.2122 -51.3299,51.9024 -3.1649,4.3908 -5.2116,9.685 -5.5625,15.0313 58.2812,-0.042 116.7292,0.083 174.9063,-0.063 -0.723,-15.0722 -14.844,-23.7682 -24.0397,-33.9792 -15.7089,-15.2187 -30.5296,-31.6073 -46.7416,-46.1458 -5.0231,-3.5423 -11.1737,-5.3579 -17.3125,-5.2187 z"
  699 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  700 + <path
  701 + inkscape:connector-curvature="0"
  702 + id="rect5245-5-4-7-7-7-6-4"
  703 + d="m 6438.0625,5011.4375 c 0.8844,15.3847 15.3961,24.0874 24.746,34.5934 15.4791,14.996 30.0891,31.1552 46.0665,45.4691 12.3222,9.0732 30.8351,5.8023 40.1691,-6.1752 19.3539,-19.7366 39.5157,-38.9385 58.3934,-58.981 3.1742,-4.3805 5.1726,-9.6672 5.5625,-15.0313 -58.2707,0.083 -116.8752,-0.1665 -174.9375,0.125 z"
  704 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  705 + <path
  706 + inkscape:connector-curvature="0"
  707 + id="rect5245-5-4-7-4-6-5-1-4"
  708 + d="m 6701.375,5008.75 c 27.4204,26.9864 54.2733,54.7791 81.9688,81.3438 12.312,9.5662 31.425,6.3692 40.8246,-5.801 59.1737,-59.3365 118.4716,-118.5488 177.7691,-177.7616 -100.1875,34.073 -200.375,68.1459 -300.5625,102.2188 z"
  709 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  710 + <path
  711 + inkscape:connector-curvature="0"
  712 + id="rect5245-5-4-7-4-3-3-0-1-4"
  713 + d="m 6998.8438,4984.5938 c -47.3943,47.7404 -95.4006,94.9567 -142.5626,142.875 -10.1297,12.3088 -7.0115,31.8296 5.3324,41.3869 25.2451,25.1714 50.3907,50.4424 75.5426,75.7068 34.073,-100.1875 68.1459,-200.375 102.2188,-300.5625 -13.5104,13.5313 -27.0208,27.0625 -40.5312,40.5938 z"
  714 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  715 + <path
  716 + inkscape:connector-curvature="0"
  717 + id="rect5245-5-4-7-4-2-3-8-5-7"
  718 + d="m 6114.2812,5244.9062 c 26.8771,-27.2005 54.2641,-54.0149 80.875,-81.4062 10.0644,-12.3049 7.0844,-31.7597 -5.3634,-41.3871 -59.3365,-59.1737 -118.5488,-118.4717 -177.7616,-177.7691 34.0834,100.1874 68.1667,200.375 102.25,300.5624 z"
  719 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  720 + <path
  721 + inkscape:connector-curvature="0"
  722 + id="rect5245-5-4-7-4-3-5-8-2-9-6"
  723 + d="m 6090.125,4947.4375 c 47.677,47.4093 94.9671,95.3478 142.7812,142.5313 12.3096,10.0651 31.7648,7.1628 41.4222,-5.2972 25.2023,-25.2247 50.4579,-50.3961 75.7341,-75.5466 -100.1875,-34.0729 -200.375,-68.1458 -300.5625,-102.2188 13.5417,13.5105 27.0833,27.0209 40.625,40.5313 z"
  724 + style="fill:#d38d5f;fill-opacity:1;stroke:none" />
  725 + </g>
632 </g> 726 </g>
633 <g 727 <g
634 id="g4835" 728 id="g4835"
plugins/shopping_cart/public/style.css
1 @import url(colorbox/colorbox.css); 1 @import url(colorbox/colorbox.css);
2 2
3 -.cart-add-item {  
4 - position: absolute;  
5 - right: 5px;  
6 - top: 5px; 3 +.cart-add-item .ui-icon-cart {
  4 + background: url("images/button-icon.png") no-repeat scroll left center transparent;
  5 + width: 22px;
  6 + }
  7 + .cart-buy .ui-icon-cart {
  8 + background: url("images/button-icon.png") no-repeat scroll left center transparent;
  9 + width: 22px;
  10 + }
  11 +.cart-add-item .ui-button-text {
  12 + padding-left: 2.6em;
7 } 13 }
8 14
9 .product-item .cart-add-item { 15 .product-item .cart-add-item {
@@ -13,16 +19,15 @@ @@ -13,16 +19,15 @@
13 .cart { 19 .cart {
14 position: fixed; 20 position: fixed;
15 right: 20px; 21 right: 20px;
16 - top: 0px; 22 + bottom: 0px;
17 width: 200px; 23 width: 200px;
18 z-index: 1000; 24 z-index: 1000;
19 border: 1px solid #777; 25 border: 1px solid #777;
20 - border-top: none;  
21 - background: rgba(200,200,200,0.6); 26 + background: rgba(100,100,100,0.8);
22 } 27 }
23 28
24 .cart h3 { 29 .cart h3 {
25 - color: #888; 30 + color: #000;
26 margin: 0px 0px 0px 5px; 31 margin: 0px 0px 0px 5px;
27 } 32 }
28 33
@@ -108,7 +113,7 @@ @@ -108,7 +113,7 @@
108 padding: 0px 5px; 113 padding: 0px 5px;
109 } 114 }
110 .cart-toggle:hover { 115 .cart-toggle:hover {
111 - color: #555; 116 + color: #fff;
112 } 117 }
113 118
114 #cart-request-box { 119 #cart-request-box {
@@ -120,6 +125,15 @@ @@ -120,6 +125,15 @@
120 float: left; 125 float: left;
121 } 126 }
122 127
  128 +#cart-request-box .cart-box-close {
  129 + position: absolute;
  130 + right: 10px;
  131 + bottom: 10px;
  132 + width: 16px;
  133 + height: 16px;
  134 + background-repeat: no-repeat;
  135 +}
  136 +
123 #cart-form-main { 137 #cart-form-main {
124 border: 2px solid #FFF; 138 border: 2px solid #FFF;
125 padding: 0px 10px; 139 padding: 0px 10px;
@@ -171,8 +185,8 @@ label.error { @@ -171,8 +185,8 @@ label.error {
171 185
172 .controller-profile_editor a.control-panel-shopping-cart-purchase-report {background-image: url(images/control-panel/purchase-report.png)} 186 .controller-profile_editor a.control-panel-shopping-cart-purchase-report {background-image: url(images/control-panel/purchase-report.png)}
173 .controller-profile_editor .msie6 a.control-panel-shopping-cart-purchase-report {background-image: url(images/control-panel/purchase-report.gif)} 187 .controller-profile_editor .msie6 a.control-panel-shopping-cart-purchase-report {background-image: url(images/control-panel/purchase-report.gif)}
174 -.controller-profile_editor a.control-panel-shopping-cart {background-image: url(/images/control-panel/shopping-cart.png)}  
175 -.controller-profile_editor .msie6 a.control-panel-shopping-cart {background-image: url(/images/control-panel/shopping-cart.gif)} 188 +.controller-profile_editor a.control-panel-shopping-cart-icon {background-image: url(images/control-panel/icon.png)}
  189 +.controller-profile_editor .msie6 a.control-panel-shopping-cart-icon {background-image: url(images/control-panel/icon.gif)}
176 190
177 .action-shopping_cart_plugin_myprofile-reports td.order-info { 191 .action-shopping_cart_plugin_myprofile-reports td.order-info {
178 padding: 0px; 192 padding: 0px;
plugins/shopping_cart/views/cart.html.erb
1 <div id="cart1" class="cart" style="display:none" 1 <div id="cart1" class="cart" style="display:none"
2 data-l10nRemoveItem="<%=_('Are you sure you want to remove this item?')%>" 2 data-l10nRemoveItem="<%=_('Are you sure you want to remove this item?')%>"
3 - data-l10nCleanCart="<%=_('Are you sure you want to clean your cart?')%>"> 3 + data-l10nCleanCart="<%=_('Are you sure you want to clean your basket?')%>">
4 <div class="cart-inner"> 4 <div class="cart-inner">
5 <div class="cart-content"> 5 <div class="cart-content">
6 - <h3><%= _("Cart") %></h3>  
7 - <a href="cart:clean" onclick="Cart.clean(this); return false" class="cart-clean"><%=_('Clean cart')%></a> 6 + <h3><%= _("Basket") %></h3>
  7 + <a href="cart:clean" onclick="Cart.clean(this); return false" class="cart-clean"><%=_('Clean basket')%></a>
8 <ul class="cart-items"></ul> 8 <ul class="cart-items"></ul>
9 <div class="cart-total"><%=_('Total:')%> <b></b></div> 9 <div class="cart-total"><%=_('Total:')%> <b></b></div>
10 <a href="cart:buy" class="cart-buy"><%=_('Shopping checkout')%></a> 10 <a href="cart:buy" class="cart-buy"><%=_('Shopping checkout')%></a>
11 </div> 11 </div>
12 <a href="#" onclick="Cart.toggle(this); return false" class="cart-toggle"> 12 <a href="#" onclick="Cart.toggle(this); return false" class="cart-toggle">
13 - <span class="str-show"><%=_('Show cart')%></span>  
14 - <span class="str-hide" style="display:none"><%=_('Hide cart')%></span> 13 + <span class="str-show"><%=_('Show basket')%></span>
  14 + <span class="str-hide" style="display:none"><%=_('Hide basket')%></span>
15 </a> 15 </a>
16 </div> 16 </div>
17 </div> 17 </div>
plugins/shopping_cart/views/shopping_cart_plugin_myprofile/edit.html.erb
1 -<h1> <%= _('Cart options') %> </h1> 1 +<h1> <%= _('Basket options') %> </h1>
2 2
3 <% form_for(:profile_attr, profile, :url => {:action => 'edit'}, :html => {:method => 'post'}) do |f| %> 3 <% form_for(:profile_attr, profile, :url => {:action => 'edit'}, :html => {:method => 'post'}) do |f| %>
4 <%= labelled_form_field(_('Enabled?'), f.check_box(:shopping_cart)) %> 4 <%= labelled_form_field(_('Enabled?'), f.check_box(:shopping_cart)) %>
plugins/shopping_cart/views/shopping_cart_plugin_profile/buy.html.erb
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 </div> 17 </div>
18 <% end %> 18 <% end %>
19 <%= items_table(session[:cart][:items], profile) %> 19 <%= items_table(session[:cart][:items], profile) %>
  20 + <%= link_to '', '#', :onclick => "Cart.colorbox_close(this);", :class => 'cart-box-close icon-cancel' %>
20 </div> 21 </div>
21 22
22 <script type="text/javascript"> 23 <script type="text/javascript">
public/images/catalog-expanders.png 0 → 100644

777 Bytes

public/javascripts/application.js
@@ -255,7 +255,7 @@ function toggleSubmenu(trigger, title, link_list) { @@ -255,7 +255,7 @@ function toggleSubmenu(trigger, title, link_list) {
255 if (!hide) { 255 if (!hide) {
256 var direction = 'down'; 256 var direction = 'down';
257 if (submenu.hasClass('up')) direction = 'up'; 257 if (submenu.hasClass('up')) direction = 'up';
258 - submenu.show('slide', { 'direction' : direction }, 'slow'); 258 + jQuery(submenu).fadeIn();
259 } 259 }
260 } 260 }
261 return false; 261 return false;
@@ -283,18 +283,18 @@ function toggleSubmenu(trigger, title, link_list) { @@ -283,18 +283,18 @@ function toggleSubmenu(trigger, title, link_list) {
283 content.append(list); 283 content.append(list);
284 submenu.append(header).append(content).append(footer); 284 submenu.append(header).append(content).append(footer);
285 jQuery(trigger).before(submenu); 285 jQuery(trigger).before(submenu);
286 - submenu.show('slide', { 'direction' : direction }, 'slow'); 286 + jQuery(submenu).fadeIn();
287 } 287 }
288 288
289 function toggleMenu(trigger) { 289 function toggleMenu(trigger) {
290 hideAllSubmenus(); 290 hideAllSubmenus();
291 - jQuery(trigger).siblings('.simplemenu-submenu').toggle('slide', {direction: 'up'}, 'slow').toggleClass('opened'); 291 + jQuery(trigger).siblings('.simplemenu-submenu').toggle().toggleClass('opened');
292 } 292 }
293 293
294 function hideAllSubmenus() { 294 function hideAllSubmenus() {
295 - jQuery('.menu-submenu.up:visible').hide('slide', { 'direction' : 'up' }, 'slow');  
296 - jQuery('.simplemenu-submenu:visible').hide('slide', { 'direction' : 'up' }, 'slow').toggleClass('opened');  
297 - jQuery('.menu-submenu.down:visible').hide('slide', { 'direction' : 'down' }, 'slow'); 295 + jQuery('.menu-submenu.up:visible').fadeOut('slow');
  296 + jQuery('.simplemenu-submenu:visible').hide().toggleClass('opened');
  297 + jQuery('.menu-submenu.down:visible').fadeOut('slow');
298 jQuery('#chat-online-users-content').hide(); 298 jQuery('#chat-online-users-content').hide();
299 } 299 }
300 300
@@ -694,3 +694,14 @@ jQuery(function() { @@ -694,3 +694,14 @@ jQuery(function() {
694 target: "#ajax-form-message-area" 694 target: "#ajax-form-message-area"
695 }) 695 })
696 }); 696 });
  697 +
  698 +// from http://jsfiddle.net/naveen/HkxJg/
  699 +// Function to get the Max value in Array
  700 +Array.max = function(array) {
  701 + return Math.max.apply(Math, array);
  702 +};
  703 +// Function to get the Min value in Array
  704 +Array.min = function(array) {
  705 + return Math.min.apply(Math, array);
  706 +};
  707 +
public/javascripts/catalog.js 0 → 100644
@@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
  1 +(function($) {
  2 +
  3 +$('#product-list .product .expand-box').live('click', function () {
  4 + $('.expand-box').each(function(index, element){ this.clicked = false; toggle_expandbox(this); });
  5 + this.clicked = !this.clicked;
  6 + toggle_expandbox(this);
  7 + $.each($(this).siblings('.expand-box'), function(index, value) { value.clicked = false; toggle_expandbox(value); });
  8 +
  9 + return false;
  10 +});
  11 +
  12 +$(document).live('click', function() {
  13 + $.each($('#product-list .product .expand-box'), function(index, value) { value.clicked = false; toggle_expandbox(value); });
  14 +});
  15 +
  16 +$(document).click(function (event) {
  17 + if ($(event.target).parents('.expand-box').length == 0) {
  18 + $('.expand-box').each(function(index, element){
  19 + $(element).removeClass('open');
  20 + $(element).children('div').toggle(false);
  21 + });
  22 + }
  23 +});
  24 +
  25 +var rows = {};
  26 +$('#product-list .product').each(function (index, element) {
  27 + obj = rows[$(element).offset().top] || {};
  28 +
  29 + obj.heights = obj.heights || [];
  30 + obj.elements = obj.elements || [];
  31 + obj.heights.push($(element).height());
  32 + obj.elements.push(element);
  33 +
  34 + rows[$(element).offset().top] = obj;
  35 +});
  36 +
  37 +$.each(rows, function(top, obj) {
  38 + maxWidth = Array.max(obj.heights);
  39 + $(obj.elements).height(maxWidth);
  40 +});
  41 +
  42 +})(jQuery);
  43 +
  44 +function toggle_expandbox(e) {
  45 + jQuery(e).toggleClass('open', e.clicked);
  46 + jQuery(e).children('div').toggle(e.clicked).css({left: jQuery(e).position().left-180, top: jQuery(e).position().top-10});
  47 +}
public/javascripts/manage-products.js 0 → 100644
@@ -0,0 +1,183 @@ @@ -0,0 +1,183 @@
  1 +(function($) {
  2 +
  3 + $("#manage-product-details-button").live('click', function() {
  4 + $("#product-price-details").find('.loading-area').addClass('small-loading');
  5 + url = $(this).attr('href');
  6 + $.get(url, function(data){
  7 + $("#manage-product-details-button").hide();
  8 + $("#display-price-details").hide();
  9 + $("#display-manage-price-details").html(data);
  10 + $("#product-price-details").find('.loading-area').removeClass('small-loading');
  11 + });
  12 + return false;
  13 + });
  14 +
  15 + $(".cancel-price-details").live('click', function() {
  16 + if ( !$(this).hasClass('form-changed') ) {
  17 + cancelPriceDetailsEdition();
  18 + } else {
  19 + if (confirm($(this).attr('data-confirm'))) {
  20 + cancelPriceDetailsEdition();
  21 + }
  22 + }
  23 + return false;
  24 + });
  25 +
  26 + $("#manage-product-details-form").live('submit', function(data) {
  27 + var form = this;
  28 + $(form).find('.loading-area').addClass('small-loading');
  29 + $(form).css('cursor', 'progress');
  30 + $.post(form.action, $(form).serialize(), function(data) {
  31 + $("#display-manage-price-details").html(data);
  32 + $("#manage-product-details-button").show();
  33 + });
  34 + if ($('#progressbar-icon').hasClass('ui-icon-check')) {
  35 + display_notice($('#progressbar-icon').attr('data-price-described-notice'));
  36 + }
  37 + return false;
  38 + });
  39 +
  40 + $("#add-new-cost").live('click', function() {
  41 + $('#display-product-price-details tbody').append($('#new-cost-fields tbody').html());
  42 + return false;
  43 + });
  44 +
  45 + $(".cancel-new-cost").live('click', function() {
  46 + $(this).parents('tr').remove();
  47 + calculateValuesForBar();
  48 + return false;
  49 + });
  50 +
  51 + $("#product-info-form").live('submit', function(data) {
  52 + var form = this;
  53 + updatePriceCompositionBar(form);
  54 + });
  55 +
  56 + $("form.edit_input").live('submit', function(data) {
  57 + var form = this;
  58 + inputs_cost_update_url = $(form).find('#inputs-cost-update-url').val();
  59 + $.get(inputs_cost_update_url, function(data){
  60 + $(".inputs-cost span").html(data);
  61 + });
  62 + updatePriceCompositionBar(form);
  63 + return false;
  64 + });
  65 +
  66 + $("#manage-product-details-form .price-details-price").live('blur', function(data) { calculateValuesForBar(); });
  67 +
  68 + function cancelPriceDetailsEdition() {
  69 + $("#manage-product-details-button").show();
  70 + $("#display-price-details").show();
  71 + $("#display-manage-price-details").html('');
  72 + };
  73 +
  74 +})(jQuery);
  75 +
  76 +function updatePriceCompositionBar(form) {
  77 + bar_url = jQuery(form).find('.bar-update-url').val();
  78 + jQuery.ajax({
  79 + url : bar_url,
  80 + success : function(data) {
  81 + jQuery("#price-composition-bar").html(data);
  82 + },
  83 + complete : function() {
  84 + jQuery('form #product_price').val(currencyToFloat(jQuery('#progressbar-text .product_price').html(), currency_format.separator, currency_format.delimiter));
  85 + jQuery('form #product_inputs_cost').val(currencyToFloat(jQuery('#display-product-price-details .inputs-cost span').html(), currency_format.separator, currency_format.delimiter, currency_format.unit));
  86 + calculateValuesForBar();
  87 + }
  88 + });
  89 +};
  90 +
  91 +function enablePriceDetailSubmit() {
  92 + jQuery('#manage-product-details-form input.submit').removeAttr("disabled").removeClass('disabled');
  93 +}
  94 +
  95 +function calculateValuesForBar() {
  96 + jQuery('.cancel-price-details').addClass('form-changed');
  97 + var product_price = parseFloat(jQuery('form #product_price').val());
  98 + var total_cost = parseFloat(jQuery('form #product_inputs_cost').val());
  99 +
  100 + jQuery('form .price-details-price').each(function() {
  101 + var this_val = parseFloat(jQuery(this).val().replace(currency_format.separator, '.')) || 0;
  102 + total_cost = total_cost + this_val;
  103 + });
  104 + enablePriceDetailSubmit();
  105 +
  106 + var described = (product_price - total_cost) == 0;
  107 + var percentage = total_cost * 100 / product_price;
  108 + priceCompositionBar(percentage, described, total_cost, product_price);
  109 +}
  110 +
  111 +function addCommas(nStr) {
  112 + nStr += '';
  113 + var x = nStr.split('.');
  114 + var x1 = x[0];
  115 + var x2 = x.length > 1 ? '.' + x[1] : '';
  116 + var rgx = /(\d+)(\d{3})/;
  117 + while (rgx.test(x1)) {
  118 + x1 = x1.replace(rgx, '$1' + ',' + '$2');
  119 + }
  120 + return x1 + x2;
  121 +}
  122 +
  123 +function floatToCurrency(value, sep, del, cur) {
  124 + var ret = '';
  125 + if (cur) ret = cur + ' ';
  126 + if (!sep) sep = '.';
  127 + if (!del) del = ',';
  128 + return ret + addCommas(parseFloat(value).toFixed(2).toString()).replace('.', '%sep%').replace(',', del).replace('%sep%', sep);
  129 +}
  130 +
  131 +function currencyToFloat(value, sep, del, cur) {
  132 + var val = value;
  133 + if (cur) val = val.replace(cur + ' ', '');
  134 + if (!sep) sep = '.';
  135 + if (!del) del = ',';
  136 + return parseFloat(val.replace(del, '').replace(sep, '.'));
  137 +}
  138 +
  139 +function productionCostTypeChange(select, url, question, error_msg) {
  140 + if (select.value == '') {
  141 + var newType = prompt(question);
  142 + if (newType) {
  143 + jQuery.ajax({
  144 + url: url + "/" + newType,
  145 + dataType: 'json',
  146 + success: function(data, status, ajax){
  147 + if (data.ok) {
  148 + var opt = jQuery('<option value="' + data.id + '">' + newType + '</option>');
  149 + opt.insertBefore(jQuery("option:last", select));
  150 + select.selectedIndex = select.options.length - 2;
  151 + opt.clone().insertBefore('#new-cost-fields .production-cost-selection option:last');
  152 + } else {
  153 + alert(data.error_msg);
  154 + }
  155 + },
  156 + error: function(ajax, status, error){
  157 + alert(error_msg);
  158 + }
  159 + });
  160 + }
  161 + }
  162 +}
  163 +
  164 +function priceCompositionBar(value, described, total_cost, price) {
  165 + jQuery(function($) {
  166 + var bar_area = $('#price-composition-bar');
  167 + $(bar_area).find('#progressbar').progressbar({
  168 + value: value
  169 + });
  170 + $(bar_area).find('.production_cost').html(floatToCurrency(total_cost, currency_format.separator, currency_format.delimiter));
  171 + $(bar_area).find('.product_price').html(floatToCurrency(price, currency_format.separator, currency_format.delimiter));
  172 + if (described) {
  173 + $(bar_area).find('#progressbar-icon').addClass('ui-icon-check');
  174 + $(bar_area).find('#progressbar-icon').attr('title', $('#progressbar-icon').attr('data-price-described-message'));
  175 + $(bar_area).find('div.ui-progressbar-value').addClass('price-described');
  176 + } else {
  177 + $(bar_area).find('#progressbar-icon').removeClass('ui-icon-check');
  178 + $(bar_area).find('#progressbar-icon').attr('title', $('#progressbar-icon').attr('data-price-not-described-message'));
  179 + $(bar_area).find('div.ui-progressbar-value').removeClass('price-described');
  180 +
  181 + }
  182 + });
  183 +}
public/stylesheets/application.css
@@ -1075,10 +1075,12 @@ code input { @@ -1075,10 +1075,12 @@ code input {
1075 text-align: left; 1075 text-align: left;
1076 background: transparent url(/images/black-alpha-pixel.png); 1076 background: transparent url(/images/black-alpha-pixel.png);
1077 border-bottom: 1px solid #333; 1077 border-bottom: 1px solid #333;
  1078 + text-decoration: none;
1078 } 1079 }
1079 1080
1080 .zoomable-image:hover .zoomify-image { 1081 .zoomable-image:hover .zoomify-image {
1081 display: block; 1082 display: block;
  1083 + text-decoration: none;
1082 } 1084 }
1083 1085
1084 #article .zoomify-image { 1086 #article .zoomify-image {
@@ -1093,6 +1095,7 @@ code input { @@ -1093,6 +1095,7 @@ code input {
1093 border-bottom: 1px solid #eee; 1095 border-bottom: 1px solid #eee;
1094 display: block; 1096 display: block;
1095 background: transparent url(/images/zoom.png) left center no-repeat; 1097 background: transparent url(/images/zoom.png) left center no-repeat;
  1098 + color: #fff;
1096 } 1099 }
1097 1100
1098 #article pre { 1101 #article pre {
@@ -2962,71 +2965,228 @@ div#activation_enterprise div { @@ -2962,71 +2965,228 @@ div#activation_enterprise div {
2962 2965
2963 /* ==> public/stylesheets/controller_catalog.css <== */ 2966 /* ==> public/stylesheets/controller_catalog.css <== */
2964 /* ==> @import url('products.css'); <== */ 2967 /* ==> @import url('products.css'); <== */
  2968 +/* * * Products catalog * * * * * * * * * * * * */
2965 2969
2966 -/* * * List Products * * * * * * * * * * * * */  
2967 -  
2968 -#product_list { 2970 +#product-list {
  2971 + line-height: 20px;
2969 margin: 0px; 2972 margin: 0px;
2970 padding: 0px; 2973 padding: 0px;
2971 } 2974 }
2972 -  
2973 -#product_list ul { 2975 +#product-list ul {
2974 margin: 0px; 2976 margin: 0px;
2975 padding: 0px; 2977 padding: 0px;
2976 } 2978 }
2977 -  
2978 -#content #product_list li { 2979 +#product-list li {
2979 margin: 0px; 2980 margin: 0px;
2980 padding: 0px; 2981 padding: 0px;
2981 list-style: none; 2982 list-style: none;
2982 } 2983 }
2983 -  
2984 -#content #product_list li.product {  
2985 - border: 1px solid #888; 2984 +#product-list li.product {
  2985 + width: 200px;
  2986 + min-height: 280px;
  2987 + float: left;
  2988 + padding: 10px 30px 10px 0;
2986 margin-bottom: 10px; 2989 margin-bottom: 10px;
2987 - padding: 5px 10px;  
2988 - position: relative;  
2989 } 2990 }
2990 -  
2991 -#product_list .product-pic { 2991 +#product-list .expand-box:hover {
  2992 + background-color: #28F091;
  2993 +}
  2994 +#product-list .expand-box {
  2995 + background-color: #1EB46D;
  2996 + margin-bottom: 3px;
  2997 + -moz-border-radius: 10px 0px 0px 10px;
  2998 + -webkit-border-radius: 10px 0px 0px 10px;
  2999 + border-radius: 10px 0px 0px 10px;
  3000 + width: 202px;
  3001 +}
  3002 +#product-list .expand-box > span {
  3003 + padding-left: 20px;
  3004 + font-size: 0.70em;
  3005 + color: white;
  3006 + font-weight: bold;
  3007 + background: url(/images/catalog-expanders.png) no-repeat;
2992 display: block; 3008 display: block;
2993 - width: 64px;  
2994 - height: 64px;  
2995 - background-repeat: no-repeat;  
2996 - background-position: 50% 50%; 3009 + line-height: 15px;
  3010 + background-position: left 100%;
  3011 + cursor: pointer;
  3012 +}
  3013 +#product-list .expand-box.open > span {
  3014 + background-position: left top;
  3015 +}
  3016 +#product-list .expand-box-corner {
  3017 +}
  3018 +#product-list li.product .product-qualifiers {
  3019 + font-size: 9px;
  3020 + line-height: normal;
  3021 +}
  3022 +#product-list li.product .product-qualifiers > span {
  3023 + font-weight: bold;
  3024 + display: block;
  3025 + margin-top: 8px;
  3026 +}
  3027 +#product-list li.product .product-qualifiers > span,
  3028 +#product-list li.product .expand-box > span {
  3029 + text-transform: uppercase;
  3030 +}
  3031 +#product-list li.product .expand-box > div {
  3032 + display: none;
  3033 + position: absolute;
  3034 + z-index: 10;
  3035 +}
  3036 +#product-list li.product .expand-box .content {
  3037 + font-size: 11px;
  3038 + padding: 6px 5px;
  3039 + overflow: auto;
  3040 + max-height: 200px;
  3041 + width: 160px;
  3042 + border-radius: 5px;
  3043 + -moz-border-radius: 5px;
  3044 + -webkit-border-radius: 5px;
  3045 + background: #DCFFD7;
  3046 + border: 2px solid #1EB46D;
  3047 + min-height: 30px;
2997 float: left; 3048 float: left;
2998 - margin-right: 15px;  
2999 - position: relative; /* work arround msie bug */ 3049 + text-align: left;
  3050 +}
  3051 +#product-list li.product .expand-box .arrow {
  3052 + border-left: 6px solid #1EB46D;
  3053 + border-top: 5px solid transparent;
  3054 + border-bottom: 5px solid transparent;
  3055 + margin-top: 13px;
  3056 + float: right;
3000 } 3057 }
3001 3058
3002 -#product_list .product-pic span {  
3003 - display: none; 3059 +#product-list li.product.not-available .expand-box {
  3060 + background-color: #DCF3E9;
  3061 +}
  3062 +#product-list li.product.not-available .product-link a,
  3063 +#product-list li.product.not-available .product-qualifiers,
  3064 +#product-list li.product.not-available .product-price-line,
  3065 +#product-list li.product.not-available .product-price,
  3066 +#product-list li.product.not-available .product-unit {
  3067 + color: #ACACAC !important;
  3068 +}
  3069 +#product-list .product-link {
  3070 + margin-top: 5px;
  3071 + margin-bottom: 5px;
  3072 + color: #006672;
  3073 + font-weight: bold;
  3074 + text-align: left;
  3075 +}
  3076 +#product-list .prop {
  3077 + float:right;
  3078 + width:1px;
  3079 +}
  3080 +#product-list .min50px {
  3081 + height:50px;
  3082 +}
  3083 +#product-list .product-row-clear {
  3084 + clear:both;
  3085 + height:1px;
  3086 + overflow:hidden;
3004 } 3087 }
3005 3088
3006 -#content #product_list h3 {  
3007 - margin: 0px;  
3008 - padding: 0px;  
3009 - font-size: 120%; 3089 +#product-list .product-price-line {
  3090 + margin: 0 0 8px;
  3091 + display: block;
  3092 + clear: both;
3010 } 3093 }
3011 -.msie #content #product_list h3 {  
3012 - margin-top: -15px; 3094 +#product-list .product-price {
  3095 + font-weight: bold;
3013 } 3096 }
3014 -#product_list h3 a {  
3015 - text-decoration: none; 3097 +#product-list .product-price,
  3098 +#product-list .product-unit {
  3099 + color: #0194C7;
  3100 +}
  3101 +#product-list .product-unit,
  3102 +#product-list .product-discount,
  3103 +#product-list .product-discount-by {
  3104 + font-size: 9px;
  3105 + margin-right: 3px;
  3106 +}
  3107 +#product-list .product-discount,
  3108 +#product-list .product-price {
  3109 + float: left;
  3110 + line-height: 15px;
  3111 + margin-right: 3px;
  3112 +}
  3113 +#product-list .product-discount span {
  3114 + text-decoration: line-through;
  3115 +}
  3116 +#product-list .product-discount-by {
  3117 + text-decoration: none !important;
3016 } 3118 }
3017 3119
3018 -#product_list .product_category {  
3019 - font-size: 11px; 3120 +#product-list .search-product-input-dots-to-price {
  3121 + width: 100%;
  3122 + margin: 0;
  3123 +}
  3124 +#product-list .search-product-input-name {
  3125 + background-color: #DCFFD7;
  3126 + max-width: 101px;
  3127 +}
  3128 +#product-list .search-product-input-price {
  3129 + background-color: #DCFFD7;
3020 } 3130 }
3021 3131
3022 -#product_list .description {  
3023 - clear: left;  
3024 - font-size: 11px;  
3025 - text-align: justify;  
3026 - padding: 5px 10px 0px 10px; 3132 +#product-list .catalog-item-extras {
  3133 + position: absolute;
  3134 + bottom: 0px;
  3135 + right: 0px;
  3136 +}
  3137 +#product-list .product-big {
  3138 + background-repeat: no-repeat;
  3139 + background-position: 50% 50%;
  3140 + display: block;
  3141 + width: 200px;
  3142 + height: 160px;
  3143 +}
  3144 +#product-list .ui-button {
  3145 + margin: 0;
  3146 +}
  3147 +#product-list .ui-button-text {
  3148 + color: #D38D5F;
  3149 + font-size: 0.8em;
  3150 + padding: 0.1em 0.3em 0.1em 3em;
  3151 +}
  3152 +#product-list .product-big span {
  3153 + display: none;
  3154 +}
  3155 +#product-list .product-image-link {
  3156 + position: relative;
  3157 + width: 200px;
  3158 + height: 160px;
  3159 + border: 1px solid #BFBFBF;
  3160 + position: relative; /* work arround msie bug */
  3161 + text-align: center;
  3162 + vertical-align: middle;
3027 } 3163 }
3028 -.msie #product_list .description {  
3029 - padding: 5px 10px 10px 10px; 3164 +#product-list .product-image-link .no-image {
  3165 + line-height: 145px;
  3166 + text-align: center;
  3167 + color: #777;
  3168 + font-size: 9px;
  3169 + font-weight: bold;
  3170 + text-transform: uppercase;
  3171 + letter-spacing: 1px;
  3172 + user-select: none;
  3173 + -moz-user-select: none;
  3174 + -khtml-user-select: none;
  3175 + -webkit-user-select: none;
  3176 +}
  3177 +#product-list li.product-unavailable {
  3178 + text-transform: uppercase;
  3179 + color: #FF6261;
  3180 + font-weight: bold;
  3181 + line-height: 40px;
  3182 +}
  3183 +#product-list h3 {
  3184 + margin: 0px;
  3185 + padding: 0px;
  3186 + font-size: 120%;
  3187 +}
  3188 +.msie #product-list h3 {
  3189 + margin-top: -15px;
3030 } 3190 }
3031 3191
3032 /* * * Show Product * * * * * * * * * * * * */ 3192 /* * * Show Product * * * * * * * * * * * * */
@@ -3223,6 +3383,86 @@ div#activation_enterprise div { @@ -3223,6 +3383,86 @@ div#activation_enterprise div {
3223 font-weight: bold; 3383 font-weight: bold;
3224 } 3384 }
3225 3385
  3386 +/* * * * * * Price details * * * * * */
  3387 +
  3388 +#display-price-details .price-details-list {
  3389 + padding-left: 0px;
  3390 +}
  3391 +
  3392 +#display-price-details .price-details-list li {
  3393 + list-style: none;
  3394 +}
  3395 +
  3396 +#display-price-details .price-details-list li .price-detail-name {
  3397 + width: 200px;
  3398 +}
  3399 +
  3400 +#display-price-details .price-details-list li .price-detail-name,
  3401 +#display-price-details .price-details-list li .price-detail-price {
  3402 + display: inline-block;
  3403 +}
  3404 +
  3405 +#manage-product-details-form .formlabel,
  3406 +#manage-product-details-form .formfield {
  3407 + display: inline-block;
  3408 +}
  3409 +
  3410 +#manage-product-details-form #add-new-cost {
  3411 + float: right;
  3412 +}
  3413 +
  3414 +/* * * Progress bar on price details edition * * */
  3415 +
  3416 +#display-manage-price-details .ui-widget-content {
  3417 + border: 1px solid #DDD;
  3418 +}
  3419 +
  3420 +#display-manage-price-details .ui-progressbar {
  3421 + height: 20px;
  3422 +}
  3423 +
  3424 +#display-manage-price-details .ui-progressbar .ui-progressbar-value {
  3425 + margin: 0px;
  3426 + background-color: #A40000;
  3427 + filter:alpha(opacity=70);
  3428 + -moz-opacity: 0.7;
  3429 + opacity: 0.7;
  3430 +}
  3431 +
  3432 +#display-manage-price-details .ui-progressbar .ui-progressbar-value.price-described {
  3433 + background-color: #4E9A06;
  3434 +}
  3435 +
  3436 +#display-manage-price-details #price-details-info {
  3437 + margin: 10px 0px;
  3438 +}
  3439 +
  3440 +#display-manage-price-details #details-progressbar {
  3441 + position: relative;
  3442 + width: 410px;
  3443 + display: inline-block;
  3444 +}
  3445 +
  3446 +#display-manage-price-details #progressbar-text {
  3447 + position: absolute;
  3448 + top: 5px;
  3449 + right: 7px;
  3450 + font-size: 11px;
  3451 +}
  3452 +
  3453 +#display-manage-price-details #progressbar-icon {
  3454 + display: inline-block;
  3455 + cursor: pointer;
  3456 +}
  3457 +
  3458 +#display-manage-price-details #details-progressbar .ui-corner-left,
  3459 +#display-manage-price-details #details-progressbar .ui-corner-right {
  3460 + -moz-border-radius-bottomleft: 0px;
  3461 + -moz-border-radius-bottomright: 0px;
  3462 + -moz-border-radius-topleft: 0px;
  3463 + -moz-border-radius-topright: 0px;
  3464 +}
  3465 +
3226 /* ==> public/stylesheets/controller_cms.css <== */ 3466 /* ==> public/stylesheets/controller_cms.css <== */
3227 3467
3228 3468
public/stylesheets/colorbox/colorbox.css
@@ -18,29 +18,29 @@ @@ -18,29 +18,29 @@
18 Change the following styles to modify the appearance of ColorBox. They are 18 Change the following styles to modify the appearance of ColorBox. They are
19 ordered & tabbed in a way that represents the nesting of the generated HTML. 19 ordered & tabbed in a way that represents the nesting of the generated HTML.
20 */ 20 */
21 -#cboxOverlay{background:url(images/overlay.png) repeat 0 0;} 21 +#cboxOverlay{background:url(/stylesheets/colorbox/images/overlay.png) repeat 0 0;}
22 #colorbox{} 22 #colorbox{}
23 - #cboxTopLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -100px 0;}  
24 - #cboxTopRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -129px 0;}  
25 - #cboxBottomLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -100px -29px;}  
26 - #cboxBottomRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -129px -29px;}  
27 - #cboxMiddleLeft{width:21px; background:url(images/controls.png) left top repeat-y;}  
28 - #cboxMiddleRight{width:21px; background:url(images/controls.png) right top repeat-y;}  
29 - #cboxTopCenter{height:21px; background:url(images/border.png) 0 0 repeat-x;}  
30 - #cboxBottomCenter{height:21px; background:url(images/border.png) 0 -29px repeat-x;} 23 + #cboxTopLeft{width:21px; height:21px; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -100px 0;}
  24 + #cboxTopRight{width:21px; height:21px; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -129px 0;}
  25 + #cboxBottomLeft{width:21px; height:21px; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -100px -29px;}
  26 + #cboxBottomRight{width:21px; height:21px; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -129px -29px;}
  27 + #cboxMiddleLeft{width:21px; background:url(/stylesheets/colorbox/images/controls.png) left top repeat-y;}
  28 + #cboxMiddleRight{width:21px; background:url(/stylesheets/colorbox/images/controls.png) right top repeat-y;}
  29 + #cboxTopCenter{height:21px; background:url(/stylesheets/colorbox/images/border.png) 0 0 repeat-x;}
  30 + #cboxBottomCenter{height:21px; background:url(/stylesheets/colorbox/images/border.png) 0 -29px repeat-x;}
31 #cboxContent{background:#fff; overflow:hidden;} 31 #cboxContent{background:#fff; overflow:hidden;}
32 #cboxError{padding:50px; border:1px solid #ccc;} 32 #cboxError{padding:50px; border:1px solid #ccc;}
33 #cboxLoadedContent{margin-bottom:28px;} 33 #cboxLoadedContent{margin-bottom:28px;}
34 #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;} 34 #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;}
35 #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;} 35 #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;}
36 #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;} 36 #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;}
37 - #cboxPrevious{position:absolute; bottom:0; left:0; background:url(images/controls.png) no-repeat -75px 0; width:25px; height:25px; text-indent:-9999px;} 37 + #cboxPrevious{position:absolute; bottom:0; left:0; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -75px 0; width:25px; height:25px; text-indent:-9999px;}
38 #cboxPrevious.hover{background-position:-75px -25px;} 38 #cboxPrevious.hover{background-position:-75px -25px;}
39 - #cboxNext{position:absolute; bottom:0; left:27px; background:url(images/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;} 39 + #cboxNext{position:absolute; bottom:0; left:27px; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;}
40 #cboxNext.hover{background-position:-50px -25px;} 40 #cboxNext.hover{background-position:-50px -25px;}
41 - #cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;}  
42 - #cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;}  
43 - #cboxClose{position:absolute; bottom:0; right:0; background:url(images/controls.png) no-repeat -25px 0; width:25px; height:25px; text-indent:-9999px;} 41 + #cboxLoadingOverlay{background:url(/stylesheets/colorbox/images/loading_background.png) no-repeat center center;}
  42 + #cboxLoadingGraphic{background:url(/stylesheets/colorbox/images/loading.gif) no-repeat center center;}
  43 + #cboxClose{position:absolute; bottom:0; right:0; background:url(/stylesheets/colorbox/images/controls.png) no-repeat -25px 0; width:25px; height:25px; text-indent:-9999px;}
44 #cboxClose.hover{background-position:-25px -25px;} 44 #cboxClose.hover{background-position:-25px -25px;}
45 45
46 /* 46 /*
@@ -61,14 +61,14 @@ @@ -61,14 +61,14 @@
61 /* 61 /*
62 The following provides PNG transparency support for IE6 62 The following provides PNG transparency support for IE6
63 */ 63 */
64 -.cboxIE6 #cboxTopLeft{background:url(images/ie6/borderTopLeft.png);}  
65 -.cboxIE6 #cboxTopCenter{background:url(images/ie6/borderTopCenter.png);}  
66 -.cboxIE6 #cboxTopRight{background:url(images/ie6/borderTopRight.png);}  
67 -.cboxIE6 #cboxBottomLeft{background:url(images/ie6/borderBottomLeft.png);}  
68 -.cboxIE6 #cboxBottomCenter{background:url(images/ie6/borderBottomCenter.png);}  
69 -.cboxIE6 #cboxBottomRight{background:url(images/ie6/borderBottomRight.png);}  
70 -.cboxIE6 #cboxMiddleLeft{background:url(images/ie6/borderMiddleLeft.png);}  
71 -.cboxIE6 #cboxMiddleRight{background:url(images/ie6/borderMiddleRight.png);} 64 +.cboxIE6 #cboxTopLeft{background:url(/stylesheets/colorbox/images/ie6/borderTopLeft.png);}
  65 +.cboxIE6 #cboxTopCenter{background:url(/stylesheets/colorbox/images/ie6/borderTopCenter.png);}
  66 +.cboxIE6 #cboxTopRight{background:url(/stylesheets/colorbox/images/ie6/borderTopRight.png);}
  67 +.cboxIE6 #cboxBottomLeft{background:url(/stylesheets/colorbox/images/ie6/borderBottomLeft.png);}
  68 +.cboxIE6 #cboxBottomCenter{background:url(/stylesheets/colorbox/images/ie6/borderBottomCenter.png);}
  69 +.cboxIE6 #cboxBottomRight{background:url(/stylesheets/colorbox/images/ie6/borderBottomRight.png);}
  70 +.cboxIE6 #cboxMiddleLeft{background:url(/stylesheets/colorbox/images/ie6/borderMiddleLeft.png);}
  71 +.cboxIE6 #cboxMiddleRight{background:url(/stylesheets/colorbox/images/ie6/borderMiddleRight.png);}
72 72
73 .cboxIE6 #cboxTopLeft, 73 .cboxIE6 #cboxTopLeft,
74 .cboxIE6 #cboxTopCenter, 74 .cboxIE6 #cboxTopCenter,
script/sample-profiles
@@ -98,7 +98,7 @@ end @@ -98,7 +98,7 @@ end
98 done 98 done
99 99
100 print "Activating users: " 100 print "Activating users: "
101 -User.all(:conditions => ['login NOT LIKE "%%_template"']).each do |user| 101 +User.all(:conditions => ["login NOT LIKE '%%_template'"]).each do |user|
102 user.activate 102 user.activate
103 print '.' 103 print '.'
104 end 104 end
test/factories.rb
@@ -449,4 +449,12 @@ module Noosfero::Factory @@ -449,4 +449,12 @@ module Noosfero::Factory
449 { :singular => 'Litre', :plural => 'Litres', :environment_id => 1 } 449 { :singular => 'Litre', :plural => 'Litres', :environment_id => 1 }
450 end 450 end
451 451
  452 + ###############################################
  453 + # Production Cost
  454 + ###############################################
  455 +
  456 + def defaults_for_production_cost
  457 + { :name => 'Production cost ' + factory_num_seq.to_s }
  458 + end
  459 +
452 end 460 end
test/fixtures/files/agrotox.png 0 → 100644

13.4 KB

test/fixtures/files/semterrinha.png 0 → 100644

17.2 KB

test/functional/catalog_controller_test.rb
@@ -46,7 +46,7 @@ class CatalogControllerTest &lt; ActionController::TestCase @@ -46,7 +46,7 @@ class CatalogControllerTest &lt; ActionController::TestCase
46 46
47 assert_equal 12, @enterprise.products.count 47 assert_equal 12, @enterprise.products.count
48 get :index, :profile => @enterprise.identifier 48 get :index, :profile => @enterprise.identifier
49 - assert_equal 10, assigns(:products).count 49 + assert_equal 9, assigns(:products).count
50 assert_tag :a, :attributes => {:class => 'next_page'} 50 assert_tag :a, :attributes => {:class => 'next_page'}
51 end 51 end
52 52
@@ -63,21 +63,13 @@ class CatalogControllerTest &lt; ActionController::TestCase @@ -63,21 +63,13 @@ class CatalogControllerTest &lt; ActionController::TestCase
63 should 'not show product price when listing products if not informed' do 63 should 'not show product price when listing products if not informed' do
64 prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category) 64 prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
65 get :index, :profile => @enterprise.identifier 65 get :index, :profile => @enterprise.identifier
66 - assert_no_tag :tag => 'li', :attributes => { :class => 'product_price' }, :content => /Price:/ 66 + assert_no_tag :tag => 'span', :attributes => { :class => 'product-price with-discount' }, :content => /50.00/
67 end 67 end
68 68
69 should 'show product price when listing products if informed' do 69 should 'show product price when listing products if informed' do
70 prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => @product_category) 70 prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => @product_category)
71 get :index, :profile => @enterprise.identifier 71 get :index, :profile => @enterprise.identifier
72 - assert_tag :tag => 'li', :attributes => { :class => 'product_price' }, :content => /Price:/  
73 - end  
74 -  
75 - should 'link to assets products wiht product category in the link to product category on index' do  
76 - pc = ProductCategory.create!(:name => 'some product', :environment => enterprise.environment)  
77 - prod = enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => pc)  
78 -  
79 - get :index, :profile => enterprise.identifier  
80 - assert_tag :tag => 'a', :attributes => {:href => /assets\/products\?product_category=#{pc.id}/} 72 + assert_tag :tag => 'span', :attributes => { :class => 'product-price with-discount' }, :content => /50.00/
81 end 73 end
82 74
83 should 'add an zero width space every 4 caracters of comment urls' do 75 should 'add an zero width space every 4 caracters of comment urls' do
test/functional/manage_products_controller_test.rb
@@ -468,4 +468,47 @@ class ManageProductsControllerTest &lt; ActionController::TestCase @@ -468,4 +468,47 @@ class ManageProductsControllerTest &lt; ActionController::TestCase
468 assert_response 403 468 assert_response 403
469 end 469 end
470 470
  471 + should 'remove price detail of a product' do
  472 + product = fast_create(Product, :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  473 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  474 + detail = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  475 +
  476 + assert_equal [detail], product.price_details
  477 +
  478 + post :remove_price_detail, :id => detail.id, :product => product, :profile => @enterprise.identifier
  479 + product.reload
  480 + assert_equal [], product.price_details
  481 + end
  482 +
  483 + should 'create a production cost for enterprise' do
  484 + get :create_production_cost, :profile => @enterprise.identifier, :id => 'Taxes'
  485 +
  486 + assert_equal ['Taxes'], Enterprise.find(@enterprise.id).production_costs.map(&:name)
  487 + resp = ActiveSupport::JSON.decode(@response.body)
  488 + assert_equal 'Taxes', resp['name']
  489 + assert resp['id'].kind_of?(Integer)
  490 + assert resp['ok']
  491 + assert_nil resp['error_msg']
  492 + end
  493 +
  494 + should 'display error if production cost has no name' do
  495 + get :create_production_cost, :profile => @enterprise.identifier
  496 +
  497 + resp = ActiveSupport::JSON.decode(@response.body)
  498 + assert_nil resp['name']
  499 + assert_nil resp['id']
  500 + assert !resp['ok']
  501 + assert_match /blank/, resp['error_msg']
  502 + end
  503 +
  504 + should 'display error if name of production cost is too long' do
  505 + get :create_production_cost, :profile => @enterprise.identifier, :id => 'a'*60
  506 +
  507 + resp = ActiveSupport::JSON.decode(@response.body)
  508 + assert_nil resp['name']
  509 + assert_nil resp['id']
  510 + assert !resp['ok']
  511 + assert_match /too long/, resp['error_msg']
  512 + end
  513 +
471 end 514 end
test/unit/application_helper_test.rb
@@ -625,13 +625,18 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase @@ -625,13 +625,18 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase
625 env = Environment.default 625 env = Environment.default
626 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(false) 626 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(false)
627 stubs(:environment).returns(env) 627 stubs(:environment).returns(env)
628 - assert_nil add_zoom_to_images 628 + assert_nil add_zoom_to_article_images
629 end 629 end
630 630
631 should 'return code when :show_zoom_button_on_article_images is enabled in environment' do 631 should 'return code when :show_zoom_button_on_article_images is enabled in environment' do
632 env = Environment.default 632 env = Environment.default
633 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(true) 633 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(true)
634 stubs(:environment).returns(env) 634 stubs(:environment).returns(env)
  635 + assert_not_nil add_zoom_to_article_images
  636 + end
  637 +
  638 + should 'return code when add_zoom_to_images' do
  639 + env = Environment.default
635 assert_not_nil add_zoom_to_images 640 assert_not_nil add_zoom_to_images
636 end 641 end
637 642
test/unit/enterprise_homepage_test.rb
@@ -16,43 +16,9 @@ class EnterpriseHomepageTest &lt; ActiveSupport::TestCase @@ -16,43 +16,9 @@ class EnterpriseHomepageTest &lt; ActiveSupport::TestCase
16 assert_kind_of String, EnterpriseHomepage.description 16 assert_kind_of String, EnterpriseHomepage.description
17 end 17 end
18 18
19 - should 'display profile info' do  
20 - e = Enterprise.create!(:name => 'my test enterprise', :identifier => 'mytestenterprise', :contact_email => 'ent@noosfero.foo.bar', :contact_phone => '5555 5555')  
21 - a = EnterpriseHomepage.new(:name => 'article homepage')  
22 - e.articles << a  
23 - result = a.to_html  
24 - assert_match /ent@noosfero.foo.bar/, result  
25 - assert_match /5555 5555/, result  
26 - end  
27 -  
28 - should 'display products list' do  
29 - ent = fast_create(Enterprise, :identifier => 'test_enterprise', :name => 'Test enteprise')  
30 - prod = ent.products.create!(:name => 'Product test', :product_category => @product_category)  
31 - a = EnterpriseHomepage.new(:name => 'article homepage')  
32 - ent.articles << a  
33 - result = a.to_html  
34 - assert_match /Product test/, result  
35 - end  
36 -  
37 - should 'not display products list if environment do not let' do  
38 - e = Environment.default  
39 - e.enable('disable_products_for_enterprises')  
40 - e.save!  
41 - ent = fast_create(Enterprise, :identifier => 'test_enterprise', :name => 'Test enteprise', :environment_id => e.id)  
42 - prod = ent.products.create!(:name => 'Product test', :product_category => @product_category)  
43 - a = EnterpriseHomepage.new(:name => 'article homepage')  
44 - ent.articles << a  
45 - result = a.to_html  
46 - assert_no_match /Product test/, result  
47 - end  
48 -  
49 - should 'display link to product' do  
50 - ent = fast_create(Enterprise, :identifier => 'test_enterprise', :name => 'Test enteprise')  
51 - prod = ent.products.create!(:name => 'Product test', :product_category => @product_category)  
52 - a = EnterpriseHomepage.new(:name => 'article homepage')  
53 - ent.articles << a  
54 - result = a.to_html  
55 - assert_match /\/test_enterprise\/manage_products\/show\/#{prod.id}/, result 19 + should 'return a valid body' do
  20 + e = EnterpriseHomepage.new(:name => 'sample enterprise homepage')
  21 + assert_not_nil e.to_html
56 end 22 end
57 23
58 should 'can display hits' do 24 should 'can display hits' do
test/unit/enterprise_test.rb
@@ -446,4 +446,8 @@ class EnterpriseTest &lt; ActiveSupport::TestCase @@ -446,4 +446,8 @@ class EnterpriseTest &lt; ActiveSupport::TestCase
446 assert_equal false, enterprise.receives_scrap_notification? 446 assert_equal false, enterprise.receives_scrap_notification?
447 end 447 end
448 448
  449 + should 'have production cost' do
  450 + e = fast_create(Enterprise)
  451 + assert_respond_to e, :production_costs
  452 + end
449 end 453 end
test/unit/environment_test.rb
@@ -1200,4 +1200,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase @@ -1200,4 +1200,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
1200 assert_not_includes environment.enabled_plugins, plugin 1200 assert_not_includes environment.enabled_plugins, plugin
1201 end 1201 end
1202 1202
  1203 + should 'have production costs' do
  1204 + assert_respond_to Environment.default, :production_costs
  1205 + end
1203 end 1206 end
test/unit/highlights_block_test.rb
@@ -77,6 +77,7 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase @@ -77,6 +77,7 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase
77 file = mock() 77 file = mock()
78 UploadedFile.expects(:find).with(1).returns(file) 78 UploadedFile.expects(:find).with(1).returns(file)
79 file.expects(:public_filename).returns('address') 79 file.expects(:public_filename).returns('address')
  80 + UploadedFile.expects(:find).with(0).returns(nil)
80 block = HighlightsBlock.new(:images => [{:image_id => 1, :address => '/address', :position => 1, :title => 'address'}, {:image_id => '', :address => 'some', :position => '2', :title => 'Some'}]) 81 block = HighlightsBlock.new(:images => [{:image_id => 1, :address => '/address', :position => 1, :title => 'address'}, {:image_id => '', :address => 'some', :position => '2', :title => 'Some'}])
81 block.save! 82 block.save!
82 block.reload 83 block.reload
@@ -115,6 +116,12 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase @@ -115,6 +116,12 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase
115 f3 = mock() 116 f3 = mock()
116 f3.expects(:public_filename).returns('address') 117 f3.expects(:public_filename).returns('address')
117 UploadedFile.expects(:find).with(3).returns(f3) 118 UploadedFile.expects(:find).with(3).returns(f3)
  119 + f4 = mock()
  120 + f4.expects(:public_filename).returns('address')
  121 + UploadedFile.expects(:find).with(4).returns(f4)
  122 + f5 = mock()
  123 + f5.expects(:public_filename).returns('address')
  124 + UploadedFile.expects(:find).with(5).returns(f5)
118 block = HighlightsBlock.new 125 block = HighlightsBlock.new
119 i1 = {:image_id => 1, :address => '/address', :position => 3, :title => 'address'} 126 i1 = {:image_id => 1, :address => '/address', :position => 3, :title => 'address'}
120 i2 = {:image_id => 2, :address => '/address', :position => 1, :title => 'address'} 127 i2 = {:image_id => 2, :address => '/address', :position => 1, :title => 'address'}
test/unit/input_test.rb
@@ -162,4 +162,19 @@ class InputTest &lt; ActiveSupport::TestCase @@ -162,4 +162,19 @@ class InputTest &lt; ActiveSupport::TestCase
162 assert_kind_of Unit, input.build_unit 162 assert_kind_of Unit, input.build_unit
163 end 163 end
164 164
  165 + should 'calculate cost of input' do
  166 + input = Input.new(:amount_used => 10, :price_per_unit => 2.00)
  167 + assert_equal 20.00, input.cost
  168 + end
  169 +
  170 + should 'cost 0 if amount not defined' do
  171 + input = Input.new(:price_per_unit => 2.00)
  172 + assert_equal 0.00, input.cost
  173 + end
  174 +
  175 + should 'cost 0 if price_per_unit is not defined' do
  176 + input = Input.new(:amount_used => 10)
  177 + assert_equal 0.00, input.cost
  178 + end
  179 +
165 end 180 end
test/unit/price_detail_test.rb 0 → 100644
@@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class PriceDetailTest < ActiveSupport::TestCase
  4 +
  5 + should 'have price 0 by default' do
  6 + p = PriceDetail.new
  7 +
  8 + assert p.price.zero?
  9 + end
  10 +
  11 + should 'return zero on price if it is blank' do
  12 + p = PriceDetail.new(:price => '')
  13 +
  14 + assert p.price.zero?
  15 + end
  16 +
  17 + should 'accept price in american\'s or brazilian\'s currency format' do
  18 + [
  19 + [12.34, 12.34],
  20 + ["12.34", 12.34],
  21 + ["12,34", 12.34],
  22 + ["12.345.678,90", 12345678.90],
  23 + ["12,345,678.90", 12345678.90],
  24 + ["12.345.678", 12345678.00],
  25 + ["12,345,678", 12345678.00]
  26 + ].each do |input, output|
  27 + new_price_detail = PriceDetail.new(:price => input)
  28 + assert_equal output, new_price_detail.price
  29 + end
  30 + end
  31 +
  32 + should 'belongs to a product' do
  33 + p = PriceDetail.new
  34 +
  35 + assert_respond_to p, :product
  36 + end
  37 +
  38 + should 'product be mandatory' do
  39 + p = PriceDetail.new
  40 + p.valid?
  41 +
  42 + assert p.errors.invalid?(:product_id)
  43 + end
  44 +
  45 + should 'have production cost' do
  46 + product = fast_create(Product)
  47 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  48 + detail = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  49 +
  50 + assert_equal cost, PriceDetail.find(detail.id).production_cost
  51 + end
  52 +
  53 + should 'production cost be mandatory' do
  54 + p = PriceDetail.new
  55 + p.valid?
  56 +
  57 + assert p.errors.invalid?(:production_cost_id)
  58 + end
  59 +
  60 + should 'th production cost be unique on scope of product' do
  61 + product = fast_create(Product)
  62 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'environment')
  63 +
  64 + detail1 = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  65 + detail2 = product.price_details.build(:production_cost_id => cost.id, :price => 10)
  66 +
  67 + detail2.valid?
  68 + assert detail2.errors.invalid?(:production_cost_id)
  69 + end
  70 +
  71 + should 'format values to float with 2 decimals' do
  72 + enterprise = fast_create(Enterprise)
  73 + product = fast_create(Product, :enterprise_id => enterprise.id)
  74 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'environment')
  75 +
  76 + price_detail = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  77 +
  78 + assert_equal "10.00", price_detail.formatted_value(:price)
  79 + end
  80 +
  81 +end
test/unit/product_test.rb
@@ -382,4 +382,132 @@ class ProductTest &lt; ActiveSupport::TestCase @@ -382,4 +382,132 @@ class ProductTest &lt; ActiveSupport::TestCase
382 assert_includes Product.find_by_contents('thing'), p2 382 assert_includes Product.find_by_contents('thing'), p2
383 end 383 end
384 384
  385 + should 'respond to price details' do
  386 + product = Product.new
  387 + assert_respond_to product, :price_details
  388 + end
  389 +
  390 + should 'return total value of inputs' do
  391 + product = fast_create(Product)
  392 + first = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 20.0, :amount_used => 2)
  393 + second = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 10.0, :amount_used => 1)
  394 +
  395 + assert_equal 50.0, product.inputs_cost
  396 + end
  397 +
  398 + should 'return 0 on total value of inputs if has no input' do
  399 + product = fast_create(Product)
  400 +
  401 + assert product.inputs_cost.zero?
  402 + end
  403 +
  404 + should 'know if price is described' do
  405 + product = fast_create(Product, :price => 30.0)
  406 +
  407 + first = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 20.0, :amount_used => 1)
  408 + assert !Product.find(product.id).price_described?
  409 +
  410 + second = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 10.0, :amount_used => 1)
  411 + assert Product.find(product.id).price_described?
  412 + end
  413 +
  414 + should 'return false on price_described if price of product is not defined' do
  415 + product = fast_create(Product)
  416 +
  417 + assert_equal false, product.price_described?
  418 + end
  419 +
  420 + should 'create price details' do
  421 + product = fast_create(Product)
  422 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  423 + assert product.price_details.empty?
  424 +
  425 + product.update_price_details([{:production_cost_id => cost.id, :price => 10}])
  426 + assert_equal 1, Product.find(product.id).price_details.size
  427 + end
  428 +
  429 + should 'update price of a cost on price details' do
  430 + product = fast_create(Product)
  431 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  432 + cost2 = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  433 + price_detail = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  434 + assert !product.price_details.empty?
  435 +
  436 + product.update_price_details([{:production_cost_id => cost.id, :price => 20}, {:production_cost_id => cost2.id, :price => 30}])
  437 + assert_equal 20, product.price_details.find_by_production_cost_id(cost.id).price
  438 + assert_equal 2, Product.find(product.id).price_details.size
  439 + end
  440 +
  441 + should 'destroy price details if product is removed' do
  442 + product = fast_create(Product)
  443 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  444 + price_detail = product.price_details.create(:production_cost_id => cost.id, :price => 10)
  445 +
  446 + assert_difference PriceDetail, :count, -1 do
  447 + product.destroy
  448 + end
  449 + end
  450 +
  451 + should 'have production costs' do
  452 + product = fast_create(Product)
  453 + cost = fast_create(ProductionCost, :owner_id => Environment.default.id, :owner_type => 'Environment')
  454 + product.price_details.create(:production_cost_id => cost.id, :price => 10)
  455 + assert_equal [cost], Product.find(product.id).production_costs
  456 + end
  457 +
  458 + should 'return production costs from enterprise and environment' do
  459 + ent = fast_create(Enterprise)
  460 + product = fast_create(Product, :enterprise_id => ent.id)
  461 + ent_production_cost = fast_create(ProductionCost, :owner_id => ent.id, :owner_type => 'Profile')
  462 + env_production_cost = fast_create(ProductionCost, :owner_id => ent.environment.id, :owner_type => 'Environment')
  463 +
  464 + assert_equal [env_production_cost, ent_production_cost], product.available_production_costs
  465 + end
  466 +
  467 + should 'return all production costs' do
  468 + ent = fast_create(Enterprise)
  469 + product = fast_create(Product, :enterprise_id => ent.id)
  470 +
  471 + env_production_cost = fast_create(ProductionCost, :owner_id => ent.environment.id, :owner_type => 'Environment')
  472 + ent_production_cost = fast_create(ProductionCost, :owner_id => ent.id, :owner_type => 'Profile')
  473 + product.price_details.create(:production_cost => env_production_cost, :product => product)
  474 + assert_equal [env_production_cost, ent_production_cost], product.available_production_costs
  475 + end
  476 +
  477 + should 'return total value of production costs' do
  478 + ent = fast_create(Enterprise)
  479 + product = fast_create(Product, :enterprise_id => ent.id)
  480 +
  481 + env_production_cost = fast_create(ProductionCost, :owner_id => ent.environment.id, :owner_type => 'Environment')
  482 + price_detail = product.price_details.create(:production_cost => env_production_cost, :price => 10)
  483 +
  484 + input = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 20.0, :amount_used => 2)
  485 +
  486 + assert_equal price_detail.price + input.cost, product.total_production_cost
  487 + end
  488 +
  489 + should 'return inputs cost as total value of production costs if has no price details' do
  490 + ent = fast_create(Enterprise)
  491 + product = fast_create(Product, :enterprise_id => ent.id)
  492 +
  493 + input = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 20.0, :amount_used => 2)
  494 +
  495 + assert_equal input.cost, product.total_production_cost
  496 + end
  497 +
  498 + should 'return 0 on total production cost if has no input and price details' do
  499 + product = fast_create(Product)
  500 +
  501 + assert product.total_production_cost.zero?
  502 + end
  503 +
  504 + should 'format inputs cost values to float with 2 decimals' do
  505 + ent = fast_create(Enterprise)
  506 + product = fast_create(Product, :enterprise_id => ent.id)
  507 + first = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 20.0, :amount_used => 2)
  508 + second = fast_create(Input, :product_id => product.id, :product_category_id => fast_create(ProductCategory).id, :price_per_unit => 10.0, :amount_used => 1)
  509 +
  510 + assert_equal "50.00", product.formatted_value(:inputs_cost)
  511 + end
  512 +
385 end 513 end
test/unit/production_cost_test.rb 0 → 100644
@@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class ProductionCostTest < ActiveSupport::TestCase
  4 +
  5 + should 'have name' do
  6 + p = ProductionCost.new
  7 + p.valid?
  8 + assert p.errors.invalid?(:name)
  9 +
  10 + p.name = 'Taxes'
  11 + p.valid?
  12 + assert !p.errors.invalid?(:name)
  13 + end
  14 +
  15 + should 'not validates name if it is blank' do
  16 + p = ProductionCost.new
  17 +
  18 + p.valid?
  19 + assert_equal 1, p.errors['name'].to_a.count
  20 + end
  21 +
  22 + should 'not have a too long name' do
  23 + p = ProductionCost.new
  24 +
  25 + p.name = 'a'*40
  26 + p.valid?
  27 + assert p.errors.invalid?(:name)
  28 +
  29 + p.name = 'a'*30
  30 + p.valid?
  31 + assert !p.errors.invalid?(:name)
  32 + end
  33 +
  34 + should 'not have duplicated name on same environment' do
  35 + cost = ProductionCost.create(:name => 'Taxes', :owner => Environment.default)
  36 +
  37 + invalid_cost = ProductionCost.new(:name => 'Taxes', :owner => Environment.default)
  38 + invalid_cost.valid?
  39 +
  40 + assert invalid_cost.errors.invalid?(:name)
  41 + end
  42 +
  43 + should 'not have duplicated name on same enterprise' do
  44 + enterprise = fast_create(Enterprise)
  45 + cost = ProductionCost.create(:name => 'Taxes', :owner => enterprise)
  46 +
  47 + invalid_cost = ProductionCost.new(:name => 'Taxes', :owner => enterprise)
  48 + invalid_cost.valid?
  49 +
  50 + assert invalid_cost.errors.invalid?(:name)
  51 + end
  52 +
  53 + should 'not allow same name on enterprise if already has on environment' do
  54 + enterprise = fast_create(Enterprise)
  55 +
  56 + cost1 = ProductionCost.create(:name => 'Taxes', :owner => Environment.default)
  57 + cost2 = ProductionCost.new(:name => 'Taxes', :owner => enterprise)
  58 +
  59 + cost2.valid?
  60 +
  61 + assert !cost2.errors.invalid?(:name)
  62 + end
  63 +
  64 + should 'allow duplicated name on different enterprises' do
  65 + enterprise = fast_create(Enterprise)
  66 + enterprise2 = fast_create(Enterprise)
  67 +
  68 + cost1 = ProductionCost.create(:name => 'Taxes', :owner => enterprise)
  69 + cost2 = ProductionCost.new(:name => 'Taxes', :owner => enterprise2)
  70 +
  71 + cost2.valid?
  72 +
  73 + assert !cost2.errors.invalid?(:name)
  74 + end
  75 +
  76 + should 'be associated to an environment as owner' do
  77 + p = ProductionCost.new
  78 + p.valid?
  79 + assert p.errors.invalid?(:owner)
  80 +
  81 + p.owner = Environment.default
  82 + p.valid?
  83 + assert !p.errors.invalid?(:owner)
  84 + end
  85 +
  86 + should 'be associated to an enterprise as owner' do
  87 + enterprise = fast_create(Enterprise)
  88 + p = ProductionCost.new
  89 + p.valid?
  90 + assert p.errors.invalid?(:owner)
  91 +
  92 + p.owner = enterprise
  93 + p.valid?
  94 + assert !p.errors.invalid?(:owner)
  95 + end
  96 +
  97 + should 'create a production cost on an enterprise' do
  98 + enterprise = fast_create(Enterprise)
  99 + enterprise.production_costs.create(:name => 'Energy')
  100 + assert_equal ['Energy'], enterprise.production_costs.map(&:name)
  101 + end
  102 +end