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 111 end
112 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 144 def destroy
115 145 @product = @profile.products.find(params[:id])
116 146 if @product.destroy
... ... @@ -167,4 +197,18 @@ class ManageProductsController &lt; ApplicationController
167 197 end
168 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 214 end
... ...
app/controllers/public/catalog_controller.rb
... ... @@ -4,10 +4,11 @@ class CatalogController &lt; PublicController
4 4 before_filter :check_enterprise_and_environment
5 5  
6 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 8 end
9 9  
10 10 protected
  11 +
11 12 def check_enterprise_and_environment
12 13 unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises')
13 14 redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index'
... ...
app/helpers/application_helper.rb
... ... @@ -1254,25 +1254,27 @@ module ApplicationHelper
1254 1254 task.information[:message] % values
1255 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 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 1278 end
1277 1279  
1278 1280 def render_dialog_error_messages(instance_name)
... ...
app/helpers/catalog_helper.rb
1 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 6 end
... ...
app/helpers/display_helper.rb
... ... @@ -8,6 +8,20 @@ module DisplayHelper
8 8 opts
9 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 25 def product_path(product)
12 26 product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
13 27 end
... ...
app/helpers/manage_products_helper.rb
... ... @@ -271,4 +271,23 @@ module ManageProductsHelper
271 271 return input_amount_used if input.unit.blank?
272 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 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 293 end
... ...
app/models/enterprise.rb
... ... @@ -6,6 +6,7 @@ class Enterprise &lt; Organization
6 6  
7 7 has_many :products, :dependent => :destroy, :order => 'name ASC'
8 8 has_many :inputs, :through => :products
  9 + has_many :production_costs, :as => :owner
9 10  
10 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 12 profile.nil? ? _('Homepage') : profile.name
13 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 22 end
28 23  
29 24 def can_display_hits?
... ...
app/models/environment.rb
... ... @@ -174,6 +174,7 @@ class Environment &lt; ActiveRecord::Base
174 174 acts_as_accessible
175 175  
176 176 has_many :units, :order => 'position'
  177 + has_many :production_costs, :as => :owner
177 178  
178 179 def superior_intances
179 180 [self, nil]
... ...
app/models/image.rb
... ... @@ -9,15 +9,15 @@ class Image &lt; ActiveRecord::Base
9 9 has_attachment :content_type => :image,
10 10 :storage => :file_system,
11 11 :path_prefix => 'public/image_uploads',
12   - :resize_to => '320x200>',
  12 + :resize_to => '800x600>',
13 13 :thumbnails => { :big => '150x150',
14 14 :thumb => '100x100',
15 15 :portrait => '64x64',
16 16 :minor => '50x50',
17 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 22 delay_attachment_fu_thumbnails
23 23  
... ...
app/models/input.rb
... ... @@ -45,6 +45,18 @@ class Input &lt; ActiveRecord::Base
45 45 %w[price_per_unit amount_used].each do |field|
46 46 return true unless self.send(field).blank?
47 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 61 end
50 62 end
... ...
app/models/price_detail.rb 0 → 100644
... ... @@ -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 5 has_many :product_qualifiers
6 6 has_many :qualifiers, :through => :product_qualifiers
7 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 11 validates_uniqueness_of :name, :scope => :enterprise_id, :allow_nil => true
10 12 validates_presence_of :product_category_id
... ... @@ -101,12 +103,13 @@ class Product &lt; ActiveRecord::Base
101 103 enterprise.public_profile
102 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 109 end
107 110  
108 111 def price_with_discount
109   - price - discount if discount
  112 + discount ? (price - discount) : price
110 113 end
111 114  
112 115 def price=(value)
... ... @@ -125,6 +128,23 @@ class Product &lt; ActiveRecord::Base
125 128 end
126 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 148 def has_basic_info?
129 149 %w[unit price discount].each do |field|
130 150 return true if !self.send(field).blank?
... ... @@ -153,4 +173,43 @@ class Product &lt; ActiveRecord::Base
153 173 true
154 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 215 end
... ...
app/models/production_cost.rb 0 → 100644
... ... @@ -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 @@
  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 91 </div><!-- end class="comments" -->
92 92  
93 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 2 <% language = FastGettext.locale %>
3 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 11 <%= stylesheet_link_tag noosfero_stylesheets, :cache => 'cache' %>
12 12 <%= stylesheet_link_tag template_stylesheet_path %>
13 13 <%= stylesheet_link_tag icon_theme_stylesheet_path %>
14   - <%= stylesheet_link_tag theme_stylesheet_path %>
15 14 <%= stylesheet_link_tag jquery_ui_theme_stylesheet_path %>
16 15 <% @plugins.enabled_plugins.each do |plugin| %>
17 16 <% if plugin.stylesheet? %>
18 17 <%= stylesheet_tag plugin.class.public_path('style.css'), {} %>
19 18 <% end %>
20 19 <% end %>
  20 + <%= stylesheet_link_tag theme_stylesheet_path %>
21 21  
22 22 <%# Add custom tags/styles/etc via content_for %>
23 23 <%= yield :head %>
... ... @@ -30,7 +30,7 @@
30 30 <%=
31 31 @plugins.map(:head_ending).collect do |content|
32 32 content.respond_to?(:call) ? content.call : content
33   - end.join('\n')
  33 + end.join("\n")
34 34 %>
35 35 </head>
36 36 <body class="<%=
... ... @@ -45,7 +45,7 @@
45 45 <%=
46 46 @plugins.map(:body_beginning).collect do |content|
47 47 content.respond_to?(:call) ? content.call : content
48   - end.join('\n')
  48 + end.join("\n")
49 49 %>
50 50  
51 51 <div id="wrap-1">
... ...
app/views/layouts/application.rhtml
... ... @@ -27,11 +27,8 @@
27 27 <%# Add custom tags/styles/etc via content_for %>
28 28 <%= yield :head %>
29 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 32 </head>
36 33  
37 34 <body class='noosfero category<%= category_color %><%=
... ...
app/views/manage_products/_display_price_details.rhtml 0 → 100644
... ... @@ -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 49 <%= hidden_field_tag "product[qualifiers_list]" %>
50 50 <% end %>
51 51  
  52 + <%= hidden_field_tag 'info-bar-update-url', @product.price_composition_bar_display_url, :class => 'bar-update-url' %>
  53 +
52 54 <% button_bar do %>
53 55 <%= submit_button :save, _('Save') %>
54 56 <%= cancel_edit_product_link(@product, 'info') %>
55 57 <% end %>
56 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 1 <% form_for(@input, :url => {:controller => 'manage_products', :action => 'edit_input', :id => @input},
2 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 7 <table>
4 8 <tr>
5 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 @@
  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 @@
  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 @@
  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 @@
  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 23  
24 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 27 <div class='ui-tabs' id='product-<%= @product.id %>-tabs'>
28 28 <ul>
29 29 <% if !@product.description.blank? || @allowed_user %>
... ... @@ -32,6 +32,9 @@
32 32 <% if !@product.inputs.empty? || @allowed_user %>
33 33 <li class='tab'><a href='#product-inputs'><%= _('Inputs and raw material') %></a></li>
34 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 38 </ul>
36 39 <div id='product-description'>
37 40 <%= render :partial => 'manage_products/display_description' %>
... ... @@ -39,6 +42,12 @@
39 42 <div id='product-inputs'>
40 43 <%= render :partial => 'manage_products/display_inputs' %>
41 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 51 </div>
43 52 <% end %>
44 53  
... ...
app/views/shared/_numbers_only_javascript.rhtml 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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
... ...
db/schema.rb
... ... @@ -323,6 +323,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do
323 323 t.datetime "updated_at"
324 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 334 create_table "product_categorizations", :force => true do |t|
327 335 t.integer "category_id"
328 336 t.integer "product_id"
... ... @@ -342,6 +350,14 @@ ActiveRecord::Schema.define(:version =&gt; 20111004184104) do
342 350 t.datetime "updated_at"
343 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 361 create_table "products", :force => true do |t|
346 362 t.integer "enterprise_id"
347 363 t.integer "product_category_id"
... ...
features/admin_categories.feature
... ... @@ -43,7 +43,7 @@ Feature: manage categories
43 43 When I follow "Show"
44 44 Then I should see "Vegetarian"
45 45 And I should see "Steak"
46   - When I follow "Hide"
  46 + When I follow "Hide" and sleep 1 second
47 47 Then I should not see "Vegetarian"
48 48 And I should not see "Steak"
49 49  
... ...
features/browse_catalogs.feature 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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 35 | redemoinho | bicycle | Bike J | bicycle 10 |
36 36 | redemoinho | bicycle | Bike K | bicycle 11 |
37 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 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 53 Scenario: listing products and services
53 54 Given I am logged in as "joaosilva"
... ...
features/profile_domain.feature
1 1 Feature: domain for profile
  2 +
2 3 As a user
3 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 150 data = item.dup
151 151 owner = Enterprise[data.delete("owner")]
152 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 163 end
156 164 end
157 165  
... ... @@ -214,6 +222,22 @@ Given /^the following certifiers$/ do |table|
214 222 end
215 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 241 Given /^I am logged in as "(.+)"$/ do |username|
218 242 visit('/account/logout')
219 243 visit('/account/login')
... ... @@ -493,3 +517,32 @@ end
493 517 When 'I log off' do
494 518 visit '/account/logout'
495 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 117 category = Category.find_by_name(name)
118 118 selenium.is_visible(string_to_element_locator("option=#{category.id}")).should be_true
119 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 19 When /^I visit "([^\"]*)" and wait$/ do |page_name|
20 20 visit path_to(page_name)
21 21 selenium.wait_for_page_to_load(10000)
  22 +# selenium.wait_for_page
22 23 end
23 24  
24 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 118 session[:cart][:visibility] = true
119 119 render :text => {
120 120 :ok => true,
121   - :message => _('Cart displayed.'),
  121 + :message => _('Basket displayed.'),
122 122 :error => {:code => 0}
123 123 }.to_json
124 124 rescue Exception => exception
... ... @@ -137,7 +137,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
137 137 session[:cart][:visibility] = false
138 138 render :text => {
139 139 :ok => true,
140   - :message => _('Cart Hidden.'),
  140 + :message => _('Basket hidden.'),
141 141 :error => {:code => 0}
142 142 }.to_json
143 143 rescue Exception => exception
... ... @@ -173,7 +173,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
173 173 :ok => false,
174 174 :error => {
175 175 :code => 2,
176   - :message => _("There is no cart.")
  176 + :message => _("There is no basket.")
177 177 }
178 178 }.to_json
179 179 return false
... ... @@ -203,7 +203,7 @@ class ShoppingCartPluginProfileController &lt; ProfileController
203 203 :ok => false,
204 204 :error => {
205 205 :code => 4,
206   - :message => _("The cart doesn't have this product.")
  206 + :message => _("The basket doesn't have this product.")
207 207 }
208 208 }.to_json
209 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 4 class ShoppingCartPlugin < Noosfero::Plugin
5 5  
6 6 def self.plugin_name
7   - "Shopping Cart"
  7 + "Shopping Basket"
8 8 end
9 9  
10 10 def self.plugin_description
11   - _("A shopping cart feature for enterprises")
  11 + _("A shopping basket feature for enterprises")
12 12 end
13 13  
14 14 def add_to_cart_button(item, enterprise = context.profile)
15 15 if enterprise.shopping_cart && item.available
16 16 lambda {
17   - link_to(_('Add to cart'), "add:#{item.name}",
  17 + link_to(_('Add to basket'), "add:#{item.name}",
18 18 :class => 'cart-add-item',
19 19 :onclick => "Cart.addItem('#{enterprise.identifier}', #{item.id}, this); return false"
20 20 )
... ... @@ -41,10 +41,10 @@ class ShoppingCartPlugin &lt; Noosfero::Plugin
41 41 def control_panel_buttons
42 42 buttons = []
43 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 45 end
46 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 48 end
49 49  
50 50 buttons
... ...
plugins/shopping_cart/public/cart.js
... ... @@ -62,7 +62,7 @@ function Cart(config) {
62 62 '<div class="item-price">' +
63 63 '<input size="1" value="'+item.quantity+'" />'+ (item.price ? '&times; '+ item.price : '') +'</div>' +
64 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 66 ).appendTo(li);
67 67 var input = $("input", li)[0];
68 68 input.lastValue = input.value;
... ... @@ -304,6 +304,10 @@ function Cart(config) {
304 304 });
305 305 }
306 306  
  307 + Cart.colorbox_close = function() {
  308 + $.colorbox.close();
  309 + }
  310 +
307 311 $(function(){
308 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 @@
  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 14 height="48"
15 15 id="svg2"
16 16 version="1.1"
17   - inkscape:version="0.47 r22583"
  17 + inkscape:version="0.48.2 r9819"
18 18 sodipodi:docname="purchase-report.svg"
19 19 inkscape:export-filename="/home/aurium/purchase-report.png"
20 20 inkscape:export-xdpi="90"
... ... @@ -352,10 +352,10 @@
352 352 inkscape:document-units="px"
353 353 inkscape:current-layer="layer1"
354 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 359 inkscape:window-maximized="1">
360 360 <inkscape:grid
361 361 type="xygrid"
... ... @@ -419,120 +419,17 @@
419 419 ry="0" />
420 420 <g
421 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 424 <path
529 425 sodipodi:nodetypes="cccccc"
530 426 d="m 29.5,1015.8622 11,0 m -11,-3 11,0 m -11,-3 11,0"
531 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 430 <path
534 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 433 sodipodi:ry="1"
537 434 sodipodi:rx="1"
538 435 sodipodi:cy="5.5"
... ... @@ -548,11 +445,11 @@
548 445 sodipodi:cy="5.5"
549 446 sodipodi:rx="1"
550 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 449 transform="translate(0,1007.3622)" />
553 450 <path
554 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 453 sodipodi:ry="1"
557 454 sodipodi:rx="1"
558 455 sodipodi:cy="5.5"
... ... @@ -564,7 +461,8 @@
564 461 id="path4811"
565 462 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
566 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 466 <path
569 467 sodipodi:type="arc"
570 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 471 sodipodi:cy="5.5"
574 472 sodipodi:rx="1"
575 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 475 transform="translate(0,1018.3622)" />
578 476 <path
579 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 479 sodipodi:ry="1"
582 480 sodipodi:rx="1"
583 481 sodipodi:cy="5.5"
... ... @@ -593,15 +491,16 @@
593 491 sodipodi:cy="5.5"
594 492 sodipodi:rx="1"
595 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 495 transform="translate(0,1024.3622)" />
598 496 <path
599 497 id="path4827"
600 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 501 <path
603 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 504 sodipodi:ry="1"
606 505 sodipodi:rx="1"
607 506 sodipodi:cy="5.5"
... ... @@ -617,11 +516,11 @@
617 516 sodipodi:cy="5.5"
618 517 sodipodi:rx="1"
619 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 520 transform="translate(0,1036.3622)" />
622 521 <path
623 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 524 sodipodi:ry="1"
626 525 sodipodi:rx="1"
627 526 sodipodi:cy="5.5"
... ... @@ -629,6 +528,201 @@
629 528 id="path4833"
630 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 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 726 </g>
633 727 <g
634 728 id="g4835"
... ...
plugins/shopping_cart/public/style.css
1 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 15 .product-item .cart-add-item {
... ... @@ -13,16 +19,15 @@
13 19 .cart {
14 20 position: fixed;
15 21 right: 20px;
16   - top: 0px;
  22 + bottom: 0px;
17 23 width: 200px;
18 24 z-index: 1000;
19 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 29 .cart h3 {
25   - color: #888;
  30 + color: #000;
26 31 margin: 0px 0px 0px 5px;
27 32 }
28 33  
... ... @@ -108,7 +113,7 @@
108 113 padding: 0px 5px;
109 114 }
110 115 .cart-toggle:hover {
111   - color: #555;
  116 + color: #fff;
112 117 }
113 118  
114 119 #cart-request-box {
... ... @@ -120,6 +125,15 @@
120 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 137 #cart-form-main {
124 138 border: 2px solid #FFF;
125 139 padding: 0px 10px;
... ... @@ -171,8 +185,8 @@ label.error {
171 185  
172 186 .controller-profile_editor a.control-panel-shopping-cart-purchase-report {background-image: url(images/control-panel/purchase-report.png)}
173 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 191 .action-shopping_cart_plugin_myprofile-reports td.order-info {
178 192 padding: 0px;
... ...
plugins/shopping_cart/views/cart.html.erb
1 1 <div id="cart1" class="cart" style="display:none"
2 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 4 <div class="cart-inner">
5 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 8 <ul class="cart-items"></ul>
9 9 <div class="cart-total"><%=_('Total:')%> <b></b></div>
10 10 <a href="cart:buy" class="cart-buy"><%=_('Shopping checkout')%></a>
11 11 </div>
12 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 15 </a>
16 16 </div>
17 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 3 <% form_for(:profile_attr, profile, :url => {:action => 'edit'}, :html => {:method => 'post'}) do |f| %>
4 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 17 </div>
18 18 <% end %>
19 19 <%= items_table(session[:cart][:items], profile) %>
  20 + <%= link_to '', '#', :onclick => "Cart.colorbox_close(this);", :class => 'cart-box-close icon-cancel' %>
20 21 </div>
21 22  
22 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 255 if (!hide) {
256 256 var direction = 'down';
257 257 if (submenu.hasClass('up')) direction = 'up';
258   - submenu.show('slide', { 'direction' : direction }, 'slow');
  258 + jQuery(submenu).fadeIn();
259 259 }
260 260 }
261 261 return false;
... ... @@ -283,18 +283,18 @@ function toggleSubmenu(trigger, title, link_list) {
283 283 content.append(list);
284 284 submenu.append(header).append(content).append(footer);
285 285 jQuery(trigger).before(submenu);
286   - submenu.show('slide', { 'direction' : direction }, 'slow');
  286 + jQuery(submenu).fadeIn();
287 287 }
288 288  
289 289 function toggleMenu(trigger) {
290 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 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 298 jQuery('#chat-online-users-content').hide();
299 299 }
300 300  
... ... @@ -694,3 +694,14 @@ jQuery(function() {
694 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 @@
  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 @@
  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 1075 text-align: left;
1076 1076 background: transparent url(/images/black-alpha-pixel.png);
1077 1077 border-bottom: 1px solid #333;
  1078 + text-decoration: none;
1078 1079 }
1079 1080  
1080 1081 .zoomable-image:hover .zoomify-image {
1081 1082 display: block;
  1083 + text-decoration: none;
1082 1084 }
1083 1085  
1084 1086 #article .zoomify-image {
... ... @@ -1093,6 +1095,7 @@ code input {
1093 1095 border-bottom: 1px solid #eee;
1094 1096 display: block;
1095 1097 background: transparent url(/images/zoom.png) left center no-repeat;
  1098 + color: #fff;
1096 1099 }
1097 1100  
1098 1101 #article pre {
... ... @@ -2962,71 +2965,228 @@ div#activation_enterprise div {
2962 2965  
2963 2966 /* ==> public/stylesheets/controller_catalog.css <== */
2964 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 2972 margin: 0px;
2970 2973 padding: 0px;
2971 2974 }
2972   -
2973   -#product_list ul {
  2975 +#product-list ul {
2974 2976 margin: 0px;
2975 2977 padding: 0px;
2976 2978 }
2977   -
2978   -#content #product_list li {
  2979 +#product-list li {
2979 2980 margin: 0px;
2980 2981 padding: 0px;
2981 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 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 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 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 3192 /* * * Show Product * * * * * * * * * * * * */
... ... @@ -3223,6 +3383,86 @@ div#activation_enterprise div {
3223 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 3466 /* ==> public/stylesheets/controller_cms.css <== */
3227 3467  
3228 3468  
... ...
public/stylesheets/colorbox/colorbox.css
... ... @@ -18,29 +18,29 @@
18 18 Change the following styles to modify the appearance of ColorBox. They are
19 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 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 31 #cboxContent{background:#fff; overflow:hidden;}
32 32 #cboxError{padding:50px; border:1px solid #ccc;}
33 33 #cboxLoadedContent{margin-bottom:28px;}
34 34 #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;}
35 35 #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;}
36 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 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 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 44 #cboxClose.hover{background-position:-25px -25px;}
45 45  
46 46 /*
... ... @@ -61,14 +61,14 @@
61 61 /*
62 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 73 .cboxIE6 #cboxTopLeft,
74 74 .cboxIE6 #cboxTopCenter,
... ...
script/sample-profiles
... ... @@ -98,7 +98,7 @@ end
98 98 done
99 99  
100 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 102 user.activate
103 103 print '.'
104 104 end
... ...
test/factories.rb
... ... @@ -449,4 +449,12 @@ module Noosfero::Factory
449 449 { :singular => 'Litre', :plural => 'Litres', :environment_id => 1 }
450 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 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 46  
47 47 assert_equal 12, @enterprise.products.count
48 48 get :index, :profile => @enterprise.identifier
49   - assert_equal 10, assigns(:products).count
  49 + assert_equal 9, assigns(:products).count
50 50 assert_tag :a, :attributes => {:class => 'next_page'}
51 51 end
52 52  
... ... @@ -63,21 +63,13 @@ class CatalogControllerTest &lt; ActionController::TestCase
63 63 should 'not show product price when listing products if not informed' do
64 64 prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
65 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 67 end
68 68  
69 69 should 'show product price when listing products if informed' do
70 70 prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => @product_category)
71 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 73 end
82 74  
83 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 468 assert_response 403
469 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 514 end
... ...
test/unit/application_helper_test.rb
... ... @@ -625,13 +625,18 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase
625 625 env = Environment.default
626 626 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(false)
627 627 stubs(:environment).returns(env)
628   - assert_nil add_zoom_to_images
  628 + assert_nil add_zoom_to_article_images
629 629 end
630 630  
631 631 should 'return code when :show_zoom_button_on_article_images is enabled in environment' do
632 632 env = Environment.default
633 633 env.stubs(:enabled?).with(:show_zoom_button_on_article_images).returns(true)
634 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 640 assert_not_nil add_zoom_to_images
636 641 end
637 642  
... ...
test/unit/enterprise_homepage_test.rb
... ... @@ -16,43 +16,9 @@ class EnterpriseHomepageTest &lt; ActiveSupport::TestCase
16 16 assert_kind_of String, EnterpriseHomepage.description
17 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 22 end
57 23  
58 24 should 'can display hits' do
... ...
test/unit/enterprise_test.rb
... ... @@ -446,4 +446,8 @@ class EnterpriseTest &lt; ActiveSupport::TestCase
446 446 assert_equal false, enterprise.receives_scrap_notification?
447 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 453 end
... ...
test/unit/environment_test.rb
... ... @@ -1200,4 +1200,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
1200 1200 assert_not_includes environment.enabled_plugins, plugin
1201 1201 end
1202 1202  
  1203 + should 'have production costs' do
  1204 + assert_respond_to Environment.default, :production_costs
  1205 + end
1203 1206 end
... ...
test/unit/highlights_block_test.rb
... ... @@ -77,6 +77,7 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase
77 77 file = mock()
78 78 UploadedFile.expects(:find).with(1).returns(file)
79 79 file.expects(:public_filename).returns('address')
  80 + UploadedFile.expects(:find).with(0).returns(nil)
80 81 block = HighlightsBlock.new(:images => [{:image_id => 1, :address => '/address', :position => 1, :title => 'address'}, {:image_id => '', :address => 'some', :position => '2', :title => 'Some'}])
81 82 block.save!
82 83 block.reload
... ... @@ -115,6 +116,12 @@ class HighlightsBlockTest &lt; ActiveSupport::TestCase
115 116 f3 = mock()
116 117 f3.expects(:public_filename).returns('address')
117 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 125 block = HighlightsBlock.new
119 126 i1 = {:image_id => 1, :address => '/address', :position => 3, :title => 'address'}
120 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 162 assert_kind_of Unit, input.build_unit
163 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 180 end
... ...
test/unit/price_detail_test.rb 0 → 100644
... ... @@ -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 382 assert_includes Product.find_by_contents('thing'), p2
383 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 513 end
... ...
test/unit/production_cost_test.rb 0 → 100644
... ... @@ -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
... ...