Commit c99dd6dac26cff00c91a6a1b2f840b2788949628

Authored by Rodrigo Souto
1 parent 6a38c0b2

Add new consumption plugins

This patch adds 3 new consumption plugins:
  * orders_cycle
  * suppliers
  * volunteers

These plugins are not properly tested yet but they are necessary for the
orders and delivery plugins (that are properly tested) to work corretctly.
Showing 237 changed files with 8301 additions and 0 deletions   Show diff stats
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_admin_item_controller.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class OrdersCyclePluginAdminItemController < OrdersPluginAdminItemController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + helper OrdersCyclePlugin::TranslationHelper
  10 + helper OrdersCyclePlugin::DisplayHelper
  11 +
  12 + protected
  13 +
  14 + extend HMVC::ClassMethods
  15 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  16 +
  17 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_cycle_controller.rb 0 → 100644
... ... @@ -0,0 +1,156 @@
  1 +class OrdersCyclePluginCycleController < OrdersPluginAdminController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + protect 'edit_profile', :profile
  10 + before_filter :set_admin
  11 +
  12 + helper OrdersCyclePlugin::TranslationHelper
  13 + helper OrdersCyclePlugin::DisplayHelper
  14 +
  15 + def index
  16 + @closed_cycles = search_scope(profile.orders_cycles.closing).all
  17 + if request.xhr?
  18 + render partial: 'results'
  19 + else
  20 + @open_cycles = profile.orders_cycles.opened
  21 + end
  22 + end
  23 +
  24 + def new
  25 + if request.put?
  26 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  27 +
  28 + params[:cycle][:status] = 'orders' if @open = params[:open] == '1'
  29 + @success = @cycle.update_attributes params[:cycle]
  30 +
  31 + if @success
  32 + session[:notice] = t('controllers.myprofile.cycle_controller.cycle_created')
  33 + if params[:sendmail]
  34 + OrdersCyclePlugin::Mailer.delay(run_at: @cycle.start).open_cycle(
  35 + @cycle.profile, @cycle ,t('controllers.myprofile.cycle_controller.new_open_cycle')+": "+@cycle.name, @cycle.opening_message)
  36 + end
  37 + else
  38 + render action: :edit
  39 + end
  40 + else
  41 + count = OrdersCyclePlugin::Cycle.count conditions: {profile_id: profile}
  42 + @cycle = OrdersCyclePlugin::Cycle.create! profile: profile, status: 'new',
  43 + name: t('controllers.myprofile.cycle_controller.cycle_n_n') % {n: count+1}
  44 + end
  45 + end
  46 +
  47 + def edit
  48 + # editing an order
  49 + return super if params[:actor_name]
  50 +
  51 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  52 + @products = products
  53 +
  54 + if request.xhr?
  55 + if params[:commit]
  56 + params[:cycle][:status] = 'orders' if @open = params[:open] == '1'
  57 + @success = @cycle.update_attributes params[:cycle]
  58 +
  59 + if params[:sendmail]
  60 + OrdersCyclePlugin::Mailer.delay(run_at: @cycle.start).open_cycle(@cycle.profile,
  61 + @cycle, t('controllers.myprofile.cycle_controller.new_open_cycle')+": "+@cycle.name, @cycle.opening_message)
  62 + end
  63 + end
  64 + end
  65 + end
  66 +
  67 + def products_load
  68 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  69 + @products = products
  70 +
  71 + if @cycle.add_products_job
  72 + render nothing: true
  73 + else
  74 + render partial: 'product_lines'
  75 + end
  76 + end
  77 +
  78 + def destroy
  79 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  80 + @cycle.destroy
  81 + redirect_to action: :index
  82 + end
  83 +
  84 + def step
  85 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  86 + @cycle.step
  87 + @cycle.save!
  88 + redirect_to action: :edit, id: @cycle.id
  89 + end
  90 +
  91 + def step_back
  92 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  93 + @cycle.step_back
  94 + @cycle.save!
  95 + redirect_to action: :edit, id: @cycle.id
  96 + end
  97 +
  98 + def add_missing_products
  99 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  100 + @cycle.add_products
  101 + render partial: 'suppliers_plugin/shared/pagereload'
  102 + end
  103 +
  104 + def report_products
  105 + return super if params[:ids].present?
  106 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  107 + report_file = report_products_by_supplier @cycle.supplier_products_by_suppliers(@cycle.sales.ordered)
  108 +
  109 + send_file report_file, type: 'application/xlsx',
  110 + disposition: 'attachment',
  111 + filename: t('controllers.myprofile.admin.products_report') % {
  112 + date: DateTime.now.strftime("%Y-%m-%d"), profile_identifier: profile.identifier, name: @cycle.name_with_code}
  113 + end
  114 +
  115 + def report_orders
  116 + return super if params[:ids].present?
  117 + @cycle = OrdersCyclePlugin::Cycle.find params[:id]
  118 + report_file = report_orders_by_consumer @cycle.sales.ordered
  119 +
  120 + send_file report_file, type: 'application/xlsx',
  121 + disposition: 'attachment',
  122 + filename: t('controllers.myprofile.admin.orders_report') % {date: DateTime.now.strftime("%Y-%m-%d"), profile_identifier: profile.identifier, name: @cycle.name_with_code}
  123 + end
  124 +
  125 + def filter
  126 + @cycle = profile.orders_cycles.find params[:owner_id]
  127 + @scope = @cycle
  128 +
  129 + params[:code].gsub!(/^#{@cycle.code}\./, '') if params[:code].present?
  130 + super
  131 + end
  132 +
  133 + protected
  134 +
  135 + attr_accessor :cycle
  136 +
  137 + extend HMVC::ClassMethods
  138 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  139 +
  140 + def search_scope scope
  141 + params[:date] ||= {}
  142 + scope = scope.by_year params[:date][:year] if params[:date][:year].present?
  143 + scope = scope.by_month params[:date][:month] if params[:date][:month].present?
  144 + scope = scope.by_status params[:status] if params[:status].present?
  145 + scope
  146 + end
  147 +
  148 + def set_admin
  149 + @admin = true
  150 + end
  151 +
  152 + def products
  153 + @cycle.products.unarchived.paginate per_page: 15, page: params["page"]
  154 + end
  155 +
  156 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_delivery_option_controller.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class OrdersCyclePluginDeliveryOptionController < DeliveryPlugin::AdminOptionsController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + helper OrdersCyclePlugin::TranslationHelper
  10 + helper OrdersCyclePlugin::DisplayHelper
  11 +
  12 + protected
  13 +
  14 + extend HMVC::ClassMethods
  15 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  16 +
  17 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_item_controller.rb 0 → 100644
... ... @@ -0,0 +1,67 @@
  1 +class OrdersCyclePluginItemController < OrdersPluginItemController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + helper OrdersCyclePlugin::TranslationHelper
  10 + helper OrdersCyclePlugin::DisplayHelper
  11 +
  12 + def new
  13 + @offered_product = Product.find params[:offered_product_id]
  14 + @consumer = user
  15 + return render_not_found unless @offered_product
  16 + raise 'Please login to place an order' if @consumer.blank?
  17 +
  18 + if params[:order_id] == 'new'
  19 + @cycle = @offered_product.cycle
  20 + raise 'Cycle closed for orders' unless @cycle.may_order? @consumer
  21 + @order = OrdersCyclePlugin::Sale.create! cycle: @cycle, profile: profile, consumer: @consumer
  22 + else
  23 + @order = OrdersCyclePlugin::Sale.find params[:order_id]
  24 + @cycle = @order.cycle
  25 + raise 'Order confirmed or cycle is closed for orders' unless @order.open?
  26 + raise 'You are not the owner of this order' unless @order.may_edit? @consumer, @admin
  27 + end
  28 +
  29 + @item = OrdersCyclePlugin::Item.where(order_id: @order.id, product_id: @offered_product.id).first
  30 + @item ||= OrdersCyclePlugin::Item.new
  31 + @item.sale = @order
  32 + @item.product = @offered_product
  33 + if set_quantity_consumer_ordered(params[:quantity_consumer_ordered] || 1)
  34 + @item.update_attributes! quantity_consumer_ordered: @quantity_consumer_ordered
  35 + end
  36 + end
  37 +
  38 + def edit
  39 + return redirect_to params.merge(action: :admin_edit) if @admin_edit
  40 + super
  41 + @offered_product = @item.product
  42 + @cycle = @order.cycle
  43 + end
  44 +
  45 + def admin_edit
  46 + @item = OrdersCyclePlugin::Item.find params[:id]
  47 + @order = @item.order
  48 + @cycle = @order.cycle
  49 +
  50 + #update on association for total
  51 + @order.items.each{ |i| i.attributes = params[:item] if i.id == @item.id }
  52 +
  53 + @item.update_attributes = params[:item]
  54 + end
  55 +
  56 + def destroy
  57 + super
  58 + @offered_product = @product
  59 + @cycle = @order.cycle
  60 + end
  61 +
  62 + protected
  63 +
  64 + extend HMVC::ClassMethods
  65 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  66 +
  67 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_message_controller.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class OrdersCyclePluginMessageController < OrdersPluginMessageController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + helper OrdersCyclePlugin::TranslationHelper
  10 + helper OrdersPlugin::FieldHelper
  11 +
  12 + protected
  13 +
  14 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_product_controller.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +class OrdersCyclePluginProductController < SuppliersPlugin::ProductController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + helper OrdersCyclePlugin::TranslationHelper
  10 + helper OrdersCyclePlugin::DisplayHelper
  11 +
  12 + def edit
  13 + super
  14 + @units = environment.units.all
  15 + end
  16 +
  17 + def remove_from_order
  18 + @offered_product = OrdersCyclePlugin::OfferedProduct.find params[:id]
  19 + @order = OrdersCyclePlugin::Sale.find params[:order_id]
  20 + raise 'Order confirmed or cycle is closed for orders' unless @order.open?
  21 + @item = @order.items.find_by_product_id @offered_product.id
  22 + @item.destroy rescue render nothing: true
  23 + end
  24 +
  25 + def cycle_edit
  26 + @product = OrdersCyclePlugin::OfferedProduct.find params[:id]
  27 + if request.xhr?
  28 + @product.update_attributes! params[:product]
  29 + respond_to do |format|
  30 + format.js
  31 + end
  32 + end
  33 + end
  34 +
  35 + def cycle_destroy
  36 + @product = OrdersCyclePlugin::OfferedProduct.find params[:id]
  37 + @product.destroy
  38 + flash[:notice] = t('controllers.myprofile.product_controller.product_removed_from_')
  39 + end
  40 +
  41 + protected
  42 +
  43 + extend HMVC::ClassMethods
  44 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  45 +
  46 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_supplier_controller.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +class OrdersCyclePluginSupplierController < SuppliersPluginMyprofileController
  2 +
  3 + no_design_blocks
  4 +
  5 + # FIXME: remove me when styles move from consumers_coop plugin
  6 + include ConsumersCoopPlugin::ControllerHelper
  7 + include OrdersCyclePlugin::TranslationHelper
  8 +
  9 + protect 'edit_profile', :profile
  10 +
  11 + helper OrdersCyclePlugin::TranslationHelper
  12 + helper OrdersCyclePlugin::DisplayHelper
  13 +
  14 + def margin_change
  15 + super
  16 + profile.orders_cycles_products_default_margins if params[:apply_to_open_cycles]
  17 + end
  18 +
  19 + protected
  20 +
  21 + extend HMVC::ClassMethods
  22 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  23 +
  24 +end
... ...
plugins/orders_cycle/controllers/myprofile/orders_cycle_plugin_volunteers_controller.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class OrdersCyclePluginVolunteersController < VolunteersPluginMyprofileController
  2 +
  3 + no_design_blocks
  4 + include OrdersCyclePlugin::TranslationHelper
  5 +
  6 + helper OrdersCyclePlugin::TranslationHelper
  7 +
  8 + protected
  9 +
  10 + extend HMVC::ClassMethods
  11 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  12 +
  13 +end
... ...
plugins/orders_cycle/controllers/profile/orders_cycle_plugin_order_controller.rb 0 → 100644
... ... @@ -0,0 +1,183 @@
  1 +class OrdersCyclePluginOrderController < OrdersPluginOrderController
  2 +
  3 + # FIXME: remove me when styles move from consumers_coop plugin
  4 + include ConsumersCoopPlugin::ControllerHelper
  5 + include OrdersCyclePlugin::TranslationHelper
  6 +
  7 + no_design_blocks
  8 + before_filter :login_required, except: [:index]
  9 +
  10 + helper OrdersCyclePlugin::TranslationHelper
  11 + helper OrdersCyclePlugin::DisplayHelper
  12 +
  13 + def index
  14 + @current_year = DateTime.now.year.to_s
  15 + @year = (params[:year] || @current_year).to_s
  16 +
  17 + @years_with_cycles = profile.orders_cycles_without_order.years.collect &:year
  18 + @years_with_cycles.unshift @current_year unless @years_with_cycles.include? @current_year
  19 +
  20 + @cycles = profile.orders_cycles.by_year @year
  21 + @consumer = user
  22 + end
  23 +
  24 + def new
  25 + if user.blank?
  26 + session[:notice] = t('orders_plugin.controllers.profile.consumer.please_login_first')
  27 + redirect_to action: :index
  28 + return
  29 + end
  30 +
  31 + if not profile.members.include? user
  32 + render_access_denied
  33 + else
  34 + @consumer = user
  35 + @cycle = profile.orders_cycles.find params[:cycle_id]
  36 + @order = OrdersCyclePlugin::Sale.new
  37 + @order.profile = profile
  38 + @order.consumer = @consumer
  39 + @order.cycle = @cycle
  40 + @order.save!
  41 + redirect_to params.merge(action: :edit, id: @order.id)
  42 + end
  43 + end
  44 +
  45 + def repeat
  46 + @consumer = user
  47 + @order = profile.orders_cycles_sales.where(id: params[:order_id], consumer_id: @consumer.id).first
  48 + @cycle = profile.orders_cycles.find params[:cycle_id]
  49 + if @order
  50 + @order.repeat_cycle = @cycle
  51 + @repeat_order = OrdersCyclePlugin::Sale.new profile: profile, consumer: @consumer, cycle: @cycle
  52 + @order.items.each do |item|
  53 + next unless item.repeat_product and item.repeat_product.available
  54 + @repeat_order.items.build sale: @repeat_order, product: item.repeat_product, quantity_consumer_ordered: item.quantity_consumer_ordered
  55 + end
  56 + @repeat_order.supplier_delivery = @order.supplier_delivery
  57 + @repeat_order.save!
  58 + redirect_to params.merge(action: :edit, id: @repeat_order.id)
  59 + else
  60 + @orders = @cycle.consumer_previous_orders(@consumer).last(5).reverse
  61 + @orders.each{ |o| o.enable_product_diff }
  62 + @orders.each{ |o| o.repeat_cycle = @cycle }
  63 + render template: 'orders_plugin/repeat'
  64 + end
  65 + end
  66 +
  67 + def edit
  68 + return show_more if params[:page].present?
  69 +
  70 + if request.xhr? and params[:order].present?
  71 + status = params[:order][:status]
  72 + if status == 'ordered'
  73 + if @order.items.size > 0
  74 + @order.to_yaml # most strange workaround to avoid a crash in the next line
  75 + @order.update_attributes! params[:order]
  76 + session[:notice] = t('orders_plugin.controllers.profile.consumer.order_confirmed')
  77 + else
  78 + session[:notice] = t('orders_plugin.controllers.profile.consumer.can_not_confirm_your_')
  79 + end
  80 + end
  81 + return
  82 + end
  83 +
  84 + if cycle_id = params[:cycle_id]
  85 + @cycle = profile.orders_cycles.where(id: cycle_id).first
  86 + return render_not_found unless @cycle
  87 + @consumer = user
  88 +
  89 + # load the first order
  90 + unless @order
  91 + @consumer_orders = @cycle.sales.for_consumer @consumer
  92 + if @consumer_orders.size == 1
  93 + @order = @consumer_orders.first
  94 + redirect_to action: :edit, id: @order.id
  95 + elsif @consumer_orders.size > 1
  96 + # get the first open
  97 + @order = @consumer_orders.find{ |o| o.open? }
  98 + redirect_to action: :edit, id: @order.id if @order
  99 + end
  100 + end
  101 + else
  102 + return render_not_found unless @order
  103 + # an order was loaded on load_order
  104 +
  105 + @cycle = @order.cycle
  106 +
  107 + @consumer = @order.consumer
  108 + @admin_edit = (user and user.in?(profile.admins) and user != @consumer)
  109 + return render_access_denied unless @user_is_admin or @admin_edit or user == @consumer
  110 +
  111 + @consumer_orders = @cycle.sales.for_consumer @consumer
  112 + end
  113 +
  114 + load_products_for_order
  115 + @product_categories = @cycle.product_categories
  116 + @consumer_orders = @cycle.sales.for_consumer @consumer
  117 + end
  118 +
  119 + def reopen
  120 + @order.update_attributes! status: 'draft'
  121 + render 'edit'
  122 + end
  123 +
  124 + def cancel
  125 + @order.update_attributes! status: 'cancelled'
  126 + session[:notice] = t('orders_plugin.controllers.profile.consumer.order_cancelled')
  127 + render 'edit'
  128 + end
  129 +
  130 + def remove
  131 + super
  132 + redirect_to action: :index, cycle_id: @order.cycle.id
  133 + end
  134 +
  135 + def admin_new
  136 + return redirect_to action: :index unless profile.has_admin? user
  137 +
  138 + @consumer = user
  139 + @cycle = profile.orders_cycles.find params[:cycle_id]
  140 + @order = OrdersCyclePlugin::Sale.create! cycle: @cycle, consumer: @consumer
  141 + redirect_to action: :edit, id: @order.id, profile: profile.identifier
  142 + end
  143 +
  144 + def filter
  145 + if id = params[:id]
  146 + @order = OrdersCyclePlugin::Sale.find id rescue nil
  147 + @cycle = @order.cycle
  148 + else
  149 + @cycle = profile.orders_cycles.find params[:cycle_id]
  150 + @order = OrdersCyclePlugin::Sale.find params[:order_id] rescue nil
  151 + end
  152 + load_products_for_order
  153 +
  154 + render partial: 'filter', locals: {
  155 + order: @order, cycle: @cycle,
  156 + products_for_order: @products,
  157 + }
  158 + end
  159 +
  160 + def show_more
  161 + filter
  162 + end
  163 +
  164 + def supplier_balloon
  165 + @supplier = SuppliersPlugin::Supplier.find params[:id]
  166 + end
  167 + def product_balloon
  168 + @product = OrdersCyclePlugin::OfferedProduct.find params[:id]
  169 + end
  170 +
  171 + protected
  172 +
  173 + def load_products_for_order
  174 + scope = @cycle.products_for_order
  175 + page, per_page = params[:page].to_i, 20
  176 + page = 1 if page < 1
  177 + @products = OrdersCyclePlugin::OfferedProduct.search_scope(scope, params).paginate page: page, per_page: per_page
  178 + end
  179 +
  180 + extend HMVC::ClassMethods
  181 + hmvc OrdersCyclePlugin, orders_context: OrdersCyclePlugin
  182 +
  183 +end
... ...
plugins/orders_cycle/db/migrate/20130909175738_create_orders_cycle_plugin_tables.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +class CreateOrdersCyclePluginTables < ActiveRecord::Migration
  2 +
  3 + def change
  4 + # check if distribution plugin already moved the table
  5 + return if ActiveRecord::Base.connection.table_exists? :orders_cycle_plugin_cycles
  6 +
  7 + create_table :orders_cycle_plugin_cycle_orders do |t|
  8 + t.integer :cycle_id
  9 + t.integer :order_id
  10 + t.datetime :created_at
  11 + t.datetime :updated_at
  12 + end
  13 +
  14 + create_table :orders_cycle_plugin_cycle_products do |t|
  15 + t.integer :cycle_id
  16 + t.integer :product_id
  17 + end
  18 +
  19 + create_table :orders_cycle_plugin_cycles do |t|
  20 + t.integer :profile_id
  21 + t.integer :code
  22 + t.string :name
  23 + t.text :description
  24 + t.datetime :start
  25 + t.datetime :finish
  26 + t.string :status
  27 + t.text :opening_message
  28 + t.datetime :delivery_start
  29 + t.datetime :delivery_finish
  30 + t.decimal :margin_percentage
  31 + t.datetime :created_at
  32 + t.datetime :updated_at
  33 + end
  34 +
  35 + add_index :orders_cycle_plugin_cycles, [:profile_id]
  36 + add_index :orders_cycle_plugin_cycles, [:status]
  37 + end
  38 +
  39 +end
... ...
plugins/orders_cycle/db/migrate/20131001162741_orders_cycle_plugin_index_filtered_fields.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +class OrdersCyclePluginIndexFilteredFields < ActiveRecord::Migration
  2 +
  3 + def up
  4 + add_index :orders_cycle_plugin_cycle_orders, [:cycle_id]
  5 + add_index :orders_cycle_plugin_cycle_orders, [:order_id]
  6 + add_index :orders_cycle_plugin_cycle_orders, [:cycle_id, :order_id]
  7 +
  8 + add_index :orders_cycle_plugin_cycle_products, [:cycle_id], name: :orders_cycle_plugin_index_dqaEe7Hf
  9 + add_index :orders_cycle_plugin_cycle_products, [:product_id], name: :orders_cycle_plugin_index_f5DmQ6w5Y
  10 + add_index :orders_cycle_plugin_cycle_products, [:cycle_id, :product_id], name: :orders_cycle_plugin_index_PhBVTRFB
  11 +
  12 + add_index :orders_cycle_plugin_cycles, [:code]
  13 + end
  14 +
  15 + def down
  16 + say "this migration can't be reverted"
  17 + end
  18 +
  19 +end
... ...
plugins/orders_cycle/db/migrate/20140406155248_refactor_orders_cycle_plugin_cycle_order.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class RefactorOrdersCyclePluginCycleOrder < ActiveRecord::Migration
  2 +
  3 + def up
  4 + rename_column :orders_cycle_plugin_cycle_orders, :order_id, :sale_id
  5 + add_column :orders_cycle_plugin_cycle_orders, :purchase_id, :integer
  6 +
  7 + add_index :orders_cycle_plugin_cycle_orders, :sale_id
  8 + add_index :orders_cycle_plugin_cycle_orders, :purchase_id
  9 + add_index :orders_cycle_plugin_cycle_orders, [:cycle_id, :sale_id]
  10 + add_index :orders_cycle_plugin_cycle_orders, [:cycle_id, :purchase_id], name: :index_orders_cycle_plugin_cycle_orders_cycle_purchase
  11 + end
  12 +
  13 + def down
  14 + say "this migration can't be reverted"
  15 + end
  16 +
  17 +end
... ...
plugins/orders_cycle/db/migrate/20140911210514_add_serialized_data_to_orders_cycle_plugin_cycle.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class AddSerializedDataToOrdersCyclePluginCycle < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :orders_cycle_plugin_cycles, :data, :text, :default => {}.to_yaml
  4 + execute "update orders_cycle_plugin_cycles set data = '#{{}.to_yaml}'"
  5 + end
  6 +
  7 + def self.down
  8 + remove_column :orders_cycle_plugin_cycles, :data
  9 + end
  10 +end
... ...
plugins/orders_cycle/db/migrate/20150119173244_move_items_to_orders_cycle_plugin_item.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class MoveItemsToOrdersCyclePluginItem < ActiveRecord::Migration
  2 + def up
  3 + OrdersCyclePlugin::Cycle.find_each batch_size: 5 do |cycle|
  4 + cycle.items_selled.update_all type: 'OrdersCyclePlugin::Item'
  5 + cycle.items_purchased.update_all type: 'OrdersCyclePlugin::Item'
  6 + end
  7 + end
  8 +
  9 + def down
  10 + say "this migration can't be reverted"
  11 + end
  12 +end
... ...
plugins/orders_cycle/db/migrate/20150506220607_fill_default_delivery_method_to_orders_cycle_sales.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class FillDefaultDeliveryMethodToOrdersCycleSales < ActiveRecord::Migration
  2 + def up
  3 + OrdersCyclePlugin::Sale.find_each batch_size: 50 do |sale|
  4 + next unless sale.cycle.present?
  5 + sale.update_column :supplier_delivery_id, sale.supplier_delivery_id
  6 + end
  7 + end
  8 +
  9 + def down
  10 + say "this migration can't be reverted"
  11 + end
  12 +end
... ...
plugins/orders_cycle/lib/ext/delivery_plugin/option.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require_dependency 'delivery_plugin/option'
  2 +
  3 +class DeliveryPlugin::Option
  4 +
  5 + belongs_to :cycle, class_name: 'OrdersCyclePlugin::Cycle',
  6 + foreign_key: :owner_id, conditions: ["delivery_plugin_options.owner_type = 'OrdersCyclePlugin::Cycle'"]
  7 +
  8 +end
... ...
plugins/orders_cycle/lib/ext/product.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +require_dependency 'product'
  2 +
  3 +# based on orders/lib/ext/product.rb
  4 +class Product
  5 +
  6 + has_many :orders_cycles_items, class_name: 'OrdersCyclePlugin::Item', foreign_key: :product_id
  7 +
  8 + has_many :orders_cycles_orders, through: :orders_cycles_items, source: :order
  9 + has_many :orders_cycles_sales, through: :orders_cycles_items, source: :sale
  10 + has_many :orders_cycles_purchases, through: :orders_cycles_items, source: :purchase
  11 +
  12 +end
... ...
plugins/orders_cycle/lib/ext/profile.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +require_dependency 'profile'
  2 +
  3 +class Profile
  4 +
  5 + has_many :orders_cycles, class_name: 'OrdersCyclePlugin::Cycle', dependent: :destroy, order: 'created_at DESC',
  6 + conditions: ["orders_cycle_plugin_cycles.status <> 'new'"]
  7 + has_many :orders_cycles_without_order, class_name: 'OrdersCyclePlugin::Cycle',
  8 + conditions: ["orders_cycle_plugin_cycles.status <> 'new'"]
  9 +
  10 + has_many :orders_cycles_sales, through: :orders_cycles, source: :sales
  11 + has_many :orders_cycles_purchases, through: :orders_cycles, source: :purchases
  12 +
  13 + has_many :offered_products, class_name: 'OrdersCyclePlugin::OfferedProduct', order: 'products.name ASC'
  14 +
  15 + def orders_cycles_closed_date_range
  16 + list = self.orders_cycles.closing.all order: 'start ASC'
  17 + return DateTime.now..DateTime.now if list.blank?
  18 + list.first.start.to_date..list.last.finish.to_date
  19 + end
  20 +
  21 + def orders_cycles_products_default_margins
  22 + self.class.transaction do
  23 + self.orders_cycles.opened.each do |cycle|
  24 + cycle.products.each do |product|
  25 + product.margin_percentage = margin_percentage
  26 + product.save!
  27 + end
  28 + end
  29 + end
  30 + end
  31 +
  32 +end
... ...
plugins/orders_cycle/lib/ext/suppliers_plugin/base_product.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +require_dependency 'product'
  2 +
  3 +class Product
  4 +
  5 + scope :in_cycle, -> { where type: 'OrdersCyclePlugin::OfferedProduct' }
  6 +
  7 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +module OrdersCyclePlugin
  2 +
  3 + extend Noosfero::Plugin::ParentMethods
  4 +
  5 + def self.plugin_name
  6 + I18n.t('orders_cycle_plugin.lib.plugin.name')
  7 + end
  8 +
  9 + def self.plugin_description
  10 + I18n.t('orders_cycle_plugin.lib.plugin.description')
  11 + end
  12 +
  13 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/base.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +require_dependency "#{File.dirname __FILE__}/../ext/delivery_plugin/option"
  2 +
  3 +class OrdersCyclePlugin::Base < Noosfero::Plugin
  4 +
  5 + def stylesheet?
  6 + true
  7 + end
  8 +
  9 + def js_files
  10 + ['orders_cycle'].map{ |j| "javascripts/#{j}" }
  11 + end
  12 +
  13 +end
  14 +
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/cycle_helper.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +module OrdersCyclePlugin::CycleHelper
  2 +
  3 + protected
  4 +
  5 + def timeline_class cycle, status, selected
  6 + klass = ""
  7 + if cycle.status == status
  8 + klass += " cycle-timeline-current-item"
  9 + elsif cycle.passed_by? status
  10 + klass += " cycle-timeline-passed-item"
  11 + else
  12 + klass += " cycle-timeline-next-item"
  13 + end
  14 + klass += " cycle-timeline-selected-item" if selected == status
  15 + klass
  16 + end
  17 +
  18 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/display_helper.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +module OrdersCyclePlugin::DisplayHelper
  2 +
  3 + protected
  4 +
  5 + include ::ActionView::Helpers::JavaScriptHelper # we want the original button_to_function!
  6 +
  7 + include OrdersPlugin::DisplayHelper
  8 +
  9 + include OrdersCyclePlugin::RepeatHelper
  10 + include OrdersCyclePlugin::CycleHelper
  11 +
  12 + include SuppliersPlugin::DisplayHelper
  13 + include SuppliersPlugin::ProductHelper
  14 +
  15 + include DeliveryPlugin::DisplayHelper
  16 +
  17 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/mailer.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +class OrdersCyclePlugin::Mailer < Noosfero::Plugin::MailerBase
  2 +
  3 + include OrdersCyclePlugin::TranslationHelper
  4 +
  5 + helper ApplicationHelper
  6 + helper OrdersCyclePlugin::TranslationHelper
  7 +
  8 + attr_accessor :environment
  9 + attr_accessor :profile
  10 +
  11 + def open_cycle profile, cycle, subject, message
  12 + self.environment = profile.environment
  13 + @profile = profile
  14 + @cycle = cycle
  15 + @message = message
  16 +
  17 + mail bcc: organization_members(@profile),
  18 + from: environment.noreply_email,
  19 + reply_to: profile_recipients(@profile),
  20 + subject: t('lib.mailer.profile_subject') % {profile: profile.name, subject: subject}
  21 + end
  22 +
  23 + protected
  24 +
  25 + def profile_recipients profile
  26 + if profile.person?
  27 + profile.contact_email
  28 + else
  29 + profile.admins.map{ |p| p.contact_email }
  30 + end
  31 + end
  32 +
  33 + def organization_members profile
  34 + if profile.organization?
  35 + profile.members.map{ |p| p.contact_email }
  36 + end
  37 + end
  38 +
  39 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/order_helper.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +module OrdersCyclePlugin::OrderHelper
  2 +
  3 + protected
  4 +
  5 + include OrdersCyclePlugin::DisplayHelper
  6 +
  7 +end
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/repeat_helper.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +module OrdersCyclePlugin::RepeatHelper
  2 +
  3 + def repeat_checkout_order_button order
  4 + button :check, t('views.public.repeat.checkout'), {controller: :orders_cycle_plugin_order, action: :repeat, order_id: order.id, cycle_id: @cycle.id},
  5 + class: 'repeat-checkout-order'
  6 + end
  7 +
  8 + def repeat_choose_order_button order
  9 + nil
  10 + end
  11 +
  12 +end
  13 +
... ...
plugins/orders_cycle/lib/orders_cycle_plugin/translation_helper.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +module OrdersCyclePlugin::TranslationHelper
  2 +
  3 + protected
  4 +
  5 + # included here to be used on controller's t calls
  6 + include TermsHelper
  7 +
  8 + def i18n_scope
  9 + ['orders_cycle_plugin', 'orders_plugin', 'suppliers_plugin', 'volunteers_plugin']
  10 + end
  11 +
  12 +end
... ...
plugins/orders_cycle/locales/en-US.yml 0 → 100644
... ... @@ -0,0 +1,259 @@
  1 +en-US: &en-US
  2 +
  3 + orders_cycle_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "Orders' Cycle"
  7 + description: "Create and manage orders' cycle"
  8 + ext:
  9 + orders_plugin:
  10 + order:
  11 + cyclecode_ordercode: "%{cyclecode}.%{ordercode}"
  12 + mailer:
  13 + profile_subject: "[%{profile}] %{subject}"
  14 + order_was_changed: "[%{profile}] Your order was modificado"
  15 + order_block:
  16 + distribution_orders_c: "Distribution orders' cycles for consumers"
  17 + offer_cycles_for_you_: "Offer cycles for you consumers to make orders"
  18 + orders_cycles: "Orders' cycles"
  19 + controllers:
  20 + myprofile:
  21 + message_controller:
  22 + message_sent: "Message sent"
  23 + product_controller:
  24 + product_removed_from_: "Product removed from cycle"
  25 + product_removed_succe: "Product removed successfully"
  26 + the_product_was_not_r: "The product was not removed"
  27 + cycle_controller:
  28 + cycle_created: "Cycle created"
  29 + cycle_n_n: "Cycle n.%{n}"
  30 + new_open_cycle: "New open cycle: "
  31 +
  32 + models:
  33 + cycle:
  34 + code_name: "%{code}. %{name}"
  35 + delivery_period_befor: "Delivery' period before orders' period"
  36 + invalid_delivery_peri: "Invalid delivery' period"
  37 + invalid_orders_period: "Invalid orders' period"
  38 + statuses:
  39 + edition: Edition
  40 + orders: Orders
  41 + purchases: Purchases
  42 + receipts: Receipts
  43 + separation: Separation
  44 + delivery: Delivery
  45 + closing: Closing
  46 + views:
  47 + gadgets:
  48 + _cycle:
  49 + happening: Happening
  50 + orders_open_b_cycle: "Orders open: <b>%{cycle}</b>"
  51 + place_an_order: "place an order"
  52 + see_orders_cycle: "see orders' cycle"
  53 + cycles:
  54 + all_cycles: "all cycles"
  55 + mailer:
  56 + open_cycle:
  57 + a_new_cycle_is_open_c: "A new cycle is open called "
  58 + hello_member_of_name: "Hello consumer of %{name},"
  59 + the_administrator_let: "The administrator let a message about this cycle"
  60 + the_cycle_description: "The cycle description is.."
  61 + profile:
  62 + order:
  63 + _consumer_orders:
  64 + caution: "<strong>Caution</strong>, you are editing the orders of \"%{consumer}\". It is preferable to make small editions through the cycle's administration, this way the person will be properly warned of the updates. We recommend using this page only if you're doing the order for another person."
  65 + show_cancelled_orders: "show cancelled orders"
  66 + hide_cancelled_orders: "hide cancelled orders"
  67 + administration_of_thi: "Administration of this cycle"
  68 + before_the_closing: "(before the closing)"
  69 + change_order: "reopen order"
  70 + edit_your_orders: "Edit your orders"
  71 + login: login
  72 + new_order: "New order"
  73 + repeat_order: "Repetir order"
  74 + orders_from_another_m: "Orders from another consumer"
  75 + orders_from_consumer_: "Orders from \"%{consumer}\" on this cycle"
  76 + send_message_to_the_m: "send message to the managers"
  77 + sign_up: "sign up"
  78 + this_cycle_is_already: "This cycle is already closed."
  79 + this_cycle_is_not_ope: "This cycle is not open yet."
  80 + the_time_for_orders_is: "The orders' period for this cycle is from %{start} to %{finish}"
  81 + to_place_an_order_you: "To place an order you need to be logged in and registered %{terms.profile.at_article.singular}. Please %{login} or %{signup}."
  82 + you_haven_t_placed_an: "You haven't placed any order on this cycle yet."
  83 + you_still_can: "You still can:"
  84 + your_order_is_confirm: "Your order is confirmed and registered. Please follow the guidelines of the delivery method below, so that it happens without problems."
  85 + your_order_was_cancel: "Your order was cancelled."
  86 + your_order_wasn_t_con: "Your order wasn't confirmed and the cycle orders period already ended."
  87 + your_orders_on_this_c: "Your orders on this cycle"
  88 + associate_to_order: "Associate to make orders"
  89 + _filter_products:
  90 + active: active
  91 + all_the_categories: "all the categories"
  92 + all_the_suppliers: "all %{terms.supplier.article.plural}"
  93 + and_being: "and being"
  94 + anyone: anyone
  95 + bigger_than_the_stock: "bigger than the stock"
  96 + filter: Filter
  97 + in_any_state: "In any state"
  98 + inactive: inactive
  99 + product_name: "Product Name"
  100 + supplier: "%{terms.supplier.singular.capitalize}"
  101 + whose_qty_available_i: "whose qty. available is"
  102 + _status:
  103 + code_status_message: "%{code} %{status_message}"
  104 + open_it: "open it"
  105 + index:
  106 + code: "%{code}."
  107 + orders_cycles: "Orders' cycles"
  108 + place_an_order: "Place an order"
  109 + place_another_order: "Place another order"
  110 + there_s_no_open_sessi: "There's no open cycle"
  111 + your_orders: "Your orders:"
  112 + product:
  113 + _order_edit:
  114 + add: Add
  115 + cancel: cancel
  116 + change: Change
  117 + city: City
  118 + city_state: "%{city}/%{state}"
  119 + include: include
  120 + more_about_the_produc: "More about the producer \"%{supplier}\""
  121 + no_description: "No description"
  122 + opening_new_order_for: "Opening new order for your product inclusion"
  123 + opening_order_code_fo: "Opening order %{code} for your product inclusion"
  124 + price_percent_price_w: "%{price} + %{percent}% = %{price_with_margin}"
  125 + price_s_descriptive: "price's descriptive"
  126 + product_image: "Product Image"
  127 + _order_search:
  128 + order_qty: "Order qty"
  129 + category: Category
  130 + producer: Producer
  131 + price: Price
  132 + product: Product
  133 + this_search_hasn_t_re: "This search hasn't returned any product"
  134 + _cycle_edit:
  135 + all_ordered_products: "All ordered products from this product will also be removed; you should first warn consumers that ordered this products"
  136 + buy_price: "Buy price"
  137 + buy_unit: "Buy unit"
  138 + cancel_updates: "cancel updates"
  139 + default_margin: "Default margin"
  140 + default_sell_price: "Default sell price"
  141 + edit_product: "edit product"
  142 + margin: Margin
  143 + qty_in_stock: "Qty. in stock"
  144 + qty_offered: "Qty. offered"
  145 + remove_from_cycle: "remove from cycle"
  146 + save: Save
  147 + sell_price: "Sell price"
  148 + sell_unit: "Sell unit"
  149 + cycle:
  150 + _brief:
  151 + confirmed_orders:
  152 + zero: "(no confirmed orders)"
  153 + one: "(1 confirmed order)"
  154 + other: "(%{count} confirmed orders)"
  155 + code: "%{code}."
  156 + delivery: Delivery
  157 + orders: Orders
  158 + _closed:
  159 + cycle_already_finishe: "Cycle already finished"
  160 + _edit_fields:
  161 + add_method: "Add method"
  162 + add_all_methods: "(add all)"
  163 + available_delivery_me: "Available delivery methods"
  164 + cancel_changes: "cancel changes"
  165 + remove: "remove"
  166 + confirm_remove: "Are you sure you want to remove this cycle?"
  167 + create_new_cycle: "Create new cycle"
  168 + deliveries_interval: "Deliveries Interval"
  169 + description: Description
  170 + general_settings: "General settings"
  171 + name: Name
  172 + notify_members_of_ope: "Notify consumer of open orders"
  173 + opening_message: "Opening Message"
  174 + orders_interval: "Orders Interval"
  175 + save: Save
  176 + save_and_open_orders: Save and open orders
  177 + create_and_open_orders: Create and open orders
  178 + this_message_will_be_: "This message will be sent by mail for the consumers %{terms.profile.from_article.singular} "
  179 + _edit_popin:
  180 + close: Close
  181 + cycle_editing: "Cycle editing"
  182 + cycle_saved: "Cycle saved."
  183 + _edition:
  184 + info: 'The edition time is gone and the cycle is already public. Actually, the cycle is in a supply call period.<br/><br/> It is still possible to edit some Cycle parameters through this page, however, beware of the risk. Some operations have different implications depending on the fase you are. When needed, you will be notified by a notification window of the consequences of the changes made.'
  185 + add_product: "Add product"
  186 + it_was_automatically_: "It was automatically created from the active products. See the list below and check for needed changes."
  187 + the_following_list_of: "The following list of products are available in this cycle."
  188 + the_products: "The products"
  189 + _products_loading: "The products are being loaded into the cycle."
  190 + _orders:
  191 + header_help: "In this phase the orders %{terms.consumer.from_article.plural} are received, and it is possible supervise them if by chance there is some more severe error."
  192 + the_orders_period_is_: "The orders period is still on, take care to edit the orders already open, it may confuse the users"
  193 + already_closed: "The orders period was already closed, It's not possible to edit the originals orders. In the redistribution phase it is possible to edit the order, before the delivery it also possible to edit this order."
  194 + _purchases:
  195 + header_help: "In this phase, using the received orders, the purchases for each supplier are done to supply the demand of all orders."
  196 + _receipts:
  197 + header_help: "In this phase are registered the receipts of the purchases e are edited any errors that may exist."
  198 + _separation:
  199 + header_help: "In this phase each order é separated acording to the availability of the products purchased that arrived."
  200 + _delivery:
  201 + header_help: "In this phase are registered the deliveries %{terms.consumer.to_article.plural}. This is the moment to register the changes in relation to what was separated."
  202 + _product_lines:
  203 + category: Category
  204 + price: Price
  205 + product: Product
  206 + qty_avail: "Qty. avail."
  207 + showing_pcount_produc: "Showing %{pcount} products of %{allpcount}"
  208 + supplier: "%{terms.supplier.singular.capitalize}"
  209 + _results:
  210 + no_cycles_to_show: "No cycles to show"
  211 + _timeline:
  212 + are_you_sure_you_want_to_reopen: "Are you sure you want to reopen the orders cycle?"
  213 + are_you_sure_you_want_to_close: "Are you sure you want to close the orders cycle?"
  214 + call: Call
  215 + close: Close
  216 + close_status: "Close %{status}"
  217 + finish_cycle_editing: "Open orders for cycle"
  218 + reopen_orders_period: "Reopen orders period"
  219 + _title:
  220 + new_cycle: "New cycle"
  221 + order_cycle: "Order Cycle: "
  222 + _view_dates:
  223 + delivery: "Delivery: "
  224 + happening: Happening
  225 + orders: "Orders: "
  226 + _view_header:
  227 + ? ", "
  228 + : ", "
  229 + all_orders_cycles: "all orders cycles"
  230 + orders_cycle_cycle: "Orders' cycle: %{cycle}"
  231 + other_open_cycles_lis: "Other open cycles: %{list}. See also %{all}"
  232 + see_also_all: "See also %{all}"
  233 + _view_products:
  234 + the_products: "The products"
  235 + add_products:
  236 + add_all_missing_produ: "add all missing products %{terms.profile.to_article.singular}"
  237 + add_product_to_cycle_: "Add product to cycle's products"
  238 + cancel: cancel
  239 + close: close
  240 + or: or
  241 + search_for_a_product_: "Search for a product in our products"
  242 + send: Send
  243 + type_in_a_name: "Type in a name"
  244 + you_already_have_all_: "You already have all your distributed products added"
  245 + index:
  246 + and_are_from_the_mont: "and are from the month of"
  247 + closed_cycles: "Closed Cycles"
  248 + filter: Filter
  249 + new_cycle: "New cycle"
  250 + no_cycles_to_show: "No cycles to show"
  251 + open_cycles: "Open Cycles"
  252 + orders_cycles: "Orders' Cycles"
  253 + show_cycles_from_year: "Show cycles from year"
  254 +
  255 +en_US:
  256 + <<: *en-US
  257 +en:
  258 + <<: *en-US
  259 +
... ...
plugins/orders_cycle/locales/pt-BR.yml 0 → 100644
... ... @@ -0,0 +1,257 @@
  1 +pt-BR: &pt-BR
  2 +
  3 + orders_cycle_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "Ciclo de pedidos"
  7 + description: "Criar e administrar ciclos de pedidos"
  8 + ext:
  9 + orders_plugin:
  10 + order:
  11 + cyclecode_ordercode: "%{cyclecode}.%{ordercode}"
  12 + mailer:
  13 + order_was_changed: "[%{profile}] Seu pedido foi modificado"
  14 + order_block:
  15 + distribution_orders_c: "Ciclos de pedidos para os(as) consumidores(as)"
  16 + offer_cycles_for_you_: "Ciclo de oferta para os(as) consumidores(as) fazerem pedidos"
  17 + orders_cycles: "Ciclos de pedidos"
  18 + controllers:
  19 + myprofile:
  20 + message_controller:
  21 + message_sent: "A mensagem foi enviada"
  22 + product_controller:
  23 + product_removed_from_: "Produto removido do ciclo"
  24 + product_removed_succe: "Produto removido com sucesso"
  25 + the_product_was_not_r: "O produto não foi removido"
  26 + cycle_controller:
  27 + cycle_created: "Ciclo criado"
  28 + cycle_n_n: "Ciclo n.%{n}"
  29 + new_open_cycle: "Novo ciclo aberto"
  30 +
  31 + models:
  32 + cycle:
  33 + code_name: "%{code}. %{name}"
  34 + delivery_period_befor: "Período de entrega antes do período de pedidos"
  35 + invalid_delivery_peri: "Período de entrega inválido"
  36 + invalid_orders_period: "Período de pedidos inválido"
  37 + statuses:
  38 + edition: Edição
  39 + orders: Pedidos
  40 + purchases: Compras
  41 + receipts: Recebimentos
  42 + separation: Separação
  43 + delivery: Entrega
  44 + closing: Fechamento
  45 + views:
  46 + gadgets:
  47 + _cycle:
  48 + happening: Acontecendo
  49 + orders_open_b_cycle: "Pedidos abertos: <b>%{cycle}</b>"
  50 + place_an_order: "place an order"
  51 + see_orders_cycle: "Ver ciclo de pedidos"
  52 + cycles:
  53 + all_cycles: "todos ciclos"
  54 + mailer:
  55 + open_cycle:
  56 + a_new_cycle_is_open_c: "Um novo ciclo de pedidos está aberto chamado "
  57 + hello_member_of_name: "Olá membros da comunidade %{name}!"
  58 + the_administrator_let: "A/O administrador(a) deixou uma mensagem sobre este ciclo"
  59 + the_cycle_description: "A descrição do ciclo é.."
  60 + order:
  61 + _consumer_orders:
  62 + caution: "<strong>Cuidado</strong>, voce está editando os pedidos de \"%{consumer}\". É preferível fazer pequenas edições pela administração do ciclo. Desta forma, a pessoa será propriamente avisada das mudanças. Nós recomendamos que esta página seja usada somente se você está fazendo o pedido para outra pessoa."
  63 + show_cancelled_orders: "mostrar pedidos cancelados"
  64 + hide_cancelled_orders: "esconder pedidos cancelados"
  65 + administration_of_thi: "Administração deste ciclo"
  66 + before_the_closing: "(antes do fechamento)"
  67 + change_order: "Reabrir o pedido"
  68 + edit_your_orders: "Edite seus pedidos"
  69 + login: login
  70 + new_order: "Novo pedido"
  71 + repeat_order: "Repetir pedido"
  72 + orders_from_another_m: "Orders from another member"
  73 + orders_from_consumer_: "Pedidos de \"%{consumer}\" neste ciclo"
  74 + send_message_to_the_m: "Enviar mensagem para gestores"
  75 + sign_up: registre-se
  76 + this_cycle_is_already: "Este ciclo já foi fechado."
  77 + this_cycle_is_not_ope: "Este ciclo ainda não está aberto."
  78 + the_time_for_orders_is: "O período de pedidos para esse ciclo é de %{start} até %{finish}"
  79 + to_place_an_order_you: "Para fazer um pedido você precisa estar logado e registrado %{terms.profile.at_article.singular}. Por favor faça %{login} ou %{signup}."
  80 + you_haven_t_placed_an: "Você ainda não fez pedidos neste ciclo."
  81 + you_still_can: "Você ainda pode:"
  82 + your_order_is_confirm: "Seu pedido está confirmado e registrado. Por favor, siga as diretrizes do método de entrega abaixo para que isso aconteça sem problemas."
  83 + your_order_was_cancel: "Seu pedido foi cancelado"
  84 + your_order_wasn_t_con: "Seu pedido não foi confirmado e o período de pedidos terminou."
  85 + your_orders_on_this_c: "Seus pedidos neste ciclo"
  86 + associate_to_order: "Associe-se para realizar pedidos"
  87 + _filter_products:
  88 + active: Ativo
  89 + all_the_categories: "todas as categorias"
  90 + all_the_suppliers: "todos %{terms.supplier.article.plural}"
  91 + and_being: "e que estejam"
  92 + anyone: anyone
  93 + bigger_than_the_stock: "maior do que o estoque"
  94 + filter: Filtro
  95 + in_any_state: "Em qualquer estado"
  96 + inactive: Inativo
  97 + product_name: "Nome do produto"
  98 + supplier: "%{terms.supplier.singular.capitalize}"
  99 + whose_qty_available_i: "cuja quantidade disponível é"
  100 + _status:
  101 + code_status_message: "%{code} %{status_message}"
  102 + open_it: abrir
  103 + index:
  104 + code: "%{code}."
  105 + orders_cycles: "Ciclos de pedidos"
  106 + place_an_order: "Faça um pedido"
  107 + place_another_order: "Faça um outro pedido"
  108 + there_s_no_open_sessi: "Nã há ciclos abertos"
  109 + your_orders: "Seus pedidos:"
  110 + product:
  111 + _order_edit:
  112 + add: Adicionar
  113 + cancel: Cancelar
  114 + change: Mudar
  115 + city: Cidade
  116 + city_state: "%{city}/%{state}"
  117 + include: Incluir
  118 + more_about_the_produc: "Mais sobre o produtor \"%{supplier}\""
  119 + no_description: "Sem Descrição"
  120 + opening_new_order_for: "Abrindo novo pedido para a inclusão do produto."
  121 + opening_order_code_fo: "Abrindo pedido %{code} para a inclusão do produto"
  122 + price_percent_price_w: "%{price} + %{percent}% = %{price_with_margin}"
  123 + price_s_descriptive: "descrictivo do preço"
  124 + product_image: "Imagem do produto"
  125 + _order_search:
  126 + order_qty: "Qtd pedida"
  127 + category: Categoria
  128 + producer: Produtor
  129 + price: Preço
  130 + product: Produto
  131 + this_search_hasn_t_re: "Esta busca não retornou produtos"
  132 + _cycle_edit:
  133 + all_ordered_products: "Todos pedidos deste produto serão removidos também; você deveria primeiro avisar os(as) consumidores(as) que pediram esse produto."
  134 + buy_price: "Preço de compra"
  135 + buy_unit: "Unidade de compra"
  136 + cancel_updates: "Cancelar atualizações"
  137 + default_margin: "Margem padrão"
  138 + default_sell_price: "Preço de venda padrão"
  139 + edit_product: "Editar produto"
  140 + margin: Margem
  141 + qty_in_stock: "Qtd em estoque"
  142 + qty_offered: "Qtd oferecida"
  143 + remove_from_cycle: "Remover do ciclo"
  144 + save: Salvar
  145 + sell_price: "Preço de venda:"
  146 + sell_unit: "Unidade de venda"
  147 + cycle:
  148 + _brief:
  149 + confirmed_orders:
  150 + zero: "(nenhum pedido confirmado)"
  151 + one: "(1 pedido confirmado)"
  152 + other: "(%{count} pedidos confirmados)"
  153 + code: "%{code}."
  154 + delivery: Entrega
  155 + orders: Pedidos
  156 + _closed:
  157 + cycle_already_finishe: "Ciclo já fechado"
  158 + _edit_fields:
  159 + add_method: "Adicionar método"
  160 + add_all_methods: "(adicionar todos)"
  161 + available_delivery_me: "Métodos de entrega disponíveis"
  162 + cancel_changes: "cancelar alterações"
  163 + remove: "remover"
  164 + confirm_remove: "Tem certeza de que quer remover este ciclo?"
  165 + create_new_cycle: "Criar novo ciclo de pedidos"
  166 + deliveries_interval: "Intervalo das Entregas"
  167 + description: Descrição
  168 + general_settings: "Configurações gerais"
  169 + name: Nome
  170 + notify_members_of_ope: "Avisar os(as) consumidores(as) da abertura de pedidos"
  171 + opening_message: "Mensagem de abertura"
  172 + orders_interval: "Intervalo dos Pedidos"
  173 + save: Salvar
  174 + save_and_open_orders: Salvar e abrir pedidos
  175 + create_and_open_orders: Criar e abrir pedidos
  176 + this_message_will_be_: "Esta mensagem será mandada por email aos(às) consumidores(as) %{terms.profile.from_article.singular}"
  177 + _edit_popin:
  178 + close: Fechar
  179 + cycle_editing: "Edição de ciclo"
  180 + cycle_saved: "Ciclo salvo"
  181 + _edition:
  182 + info: "O tempo de edição deste ciclo acabou e ele ainda está público.<br/><br/> Ainda é possível editar alguns parametros do ciclo através desta página, no entanto, esteja ciente do risco. Algumas operações têm diferentes implicações dependendo da fase em que se está. Quando assim for, você será avisado por uma janelinha de aviso das consequências das mudanças efetuadas."
  183 + add_product: "Adicionar produto"
  184 + it_was_automatically_: "Ela foi automaticamente gerada a partir dos produtos ativos e suas respectivas margens. Verifique a lista de produtos e edite-a conforme as necessidades e particularidades deste ciclo de pedidos."
  185 + the_following_list_of: "A seguinte lista de produtos está disponível neste ciclo."
  186 + the_products: "Os produtos"
  187 + _products_loading: "Os produtos estão sendo carregados no ciclo."
  188 + _orders:
  189 + header_help: "Nesta etapa são recebidos os pedidos %{terms.consumer.from_article.plural}, e é possível supervisioná-los se por acaso existe algum erro mais grave."
  190 + the_orders_period_is_: "O período de pedidos está ainda aberto, tome cuidado ao editar os pedidos abertos. Isso pode confundir os usuários."
  191 + already_closed: "O período de pedidos já foi fechado, Não é possível editar os pedidos originais. Na fase de redistribuição é possível editar o pedido, e também antes da entrega."
  192 + _purchases:
  193 + header_help: "Nesta etapa, com base nos pedidos recebidos, são feitas as compras de cada um %{terms.supplier.of_article.plural} para suprir a demanda de todos os pedidos."
  194 + _receipts:
  195 + header_help: "Nesta etapa são registradas os recebimentos das compras e são editadas quaisquer discrepâncias que possam existir."
  196 + _separation:
  197 + header_help: "Nesta etapa cada pedido é separado de acordo com a disponibilidade dos produtos encomendados que chegaram."
  198 + _delivery:
  199 + header_help: "Nesta etapa são registradas as entregas %{terms.consumer.to_article.plural}. Este é o momento de registrar as mudanças em relação ao que foi separado."
  200 + _product_lines:
  201 + category: Categoria
  202 + price: Preço
  203 + product: Produto
  204 + qty_avail: "Qtd. dispon."
  205 + showing_pcount_produc: "Mostrando %{pcount} produtos de %{allpcount}"
  206 + supplier: "%{terms.supplier.singular.capitalize}"
  207 + _results:
  208 + no_cycles_to_show: "Sem ciclos a mostrar"
  209 + _timeline:
  210 + are_you_sure_you_want_to_reopen: "Tem certeza de que deseja reabrir o ciclo de pedidos?"
  211 + are_you_sure_you_want_to_close: "Tem certeza de que deseja encerrar a etapa %{status}?"
  212 + call: Call
  213 + close: Fechar
  214 + close_status: "Encerrar %{status}"
  215 + finish_cycle_editing: "Abrir pedidos do ciclo"
  216 + reopen_orders_period: "Reabrir ciclo de pedidos"
  217 + _title:
  218 + new_cycle: "Novo ciclo"
  219 + order_cycle: "Ciclo de pedidos: "
  220 + _view_dates:
  221 + delivery: "Entrega: "
  222 + happening: Acontecendo
  223 + orders: "Pedidos: "
  224 + _view_header:
  225 + ? ", "
  226 + : ", "
  227 + all_orders_cycles: "Todos ciclos abertos"
  228 + orders_cycle_cycle: "Ciclo de pedidos: %{cycle}"
  229 + other_open_cycles_lis: "Outros ciclos abertos: %{list}. Veja também %{all}"
  230 + see_also_all: "Veja também %{all}"
  231 + _view_products:
  232 + the_products: "Os produtos"
  233 + add_products:
  234 + add_all_missing_produ: "adicionar todos produtos que faltam %{terms.profile.to_article.singular}"
  235 + add_product_to_cycle_: "Adicionar produto à lista de produtos distribuídos"
  236 + cancel: Cancelar
  237 + close: Fechar
  238 + or: ou
  239 + search_for_a_product_: "Busca por um produto em nossos produtos"
  240 + send: Enviar
  241 + type_in_a_name: "Escreva um nome"
  242 + you_already_have_all_: "Todos seus produtos distribuídos já foram adicionados"
  243 + index:
  244 + and_are_from_the_mont: "e que sejam do mês de"
  245 + closed_cycles: "Ciclos de pedidos fechados"
  246 + filter: Filtro
  247 + new_cycle: "Novo ciclo"
  248 + no_cycles_to_show: "Sem ciclos a mostrar"
  249 + open_cycles: "Ciclos de pedidos Abertos"
  250 + orders_cycles: "Ciclo de pedidos"
  251 + show_cycles_from_year: "Mostre os ciclos do ano"
  252 +
  253 +pt_BR:
  254 + <<: *pt-BR
  255 +pt:
  256 + <<: *pt-BR
  257 +
... ...
plugins/orders_cycle/models/orders_cycle_plugin/cycle.rb 0 → 100644
... ... @@ -0,0 +1,320 @@
  1 +class OrdersCyclePlugin::Cycle < ActiveRecord::Base
  2 +
  3 + attr_accessible :profile, :status, :name, :description, :opening_message
  4 +
  5 + attr_accessible :start, :finish, :delivery_start, :delivery_finish
  6 + attr_accessible :start_date, :start_time, :finish_date, :finish_time, :delivery_start_date, :delivery_start_time, :delivery_finish_date, :delivery_finish_time,
  7 +
  8 + Statuses = %w[edition orders purchases receipts separation delivery closing]
  9 + DbStatuses = %w[new] + Statuses
  10 + UserStatuses = Statuses
  11 +
  12 + # which status the sales are on each cycle status
  13 + SaleStatusMap = {
  14 + 'edition' => nil,
  15 + 'orders' => :ordered,
  16 + 'purchases' => :accepted,
  17 + 'receipts' => :accepted,
  18 + 'separation' => :accepted,
  19 + 'delivery' => :separated,
  20 + 'closing' => :delivered,
  21 + }
  22 + # which status the purchases are on each cycle status
  23 + PurchaseStatusMap = {
  24 + 'edition' => nil,
  25 + 'orders' => nil,
  26 + 'purchases' => :draft,
  27 + 'receipts' => :ordered,
  28 + 'separation' => :received,
  29 + 'delivery' => :received,
  30 + 'closing' => :received,
  31 + }
  32 +
  33 + belongs_to :profile
  34 +
  35 + has_many :delivery_options, class_name: 'DeliveryPlugin::Option', dependent: :destroy,
  36 + as: :owner, conditions: ["delivery_plugin_options.owner_type = 'OrdersCyclePlugin::Cycle'"]
  37 + has_many :delivery_methods, through: :delivery_options, source: :delivery_method
  38 +
  39 + has_many :cycle_orders, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :cycle_id, dependent: :destroy, order: 'id ASC'
  40 +
  41 + # cannot use :order because of months/years named_scope
  42 + has_many :sales, through: :cycle_orders, source: :sale
  43 + has_many :purchases, through: :cycle_orders, source: :purchase
  44 +
  45 + has_many :cycle_products, foreign_key: :cycle_id, class_name: 'OrdersCyclePlugin::CycleProduct', dependent: :destroy
  46 + has_many :products, through: :cycle_products, source: :product, order: 'products.name ASC',
  47 + include: [ :from_2x_products, :from_products, {profile: :domains}, ]
  48 +
  49 + has_many :consumers, through: :sales, source: :consumer, order: 'name ASC', uniq: true
  50 + has_many :suppliers, through: :products, source: :suppliers, order: 'suppliers_plugin_suppliers.name ASC', uniq: true
  51 + has_many :orders_suppliers, through: :sales, source: :profile, order: 'name ASC'
  52 +
  53 + has_many :from_products, through: :products, order: 'name ASC', uniq: true
  54 + has_many :supplier_products, through: :products, order: 'name ASC', uniq: true
  55 + has_many :product_categories, through: :products, order: 'name ASC', uniq: true
  56 +
  57 + has_many :orders_confirmed, through: :cycle_orders, source: :sale, order: 'id ASC',
  58 + conditions: ['orders_plugin_orders.ordered_at IS NOT NULL']
  59 +
  60 + has_many :items_selled, through: :sales, source: :items
  61 + has_many :items_purchased, through: :purchases, source: :items
  62 + # DEPRECATED
  63 + has_many :items, through: :orders_confirmed
  64 +
  65 + has_many :ordered_suppliers, through: :orders_confirmed, source: :suppliers
  66 +
  67 + has_many :ordered_offered_products, through: :orders_confirmed, source: :offered_products, uniq: true, include: [:suppliers]
  68 + has_many :ordered_distributed_products, through: :orders_confirmed, source: :distributed_products, uniq: true, include: [:suppliers]
  69 + has_many :ordered_supplier_products, through: :orders_confirmed, source: :supplier_products, uniq: true, include: [:suppliers]
  70 +
  71 + has_many :volunteers_periods, class_name: 'VolunteersPlugin::Period', as: :owner
  72 + has_many :volunteers, through: :volunteers_periods, source: :profile
  73 + attr_accessible :volunteers_periods_attributes
  74 + accepts_nested_attributes_for :volunteers_periods, allow_destroy: true
  75 +
  76 + scope :has_volunteers_periods, -> {uniq.joins [:volunteers_periods]}
  77 +
  78 + # status scopes
  79 + scope :on_edition, -> { where status: 'edition' }
  80 + scope :on_orders, -> { where status: 'orders' }
  81 + scope :on_purchases, -> { where status: 'purchases' }
  82 + scope :on_separation, -> { where status: 'separation' }
  83 + scope :on_delivery, -> { where status: 'delivery' }
  84 + scope :on_closing, -> { where status: 'closing' }
  85 +
  86 + scope :defuncts, conditions: ["status = 'new' AND created_at < ?", 2.days.ago]
  87 + scope :not_new, conditions: ["status <> 'new'"]
  88 + scope :on_orders, lambda {
  89 + {conditions: ["status = 'orders' AND ( (start <= :now AND finish IS NULL) OR (start <= :now AND finish >= :now) )",
  90 + {now: DateTime.now}]}
  91 + }
  92 + scope :not_on_orders, lambda {
  93 + {conditions: ["NOT (status = 'orders' AND ( (start <= :now AND finish IS NULL) OR (start <= :now AND finish >= :now) ) )",
  94 + {now: DateTime.now}]}
  95 + }
  96 + scope :opened, conditions: ["status <> 'new' AND status <> 'closing'"]
  97 + scope :closing, conditions: ["status = 'closing'"]
  98 + scope :by_status, lambda { |status| { conditions: {status: status} } }
  99 +
  100 + scope :months, select: 'DISTINCT(EXTRACT(months FROM start)) as month', order: 'month DESC'
  101 + scope :years, select: 'DISTINCT(EXTRACT(YEAR FROM start)) as year', order: 'year DESC'
  102 +
  103 + scope :by_month, lambda { |month| {
  104 + conditions: [ 'EXTRACT(month FROM start) <= :month AND EXTRACT(month FROM finish) >= :month', { month: month } ]}
  105 + }
  106 + scope :by_year, lambda { |year| {
  107 + conditions: [ 'EXTRACT(year FROM start) <= :year AND EXTRACT(year FROM finish) >= :year', { year: year } ]}
  108 + }
  109 + scope :by_range, lambda { |range| {
  110 + conditions: [ 'start BETWEEN :start AND :finish OR finish BETWEEN :start AND :finish',
  111 + { start: range.first, finish: range.last }
  112 + ]}
  113 + }
  114 +
  115 + validates_presence_of :profile
  116 + validates_presence_of :name, if: :not_new?
  117 + validates_presence_of :start, if: :not_new?
  118 + # FIXME: The user frequenqly forget about this, and this will crash the app in some places, so don't enable this
  119 + #validates_presence_of :delivery_options, unless: :new_or_edition?
  120 + validates_inclusion_of :status, in: DbStatuses, if: :not_new?
  121 + validates_numericality_of :margin_percentage, allow_nil: true, if: :not_new?
  122 + validate :validate_orders_dates, if: :not_new?
  123 + validate :validate_delivery_dates, if: :not_new?
  124 +
  125 + before_validation :step_new
  126 + before_validation :update_orders_status
  127 + before_save :add_products_on_edition_state
  128 + after_create :delay_purge_profile_defuncts
  129 +
  130 + extend CodeNumbering::ClassMethods
  131 + code_numbering :code, scope: Proc.new { self.profile.orders_cycles }
  132 +
  133 + extend OrdersPlugin::DateRangeAttr::ClassMethods
  134 + date_range_attr :start, :finish
  135 + date_range_attr :delivery_start, :delivery_finish
  136 +
  137 + extend SplitDatetime::SplitMethods
  138 + split_datetime :start
  139 + split_datetime :finish
  140 + split_datetime :delivery_start
  141 + split_datetime :delivery_finish
  142 +
  143 + serialize :data, Hash
  144 +
  145 + def name_with_code
  146 + I18n.t('orders_cycle_plugin.models.cycle.code_name') % {
  147 + code: code, name: name
  148 + }
  149 + end
  150 + def total_price_consumer_ordered
  151 + self.items.sum :price_consumer_ordered
  152 + end
  153 +
  154 + def status
  155 + self['status'] = 'closing' if self['status'] == 'closed'
  156 + self['status']
  157 + end
  158 +
  159 + def step
  160 + self.status = DbStatuses[DbStatuses.index(self.status)+1]
  161 + end
  162 + def step_back
  163 + self.status = DbStatuses[DbStatuses.index(self.status)-1]
  164 + end
  165 +
  166 + def passed_by? status
  167 + DbStatuses.index(self.status) > DbStatuses.index(status) rescue false
  168 + end
  169 +
  170 + def new?
  171 + self.status == 'new'
  172 + end
  173 + def not_new?
  174 + self.status != 'new'
  175 + end
  176 + def open?
  177 + !self.closing?
  178 + end
  179 + def closing?
  180 + self.status == 'closing'
  181 + end
  182 + def edition?
  183 + self.status == 'edition'
  184 + end
  185 + def new_or_edition?
  186 + self.status == 'new' or self.status == 'edition'
  187 + end
  188 + def after_orders?
  189 + now = DateTime.now
  190 + status == 'orders' && self.finish < now
  191 + end
  192 + def before_orders?
  193 + now = DateTime.now
  194 + status == 'orders' && self.start >= now
  195 + end
  196 + def orders?
  197 + now = DateTime.now
  198 + status == 'orders' && ( (self.start <= now && self.finish.nil?) || (self.start <= now && self.finish >= now) )
  199 + end
  200 + def delivery?
  201 + now = DateTime.now
  202 + status == 'delivery' && ( (self.delivery_start <= now && self.delivery_finish.nil?) || (self.delivery_start <= now && self.delivery_finish >= now) )
  203 + end
  204 +
  205 + def may_order? consumer
  206 + self.orders? and consumer.present? and consumer.in? profile.members
  207 + end
  208 +
  209 + def consumer_previous_orders consumer
  210 + self.profile.orders_cycles_sales.where(consumer_id: consumer.id).
  211 + where('orders_cycle_plugin_cycle_orders.cycle_id <> ?', self.id)
  212 + end
  213 +
  214 + def products_for_order
  215 + # FIXME name alias conflict
  216 + #self.products.unarchived.with_price.order('products.name ASC')
  217 + self.products.unarchived.with_price
  218 + end
  219 +
  220 + def supplier_products_by_suppliers orders = self.sales.ordered
  221 + OrdersCyclePlugin::Order.supplier_products_by_suppliers orders
  222 + end
  223 +
  224 + def generate_purchases sales = self.sales.ordered
  225 + return self.purchases if self.purchases.present?
  226 +
  227 + sales.each do |sale|
  228 + sale.add_purchases_items_without_delay
  229 + end
  230 +
  231 + self.purchases true
  232 + end
  233 + def regenerate_purchases sales = self.sales.ordered
  234 + self.purchases.destroy_all
  235 + self.generate_purchases sales
  236 + end
  237 +
  238 + def add_products
  239 + return if self.products.count > 0
  240 + ActiveRecord::Base.transaction do
  241 + self.profile.products.supplied.unarchived.available.find_each batch_size: 20 do |product|
  242 + self.add_product product
  243 + end
  244 + end
  245 + end
  246 +
  247 + def add_product product
  248 + OrdersCyclePlugin::OfferedProduct.create_from product, self
  249 + end
  250 +
  251 + def add_products_job
  252 + @add_products_job ||= Delayed::Job.find_by_id self.data[:add_products_job_id]
  253 + end
  254 +
  255 + protected
  256 +
  257 + def add_products_on_edition_state
  258 + return unless self.status_was == 'new'
  259 + job = self.delay.add_products
  260 + self.data[:add_products_job_id] = job.id
  261 + end
  262 +
  263 + def step_new
  264 + return if new_record?
  265 + self.step if self.new?
  266 + end
  267 +
  268 + def update_sales_status from, to
  269 + sales = self.sales.where(status: from.to_s)
  270 + sales.each do |sale|
  271 + sale.update_attributes status: to.to_s
  272 + end
  273 + end
  274 +
  275 + def update_purchases_status from, to
  276 + purchases = self.purchases.where(status: from.to_s)
  277 + purchases.each do |purchase|
  278 + purchase.update_attributes status: to.to_s
  279 + end
  280 + end
  281 +
  282 + def update_orders_status
  283 + # step orders to next_status on status change
  284 + return if self.new? or self.status_was == "new" or self.status_was == self.status
  285 +
  286 + # Don't rewind confirmed sales
  287 + unless self.status_was == 'orders' and self.status == 'edition'
  288 + sale_status_was = SaleStatusMap[self.status_was]
  289 + new_sale_status = SaleStatusMap[self.status]
  290 + self.delay.update_sales_status sale_status_was, new_sale_status unless sale_status_was == new_sale_status
  291 + end
  292 +
  293 + # Don't rewind confirmed purchases
  294 + unless self.status_was == 'receipts' and self.status == 'purchases'
  295 + purchase_status_was = PurchaseStatusMap[self.status_was]
  296 + new_purchase_status = PurchaseStatusMap[self.status]
  297 + self.delay.update_purchases_status purchase_status_was, new_purchase_status unless purchase_status_was == new_purchase_status
  298 + end
  299 + end
  300 +
  301 + def validate_orders_dates
  302 + return if self.new? or self.finish.nil?
  303 + errors.add :base, (I18n.t('orders_cycle_plugin.models.cycle.invalid_orders_period')) unless self.start < self.finish
  304 + end
  305 +
  306 + def validate_delivery_dates
  307 + return if self.new? or delivery_start.nil? or delivery_finish.nil?
  308 + errors.add :base, I18n.t('orders_cycle_plugin.models.cycle.invalid_delivery_peri') unless delivery_start < delivery_finish
  309 + errors.add :base, I18n.t('orders_cycle_plugin.models.cycle.delivery_period_befor') unless finish <= delivery_start
  310 + end
  311 +
  312 + def purge_profile_defuncts
  313 + self.class.where(profile_id: self.profile_id).defuncts.destroy_all
  314 + end
  315 +
  316 + def delay_purge_profile_defuncts
  317 + self.delay.purge_profile_defuncts
  318 + end
  319 +
  320 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/cycle_order.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class OrdersCyclePlugin::CycleOrder < ActiveRecord::Base
  2 +
  3 + belongs_to :cycle, class_name: 'OrdersCyclePlugin::Cycle'
  4 + belongs_to :sale, class_name: 'OrdersCyclePlugin::Sale', foreign_key: :sale_id, dependent: :destroy
  5 + belongs_to :purchase, class_name: 'OrdersCyclePlugin::Purchase', foreign_key: :purchase_id, dependent: :destroy
  6 +
  7 + validates_presence_of :cycle
  8 + validate :sale_or_purchase
  9 +
  10 + protected
  11 +
  12 + def sale_or_purchase
  13 + errors.add :base, "Specify a sale of purchase" unless self.sale_id or self.purchase_id
  14 + end
  15 +
  16 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/cycle_product.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class OrdersCyclePlugin::CycleProduct < ActiveRecord::Base
  2 +
  3 + self.table_name = :orders_cycle_plugin_cycle_products
  4 +
  5 + belongs_to :cycle, :class_name => 'OrdersCyclePlugin::Cycle'
  6 + belongs_to :product, :class_name => 'OrdersCyclePlugin::OfferedProduct', :dependent => :destroy # a product only belongs to one cycle
  7 +
  8 + validates_presence_of :cycle
  9 + validates_presence_of :product
  10 +
  11 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/item.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +class OrdersCyclePlugin::Item < OrdersPlugin::Item
  2 +
  3 + has_one :supplier, through: :product
  4 +
  5 + # see also: repeat_product
  6 + attr_accessor :repeat_cycle
  7 +
  8 + delegate :cycle, to: :order
  9 +
  10 + # OVERRIDE OrdersPlugin::Item
  11 + belongs_to :order, class_name: '::OrdersCyclePlugin::Order', foreign_key: :order_id, touch: true
  12 + belongs_to :sale, class_name: '::OrdersCyclePlugin::Sale', foreign_key: :order_id, touch: true
  13 + belongs_to :purchase, class_name: '::OrdersCyclePlugin::Purchase', foreign_key: :order_id, touch: true
  14 +
  15 + # WORKAROUND for direct relationship
  16 + belongs_to :offered_product, foreign_key: :product_id, class_name: 'OrdersCyclePlugin::OfferedProduct'
  17 + has_many :from_products, through: :offered_product
  18 + has_one :from_product, through: :offered_product
  19 + has_many :to_products, through: :offered_product
  20 + has_one :to_product, through: :offered_product
  21 + has_many :sources_supplier_products, through: :offered_product
  22 + has_one :sources_supplier_product, through: :offered_product
  23 + has_many :supplier_products, through: :offered_product
  24 + has_one :supplier_product, through: :offered_product
  25 + has_many :suppliers, through: :offered_product
  26 + has_one :supplier, through: :offered_product
  27 +
  28 + # what items were selled from this item
  29 + def selled_items
  30 + self.order.cycle.selled_items.where(profile_id: self.consumer.id, orders_plugin_item: {product_id: self.product_id})
  31 + end
  32 + # what items were purchased from this item
  33 + def purchased_items
  34 + self.order.cycle.purchases.where(consumer_id: self.profile.id)
  35 + end
  36 +
  37 + # override
  38 + def repeat_product
  39 + distributed_product = self.from_product
  40 + return unless self.repeat_cycle and distributed_product
  41 + self.repeat_cycle.products.where(from_products_products: {id: distributed_product.id}).first
  42 + end
  43 +
  44 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/offered_product.rb 0 → 100644
... ... @@ -0,0 +1,108 @@
  1 +class OrdersCyclePlugin::OfferedProduct < SuppliersPlugin::BaseProduct
  2 +
  3 + # FIXME: WORKAROUND for https://github.com/rails/rails/issues/6663
  4 + # OrdersCyclePlugin::Sale.find(3697).cycle.suppliers returns empty without this
  5 + def self.finder_needs_type_condition?
  6 + false
  7 + end
  8 +
  9 + has_many :cycle_products, foreign_key: :product_id, class_name: 'OrdersCyclePlugin::CycleProduct'
  10 + has_one :cycle_product, foreign_key: :product_id, class_name: 'OrdersCyclePlugin::CycleProduct'
  11 + has_many :cycles, through: :cycle_products
  12 + has_one :cycle, through: :cycle_product
  13 +
  14 + # OVERRIDE suppliers/lib/ext/product.rb
  15 + # for products in cycle, these are the products of the suppliers:
  16 + # p in cycle -> p distributed -> p from supplier
  17 + # So, sources_supplier_products is the same as sources_from_2x_products
  18 + has_many :sources_supplier_products, through: :from_products, source: :sources_from_products
  19 + has_one :sources_supplier_product, through: :from_product, source: :sources_from_product
  20 + # necessary only due to the override of sources_supplier_products, as rails somehow caches the old reference
  21 + # copied from suppliers/lib/ext/product
  22 + has_many :supplier_products, through: :sources_supplier_products, source: :from_product, order: 'id ASC'
  23 + has_one :supplier_product, through: :sources_supplier_product, source: :from_product, order: 'id ASC', autosave: true
  24 + has_many :suppliers, through: :sources_supplier_products, uniq: true, order: 'id ASC'
  25 + has_one :supplier, through: :sources_supplier_product, order: 'id ASC'
  26 +
  27 + instance_exec &OrdersPlugin::Item::DefineTotals
  28 + extend CurrencyHelper::ClassMethods
  29 + has_currency :buy_price
  30 +
  31 + # test this before use!
  32 + #validates_presence_of :cycle
  33 +
  34 + # remove on rails4
  35 + scope :with_price, conditions: 'products.price > 0'
  36 + scope :with_product_category_id, lambda { |id| { conditions: {product_category_id: id} } }
  37 + def self.search_scope scope, params
  38 + scope = scope.from_supplier_id params[:supplier_id] if params[:supplier_id].present?
  39 + scope = scope.with_available(if params[:available] == 'true' then true else false end) if params[:available].present?
  40 + scope = scope.name_like params[:name] if params[:name].present?
  41 + scope = scope.with_product_category_id params[:category_id] if params[:category_id].present?
  42 + scope
  43 + end
  44 +
  45 + def self.create_from product, cycle
  46 + op = self.new
  47 +
  48 + product.attributes.except('id').each{ |a,v| op.send "#{a}=", v }
  49 + op.freeze_default_attributes product
  50 + op.profile = product.profile
  51 + op.type = self.name
  52 +
  53 + op.from_products << product
  54 + cycle.products << op if cycle
  55 +
  56 + op
  57 + end
  58 +
  59 + # always recalculate in case something has changed
  60 + def margin_percentage
  61 + return self['margin_percentage'] if price.nil? or buy_price.nil? or price.zero? or buy_price.zero?
  62 + ((price / buy_price) - 1) * 100
  63 + end
  64 + def margin_percentage= value
  65 + self['margin_percentage'] = value
  66 + self.price = self.price_with_margins buy_price
  67 + end
  68 +
  69 + def sell_unit
  70 + self.unit || self.class.default_unit
  71 + end
  72 +
  73 + # reimplement to don't destroy this, keeping history in cycles
  74 + # offered products copy attributes
  75 + def dependent?
  76 + false
  77 + end
  78 +
  79 + # cycle products freezes properties and don't use the original
  80 + DEFAULT_ATTRIBUTES.each do |a|
  81 + define_method "default_#{a}" do
  82 + nil
  83 + end
  84 + end
  85 +
  86 + FROOZEN_DEFAULT_ATTRIBUTES = DEFAULT_ATTRIBUTES
  87 + def freeze_default_attributes from_product
  88 + FROOZEN_DEFAULT_ATTRIBUTES.each do |attr|
  89 + self[attr] = from_product.send(attr) if from_product[attr] or from_product.respond_to? attr
  90 + end
  91 + end
  92 +
  93 + def solr_index?
  94 + false
  95 + end
  96 +
  97 + protected
  98 +
  99 + after_update :sync_ordered
  100 + def sync_ordered
  101 + return unless self.price_changed?
  102 + self.items.each do |item|
  103 + item.calculate_prices self.price
  104 + item.save!
  105 + end
  106 + end
  107 +
  108 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/order.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +class OrdersCyclePlugin::Order < OrdersPlugin::Order
  2 +
  3 + # nothing here, see OrderBase
  4 + include OrdersCyclePlugin::OrderBase
  5 +
  6 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/order_base.rb 0 → 100644
... ... @@ -0,0 +1,64 @@
  1 +# This module is needed to pretend a multiple inheritance for Sale and Purchase
  2 +module OrdersCyclePlugin::OrderBase
  3 +
  4 + extend ActiveSupport::Concern
  5 + included do
  6 +
  7 + attr_accessible :cycle
  8 +
  9 + has_many :cycle_sales, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :sale_id, dependent: :destroy
  10 + has_one :cycle_sale, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :sale_id
  11 + has_many :cycle_purchases, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :purchase_id, dependent: :destroy
  12 + has_one :cycle_purchase, class_name: 'OrdersCyclePlugin::CycleOrder', foreign_key: :purchase_id
  13 + def all_cycles
  14 + self.cycle_sales.includes(:cycle).map(&:cycle) + self.cycle_purchases.includes(:cycle).map(&:cycle)
  15 + end
  16 +
  17 + # TODO: test if the has_one defined on Sale/Purchase works and these are not needed
  18 + def cycle
  19 + self.cycles.first
  20 + end
  21 + def cycle= cycle
  22 + self.cycles = [cycle]
  23 + end
  24 +
  25 + scope :for_cycle, -> (cycle) {
  26 + where('orders_cycle_plugin_cycles.id = ?', cycle.id).
  27 + joins(:cycles)
  28 + }
  29 +
  30 + has_many :items, class_name: 'OrdersCyclePlugin::Item', foreign_key: :order_id, dependent: :destroy, order: 'name ASC'
  31 +
  32 + has_many :offered_products, through: :items, source: :offered_product, uniq: true
  33 + has_many :distributed_products, through: :offered_products, source: :from_products, uniq: true
  34 + has_many :supplier_products, through: :distributed_products, source: :from_products, uniq: true
  35 +
  36 + has_many :suppliers, through: :supplier_products, uniq: true
  37 +
  38 + extend CodeNumbering::ClassMethods
  39 + code_numbering :code, scope: (proc do
  40 + if self.cycle then self.cycle.send(self.orders_name) else self.profile.orders end
  41 + end)
  42 +
  43 + def code
  44 + I18n.t('orders_cycle_plugin.lib.ext.orders_plugin.order.cyclecode_ordercode') % {
  45 + cyclecode: self.cycle.code, ordercode: self['code']
  46 + }
  47 + end
  48 +
  49 + def delivery_methods
  50 + self.cycle.delivery_methods
  51 + end
  52 +
  53 + def repeat_cycle= cycle
  54 + self.items.each{ |i| i.repeat_cycle = cycle }
  55 + end
  56 +
  57 + def available_products
  58 + self.cycle.products
  59 + end
  60 +
  61 + protected
  62 + end
  63 +
  64 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/purchase.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class OrdersCyclePlugin::Purchase < OrdersPlugin::Purchase
  2 +
  3 + include OrdersCyclePlugin::OrderBase
  4 +
  5 + has_many :cycles, through: :cycle_purchases, source: :cycle
  6 + has_one :cycle, through: :cycle_purchase, source: :cycle
  7 +
  8 +end
... ...
plugins/orders_cycle/models/orders_cycle_plugin/sale.rb 0 → 100644
... ... @@ -0,0 +1,80 @@
  1 +class OrdersCyclePlugin::Sale < OrdersPlugin::Sale
  2 +
  3 + include OrdersCyclePlugin::OrderBase
  4 +
  5 + has_many :cycles, through: :cycle_sales, source: :cycle
  6 + has_one :cycle, through: :cycle_sale, source: :cycle
  7 +
  8 + after_save :change_purchases, if: :cycle
  9 + before_destroy :remove_purchases_items, if: :cycle
  10 +
  11 + def current_status
  12 + return 'forgotten' if self.forgotten?
  13 + super
  14 + end
  15 +
  16 + def delivery?
  17 + self.cycle.delivery?
  18 + end
  19 + def forgotten?
  20 + self.draft? and !self.cycle.orders?
  21 + end
  22 +
  23 + def open?
  24 + super and self.cycle.orders?
  25 + end
  26 +
  27 + def supplier_delivery
  28 + super || (self.cycle.delivery_methods.first rescue nil)
  29 + end
  30 +
  31 + def change_purchases
  32 + return unless self.status_was.present?
  33 + if self.ordered_at_was.nil? and self.ordered_at.present?
  34 + self.add_purchases_items
  35 + elsif self.ordered_at_was.present? and self.ordered_at.nil?
  36 + self.remove_purchases_items
  37 + end
  38 + end
  39 +
  40 + def add_purchases_items
  41 + ActiveRecord::Base.transaction do
  42 + self.items.each do |item|
  43 + next unless supplier_product = item.product.supplier_product
  44 + next unless supplier = supplier_product.profile
  45 +
  46 + purchase = self.cycle.purchases.for_profile(supplier).first
  47 + purchase ||= OrdersCyclePlugin::Purchase.create! cycle: self.cycle, consumer: self.profile, profile: supplier
  48 +
  49 + purchased_item = purchase.items.for_product(supplier_product).first
  50 + purchased_item ||= purchase.items.build purchase: purchase, product: supplier_product
  51 + purchased_item.quantity_consumer_ordered ||= 0
  52 + purchased_item.quantity_consumer_ordered += item.status_quantity
  53 + purchased_item.price_consumer_ordered ||= 0
  54 + purchased_item.price_consumer_ordered += item.status_quantity * supplier_product.price
  55 + purchased_item.save!
  56 + end
  57 + end
  58 + end
  59 +
  60 + def remove_purchases_items
  61 + ActiveRecord::Base.transaction do
  62 + self.items.each do |item|
  63 + next unless supplier_product = item.product.supplier_product
  64 + next unless purchase = supplier_product.orders_cycles_purchases.for_cycle(self.cycle).first
  65 +
  66 + purchased_item = purchase.items.for_product(supplier_product).first
  67 + purchased_item.quantity_consumer_ordered -= item.status_quantity
  68 + purchased_item.price_consumer_ordered -= item.status_quantity * supplier_product.price
  69 + purchased_item.save!
  70 +
  71 + purchased_item.destroy if purchased_item.quantity_consumer_ordered.zero?
  72 + purchase.destroy if purchase.items(true).blank?
  73 + end
  74 + end
  75 + end
  76 +
  77 + handle_asynchronously :add_purchases_items
  78 + handle_asynchronously :remove_purchases_items
  79 +
  80 +end
... ...
plugins/orders_cycle/plugin.yml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +name: orders_cycle
  2 +dependencies:
  3 + - orders
  4 + - suppliers
  5 + - volunteers
... ...
plugins/orders_cycle/public/images/order-statuses.png 0 → 100644

598 Bytes

plugins/orders_cycle/public/images/progressbar.png 0 → 100644

320 Bytes

plugins/orders_cycle/public/javascripts/orders_cycle.js 0 → 100644
... ... @@ -0,0 +1,211 @@
  1 +
  2 +orders_cycle = {
  3 +
  4 + cycle: {
  5 +
  6 + edit: {
  7 + openingMessage: {
  8 + onKeyup: function(textArea) {
  9 + textArea = $(textArea)
  10 + var checked = textArea.val() ? true : false;
  11 + var checkBox = textArea.parents('#cycle-new-mail').find('input[type=checkbox]')
  12 + checkBox.prop('checked', checked)
  13 + },
  14 + },
  15 + },
  16 +
  17 + products: {
  18 + load_url: null,
  19 +
  20 + load: function () {
  21 + $.get(orders_cycle.cycle.products.load_url, function(data) {
  22 + if (data.length > 10)
  23 + $('#cycle-products .table').html(data)
  24 + else
  25 + setTimeout(orders_cycle.cycle.products.load, 5*1000);
  26 + });
  27 +
  28 + },
  29 + },
  30 + },
  31 +
  32 + /* ----- cycle ----- */
  33 +
  34 + in_cycle_order_toggle: function (context) {
  35 + container = $(context).hasClass('cycle-orders') ? $(context) : $(context).parents('.cycle-orders');
  36 + container.toggleClass('show');
  37 + container.find('.order-content').toggle();
  38 + sortable_table.edit_arrow_toggle(container);
  39 + },
  40 +
  41 + /* ----- order ----- */
  42 +
  43 + order: {
  44 +
  45 + load: function() {
  46 + $('html').click(function(e) {
  47 + $('.popover').remove()
  48 + })
  49 + },
  50 +
  51 + product: {
  52 + include_message: '',
  53 + order_id: 0,
  54 + redirect_after_include: '',
  55 + add_url: '',
  56 + remove_url: '',
  57 + balloon_url: '',
  58 +
  59 + load: function (id, state) {
  60 + var product = $('#cycle-product-'+id);
  61 + product.toggleClass('in-order', state);
  62 + product.find('input').get(0).checked = state;
  63 + toggle_edit.value_row.reload();
  64 + return product;
  65 + },
  66 +
  67 + showMore: function (url) {
  68 + $.get(url, function (data) {
  69 + var newProducts = $(data).filter('#cycle-products').find('.table-content').children()
  70 + $('.pagination').replaceWith(newProducts)
  71 + pagination.loading = false
  72 + })
  73 + },
  74 +
  75 + click: function (event, id) {
  76 + // was this a child click?
  77 + if (event != null && event.target != this && event.target.onclick)
  78 + return;
  79 +
  80 + var product = $('#cycle-product-'+id);
  81 + if (! product.hasClass('editable'))
  82 + return;
  83 +
  84 + var state = !product.hasClass('in-order');
  85 + if (state == true)
  86 + this.add(id);
  87 + else
  88 + this.remove(id);
  89 + product.find('input').get(0).checked = state;
  90 + },
  91 +
  92 + setEditable: function (editable) {
  93 + $('.order-cycle-product').toggleClass('editable', editable)
  94 + if (editable)
  95 + $('.order-cycle-product #product_ids_').removeAttr('disabled')
  96 + else
  97 + $('.order-cycle-product #product_ids_').attr('disabled', 'disabled')
  98 + },
  99 +
  100 + add: function (id) {
  101 + var product = this.load(id, true);
  102 +
  103 + if (this.include_message)
  104 + alert(this.include_message);
  105 +
  106 + loading_overlay.show(product);
  107 + $.post(this.add_url, {order_id: this.order_id, redirect: this.redirect_after_include, offered_product_id: id}, function () {
  108 + loading_overlay.hide(product);
  109 + }, 'script');
  110 + },
  111 + remove: function (id) {
  112 + var product = this.load(id, false);
  113 +
  114 + loading_overlay.show(product);
  115 + $.post(this.remove_url, {id: id, order_id: this.order_id}, function () {
  116 + loading_overlay.hide(product);
  117 + }, 'script');
  118 + },
  119 +
  120 + supplier: {
  121 + balloon_url: '',
  122 +
  123 + balloon: function (id) {
  124 + var product = $('#cycle-product-'+id)
  125 + var target = product.find('.supplier')
  126 + var supplier_id = product.attr('supplier-id')
  127 + $.get(this.balloon_url+'/'+supplier_id, function(data) {
  128 + var html = $(data)
  129 + var title = orders_cycle.order.product.balloon_title(html)
  130 + // use container to avoid conflict with row click
  131 + var options = {html: true, content: html, container: 'body', title: title}
  132 + target.popover(options).popover('show')
  133 + })
  134 + },
  135 + },
  136 +
  137 + balloon: function (id) {
  138 + var product = $('#cycle-product-'+id);
  139 + var target = product.find('.product');
  140 + $.get(this.balloon_url+'/'+id, function(data) {
  141 + var html = $(data)
  142 + var title = orders_cycle.order.product.balloon_title(html)
  143 + // use container to avoid conflict with row click
  144 + var options = {html: true, content: html, container: 'body', title: title}
  145 + target.popover(options).popover('show')
  146 + })
  147 + },
  148 +
  149 + balloon_title: function(content) {
  150 + var titleElement = $(content).find('.popover-title')
  151 + var title = titleElement.html()
  152 + titleElement.hide()
  153 + return title
  154 + },
  155 + }, // product
  156 + }, // order
  157 +
  158 + /* ----- cycle editions ----- */
  159 +
  160 + offered_product: {
  161 +
  162 + pmsync: function (context, to_price) {
  163 + p = $(context).parents('.cycle-product .box-edit');
  164 + margin = p.find('#product_margin_percentage');
  165 + price = p.find('#product_price');
  166 + buy_price = p.find('#product_buy_price');
  167 + original_price = p.find('#product_original_price');
  168 + base_price = unlocalize_currency(buy_price.val()) ? buy_price : original_price;
  169 +
  170 + if (to_price)
  171 + suppliers.price.calculate(price, margin, base_price);
  172 + else
  173 + suppliers.margin.calculate(margin, price, base_price);
  174 + },
  175 +
  176 + edit: function () {
  177 + toggle_edit.editing().find('.box-edit').toggle(toggle_edit.isEditing());
  178 + },
  179 + },
  180 +
  181 + /* ----- toggle edit ----- */
  182 +
  183 + cycle_mail_message_toggle: function () {
  184 + if ($('#cycle-new-mail-send').prop('checked')) {
  185 + $('#cycle-new-mail').removeClass('disabled');
  186 + $('#cycle-new-mail textarea').removeAttr('disabled');
  187 + } else {
  188 + $('#cycle-new-mail').addClass('disabled');
  189 + $('#cycle-new-mail textarea').attr('disabled', true);
  190 + }
  191 + },
  192 +
  193 + ajaxifyPagination: function(selector) {
  194 + $(selector).find(".pagination a").click(function() {
  195 + loading_overlay.show(selector);
  196 + $.ajax({
  197 + type: "GET",
  198 + url: $(this).attr("href"),
  199 + dataType: "script"
  200 + });
  201 + return false;
  202 + });
  203 + },
  204 +
  205 + toggleCancelledOrders: function () {
  206 + $('.consumers-coop #show-cancelled-orders a').toggle();
  207 + $('.consumers-coop #hide-cancelled-orders a').toggle();
  208 + $('.consumers-coop .consumer-order.cancelled').not('.comsumer-order.active-order').toggle();
  209 + },
  210 +
  211 +};
... ...
plugins/orders_cycle/public/style.scss 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +@import 'stylesheets/orders_cycle'
  2 +
... ...
plugins/orders_cycle/public/stylesheets/_base.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_base.scss
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/public/stylesheets/_field.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../suppliers/public/stylesheets/_field.scss
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/public/stylesheets/_orders-variables.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_orders-variables.scss
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/public/stylesheets/_sortable-table.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_sortable-table.scss
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/public/stylesheets/orders_cycle.scss 0 → 100644
... ... @@ -0,0 +1,620 @@
  1 +@import 'base';
  2 +@import 'orders-variables';
  3 +
  4 +@import 'sortable-table';
  5 +
  6 +.balloon-content.orders-cycle {
  7 + @extend .container-clean;
  8 + // the size is defined on .balloon .content (see above)
  9 +
  10 + .left-column {
  11 + width: $module01;
  12 + margin-right: $half-margin;
  13 + float: left;
  14 + }
  15 + .right-column {
  16 + width: $module02;
  17 + float: right;
  18 + }
  19 +
  20 +}
  21 +
  22 +.cycle-view {
  23 +
  24 + #cycle-others,
  25 + #cycle-header {
  26 + padding-bottom: $padding;
  27 + margin-bottom: $half-margin;
  28 + margin-left: -2*$border;
  29 + padding-right: 2*$border;
  30 + }
  31 + #cycle-header {
  32 + border-bottom: 2*$border solid #ddd;
  33 + margin-bottom: 0;
  34 +
  35 + .subtitle,
  36 + #cycle-dates {
  37 + width: 520px;
  38 +
  39 + th {
  40 + border-bottom: 0;
  41 + }
  42 + }
  43 +
  44 + .subtitle {
  45 + display: block;
  46 + margin-top: 20px;
  47 + font-style: italic;
  48 + }
  49 + }
  50 +
  51 + #cycle-products-for-order {
  52 + width: $module06 + $intercolumn;
  53 + float: left;
  54 + padding-left: 0;
  55 +
  56 + .title {
  57 + }
  58 +
  59 + .balloon {
  60 +
  61 + .content {
  62 + width: $module03 + $intercolumn; //$intercolumn for scrollbars
  63 + max-height: 10*$height;
  64 + }
  65 + }
  66 +
  67 + #filter {
  68 + margin-left: -$sortable-table-negative-area;
  69 +
  70 + .title, .filter-box, .submit {
  71 + padding-left: $sortable-table-negative-area + $padding !important;
  72 + }
  73 +
  74 + .filter-box {
  75 + padding-left: $padding;
  76 +
  77 + &.fields {
  78 +
  79 + .supplier {
  80 + padding-left: 0;
  81 + }
  82 + .name {
  83 + padding-right: 0;
  84 + }
  85 + }
  86 + }
  87 + }
  88 +
  89 + #cycle-products {
  90 +
  91 + .table-header, .table-content {
  92 + margin-left: -$sortable-table-negative-area;
  93 + }
  94 + .table-header {
  95 + padding-left: $sortable-table-negative-area;
  96 + }
  97 +
  98 +
  99 + .table-content {
  100 + //override .product-pic from application.css
  101 + .product-pic {
  102 + max-width: 70px;
  103 + max-height: 70px;
  104 + }
  105 + }
  106 +
  107 + .box-field {
  108 + &, * {
  109 + text-overflow: ellipsis;
  110 + overflow: hidden;
  111 + white-space: nowrap;
  112 + }
  113 + &.category {
  114 + width: $module02 - 3*$base;
  115 + }
  116 + &.supplier {
  117 + width: $module02;
  118 + }
  119 + &.product {
  120 + width: $module02;
  121 + }
  122 + &.price-with-unit {
  123 + width: $module01;
  124 + }
  125 + }
  126 +
  127 + .value-row {
  128 + border: none;
  129 +
  130 + #product_ids_[disabled] {
  131 + display: none;
  132 + }
  133 +
  134 + &.in-order {
  135 +
  136 + .box-view {
  137 + background-color: $sortable-table-bg-yellow;
  138 + }
  139 +
  140 + }
  141 + }
  142 + }
  143 + }
  144 +
  145 + #order-column {
  146 + float: left;
  147 + width: $order-items-width;
  148 + margin-left: $margin;
  149 + padding: 0;
  150 +
  151 + > h3 {
  152 + margin-bottom: 2*$margin;
  153 + }
  154 + > h3 a {
  155 + text-transform: none;
  156 + float: right;
  157 + margin-left: $half-margin;
  158 + &:before {
  159 + padding-right: $half-padding;
  160 + }
  161 + }
  162 +
  163 + .consumer-order,
  164 + #order-page .in-order {
  165 + background-color: $sortable-table-bg-yellow;
  166 + }
  167 + .consumer-order {
  168 + margin-bottom: 10px;
  169 + width: $order-items-width;
  170 +
  171 + &.unactive {
  172 + .order-message-title {
  173 + float: left;
  174 + }
  175 + .actions {
  176 + float: right;
  177 + padding: $half-padding $padding;
  178 + }
  179 + &.cancelled {
  180 + display: none;
  181 + }
  182 + }
  183 + }
  184 +
  185 + #order-admin-warning {
  186 + background-color: #FECBCA;
  187 + }
  188 +
  189 + #order-admin-warning {
  190 + margin-bottom: $margin;
  191 + }
  192 +
  193 + .order-message {
  194 + padding: $padding;
  195 + }
  196 +
  197 + .order-message-text {
  198 + font-size: 10px;
  199 + font-style: italic;
  200 + }
  201 + .order-message-actions {
  202 + margin-top: $half-margin;
  203 + font-size: 10px;
  204 + }
  205 +
  206 + .order-message-actions a {
  207 + font-weight: bold;
  208 + }
  209 +
  210 + #close-order,
  211 + #desist-order {
  212 + margin-top: 30px;
  213 + margin-bottom: 8px;
  214 + }
  215 +
  216 + .quantity-entry input {
  217 + width: 76px;
  218 + }
  219 + }
  220 +}
  221 +
  222 +#cycle-admin-page {
  223 + @import 'field';
  224 +
  225 + a.action-button {
  226 + margin-top: 5px;
  227 + }
  228 + #cycle-header-warning {
  229 + background-color: #FECBCA;
  230 + padding: 8px;
  231 +
  232 + #cycle-header-warning-wrap {
  233 + width: 70%;
  234 +
  235 + }
  236 + }
  237 +
  238 + #cycle-product-line .field {
  239 + width: 120px;
  240 + }
  241 +
  242 + #cycle-products {
  243 + margin-top: $margin;
  244 +
  245 + .header {
  246 + div {
  247 + width: 400px;
  248 + }
  249 + a {
  250 + line-height: 30px;
  251 + }
  252 + }
  253 +
  254 + .table {
  255 + .small-loading {
  256 + background-position: 0;
  257 + padding-left: 20px;
  258 + }
  259 + }
  260 +
  261 + .cycle-products-table {
  262 + margin-top: 30px;
  263 + }
  264 + .value-row.edit .price,
  265 + .value-row.edit .quantity-available,
  266 + .value-row.edit .actions {
  267 + visibility: hidden;
  268 + }
  269 +
  270 + .box-edit .field {
  271 + float: left;
  272 + clear: none;
  273 + margin-right: 40px;
  274 + width: 120px;
  275 + }
  276 +
  277 + .box-view .box-field {
  278 + }
  279 + .box-field.category {
  280 + width: 90px;
  281 + }
  282 + .box-field.supplier {
  283 + width: 120px;
  284 + }
  285 + .box-field.name {
  286 + width: 190px;
  287 + }
  288 + .box-field.price {
  289 + width: 160px;
  290 + }
  291 + .box-field.quantity-available {
  292 + width: 80px;
  293 + }
  294 + .quantity-available input {
  295 + width: 60px;
  296 + }
  297 + .box-edit input[type=text] {
  298 + width: 120px;
  299 + }
  300 + }
  301 +}
  302 +
  303 +#cycle-closed-listing {
  304 + padding-left: 0;
  305 +}
  306 +
  307 +#cycle-new-mail-send {
  308 + margin-bottom: 0;
  309 +}
  310 +
  311 +#cycle-new-mail label {
  312 + font-weight: bold;
  313 +}
  314 +
  315 +#cycle-new-mail .mail-message {
  316 + margin-top: 10px;
  317 +}
  318 +
  319 +
  320 +.cycle {
  321 +
  322 + .h2 {
  323 + margin-bottom: 0;
  324 + }
  325 +
  326 + .cycle-timeline {
  327 + border: 2*$border solid #CBCBCB;
  328 + margin-bottom: $margin;
  329 + @extend .container-clean;
  330 +
  331 + //wireframe size
  332 + width: $wireframe + 2*$wireframe-padding;
  333 + margin-left: -$wireframe-padding;
  334 + padding: 0 $wireframe-padding;
  335 +
  336 + &,
  337 + .cycle-timeline-passed-item,
  338 + .cycle-timeline-current-item {
  339 + background: url(/plugins/orders_cycle/images/progressbar.png) left top no-repeat;
  340 + }
  341 + & {
  342 + background-size: $wireframe-padding $wireframe-padding*2; //height is arbitrary big to fit
  343 + }
  344 +
  345 + .cycle-timeline-item {
  346 + float: left;
  347 + padding: $padding;
  348 + font-weight: bold;
  349 +
  350 + &:first-child {
  351 + padding-left: 0;
  352 + }
  353 + }
  354 +
  355 + .cycle-timeline-current-item {
  356 + background-position: 105% 0;
  357 + }
  358 + .cycle-timeline-next-item {
  359 + color: #BABABA;
  360 + }
  361 + .cycle-timeline-selected-item {
  362 + color: black;
  363 + text-decoration: none;
  364 + font-weight: bold;
  365 + }
  366 +
  367 + }
  368 +
  369 + .dates-brief {
  370 + margin-bottom: 2*$margin;
  371 + }
  372 +
  373 + .actions-bar {
  374 + clear: both;
  375 + }
  376 +}
  377 +
  378 +
  379 +.cycle-product-line *[disabled] {
  380 + background-color: transparent;
  381 +}
  382 +
  383 +#cycle-dates th {
  384 + width: 80px;
  385 + vertical-align: top;
  386 + border-bottom: 0;
  387 +
  388 + .cycle-list-item {
  389 + border-top: 1px dotted #ccc;
  390 + padding: 8px;
  391 + }
  392 + .cycle-list-item:last-child {
  393 + border-bottom: 1px dotted #ccc;
  394 + }
  395 + .cycle-name-and-code {
  396 + font-weight: bold;
  397 + }
  398 +}
  399 +
  400 +#orders-view {
  401 +
  402 + h2 {
  403 + margin-top: 20px;
  404 + }
  405 +
  406 + .consumer-orders {
  407 +
  408 + #cycle-others {
  409 + border-bottom: 2px dotted #aaa;
  410 + }
  411 + #consumer-new-order {
  412 + font-weight: bold;
  413 + }
  414 +
  415 + #order-place-new {
  416 + font-size: 10px;
  417 + border: $border solid #FFF0AD;
  418 + padding: $padding;
  419 + margin-bottom: 10px;
  420 +
  421 + &.admin {
  422 + font-size: 12px;
  423 + border: none;
  424 + float: right;
  425 + padding: $padding 0;
  426 + text-transform: lower;
  427 + }
  428 + }
  429 +
  430 + #years-filter {
  431 + margin-bottom: $margin;
  432 +
  433 + a {
  434 + font-weight: bold;
  435 +
  436 + &.current {
  437 + text-decoration: none;
  438 + color: black;
  439 + }
  440 + }
  441 + }
  442 +
  443 + .order-message-title {
  444 + float: left;
  445 + margin-right: 5px;
  446 + }
  447 + }
  448 +
  449 + .cycle-with-consumer-orders {
  450 + padding-bottom: 10px;
  451 + border-top: 2px dotted #aaa;
  452 +
  453 + &:last-child {
  454 + border-bottom: 2px dotted #aaa;
  455 + }
  456 + }
  457 +
  458 + .consumer-orders {
  459 +
  460 + > a {
  461 + float: left;
  462 + }
  463 + a {
  464 + text-decoration: none;
  465 + }
  466 + a div {
  467 + font-size: 10px;
  468 + color: black;
  469 + }
  470 + }
  471 +}
  472 +
  473 +.order-data .order-message-title.cancelled {
  474 + display: block;
  475 +}
  476 +.order-message-title {
  477 + padding: $half-padding $padding;
  478 + font-size: 13px;
  479 + font-weight: bold;
  480 + @extend .container-clean;
  481 +
  482 + div {
  483 + float: left;
  484 + background-size: 11px 30px;
  485 + }
  486 +
  487 + a.open {
  488 + display: none;
  489 + float: right;
  490 + font-weight: normal;
  491 + }
  492 +
  493 + &.open {
  494 + background-color: #FFF2A3;
  495 + div {
  496 + padding-left: 20px;
  497 + background: url(/plugins/orders_cycle/images/order-statuses.png) 0px -25px no-repeat;
  498 + padding-left: 24px;
  499 + margin-bottom: 0;
  500 + }
  501 + }
  502 +
  503 + &.ordered {
  504 + background-color: #E1F5C4;
  505 + div {
  506 + background: url(/plugins/orders_cycle/images/order-statuses.png) left -2px no-repeat;
  507 + padding-left: 24px;
  508 + }
  509 + }
  510 +
  511 + &.cancelled {
  512 + }
  513 +
  514 + &.cancelled,
  515 + &.forgotten {
  516 + background-color: #FECBCA;
  517 + }
  518 +}
  519 +
  520 +#cycles-gadget {
  521 + margin-bottom: 30px;
  522 +
  523 + #all-cycles {
  524 + float: right;
  525 + }
  526 + h2 {
  527 + color: #036475;
  528 + }
  529 +
  530 + td {
  531 + padding-right: 8px;
  532 + vertical-align: top;
  533 + }
  534 + td.first-column {
  535 + width: 300px;
  536 + }
  537 + td.second-column {
  538 + width: 400px;
  539 + }
  540 + td.third-column {
  541 + vertical-align: bottom;
  542 + }
  543 + td.third-column form {
  544 + float: right;
  545 + clear: both;
  546 + }
  547 + .cycle .happening {
  548 + background-color: #ffe546;
  549 + line-height: 20px;
  550 + text-transform: uppercase;
  551 + padding: 0 8px;
  552 + }
  553 + .cycle .info {
  554 + background: #DDF2C5;
  555 + padding: 8px;
  556 + padding-right: 0;
  557 + }
  558 +}
  559 +
  560 +#cycle_opening_message {
  561 + margin-bottom: $intercolumn;
  562 + width: 420px;
  563 +}
  564 +
  565 +#cycle-fields input {
  566 + margin-right: 7px;
  567 +}
  568 +.cycle-field-name input {
  569 + width: 195px;
  570 +}
  571 +.cycle-fields-block {
  572 + margin: 10px 0;
  573 +}
  574 +.cycle-field-description textarea {
  575 + width: 412px;
  576 + height: 100px;
  577 +}
  578 +
  579 +.action-orders_cycle_plugin_cycle-new .cycle-edit-link,
  580 +.action-orders_cycle_plugin_cycle-edit .cycle-edit-link {
  581 + height: 44px;
  582 + display: block;
  583 +}
  584 +
  585 +#cycle-delivery-options {
  586 + clear: both;
  587 +}
  588 +#cycle-delivery label {
  589 + font-weight: bold;
  590 +}
  591 +
  592 +.cycle-new {
  593 + margin-bottom: 29px;
  594 + display: block;
  595 +}
  596 +
  597 +.cycle-delivery-option {
  598 + float: left;
  599 + display: inline-block;
  600 + padding: 2px 5px;
  601 + margin-right: 10px;
  602 + background-color: #E5E0EC;
  603 + border-radius: 4px;
  604 + -moz-border-radius: 4px;
  605 + -webkit-border-radius: 4px;
  606 + -o-border-radius: 4px;
  607 +}
  608 +
  609 +.action-orders_cycle_plugin_cycle-new #colorbox, .action-orders_cycle_plugin_cycle-edit #colorbox {
  610 + top: 80%;
  611 +}
  612 +
  613 +#cycle-orders {
  614 + margin-top: $margin;
  615 +
  616 +}
  617 +
  618 +.distribution-plugin-popin {
  619 + padding: 10px;
  620 +}
... ...
plugins/orders_cycle/test/factories.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +module OrdersCyclePlugin::Factory
  2 +
  3 + def defaults_for_suppliers_plugin_supplier
  4 + {:profile => build(Profile),
  5 + :consumer => build(Profile)}
  6 + end
  7 +
  8 + def defaults_for_suppliers_plugin_distributed_product attrs = {}
  9 + profile = attrs[:profile] || build(Profile)
  10 + {:profile => profile, :name => "product-#{factory_num_seq}", :price => 2.0,
  11 + :product => build(Product, :enterprise => profile.profile, :price => 2.0),
  12 + :supplier => build(SuppliersPlugin::Supplier, :profile => profile, :consumer => profile)}
  13 + end
  14 +
  15 + def defaults_for_orders_cycle_plugin_offered_product attrs = {}
  16 + hash = defaults_for_orders_cycle_plugin_product(attrs)
  17 + profile = hash[:profile]
  18 + hash.merge({
  19 + :from_products => [build(SuppliersPlugin::DistributedProduct, :profile => profile)]})
  20 + end
  21 +
  22 + def defaults_for_delivery_plugin_method
  23 + {:profile => build(OrdersCyclePlugin::Profile),
  24 + :name => "My delivery #{factory_num_seq.to_s}",
  25 + :delivery_type => 'deliver'}
  26 + end
  27 +
  28 + def defaults_for_delivery_plugin_option
  29 + {:cycle => build(OrdersCyclePlugin::Cycle),
  30 + :delivery_method => build(DeliveryPlugin::Method)}
  31 + end
  32 +
  33 + def defaults_for_orders_plugin_order attrs = {}
  34 + profile = attrs[:profile] || build(OrdersCyclePlugin::Profile)
  35 + {:status => 'ordered',
  36 + :cycle => build(OrdersCyclePlugin::Cycle, :profile => profile),
  37 + :consumer => build(OrdersCyclePlugin::Profile),
  38 + :supplier_delivery => build(DeliveryPlugin::Method, :profile => profile),
  39 + :consumer_delivery => build(DeliveryPlugin::Method, :profile => profile)}
  40 + end
  41 +
  42 + def defaults_for_orders_plugin_items
  43 + {:order => build(OrdersPlugin::Order),
  44 + :product => build(OrdersCyclePlugin::OfferedProduct),
  45 + :quantity_shipped => 1.0, :quantity_ordered => 2.0, :quantity_accepted => 3.0,
  46 + :price_shipped => 10.0, :price_ordered => 20.0, :price_accepted => 30.0}
  47 + end
  48 +
  49 + def defaults_for_orders_cycle_plugin_cycle
  50 + {:profile => build(OrdersCyclePlugin::Profile), :status => 'orders',
  51 + :name => 'weekly', :start => Time.now, :finish => Time.now+1.days}
  52 + end
  53 +
  54 +end
  55 +
  56 +Noosfero::Factory.register_extension OrdersCyclePlugin::Factory
  57 +
... ...
plugins/orders_cycle/test/functional/orders_cycle_plugin/order_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class OrdersCyclePlugin::OrderControllerTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @controller = OrdersCyclePluginOrderController.new
  7 + @request = ActionController::TestRequest.new
  8 + @response = ActionController::TestResponse.new
  9 + end
  10 +
  11 +
  12 +end
... ...
plugins/orders_cycle/test/functional/orders_cycle_plugin/session_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class OrdersCyclePlugin::CycleControllerTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @controller = OrdersCyclePluginCycleController.new
  7 + @request = ActionController::TestRequest.new
  8 + @response = ActionController::TestResponse.new
  9 + end
  10 +
  11 + should 'create a new cycle' do
  12 + end
  13 +
  14 +end
... ...
plugins/orders_cycle/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
  2 +require 'spec'
  3 +
  4 +class ActiveRecord::TestCase < ActiveSupport::TestCase
  5 + include OrdersCyclePluginFactory
  6 +end
... ...
plugins/orders_cycle/test/unit/orders_cycle_plugin/cycle_test.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class OrdersCyclePlugin::CycleTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @profile = Enterprise.create!(:name => "trocas verdes", :identifier => "trocas-verdes")
  7 + @pc = ProductCategory.create!(:name => 'frutas', :environment_id => 1)
  8 + @profile.products = [Product.create!(:name => 'banana', :product_category => @pc),
  9 + Product.new(:name => 'mandioca', :product_category => @pc), Product.new(:name => 'alface', :product_category => @pc)]
  10 +
  11 + profile.offered_products = @profile.products.map{ |p| OrdersCyclePlugin::OfferedProduct.create!(:product => p) }
  12 + DeliveryPlugin::Method.create! :name => 'at home', :delivery_type => 'pickup', :profile => @profile
  13 + @cycle = OrdersCyclePlugin::Cycle.create!(:profile => @profile)
  14 + end
  15 +
  16 + should 'add products from profile after create' do
  17 + assert_equal @cycle.products.collect(&:product_id), @profile.products.collect(&:id)
  18 + end
  19 +
  20 + should 'have at least one delivery method unless in edition status' do
  21 + cycle = OrdersCyclePlugin::Cycle.create! :profile => @profile, :name => "Testes batidos", :start => DateTime.now, :status => 'edition'
  22 + assert cycle
  23 + cycle.status = 'orders'
  24 + assert_nil cycle.save!
  25 + end
  26 +
  27 +end
... ...
plugins/orders_cycle/test/unit/orders_cycle_plugin/offered_product_test.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class OrdersCyclePlugin::OfferedProductTest < ActiveSupport::TestCase
  4 +
  5 +
  6 +end
... ...
plugins/orders_cycle/test/unit/profile_test.rb 0 → 100644
... ... @@ -0,0 +1,127 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class OrdersCyclePlugin::ProfileTest < ActiveRecord::TestCase
  4 +
  5 + def setup
  6 + @profile = build(Profile)
  7 + @invisible_profile = build(Enterprise, :visible => false)
  8 + @other_profile = build(Enterprise)
  9 + @profile = build(OrdersCyclePlugin::profile, :profile => @profile)
  10 + @self_supplier = build(OrdersCyclePlugin::Supplier, :consumer => @profile, :profile => @profile)
  11 + @dummy_supplier = build(OrdersCyclePlugin::Supplier, :consumer => @profile, :profile => @dummy_profile)
  12 + @other_supplier = build(OrdersCyclePlugin::Supplier, :consumer => @profile, :profile => @other_profile)
  13 + end
  14 +
  15 + attr_accessor :profile, :invisible_profile, :other_profile,
  16 + :self_supplier, :dummy_supplier, :other_supplier
  17 +
  18 + should 'respond to name methods' do
  19 + profile.expects(:name).returns('name')
  20 + assert_equal 'name', profile.name
  21 + end
  22 +
  23 + should 'respond to dummy methods' do
  24 + profile.dummy = true
  25 + assert_equal true, profile.dummy?
  26 + profile.dummy = false
  27 + assert_equal false, profile.dummy
  28 + end
  29 +
  30 + should "return closed cycles' date range" do
  31 + DateTime.expects(:now).returns(1).at_least_once
  32 + assert_equal 1..1, profile.orders_cycles_closed_date_range
  33 + s1 = create(OrdersCyclePlugin::Cycle, :profile => profile, :start => Time.now-1.days, :finish => nil)
  34 + s2 = create(OrdersCyclePlugin::Cycle, :profile => profile, :finish => Time.now+1.days, :start => Time.now)
  35 + assert_equal (s1.start.to_date..s2.finish.to_date), profile.orders_cycles_closed_date_range
  36 + end
  37 +
  38 + should 'return abbreviation or the name' do
  39 + profile.name_abbreviation = 'coll.'
  40 + profile.profile.name = 'collective'
  41 + assert_equal 'coll.', profile.abbreviation_or_name
  42 + profile.name_abbreviation = nil
  43 + assert_equal 'collective', profile.abbreviation_or_name
  44 + end
  45 +
  46 + ###
  47 + # Products
  48 + ###
  49 +
  50 + should "default products's margins when asked" do
  51 + profile.update_attributes! :margin_percentage => 10
  52 + product = create(SuppliersPlugin::DistributedProduct, :profile => profile, :supplier => profile.self_supplier,
  53 + :price => 10, :default_margin_percentage => false)
  54 + cycle = create(OrdersCyclePlugin::Cycle, :profile => profile)
  55 + sproduct = cycle.products.first
  56 + sproduct.update_attributes! :margin_percentage => 5
  57 + cycleclosed = create(OrdersCyclePlugin::Cycle, :profile => profile, :status => 'closed')
  58 +
  59 + profile.orders_cycles_products_default_margins
  60 + product.reload
  61 + sproduct.reload
  62 + assert_equal true, product.default_margin_percentage
  63 + assert_equal sproduct.margin_percentage, profile.margin_percentage
  64 + end
  65 +
  66 + should 'return not yet distributed products' do
  67 + profile.save!
  68 + other_profile.save!
  69 + other_supplier.save!
  70 + product = create(SuppliersPlugin::DistributedProduct, :profile => other_profile, :supplier => other_profile.self_supplier)
  71 + profile.add_supplier_products other_supplier
  72 + product2 = create(SuppliersPlugin::DistributedProduct, :profile => other_profile, :supplier => other_profile.self_supplier)
  73 + assert_equal [product2], profile.not_distributed_products(other_supplier)
  74 + end
  75 +
  76 + ###
  77 + # Suppliers
  78 + ###
  79 +
  80 + should 'add supplier' do
  81 + @profile.save!
  82 + @other_profile.save!
  83 +
  84 + assert_difference OrdersCyclePlugin::Supplier, :count do
  85 + @profile.add_supplier @other_profile
  86 + end
  87 + assert @profile.suppliers_profiles.include?(@other_profile)
  88 + assert @other_profile.consumers_profiles.include?(@profile)
  89 + end
  90 +
  91 + should "add all supplier's products when supplier is added" do
  92 + @profile.save!
  93 + @other_profile.save!
  94 + product = create(SuppliersPlugin::DistributedProduct, :profile => @other_profile)
  95 + @profile.add_supplier @other_profile
  96 + assert_equal [product], @profile.from_products
  97 + end
  98 +
  99 + should 'remove supplier' do
  100 + @profile.save!
  101 + @other_profile.save!
  102 +
  103 + @profile.add_supplier @other_profile
  104 + assert_difference OrdersCyclePlugin::Supplier, :count, -1 do
  105 + assert_difference RoleAssignment, :count, -1 do
  106 + @profile.remove_supplier @other_profile
  107 + end
  108 + end
  109 + assert !@profile.suppliers_profiles.include?(@other_profile)
  110 + end
  111 +
  112 + should "archive supplier's products when supplier is removed" do
  113 + @profile.save!
  114 + @other_profile.save!
  115 + product = create(SuppliersPlugin::DistributedProduct, :profile => @other_profile)
  116 + @profile.add_supplier @other_profile
  117 + @profile.remove_supplier @other_profile
  118 + assert_equal [product], @profile.from_products
  119 + assert_equal 1, @profile.distributed_products.archived.count
  120 + end
  121 +
  122 + should 'create self supplier automatically' do
  123 + profile = create(OrdersCyclePlugin::profile, :profile => @profile)
  124 + assert_equal 1, profile.suppliers.count
  125 + end
  126 +
  127 +end
... ...
plugins/orders_cycle/views/orders_cycle_plugin/mailer/open_cycle.html.erb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 + <head>
  4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  5 + </head>
  6 + <body>
  7 + <p>
  8 + <%= t('views.mailer.open_cycle.hello_member_of_name') % {:name => @profile.name} %>
  9 + </p>
  10 +
  11 + <p><%= t('views.mailer.open_cycle.a_new_cycle_is_open_c') + @cycle.name %></p>
  12 + <hr />
  13 + <p><i><%= @cycle.description %></i></p>
  14 + <hr />
  15 + <p><%= t('views.mailer.open_cycle.the_administrator_let') %></p>
  16 + <p><i><%= @message %></i></p>
  17 +
  18 + <p><%= link_to t('views.order.index.place_an_order'), {:controller => :orders_cycle_plugin_order, :action => :edit, :cycle_id => @cycle.id, :profile => @profile.identifier, :only_path => false, :host => @profile.hostname || @environment.default_hostname }, :id => 'consumer-new-order' %></p>
  19 +
  20 + <p><%= @domain %>
  21 + <p>
  22 + --<br/>
  23 + </p>
  24 +
  25 + <small style="color: #888"><%= @environment.name %></small>
  26 + </body>
  27 +</html>
  28 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_closed.html.erb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +<%= render 'title', :title => t('views.cycle._closed.cycle_already_finishe') %>
  2 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_closing.html.erb 0 → 100644
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_cycle_purchases.html.slim 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 += render 'orders_plugin_admin/purchases', actors: @cycle.suppliers, orders_owner: @cycle, owner_id: @cycle.id,
  2 + orders: @cycle.purchases.default_order.paginate(per_page: 30, page: params[:page])
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_cycle_sales.html.slim 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 += render 'orders_plugin_admin/sales', actors: @cycle.consumers, orders_owner: @cycle, owner_id: @cycle.id,
  2 + orders: @cycle.sales.default_order.paginate(per_page: 30, page: params[:page])
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_delivery.html.erb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<div class="subtitle">
  2 + <%= hideable_help_text t('views.cycle._delivery.header_help') %>
  3 +</div>
  4 +
  5 +<%= render 'orders_cycle_plugin_cycle/cycle_sales' %>
  6 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_edit_fields.html.slim 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 += render 'orders_plugin/shared/daterangepicker/init'
  2 +
  3 +h3= t('views.cycle._edit_fields.general_settings')
  4 +
  5 += form_for @cycle, as: :cycle , remote: true, url: {action: @cycle.new? ? :new : :edit, id: @cycle.id }, html: {data: {loading: '#cycle-fields form'}} do |f|
  6 +
  7 + = labelled_field f, :name, t('views.cycle._edit_fields.name'), f.text_field(:name), class: 'cycle-field-name'
  8 + = labelled_field f, :description, t('views.cycle._edit_fields.description'), f.text_area(:description, class: 'mceEditor'), class: 'cycle-field-description'
  9 + = render file: 'shared/tiny_mce', locals: {mode: 'simple'}
  10 +
  11 + .cycle-fields-block
  12 + = labelled_datetime_range_field f, :start, :finish, t('views.cycle._edit_fields.orders_interval'), class: 'cycle-orders-period'
  13 + .cycle-fields-block
  14 + = labelled_datetime_range_field f, :delivery_start, :delivery_finish, t('views.cycle._edit_fields.deliveries_interval'), class: 'cycle-orders-period'
  15 +
  16 + .cycle-fields-block
  17 + #cycle-delivery.field
  18 + = f.label :delivery_methods, t('views.cycle._edit_fields.available_delivery_me')
  19 + div
  20 + #cycle-delivery-options.subtitle
  21 + = render 'delivery_plugin/admin_options/index', owner: @cycle
  22 + = modal_link_to t('views.cycle._edit_fields.add_method'),
  23 + {controller: :orders_cycle_plugin_delivery_option, action: :select, owner_id: @cycle.id, owner_type: @cycle.class.name},
  24 + class: 'subtitle'
  25 + |&nbsp;
  26 + = link_to_function t('views.cycle._edit_fields.add_all_methods'),
  27 + "$.getScript('#{url_for controller: :orders_cycle_plugin_delivery_option, action: :select_all, owner_id: @cycle.id, owner_type: @cycle.class.name}')",
  28 + class: 'subtitle'
  29 + .clean
  30 +
  31 + - if profile.volunteers_settings.cycle_volunteers_enabled
  32 + = render 'volunteers_plugin_myprofile/edit_periods', owner: @cycle, f: f
  33 +
  34 + #cycle-new-mail
  35 + = check_box_tag('sendmail', 'yes', false, id: 'cycle-new-mail-send')
  36 + = content_tag('label', t('views.cycle._edit_fields.notify_members_of_ope'), for: 'sendmail')
  37 + .mail-message
  38 + = f.label :sendmail, t('views.cycle._edit_fields.opening_message')
  39 + div= t('views.cycle._edit_fields.this_message_will_be_')
  40 + = f.text_area(:opening_message, onkeyup: 'orders_cycle.cycle.edit.openingMessage.onKeyup(this)')
  41 + javascript:
  42 + $('#cycle-new-mail-send').on('click', orders_cycle.cycle_mail_message_toggle)
  43 +
  44 + - submit_text = if @cycle.new? then t('views.cycle._edit_fields.create_new_cycle') else t('views.cycle._edit_fields.save') end
  45 + = f.submit submit_text
  46 + - unless @cycle.passed_by? 'edition'
  47 + |&nbsp;
  48 + = hidden_field_tag 'open', nil
  49 + - text = t("views.cycle._edit_fields.#{if @cycle.new? then 'create' else 'save' end}_and_open_orders")
  50 + = f.submit text, onclick: "this.form.elements['open'].value = '1'"
  51 +
  52 + |&nbsp;
  53 + = link_to t('views.cycle._edit_fields.cancel_changes'), @cycle.new? ? {action: :index} : params
  54 + - unless @cycle.new?
  55 + |&nbsp;
  56 + = link_to t('views.cycle._edit_fields.remove'), {action: :destroy, id: @cycle.id}, confirm: t('views.cycle._edit_fields.confirm_remove')
  57 +
  58 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_edit_popin.html.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<div class="popin">
  2 + <h1><%= t('views.cycle._edit_popin.cycle_editing') %></h1>
  3 +
  4 + <div>
  5 + <% if cycle.valid? %>
  6 + <%= t('views.cycle._edit_popin.cycle_saved') %>
  7 + <% else %>
  8 + <% cycle.errors.full_messages.each do |msg| %>
  9 + <div><%= msg %></div>
  10 + <% end %>
  11 + <% end %>
  12 + </div>
  13 +
  14 + <div>
  15 + <%= modal_close_button t('views.cycle._edit_popin.close') %>
  16 + </div>
  17 +
  18 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_edition.html.erb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +<% if @cycle.passed_by? 'edition' %>
  2 + <%= render 'title', :title => t('views.cycle._edition.info') %>
  3 +<% end %>
  4 +
  5 +<div id="cycle-fields">
  6 + <%= render 'edit_fields' %>
  7 +</div>
  8 +
  9 +<div id='cycle-products'>
  10 + <div class="header">
  11 + <h3><%= t('views.cycle._edition.the_products') %></h3>
  12 +
  13 + <div class="subtitle"><%= t('views.cycle._edition.the_following_list_of') %></div>
  14 + <div class="subtitle"><%= t('views.cycle._edition.it_was_automatically_') %></div>
  15 + </div>
  16 +
  17 + <div class="table">
  18 + <% if @cycle.add_products_job %>
  19 + <%= render 'products_loading' %>
  20 + <% else %>
  21 + <%= render 'product_lines' %>
  22 + <% end %>
  23 + </div>
  24 +
  25 +</div>
  26 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_filter_fields.html.erb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +<div class="field year">
  2 + <label><%=t('orders_cycle_plugin.views.cycle.index.show_cycles_from_year') %></label>
  3 + <div>
  4 + <% range = @profile.orders_cycles_closed_date_range %>
  5 + <%= select_year @year_date, {:prompt => '', :start_year => range.first.year, :end_year => range.last.year} %>
  6 + </div>
  7 +</div>
  8 +
  9 +<div class="field month">
  10 + <label><%=t('orders_cycle_plugin.views.cycle.index.and_are_from_the_mont') %></label>
  11 + <div>
  12 + <%= select_month @month_date, {:prompt => '' } %>
  13 + </div>
  14 +</div>
  15 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_header.html.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<%
  2 + actions = true if actions.nil? and @admin
  3 + timeline = true if actions.nil? and @admin
  4 + listing = false if listing.nil?
  5 +%>
  6 +
  7 +<h2>
  8 + <% if cycle.new? %>
  9 + <div class="cycle-edit-link"><%= t('views.cycle._title.new_cycle') %></div>
  10 + <% else %>
  11 + <%= link_to "#{t('views.cycle._title.order_cycle') if not listing}#{cycle.name_with_code}", {:action => 'edit', :id => cycle.id }, :class => "cycle-edit-link" %>
  12 + <% end %>
  13 +
  14 + <%= t('views.cycle._brief.confirmed_orders', :count => cycle.sales.ordered.count) if listing and not cycle.new? %>
  15 +</h2>
  16 +
  17 +<%= render 'timeline', :cycle => cycle, :actions => actions, :listing => listing if timeline %>
  18 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_orders.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<div class="subtitle">
  2 + <%= hideable_help_text t('views.cycle._orders.header_help') %>
  3 +</div>
  4 +
  5 +<% if @cycle.orders? or @cycle.passed_by? 'orders' %>
  6 + <% if @cycle.orders? %>
  7 + <% header = t('views.cycle._orders.the_orders_period_is_') %>
  8 + <% elsif @cycle.passed_by? 'orders' %>
  9 + <% header = t('views.cycle._orders.already_closed') %>
  10 + <% end %>
  11 +<% end %>
  12 +
  13 +<%= render 'orders_cycle_plugin_cycle/cycle_sales' %>
  14 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_product_lines.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<div class="cycle-products-table sortable-table">
  2 + <%= pagination_links @products %>
  3 + <div id="result-count">
  4 + <small><%= t('views.cycle._product_lines.showing_pcount_produc') % {:pcount => @products.length, :allpcount => @cycle.products.count} %></small>
  5 + </div>
  6 + <div class='table-header'>
  7 + <span class='box-field category'><%= t('views.cycle._product_lines.category') %></span>
  8 + <span class='box-field supplier'><%= t('views.cycle._product_lines.supplier') %></span>
  9 + <span class='box-field name'><%= t('views.cycle._product_lines.product') %></span>
  10 + <span class="box-field price"><%= t('views.cycle._product_lines.price') %></span>
  11 + <span class="box-field quantity-available"><%= t('views.cycle._product_lines.qty_avail') %></span>
  12 + <span class="box-field actions"></span>
  13 +
  14 + <div class="clean"></div>
  15 + </div>
  16 +
  17 + <div class='table-content'>
  18 + <% @products.each do |p| %>
  19 + <div id="cycle-product-<%=p.id%>" class='cycle-product value-row' toggle-edit="orders_cycle.offered_product.edit();">
  20 + <%= render 'orders_cycle_plugin_product/cycle_edit', :p => p %>
  21 + </div>
  22 + <% end %>
  23 + <%= javascript_tag do %>
  24 + jQuery(document).ready(function() {
  25 + orders_cycle.ajaxifyPagination("#cycle-products .table")
  26 + })
  27 + <% end %>
  28 + </div>
  29 +</div>
  30 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_products_loading.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +
  2 +<div class='small-loading'>
  3 + <%= t('views.cycle._products_loading') %>
  4 +
  5 + <%= javascript_tag do %>
  6 + orders_cycle.cycle.products.load_url = <%= url_for(:action => :products_load, :id => @cycle.id).to_json %>
  7 + orders_cycle.cycle.products.load()
  8 + <% end %>
  9 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_purchases.html.slim 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +.subtitle= hideable_help_text t('views.cycle._purchases.header_help')
  2 += render 'orders_cycle_plugin_cycle/cycle_purchases'
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_receipts.html.erb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<div class="subtitle">
  2 + <%= hideable_help_text t('views.cycle._receipts.header_help') %>
  3 +</div>
  4 +
  5 +<%= render 'orders_cycle_plugin_cycle/cycle_purchases' %>
  6 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_results.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<% if @closed_cycles.blank? %>
  2 + <div class="subtitle"><%= t('views.cycle._results.no_cycles_to_show') %></div>
  3 +<% else %>
  4 + <% @closed_cycles.each do |cycle| %>
  5 + <div id="cycle-<%= cycle.id %>" class="cycle">
  6 + <%= render 'header', :cycle => cycle, :timeline => true, :actions => false, :listing => true %>
  7 + </div>
  8 + <% end %>
  9 +<% end %>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_separation.html.erb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<div class="subtitle">
  2 + <%= hideable_help_text t('views.cycle._separation.header_help') %>
  3 +</div>
  4 +
  5 +<%= render 'orders_cycle_plugin_cycle/cycle_sales' %>
  6 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_timeline.html.erb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +<%
  2 + actions = true if actions.nil? and @admin
  3 + listing = false if listing.nil?
  4 + view_status = if OrdersCyclePlugin::Cycle::Statuses.index(params[:view]) then params[:view] else cycle.status end
  5 + status_text = t("models.cycle.statuses.#{view_status}")
  6 +%>
  7 +
  8 +<div class="cycle-timeline">
  9 + <% OrdersCyclePlugin::Cycle::UserStatuses.each do |status| %>
  10 + <%
  11 + klass = "cycle-timeline-item #{timeline_class cycle, status, view_status}"
  12 + name = t("models.cycle.statuses.#{status}")
  13 + %>
  14 +
  15 + <%= link_to name, params.merge(:action => :edit, :id => cycle.id, :view => status), :class => klass %>
  16 + <% end %>
  17 +</div>
  18 +
  19 +<% if listing %>
  20 + <div class="dates-brief">
  21 + <div class="date">
  22 + <span class="field-title"><%= t('views.cycle._brief.orders') %></span>&emsp;
  23 + <span><%= datetime_period_short cycle.start, cycle.finish %></span>
  24 + </div>
  25 + <div class="date">
  26 + <span class="field-title"><%= t('views.cycle._brief.delivery') %></span>&emsp;
  27 + <span><%= datetime_period_short cycle.delivery_start, cycle.delivery_finish %></span>
  28 + </div>
  29 + </div>
  30 +<% end %>
  31 +
  32 +<% if actions %>
  33 + <div class="actions-bar">
  34 + <% if cycle.status == view_status and cycle.status != 'closing' %>
  35 + <%= link_to t('views.cycle._timeline.close_status') % {:status => status_text},
  36 + {:action => :step, :id => cycle.id, :method => :post},
  37 + {:confirm => t('views.cycle._timeline.are_you_sure_you_want_to_close') % {:status => status_text}, :class => 'action-button'} %>
  38 + <% end %>
  39 +
  40 + <%= render "orders_cycle_plugin_cycle/actions/#{view_status}", :cycle => cycle rescue nil %>
  41 + </div>
  42 +<% end %>
  43 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_title.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div id="cycle-header-warning" class="">
  2 + <div id="cycle-header-warning-wrap">
  3 + <%= title %>
  4 + </div>
  5 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_view_dates.html.slim 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +- happening = true if !defined?(happening) or happening.nil?
  2 +
  3 +table#cycle-dates
  4 + tr class="orders-date #{"happening" if happening and cycle.orders?}"
  5 + th
  6 + strong= t'views.cycle._view_dates.orders'
  7 + td= datetime_period_with_day cycle.start, cycle.finish
  8 + - if happening and cycle.orders?
  9 + td.happening= t'views.cycle._view_dates.happening'
  10 + tr.delivery-date
  11 + th
  12 + strong= t'views.cycle._view_dates.delivery'
  13 + td= datetime_period_with_day cycle.delivery_start, cycle.delivery_finish
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_view_header.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<div id="cycle-others">
  2 + <% others = (profile.orders_cycles.on_orders - [cycle]) %>
  3 + <%= ( others.blank? ? t('views.cycle._view_header.see_also_all') : t('views.cycle._view_header.other_open_cycles_lis') ) % {
  4 + list: others.map{ |s| link_to(s.name, '') }.join(t('views.cycle._view_header., ')),
  5 + all: link_to(t('views.cycle._view_header.all_orders_cycles'), {controller: :orders_cycle_plugin_order, action: :index}), } %>
  6 +</div>
  7 +
  8 +<div id="cycle-header">
  9 + <h2><%=t('views.cycle._view_header.orders_cycle_cycle') % {cycle: content_tag(:strong, cycle.name_with_code)} %></h2>
  10 +
  11 + <%= render partial: 'orders_cycle_plugin_cycle/view_dates', locals: {cycle: cycle} %>
  12 +
  13 + <div class="subtitle"><%= cycle.description %></div>
  14 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_view_products.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<div id="cycle-products-for-order">
  2 + <div class="title">
  3 + <h3><%=t('views.cycle._view_products.the_products')%></h3>
  4 + </div>
  5 +
  6 + <%= render partial: "orders_cycle_plugin_shared/filter", locals: {type: :order, cycle: cycle, order: order} %>
  7 +
  8 + <div id="search-results">
  9 + <%= render partial: "orders_cycle_plugin_order/filter", locals: {
  10 + cycle: cycle, order: order,
  11 + products_for_order: @products,
  12 + } %>
  13 + </div>
  14 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/_with_selection_actions.html.slim 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +.btn-group
  2 + button.btn.btn-default.btn-xs.dropdown-toggle aria-expanded="false" data-toggle="dropdown" type="button"
  3 + = t('views.admin.reports.generate')
  4 + span.caret
  5 +
  6 + ul.dropdown-menu role="menu"
  7 + li= link_to_function t('views.admin.reports.orders_spreadsheet'), "orders.admin.select.report('#{url_for action: :report_orders, id: @cycle.id, orders_method: orders_method}')"
  8 + li= link_to_function t('views.admin.reports.products_spreadsheet'), "orders.admin.select.report('#{url_for action: :report_products, id: @cycle.id, orders_method: orders_method}')"
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/actions/_closed.html.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +<% if cycle.status == 'closing' %>
  2 + <%= link_to t('views.cycle._timeline.reopen_orders_period'), {action: :step_back, id: cycle.id, method: :post},
  3 + {id: 'cycle-open-cycle' , confirm: t('views.cycle._timeline.are_you_sure_you_want_to_reopen'), class: 'action-button'} %>
  4 +<% end %>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/actions/_orders.html.slim 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +.btn-group
  2 + button.btn.btn-default.btn-xs.dropdown-toggle aria-expanded="false" data-toggle="dropdown" type="button"
  3 + = t('views.admin.reports.generate')
  4 + span.caret
  5 +
  6 + ul.dropdown-menu role="menu"
  7 + li= link_to t('views.admin.reports.orders_spreadsheet'), {action: :report_orders, id: cycle.id}
  8 + li= link_to t('views.admin.reports.products_spreadsheet'), {action: :report_products, id: cycle.id}
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<div id="cycle-admin-page" class='admin-page'>
  2 +
  3 + <div id="cycle-<%= @cycle.id %>" class="cycle">
  4 + <%= render 'header', :cycle => @cycle, :timeline => true, :actions => true, :listing => false %>
  5 +
  6 + <% view = if params[:view] then params[:view] else @cycle.status end %>
  7 + <div>
  8 + <%= render view %>
  9 + </div>
  10 + </div>
  11 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/edit.js.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<% if params[:commit] %>
  2 + <% if @success and @open %>
  3 + window.location = <%= url_for(:action => :edit, :id => @cycle.id).to_json %>
  4 + <% else %>
  5 + noosfero.modal.html(<%= render('edit_popin', :cycle => @cycle).to_json %>)
  6 + <% end %>
  7 +<% else %>
  8 + jQuery("#cycle-products .table").replaceWith('<%= escape_javascript render('product_lines') %>')
  9 +<% end %>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/index.html.erb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +<% cycles = @open_cycles %>
  2 +
  3 +<div id="cycle-listing" class='admin-page'>
  4 + <h2><%= t('views.cycle.index.orders_cycles') %></h2>
  5 +
  6 + <%= link_to t('views.cycle.index.new_cycle'), {:action => :new}, :class => 'cycle-new' %>
  7 +
  8 + <div id="cycle-open-listing">
  9 + <h3><%= t('views.cycle.index.open_cycles') %></h3>
  10 +
  11 + <% if cycles.blank? %>
  12 + <div class="subtitle"><%= t('views.cycle.index.no_cycles_to_show') %></div>
  13 + <% else %>
  14 + <% cycles.each do |cycle| %>
  15 + <div id="cycle-<%= cycle.id %>" class="cycle cycle-list-item">
  16 + <%= render 'header', :cycle => cycle, :timeline => true, :actions => false, :listing => true %>
  17 + </div>
  18 + <% end %>
  19 + <% end %>
  20 + </div>
  21 +
  22 + <div id="cycle-closed-listing">
  23 + <h3><%= t('views.cycle.index.closed_cycles') %></h3>
  24 +
  25 + <%= render "orders_cycle_plugin_shared/filter", :type => :orders_cycle, :wireframe_size => true %>
  26 +
  27 + <div id="search-results">
  28 + <%= render 'results' %>
  29 + </div>
  30 + </div>
  31 +
  32 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/new.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div id="cycle-admin-page" class='admin-page'>
  2 + <%= render 'header', cycle: @cycle, timeline: false, actions: false %>
  3 +
  4 + <div id="cycle-fields">
  5 + <%= render 'edit_fields' %>
  6 + </div>
  7 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/new.js.erb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +window.location.href = <%= url_for(action: :edit, id: @cycle.id).to_json %>
  2 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_cycle/orders_filter.js.erb 0 → 100644
plugins/orders_cycle/views/orders_cycle_plugin_cycle/report_products.erb 0 → 100644
plugins/orders_cycle/views/orders_cycle_plugin_gadgets/_cycle.html.erb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +<div class="cycle">
  2 + <div class='happening'><%= t('views.gadgets._cycle.happening') %></div>
  3 + <div class='info'>
  4 + <h2>
  5 + <%= t('views.gadgets._cycle.orders_open_b_cycle') % {:cycle => cycle.name_with_code} %>
  6 + </h2>
  7 +
  8 + <table>
  9 + <tr>
  10 + <td class='first-column'>
  11 + <%= render :partial => 'orders_cycle_plugin_cycle/view_dates',
  12 + :locals => {:cycle => cycle, :happening => false} %>
  13 + </td>
  14 + <td class='second-column'>
  15 + <div class="subtitle"><%= cycle.description %></div>
  16 + </td>
  17 + <td class='third-column'>
  18 + <%= button_to t('views.gadgets._cycle.see_orders_cycle'), {:controller => :orders_cycle_plugin_order, :action => :edit, :cycle_id => cycle.id}, :class => "action-button" %>
  19 + <%# button_to t('views.gadgets._cycle.place_an_order'), {:controller => :orders_cycle_plugin_order, :action => :new, :cycle_id => cycle.id} %>
  20 + <div class='clean'></div>
  21 + </td>
  22 + <tr>
  23 + </table>
  24 + </div>
  25 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_gadgets/cycles.html.erb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +<% extend OrdersCyclePlugin::TranslationHelper %>
  2 +
  3 +<div id="cycles-gadget">
  4 + <% profile.orders_cycles.on_orders.each do |cycle| %>
  5 + <%= render :partial => 'orders_cycle_plugin_gadgets/cycle', :locals => {:cycle => cycle} %>
  6 + <% end %>
  7 +
  8 + <div>
  9 + <%= link_to t('views.gadgets.cycles.all_cycles'), {:controller => :orders_cycle_plugin_order, :profile => profile.identifier, :action => :index}, :id => "all-cycles" %>
  10 + <div class='clean'></div>
  11 + </div>
  12 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/_edit.html.erb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/views/orders_plugin_item/_edit.html.erb
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/_index.html.slim 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/views/orders_plugin_item/_index.html.slim
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/_price_total.html.erb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/views/orders_plugin_item/_price_total.html.erb
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/_quantity_price.html.slim 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/views/orders_plugin_item/_quantity_price.html.slim
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/destroy.js.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +jQuery('#consumer-order-<%=@order.id%>').html(<%= render('orders_cycle_plugin_order/active_order', order: @order, actor_name: @actor_name).to_json %>);
  2 +
  3 +orders_cycle.order.product.load(<%= @offered_product.id.to_json %>, false);
  4 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_item/new.js.erb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +<% unless params[:redirect] == '1' %>
  2 + orders_cycle.order.product.load(<%= @offered_product.id %>, true);
  3 +
  4 + $('#consumer-order-<%=@order.id%>').html(<%= render('orders_cycle_plugin_order/active_order', order: @order, actor_name: @actor_name).to_json %>);
  5 +
  6 + var item = $('#item-<%=@item.id%>');
  7 + orders.item.edit_quantity(item);
  8 +<% else %>
  9 + window.location = <%= url_for(controller: :orders_cycle_plugin_order, action: :edit, id: @order.id).to_json %>
  10 +<% end %>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/_active_order.html.erb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +<% content_for :order_header do %>
  2 + <%= render 'orders_cycle_plugin_order/status', order: @order %>
  3 +
  4 + <% unless @order.open? %>
  5 + <div id="order-status-message" class="order-message status-<%= @order.current_status %>">
  6 + <div class="order-message-text">
  7 + <% if @order.confirmed? %>
  8 + <%= t('views.order._consumer_orders.your_order_is_confirm') %>
  9 + <% elsif @order.cancelled? %>
  10 + <%= t('views.order._consumer_orders.your_order_was_cancel') %>
  11 + <% else %>
  12 + <%= t('views.order._consumer_orders.your_order_wasn_t_con') %>
  13 + <% end %>
  14 + </div>
  15 + <div class="order-message-actions">
  16 + <div><%= t('views.order._consumer_orders.you_still_can') %></div>
  17 +
  18 + <% if @order.cycle.orders? %>
  19 + <%= link_to_function t('views.order._consumer_orders.change_order'), "orders.order.reload(this, '#{url_for controller: :orders_cycle_plugin_order, action: :reopen, id: @order.id}')" %>
  20 + <%= t('views.order._consumer_orders.before_the_closing') %>&emsp;
  21 + <% end %>
  22 + <br>
  23 +
  24 + <% unless @order.cancelled? %>
  25 + <%= link_to_function t('views.order._show.cancel_order'), "orders.order.reload(this, '#{url_for controller: :orders_cycle_plugin_order, action: :cancel, id: @order.id}')" %>
  26 + <br>
  27 + <% end %>
  28 +
  29 + <%= modal_link_to t('views.order._consumer_orders.send_message_to_the_m'), {controller: :orders_plugin_message, action: :new_to_admins} %>
  30 + </div>
  31 + </div>
  32 + <% end %>
  33 +<% end %>
  34 +
  35 +<%= render 'orders_plugin_order/show', order: @order, actor_name: :consumer %>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/_consumer_orders.html.slim 0 → 100644
... ... @@ -0,0 +1,69 @@
  1 += content_for :head do
  2 + / while we are not responsive
  3 + = stylesheet_link_tag '/assets/designs/icons/awesome/scss/font-awesome.css'
  4 +
  5 +h3
  6 + - if @admin_edit
  7 + = t('views.order._consumer_orders.orders_from_consumer_') % {consumer: @consumer.name}
  8 + - elsif @consumer and @cycle.orders?
  9 + = t('views.order._consumer_orders.your_orders_on_this_c')
  10 + - if @consumer.in? @cycle.profile.members
  11 + = link_to t('views.order._consumer_orders.new_order'), {action: :new, cycle_id: @cycle.id},
  12 + class: 'btn btn-default btn-danger fa fa-plus', onclick: "$(this).attr('disabled', '')"
  13 + - if not @admin_edit and @cycle.consumer_previous_orders(@consumer).present?
  14 + = link_to t('views.order._consumer_orders.repeat_order'), {action: :repeat, cycle_id: @cycle.id},
  15 + class: 'modal-toggle btn btn-default btn-danger fa fa-repeat'
  16 +
  17 +- if @order and @order.consumer != @consumer and @admin_edit
  18 + #order-admin-warning.order-message
  19 + .order-message-text
  20 + = t'views.order._consumer_orders.caution', consumer: @consumer.name
  21 + .order-message-actions
  22 + = link_to t('views.order._consumer_orders.edit_your_orders'), {action: :edit, cycle_id: @cycle.id}
  23 + |&emsp;
  24 + = link_to t('views.order._consumer_orders.administration_of_thi'), {controller: :orders_cycle_plugin_cycle, action: :edit, id: @cycle.id}
  25 + |&emsp;
  26 + #order-place-new.admin
  27 + = link_to t('views.order._consumer_orders.new_order'), {action: :admin_new, consumer_id: @order.consumer.id, cycle_id: @cycle.id} if @cycle.orders?
  28 +- else
  29 + - if @consumer.nil?
  30 + - message = t'views.order._consumer_orders.to_place_an_order_you',
  31 + login: modal_link_to(t('views.order._consumer_orders.login'), login_url, id: 'link_login'),
  32 + signup: link_to(t('views.order._consumer_orders.sign_up'), controller: 'account', action: 'signup')
  33 + - else
  34 + - if @cycle.orders?
  35 + - if not @admin and not @consumer.in? @cycle.profile.members and @cycle.profile.community?
  36 + = t'views.order._consumer_orders.associate_to_order'
  37 + = render 'blocks/profile_info_actions/join_leave_community'
  38 + - elsif @consumer_orders.count == 0
  39 + - message = t'views.order._consumer_orders.you_haven_t_placed_an'
  40 + - elsif @cycle.edition?
  41 + - message = t'views.order._consumer_orders.this_cycle_is_not_ope'
  42 + - elsif @cycle.before_orders?
  43 + - message = t'views.order._consumer_orders.the_time_for_orders_is', start: l(@cycle.start, format: :short), finish: l(@cycle.finish, format: :short)
  44 + - elsif @cycle.after_orders?
  45 + - message = t'views.order._consumer_orders.this_cycle_is_already'
  46 + - if message
  47 + #order-place-new
  48 + = message
  49 +.clean
  50 +
  51 +- @consumer_orders.each do |order|
  52 + - if @order != order and order.current_status == 'cancelled'
  53 + #show-cancelled-orders
  54 + = link_to_function t('views.order._consumer_orders.show_cancelled_orders'), "orders_cycle.toggleCancelledOrders()"
  55 + #hide-cancelled-orders
  56 + = link_to_function t('views.order._consumer_orders.hide_cancelled_orders'), "orders_cycle.toggleCancelledOrders()", style: 'display:none'
  57 + - break
  58 +- @consumer_orders.each do |order|
  59 + - next if @order == order
  60 + div class=("consumer-order unactive #{order.current_status}") id="consumer-order-#{order.id}"
  61 + = render 'status', order: order
  62 + .actions
  63 + = link_to t('views.order._show.open'), {action: :edit, id: order.id}
  64 + .clean
  65 +
  66 +/ prints out the referenced order
  67 +- if @order
  68 + .consumer-order.edit.active-order id="consumer-order-#{@order.id}"
  69 + = render 'orders_cycle_plugin_order/active_order'
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/_filter.html.slim 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +- draft_order = cycle.sales.draft.for_consumer(user).first
  2 +- include_message = ''
  3 +- if order.nil?
  4 + - if draft_order
  5 + - order_id = draft_order.id
  6 + - include_message = t'orders_cycle_plugin.views.product._order_edit.opening_order_code_fo', code: draft_order.code
  7 + - elsif cycle.may_order? user
  8 + - order_id = 'new'
  9 + - include_message = t'orders_cycle_plugin.views.product._order_edit.opening_new_order_for'
  10 +- else
  11 + - order_id = order.id
  12 +- editable = (order.present? && order.may_edit?(user, @admin)) || order_id == 'new'
  13 +
  14 +- if products_for_order.empty?
  15 + strong= t'orders_cycle_plugin.views.product._order_search.this_search_hasn_t_re'
  16 +- else
  17 + #cycle-products.sortable-table
  18 + .table-header
  19 + .box-field.category= t'orders_cycle_plugin.views.product._order_search.category'
  20 + .box-field.supplier= t'orders_cycle_plugin.views.product._order_search.producer'
  21 + .box-field.product= t'orders_cycle_plugin.views.product._order_search.product'
  22 + .box-field.price-with-unit= t'orders_cycle_plugin.views.product._order_search.price'
  23 + .table-content
  24 + - products_for_order.each do |offered_product|
  25 + - next if offered_product.supplier.nil?
  26 + div class=("order-cycle-product value-row #{'editable' if editable}") id="cycle-product-#{offered_product.id}" onclick="orders_cycle.order.product.click(event, #{offered_product.id.to_json});" supplier-id="#{offered_product.supplier.id}" toggle-ignore=""
  27 + - item = if order.blank? then nil else order.items.find{ |op| offered_product.id == op.product_id } end
  28 + = render 'orders_cycle_plugin_product/order_edit', editable: editable,
  29 + product: offered_product, order: order, cycle: cycle, item: item
  30 +
  31 + = pagination_links products_for_order, params: {action: :edit, controller: :orders_cycle_plugin_order, cycle_id: cycle.id, order_id: order_id},
  32 + class: 'pagination infinite-scroll'
  33 +
  34 +javascript:
  35 + orders_cycle.order.load()
  36 + orders_cycle.order.product.include_message = '#{include_message}'
  37 + orders_cycle.order.product.order_id = #{order_id.to_json}
  38 + orders_cycle.order.product.redirect_after_include = '#{order.nil? ? 1 : ''}'
  39 + orders_cycle.order.product.add_url = '#{url_for controller: :orders_cycle_plugin_item, action: :new}'
  40 + orders_cycle.order.product.remove_url = '#{url_for controller: :orders_cycle_plugin_product, action: :remove_from_order}'
  41 + orders_cycle.order.product.balloon_url = '#{url_for controller: :orders_cycle_plugin_order, action: :product_balloon}'
  42 + orders_cycle.order.product.supplier.balloon_url = '#{url_for controller: :orders_cycle_plugin_order, action: :supplier_balloon}'
  43 + pagination.infiniteScroll(#{_('loading...').to_json}, {load: orders_cycle.order.product.showMore})
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/_filter_fields.html.slim 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 += hidden_field_tag :cycle_id, cycle.id
  2 += hidden_field_tag :order_id, order.id unless order.nil?
  3 +
  4 +.field.supplier
  5 + label= t'orders_cycle_plugin.views.order._filter_products.supplier'
  6 + div= select_tag :supplier_id,
  7 + options_for_select([[t('orders_cycle_plugin.views.order._filter_products.all_the_suppliers'), ""]] + supplier_choices(cycle.suppliers), params[:supplier_profile_id].to_i)
  8 +
  9 +.field.category
  10 + label= t'suppliers_plugin.views.product.index.category'
  11 + div
  12 + = select_tag :category_id, options_for_select(@product_categories.map{ |pc| [pc.name, pc.id]}.insert(0,[t('suppliers_plugin.views.product.index.all_the_categories'), ""]))
  13 +
  14 +/.field.qualifier
  15 + div= select_tag :qualifier_id, options_for_select([t('orders_cycle_plugin.views.order._filter_products.anyone'), ''])
  16 +
  17 +/.field.stock
  18 + div= t'orders_cycle_plugin.views.order._filter_products.whose_qty_available_i')
  19 + div= select_tag :stock, options_for_select([t('orders_cycle_plugin.views.order._filter_products.bigger_than_the_stock'), ''])
  20 +
  21 +.field.name
  22 + label= t'orders_cycle_plugin.views.order._filter_products.product_name'
  23 + div= text_field_tag :name, params[:name]
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/_session_edit.html.erb 0 → 100644
plugins/orders_cycle/views/orders_cycle_plugin_order/_status.html.erb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +<div class="order-message-title <%= order.current_status %> <%= 'ordered' if order.ordered? %>">
  2 + <div>
  3 + <%= t('views.order._status.code_status_message') % { :code => order.code, :status_message => order.status_message } %>
  4 + </div>
  5 +
  6 + <%= link_to t('views.order._status.open_it'), {:action => :edit, :id => order.id}, :class => 'open' if order != @order %>
  7 +
  8 +</div>
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/cycle_edit.rjs 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +page.replace_html "order-#{@order.id}", :partial => 'cycle_edit', :locals => {:order => @order}
  2 +page.replace_html "cycle-products-sums", :partial => 'orders_cycle_plugin_cycle/orders_suppliers', :locals => {:cycle => @order.cycle}
  3 +page << "toggle_edit.value_row.reload();"
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<div id="order-page" class="orders page cycle-view">
  2 +
  3 + <%= render 'orders_cycle_plugin_cycle/view_header', cycle: @cycle %>
  4 +
  5 + <%= render 'orders_cycle_plugin_cycle/view_products', cycle: @cycle, order: @order %>
  6 +
  7 + <div id="order-column">
  8 + <%= render 'consumer_orders' %>
  9 + </div>
  10 +
  11 + <%= javascript_include_tag '/assets/plugins/orders/javascripts/jquery.stickyPanel.js' %>
  12 + <%= javascript_tag do %>
  13 + jQuery(document).ready(function() {
  14 + jQuery('.consumer-order.edit').stickyPanel({
  15 + topPadding: jQuery('#cirandas-top-bar').outerHeight(),
  16 + onReAttached: orders.set_orders_container_max_height
  17 + })
  18 + });
  19 + <% end %>
  20 +
  21 + <div class="clean"></div>
  22 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/edit.js.erb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +jQuery('#order-<%=@order.id%>').replaceWith("<%= j render('orders_cycle_plugin_order/active_order', :order => @order, :actor_name => @actor_name) %>");
  2 +
  3 +orders_cycle.order.product.setEditable(<%=@order.may_edit?(user, @admin).to_json%>)
  4 +
  5 +<% if notice = session.delete(:notice) %>
  6 + display_notice(<%= notice.to_json %>)
  7 +<% end %>
  8 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/index.html.slim 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +#orders-view.orders.page
  2 + h2= t('views.order.index.orders_cycles')
  3 +
  4 + #years-filter
  5 + - @years_with_cycles.each do |year|
  6 + = link_to year, {action: :index, year: year}, class: (@year == year ? "current" : nil)
  7 + |&emsp;
  8 +
  9 + - if @cycles.blank?
  10 + div
  11 + = t('views.order.index.there_s_no_open_sessi')
  12 +
  13 + - @cycles.each do |cycle|
  14 + .cycle-with-consumer-orders
  15 + h3
  16 + span= t'views.order.index.code', code: cycle.code
  17 + = link_to cycle.name, {action: :edit, cycle_id: cycle.id}
  18 +
  19 + = render 'orders_cycle_plugin_cycle/view_dates', cycle: cycle
  20 +
  21 + - if cycle.may_order? user
  22 + - orders = cycle.sales.not_cancelled.for_consumer @consumer
  23 + - if orders.count > 0
  24 + div
  25 + strong= t('views.order.index.your_orders')
  26 + .consumer-orders
  27 + - orders.each do |order|
  28 + a href="#{url_for action: :edit, id: order.id}"
  29 + = render 'status', order: order
  30 + .clean
  31 + - if cycle.may_order? user
  32 + - if orders.empty?
  33 + = link_to t('views.order.index.place_an_order'), {action: :new, cycle_id: cycle.id}, id: 'consumer-new-order'
  34 + - else
  35 + = link_to t('views.order.index.place_another_order'), {action: :new, cycle_id: cycle.id}, id: 'consumer-new-order'
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/product_balloon.html.slim 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +#product-ballon.balloon-content.orders-cycle
  2 + h3.popover-title= link_to @product.name, @product.url, target: '_blank'
  3 + .left-column
  4 + = link_to image_tag(@product.default_image(:thumb), class: 'product-pic'), @product.url, target: '_blank'
  5 + .right-column
  6 + div= @product.description
... ...
plugins/orders_cycle/views/orders_cycle_plugin_order/supplier_balloon.html.slim 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +#supplier-ballon.balloon-content.orders-cycle
  2 + h3.popover-title= link_to @supplier.abbreviation_or_name, @supplier.profile.url, target: '_blank'
  3 + .left-column
  4 + = link_to profile_image(@supplier.profile, :portrait), @supplier.profile.url, target: '_blank'
  5 + .right-column
  6 + div= @supplier.description
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/_cycle_edit.html.slim 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +.box-view
  2 + span.box-field.category= nil
  3 + span.box-field.supplier= p.supplier.abbreviation_or_name
  4 + span.box-field.name.box-edit-link= p.name
  5 + span.box-field.price= price_span p.price_as_currency_number
  6 + span.box-field.quantity-available= p.quantity_localized || '∞'
  7 + = edit_arrow "", false, onclick: 'return false;', class: "box-field cycle-product-actions"
  8 +
  9 +.box-edit style=("display: none")
  10 + = form_for p, as: :product, remote: true, url: {controller: :orders_cycle_plugin_product, action: :cycle_edit, id: p.id } do |f|
  11 + .cycle-product-line
  12 + = labelled_field f, :price, t('views.product._cycle_edit.sell_price'), f.number_field(:price, step: 'any', onkeyup: 'orders_cycle.offered_product.pmsync(this, false)', oninput: 'this.onkeyup()')
  13 + = labelled_field f, :margin_percentage, t('views.product._cycle_edit.margin'), f.number_field(:margin_percentage, step: 'any', onkeyup: "orders_cycle.offered_product.pmsync(this, true);", oninput: 'this.onkeyup()')
  14 + .clean
  15 +
  16 + .cycle-product-line
  17 + = labelled_field f, :original_price, t('views.product._cycle_edit.default_sell_price'), f.number_field(:original_price, step: 'any', disabled: '')
  18 + = labelled_field f, :original_margin, t('views.product._cycle_edit.default_margin'), f.number_field(:original_margin_percentage, step: 'any', disabled: '')
  19 + .clean
  20 +
  21 + .cycle-product-line
  22 + = labelled_field f, :buy_price, t('views.product._cycle_edit.buy_price'), f.number_field(:buy_price, step: 'any', disabled: '')
  23 + = labelled_field f, :buy_unit, t('views.product._cycle_edit.buy_unit'), text_field_tag(:buy_unit, p.buy_unit.singular, disabled: '')
  24 + = labelled_field f, :sell_unit, t('views.product._cycle_edit.sell_unit'), text_field_tag(:sell_unit, p.sell_unit.singular, disabled: '')
  25 + .clean
  26 +
  27 + = f.submit t('views.product._cycle_edit.save')
  28 + |&emsp;
  29 + = link_to_function t('views.product._cycle_edit.cancel_updates'), 'toggle-edit' => ''
  30 + |&emsp;
  31 + = link_to t('views.product._cycle_edit.remove_from_cycle'), {controller: :orders_cycle_plugin_product, action: :cycle_destroy, id: p.id }, remote: true, data: {update: "#cycle-product-#{p.id}", confirm: if p.items.blank? then nil else t('views.product._cycle_edit.all_ordered_products') end}
  32 + |&emsp;
  33 + = link_to t('views.product._cycle_edit.edit_product'), {controller: :manage_products, action: :show, id: p.id}, target: '_blank'
  34 + |&emsp;
  35 + .clean
  36 +.clean
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/_order_edit.html.slim 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +.box-view.with-inner
  2 + .box-field.select
  3 + - options = if editable then {} else {disabled: "disabled"} end
  4 + = check_box_tag "product_ids[]", product.id, item.present?, options
  5 +
  6 + .box-view-inner
  7 + .box-field.category title="#{product.category_name}"
  8 + = product.category_name
  9 + .box-field.supplier
  10 + = link_to_function product.supplier.abbreviation_or_name, "orders_cycle.order.product.supplier.balloon(#{product.id})"
  11 + .box-field.product
  12 + = link_to_function product.name, "orders_cycle.order.product.balloon(#{product.id})"
  13 + = price_with_unit_span product.price_as_currency_number, product.unit, product.unit_detail, class: 'box-field'
  14 +
  15 +javascript:
  16 + orders_cycle.order.product.load(#{product.id}, #{item.present?})
  17 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/_session_destroy.rjs 0 → 100644
... ... @@ -0,0 +1 @@
  1 +page.remove 'cycle-product-'+@product_id.to_s
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/cycle_destroy.rjs 0 → 100644
... ... @@ -0,0 +1 @@
  1 +page.remove "cycle-product-#{@product.id}"
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/cycle_edit.rjs 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +page.replace_html "cycle-product-#{@product.id}", :partial => 'cycle_edit', :locals => {:p => @product}
  2 +page << "toggle_edit.value_row.click();"
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/destroy.rjs 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../suppliers/views/suppliers_plugin/product/destroy.rjs
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/edit.rjs 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../suppliers/views/suppliers_plugin/product/edit.rjs
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/index.html.slim 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../suppliers/views/suppliers_plugin/product/index.html.slim
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_product/remove_from_order.js.erb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../orders_cycle_plugin_item/destroy.js.erb
0 2 \ No newline at end of file
... ...
plugins/orders_cycle/views/orders_cycle_plugin_shared/_filter.html.slim 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +- wireframe_size = false unless defined? wireframe_size
  2 +
  3 +#filter class="#{'wireframe-size' if wireframe_size}"
  4 + .title.filter-box
  5 + = t'views.filter.filter'
  6 +
  7 + - url = {controller: :orders_cycle_plugin_order, action: :filter} if type == :order
  8 + = form_tag url, remote: true, id: 'filter-form', data: {loading: "#filter", update: '#search-results', type: 'html'}, method: :get do
  9 + .fields.filter-box
  10 + - if type == :supplier
  11 + = render 'suppliers_plugin_myprofile/filter_fields'
  12 + - elsif type == :product
  13 + = render 'suppliers_plugin/product/filter_fields'
  14 + - elsif type == :order
  15 + = render 'filter_fields', actor: cycle.suppliers, cycle: cycle, order: order
  16 + - else
  17 + = render 'filter_fields'
  18 + .submit
  19 + = submit_tag t('views.filter.filter_it')
... ...
plugins/orders_cycle/views/orders_cycle_plugin_supplier/margin_change.html.erb 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +<div id="suppliers-plugin-margin-change" class="popin">
  2 + <h1><%= t('views.myprofile.margin_change.change_default_margin') %></h1>
  3 +
  4 + <%= form_for @profile, as: :profile_data, remote: true, url: {controller: :suppliers_plugin_myprofile, action: :margin_change},
  5 + html: {class: 'disable-on-submit'} do |f| %>
  6 +
  7 + <div>
  8 + <%= t('views.myprofile.margin_change.notice') %>
  9 + </div>
  10 +
  11 + <%= labelled_field f, :margin_percentage, t('views.myprofile.margin_change.new_margin'), f.number_field(:margin_percentage, step: 'any') + ' ' + t('views.myprofile.margin_change.%') %>
  12 +
  13 + <div>
  14 + <%= check_box_tag :apply_to_all, 1, false, style: 'float: left' %>
  15 + <%= label_tag :apply_to_all, t('views.myprofile.margin_change.apply_to_all'), class: 'line-label' %>
  16 + </div>
  17 +
  18 + <div>
  19 + <%= check_box_tag :apply_to_open_cycles, 1, false, style: 'float: left' %>
  20 + <%= label_tag :apply_to_open_cycles, t('views.myprofile.margin_change.apply_to_open_cycles'), class: 'line-label' %>
  21 + </div>
  22 +
  23 + <div class="clean"></div>
  24 +
  25 + <%= submit_tag t('views.myprofile.margin_change.confirm') %>
  26 + <%= modal_close_link t('views.myprofile.margin_change.cancel') %>
  27 + <% end %>
  28 +</div>
  29 +
... ...
plugins/orders_cycle/views/orders_cycle_plugin_volunteers/index.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<% profile.orders_cycles.has_volunteers_periods.each do |cycle| %>
  2 + <h2>
  3 + <%= cycle.name_with_code %>
  4 + </h2>
  5 +
  6 + <% cycle.volunteers_periods.each do |period| %>
  7 + <%= render 'volunteering', period: period %>
  8 + <% end %>
  9 +<% end %>
... ...
plugins/suppliers/Gemfile 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +gem 'charlock_holmes', platform: :ruby
  2 +gem 'charlock_holmes-jruby', platform: :jruby
  3 +
... ...
plugins/suppliers/controllers/myprofile/suppliers_plugin/basket_controller.rb 0 → 100644
... ... @@ -0,0 +1,64 @@
  1 +class SuppliersPlugin::BasketController < MyProfileController
  2 +
  3 + include SuppliersPlugin::TranslationHelper
  4 +
  5 + no_design_blocks
  6 +
  7 + protect 'edit_profile', :profile
  8 + before_filter :set_allowed_user
  9 +
  10 + helper SuppliersPlugin::TranslationHelper
  11 + helper SuppliersPlugin::DisplayHelper
  12 +
  13 + def search
  14 + @product = profile.products.supplied.find params[:id]
  15 + @query = params[:query].to_s
  16 + @scope = profile.products.supplied.limit(10)
  17 + @scope = @scope.where('id NOT IN (?)', @product.id)
  18 + # not a good option as need to search on from_products to, solr is a perfect match
  19 + #@products = @scope.where('name ILIKE ? OR name ILIKE ?', "#{@query}%", "% #{@query}%")
  20 + @products = autocomplete(:catalog, @scope, @query, {per_page: 10, page: 1}, {})[:results]
  21 +
  22 + render json: @products.map{ |p|
  23 + {value: p.id, label: "#{p.name} (#{if p.respond_to? :supplier then p.supplier.name else p.profile.short_name end})"}
  24 + }
  25 + end
  26 +
  27 + def add
  28 + @product = profile.products.supplied.find params[:id]
  29 + @aggregate = profile.products.supplied.find params[:aggregate_id]
  30 +
  31 + @sp = @product.sources_from_products.where(from_product_id: @aggregate.id).first
  32 + if @sp
  33 + @sp.update_column :quantity, @sp.quantity + 1
  34 + else
  35 + @sp = @product.sources_from_products.create! from_product: @aggregate, to_product: @product
  36 + end
  37 +
  38 + render partial: 'suppliers_plugin/manage_products/basket_tab'
  39 + end
  40 +
  41 + def remove
  42 + @product = profile.products.supplied.find params[:id]
  43 + @aggregate = profile.products.supplied.find params[:aggregate_id]
  44 + @sp = @product.sources_from_products.where(from_product_id: @aggregate.id).first
  45 + @sp.destroy
  46 +
  47 + render partial: 'suppliers_plugin/manage_products/basket_tab'
  48 + end
  49 +
  50 + protected
  51 +
  52 + extend HMVC::ClassMethods
  53 + hmvc SuppliersPlugin
  54 +
  55 + def default_url_options
  56 + # avoid rails' use_relative_controller!
  57 + {use_route: '/'}
  58 + end
  59 +
  60 + def set_allowed_user
  61 + @allowed_user = true
  62 + end
  63 +
  64 +end
... ...
plugins/suppliers/controllers/myprofile/suppliers_plugin/product_controller.rb 0 → 100644
... ... @@ -0,0 +1,103 @@
  1 +class SuppliersPlugin::ProductController < MyProfileController
  2 +
  3 + include SuppliersPlugin::TranslationHelper
  4 +
  5 + no_design_blocks
  6 +
  7 + protect 'edit_profile', :profile
  8 +
  9 + helper SuppliersPlugin::TranslationHelper
  10 + helper SuppliersPlugin::DisplayHelper
  11 +
  12 + def index
  13 + filter
  14 + respond_to do |format|
  15 + format.html{ render template: 'suppliers_plugin/product/index' }
  16 + format.js{ render partial: 'suppliers_plugin/product/search' }
  17 + end
  18 + end
  19 +
  20 + def search
  21 + filter
  22 + if params[:page].present?
  23 + render partial: 'suppliers_plugin/product/results'
  24 + else
  25 + render partial: 'suppliers_plugin/product/search'
  26 + end
  27 + end
  28 +
  29 + def add
  30 +
  31 + end
  32 +
  33 + def edit
  34 + @product = profile.products.supplied.find params[:id]
  35 + @product.update_attributes params["product_#{@product.id}"]
  36 + end
  37 +
  38 + def import
  39 + if params[:csv].present?
  40 + if params[:remove_all_suppliers] == 'true'
  41 + profile.suppliers.except_self.find_each(batch_size: 20){ |s| s.delay.destroy }
  42 + end
  43 + SuppliersPlugin::Import.delay.products profile, params[:csv].read
  44 +
  45 + @notice = t'controllers.product.import_in_progress'
  46 + respond_to{ |format| format.js{ render layout: false } }
  47 + else
  48 + respond_to{ |format| format.html{ render layout: false } }
  49 + end
  50 + end
  51 +
  52 + def destroy
  53 + @product = SuppliersPlugin::DistributedProduct.find params[:id]
  54 + @product.destroy
  55 + flash[:notice] = t('controllers.myprofile.product_controller.product_removed_succe')
  56 + end
  57 +
  58 + def distribute_to_consumers
  59 + params[:consumers] ||= {}
  60 +
  61 + @product = profile.products.find params[:id]
  62 + @consumers = profile.consumers.find(params[:consumers].keys.to_a).collect &:profile
  63 + to_add = @consumers - @product.consumers
  64 + to_remove = @product.consumers - @consumers
  65 +
  66 + to_add.each{ |c| @product.distribute_to_consumer c }
  67 +
  68 + to_remove = to_remove.to_set
  69 + @product.to_products.each{ |p| p.destroy if to_remove.include? p.profile }
  70 +
  71 + @product.reload
  72 + end
  73 +
  74 + protected
  75 +
  76 + def filter
  77 + page = params[:page]
  78 + page = 1 if page.blank?
  79 +
  80 + @supplier = SuppliersPlugin::Supplier.where(id: params[:supplier_id]).first if params[:supplier_id].present?
  81 +
  82 + @scope = profile.products.unarchived.joins :from_products, :suppliers
  83 + @scope = SuppliersPlugin::BaseProduct.search_scope @scope, params
  84 + @products_count = @scope.supplied_for_count.count
  85 + @scope = @scope.supplied.select('products.*, MIN(from_products_products.name) as from_products_name').order('from_products_name ASC')
  86 + @products = @scope.paginate per_page: 20, page: page
  87 +
  88 + @product_categories = Product.product_categories_of @products
  89 + @new_product = SuppliersPlugin::DistributedProduct.new
  90 + @new_product.profile = profile
  91 + @new_product.supplier = @supplier
  92 + @units = Unit.all
  93 + end
  94 +
  95 + extend HMVC::ClassMethods
  96 + hmvc SuppliersPlugin
  97 +
  98 + def default_url_options
  99 + # avoid rails' use_relative_controller!
  100 + {use_route: '/'}
  101 + end
  102 +
  103 +end
... ...
plugins/suppliers/controllers/myprofile/suppliers_plugin_myprofile_controller.rb 0 → 100644
... ... @@ -0,0 +1,82 @@
  1 +class SuppliersPluginMyprofileController < MyProfileController
  2 +
  3 + include SuppliersPlugin::TranslationHelper
  4 +
  5 + no_design_blocks
  6 +
  7 + protect 'edit_profile', :profile
  8 +
  9 + before_filter :load_new, only: [:index, :new]
  10 +
  11 + helper SuppliersPlugin::TranslationHelper
  12 + helper SuppliersPlugin::DisplayHelper
  13 +
  14 + def index
  15 + @suppliers = search_scope(profile.suppliers.except_self).paginate(per_page: 30, page: params[:page])
  16 + @is_search = params[:name] or params[:active]
  17 +
  18 + if request.xhr?
  19 + render partial: 'suppliers_plugin_myprofile/suppliers_list', locals: {suppliers: @suppliers}
  20 + end
  21 + end
  22 +
  23 + def new
  24 + @new_supplier.update_attributes! params[:supplier]
  25 + @supplier = @new_supplier
  26 + session[:notice] = t('controllers.myprofile.supplier_created')
  27 + end
  28 +
  29 + def add
  30 + @enterprise = environment.enterprises.find params[:id]
  31 + @new_supplier = profile.suppliers.create! profile: @enterprise
  32 + end
  33 +
  34 + def edit
  35 + @supplier = profile.suppliers.find params[:id]
  36 + @supplier.update_attributes params[:supplier]
  37 + end
  38 +
  39 + def margin_change
  40 + if params[:commit]
  41 + profile.margin_percentage = params[:profile_data][:margin_percentage]
  42 + profile.save
  43 + profile.supplier_products_default_margins if params[:apply_to_all]
  44 +
  45 + render partial: 'suppliers_plugin/shared/pagereload'
  46 + end
  47 + end
  48 +
  49 + def toggle_active
  50 + @supplier = profile.suppliers.find params[:id]
  51 + @supplier.toggle! :active
  52 + end
  53 +
  54 + def destroy
  55 + @supplier = profile.suppliers.find params[:id]
  56 + @supplier.destroy
  57 + end
  58 +
  59 + def search
  60 + @query = params[:query].downcase
  61 + @enterprises = environment.enterprises.enabled.public.all limit: 12, order: 'name ASC',
  62 + conditions: ['LOWER(name) LIKE ? OR LOWER(name) LIKE ? OR identifier LIKE ?', "#{@query}%", "% #{@query}%", "#{@query}%"]
  63 + @enterprises -= profile.suppliers.collect(&:profile)
  64 + end
  65 +
  66 + protected
  67 +
  68 + def load_new
  69 + @new_supplier = SuppliersPlugin::Supplier.new_dummy consumer: profile
  70 + @new_profile = @new_supplier.profile
  71 + end
  72 +
  73 + def search_scope scope
  74 + scope = scope.by_active params[:active] if params[:active].present?
  75 + scope = scope.with_name params[:name] if params[:name].present?
  76 + scope
  77 + end
  78 +
  79 + extend HMVC::ClassMethods
  80 + hmvc OrdersPlugin
  81 +
  82 +end
... ...
plugins/suppliers/db/migrate/20130704000000_create_suppliers_plugin_tables.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class CreateSuppliersPluginTables < ActiveRecord::Migration
  2 + def self.up
  3 + # check if distribution plugin already moved the table
  4 + return if ActiveRecord::Base.connection.table_exists? :suppliers_plugin_suppliers
  5 +
  6 + create_table :suppliers_plugin_suppliers do |t|
  7 + t.integer :profile_id
  8 + t.integer :consumer_id
  9 + t.string :name
  10 + t.string :name_abbreviation
  11 + t.text :description
  12 + t.timestamps
  13 + end
  14 +
  15 + add_index :suppliers_plugin_suppliers, [:consumer_id]
  16 + end
  17 +
  18 + def self.down
  19 + drop_table :suppliers_plugin_suppliers
  20 + end
  21 +end
... ...
plugins/suppliers/db/migrate/20130704202336_create_suppliers_plugin_source_product.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +class CreateSuppliersPluginSourceProduct < ActiveRecord::Migration
  2 + def self.up
  3 + # check if distribution plugin already moved the table
  4 + return if ActiveRecord::Base.connection.table_exists? "suppliers_plugin_source_products"
  5 +
  6 + create_table :suppliers_plugin_source_products do |t|
  7 + t.integer "from_product_id"
  8 + t.integer "to_product_id"
  9 + t.integer "supplier_id"
  10 + t.decimal "quantity", :default => 0.0
  11 + t.timestamps
  12 + end
  13 +
  14 + add_index :suppliers_plugin_source_products, [:from_product_id]
  15 + add_index :suppliers_plugin_source_products, [:to_product_id]
  16 +
  17 + end
  18 +
  19 + def self.down
  20 + drop_table :suppliers_plugin_source_products
  21 + end
  22 +end
... ...
plugins/suppliers/db/migrate/20130902115916_add_active_to_suppliers_plugin_supplier.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class SuppliersPlugin::Supplier < ActiveRecord::Base
  2 +end
  3 +
  4 +class AddActiveToSuppliersPluginSupplier < ActiveRecord::Migration
  5 + def self.up
  6 + add_column :suppliers_plugin_suppliers, :active, :boolean, :default => true
  7 + SuppliersPlugin::Supplier.update_all ['active = ?', true]
  8 + end
  9 +
  10 + def self.down
  11 + remove_column :suppliers_plugin_suppliers, :active
  12 + end
  13 +end
... ...
plugins/suppliers/db/migrate/20131001180248_suppliers_plugin_index_filtered_fields.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class SuppliersPluginIndexFilteredFields < ActiveRecord::Migration
  2 + def self.up
  3 + add_index :suppliers_plugin_suppliers, [:profile_id]
  4 + add_index :suppliers_plugin_suppliers, [:profile_id, :consumer_id]
  5 +
  6 + add_index :suppliers_plugin_source_products, [:from_product_id, :to_product_id], :name => 'suppliers_plugin_index_dtBULzU3'
  7 + add_index :suppliers_plugin_source_products, [:supplier_id], :name => 'suppliers_plugin_index_Lm5QPpV8'
  8 + add_index :suppliers_plugin_source_products, [:supplier_id, :from_product_id], :name => 'suppliers_plugin_index_naHsVLS6cH'
  9 + add_index :suppliers_plugin_source_products, [:supplier_id, :to_product_id], :name => 'suppliers_plugin_index_LgsgYqCQI'
  10 + add_index :suppliers_plugin_source_products, [:supplier_id, :from_product_id, :to_product_id], :name => 'suppliers_plugin_index_VBNqyeCP'
  11 + end
  12 +
  13 + def self.down
  14 + say "this migration can't be reverted"
  15 + end
  16 +end
... ...
plugins/suppliers/db/migrate/20150119124030_suppliers_plugin_update_quantity_of_source_products.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class SuppliersPluginUpdateQuantityOfSourceProducts < ActiveRecord::Migration
  2 + def up
  3 + change_column_default :suppliers_plugin_source_products, :quantity, 1.0
  4 + SuppliersPlugin::SourceProduct.update_all quantity: 1.0
  5 + end
  6 +
  7 + def down
  8 + change_column_default :suppliers_plugin_source_products, :quantity, 0
  9 + SuppliersPlugin::SourceProduct.update_all quantity: 0
  10 + end
  11 +end
... ...
plugins/suppliers/lib/currency_helper.rb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../orders/lib/currency_helper.rb
0 2 \ No newline at end of file
... ...
plugins/suppliers/lib/default_delegate.rb 0 → 100644
... ... @@ -0,0 +1,122 @@
  1 +module DefaultDelegate
  2 +
  3 + module ClassMethods
  4 +
  5 + def default_delegate_setting field, options, &block
  6 + extend ActsAsHavingSettings::ClassMethods
  7 +
  8 + prefix = options[:prefix] || :default
  9 + default_setting = "#{prefix}_#{field}"
  10 + settings_items default_setting, default: options[:default], type: :boolean
  11 +
  12 + options[:default_setting] = default_setting
  13 + default_delegate field, options
  14 + end
  15 +
  16 + # TODO: add some documentation about the methods being added
  17 + def default_delegate field, options = {}
  18 + # rake db:migrate run?
  19 + return unless self.table_exists?
  20 +
  21 + # Rails doesn't define getters for attributes
  22 + define_method field do
  23 + self[field]
  24 + end if field.to_s.in? self.column_names and not self.method_defined? field
  25 + define_method "#{field}=" do |value|
  26 + self[field] = value
  27 + end if field.to_s.in? self.column_names and not self.method_defined? "#{field}="
  28 +
  29 + original_field_method = "original_own_#{field}".freeze
  30 + alias_method original_field_method, field
  31 + own_field = "own_#{field}".freeze
  32 + define_method own_field do
  33 + # we prefer the value from dabatase here, and the getter may give a default value
  34 + # e.g. Product#name defaults to Product#product_category.name
  35 + if field.to_s.in? self.class.column_names then self[field] else self.send original_field_method end
  36 + end
  37 + alias_method "#{own_field}=", "#{field}="
  38 +
  39 + delegated_field = "delegated_#{field}".freeze
  40 + to = options[:to].freeze
  41 + define_method delegated_field do
  42 + case to
  43 + when Symbol
  44 + object = self.send to
  45 + object.send field if object and object.respond_to? field
  46 + when Proc then instance_exec &to
  47 + end
  48 + end
  49 + alias_method "original_#{field}", delegated_field
  50 +
  51 + own_field_blank = "own_#{field}_blank?".freeze
  52 + define_method own_field_blank do
  53 + own = self.send own_field
  54 + # blank? also covers false, use nil? and empty? instead
  55 + own.nil? or (own.respond_to? :empty? and own.empty?)
  56 + end
  57 + own_field_present = "own_#{field}_present?".freeze
  58 + define_method own_field_present do
  59 + not self.send own_field_blank
  60 + end
  61 + default_if = options[:default_if].freeze
  62 + own_field_is_default = "own_#{field}_default?".freeze
  63 + define_method own_field_is_default do
  64 + default = self.send own_field_blank
  65 + default ||= case default_if
  66 + when Proc then instance_exec &default_if
  67 + when :equal?
  68 + self.send(own_field).equal? self.send(delegated_field)
  69 + when Symbol then self.send default_if
  70 + else false
  71 + end
  72 + end
  73 +
  74 + default_setting = options[:default_setting] || "#{options[:prefix] || :default}_#{field}"
  75 + # as a field may use other field's default_setting, check for definition
  76 + default_setting_with_presence = "#{default_setting}_with_presence".freeze
  77 + unless self.method_defined? default_setting_with_presence
  78 + define_method default_setting_with_presence do
  79 + original_setting = self.send "#{default_setting}_without_presence"
  80 + # if the setting is false, see if it should be true; if it is true, respect it.
  81 + original_setting = self.send own_field_is_default unless original_setting
  82 + original_setting
  83 + end
  84 + define_method "#{default_setting_with_presence}=" do |value|
  85 + # this ensures latter the getter won't get a different
  86 + self.send "#{own_field}=", nil if value
  87 + self.send "#{default_setting}_without_presence=", value
  88 + end
  89 + alias_method_chain default_setting, :presence
  90 + alias_method_chain "#{default_setting}=", :presence
  91 + end
  92 +
  93 + define_method "#{field}_with_default" do
  94 + if self.send default_setting
  95 + # delegated_field may return nil, so use own instead
  96 + # this is the case with some associations (e.g. Product#product_qualifiers)
  97 + # FIXME: this shouldn't be necessary, it seems to happens only in certain cases
  98 + # (product creation, product global search, etc)
  99 + self.send(delegated_field) || self.send(own_field)
  100 + else self.send(own_field)
  101 + end
  102 + end
  103 + define_method "#{field}_with_default=" do |*args|
  104 + own = self.send "#{own_field}=", *args
  105 + # break/set the default setting automatically, used for interfaces
  106 + # that don't have the default setting (e.g. manage_products)
  107 + self.send "#{default_setting}=", self.send(own_field_is_default)
  108 + own
  109 + end
  110 + alias_method_chain field, :default
  111 + alias_method_chain "#{field}=", :default
  112 +
  113 + include DefaultDelegate::InstanceMethods
  114 + end
  115 + end
  116 +
  117 + module InstanceMethods
  118 + end
  119 +
  120 +end
  121 +
  122 +ActiveRecord::Base.extend DefaultDelegate::ClassMethods
... ...
plugins/suppliers/lib/ext/orders_plugin/item.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +require_dependency "orders_plugin/item"
  2 +
  3 +class OrdersPlugin::Item
  4 +
  5 + delegate :supplier, to: :product
  6 +
  7 +end
... ...
plugins/suppliers/lib/ext/price_detail.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require_dependency 'price_detail'
  2 +
  3 +class PriceDetail
  4 +
  5 + # should be on core, used by SuppliersPlugin::Import
  6 + attr_accessible :production_cost
  7 +
  8 +end
... ...
plugins/suppliers/lib/ext/product.rb 0 → 100644
... ... @@ -0,0 +1,175 @@
  1 +require_dependency 'product'
  2 +
  3 +# FIXME: The lines bellow should be on the core
  4 +class Product
  5 +
  6 + extend CurrencyHelper::ClassMethods
  7 + has_currency :price
  8 + has_currency :discount
  9 +
  10 + scope :available, conditions: {available: true}
  11 + scope :unavailable, conditions: ['products.available <> true']
  12 + scope :archived, conditions: {archived: true}
  13 + scope :unarchived, conditions: ['products.archived <> true']
  14 +
  15 + scope :with_available, lambda { |available| where available: available }
  16 + scope :with_price, conditions: 'products.price > 0'
  17 + scope :with_product_category_id, lambda { |id| { conditions: {product_category_id: id} } }
  18 +
  19 + # FIXME: transliterate input and name column
  20 + scope :name_like, lambda { |name| { conditions: ["LOWER(products.name) LIKE ?", "%#{name}%"] } }
  21 +
  22 + scope :by_profile, lambda { |profile| { conditions: {profile_id: profile.id} } }
  23 + scope :by_profile_id, lambda { |profile_id| { conditions: {profile_id: profile_id} } }
  24 +
  25 + def self.product_categories_of products
  26 + ProductCategory.find products.collect(&:product_category_id).compact.select{ |id| not id.zero? }
  27 + end
  28 +
  29 + attr_accessible :external_id
  30 + settings_items :external_id, type: String, default: nil
  31 +
  32 + # should be on core, used by SuppliersPlugin::Import
  33 + attr_accessible :price_details
  34 +
  35 +end
  36 +
  37 +class Product
  38 +
  39 + attr_accessible :from_products, :supplier_id, :supplier
  40 +
  41 + has_many :sources_from_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
  42 + has_one :sources_from_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  43 + has_many :sources_to_products, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
  44 + has_one :sources_to_product, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  45 + has_many :to_products, through: :sources_to_products, order: 'id ASC'
  46 + has_one :to_product, through: :sources_to_product, order: 'id ASC', autosave: true
  47 + has_many :from_products, through: :sources_from_products, order: 'id ASC'
  48 + has_one :from_product, through: :sources_from_product, order: 'id ASC', autosave: true
  49 +
  50 + has_many :sources_from_2x_products, through: :from_products, source: :sources_from_products
  51 + has_one :sources_from_2x_product, through: :from_product, source: :sources_from_product
  52 + has_many :sources_to_2x_products, through: :to_products, source: :sources_to_products
  53 + has_one :sources_to_2x_product, through: :to_product, source: :sources_to_product
  54 + has_many :from_2x_products, through: :sources_from_2x_products, source: :from_product
  55 + has_one :from_2x_product, through: :sources_from_2x_product, source: :from_product
  56 + has_many :to_2x_products, through: :sources_to_2x_products, source: :to_product
  57 + has_one :to_2x_product, through: :sources_to_2x_product, source: :to_product
  58 +
  59 + # semantic alias for supplier_from_product(s)
  60 + has_many :sources_supplier_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  61 + has_one :sources_supplier_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  62 + has_many :supplier_products, through: :sources_supplier_products, source: :from_product, order: 'id ASC'
  63 + has_one :supplier_product, through: :sources_supplier_product, source: :from_product, order: 'id ASC', autosave: true
  64 + has_many :suppliers, through: :sources_supplier_products, uniq: true, order: 'id ASC'
  65 + has_one :supplier, through: :sources_supplier_product, order: 'id ASC'
  66 +
  67 + has_many :consumers, through: :to_products, source: :profile, uniq: true, order: 'id ASC'
  68 + has_one :consumer, through: :to_product, source: :profile, order: 'id ASC'
  69 +
  70 + # overhide original
  71 + scope :available, -> {
  72 + joins(:suppliers).
  73 + where 'products.available = ? AND suppliers_plugin_suppliers.active = ?', true, true
  74 + }
  75 + scope :unavailable, -> {
  76 + where 'products.available <> ? OR suppliers_plugin_suppliers.active <> ?', true, true
  77 + }
  78 + scope :with_available, -> (available) {
  79 + op = if available then '=' else '<>' end
  80 + cond = if available then 'AND' else 'OR' end
  81 + where "products.available #{op} ? #{cond} suppliers_plugin_suppliers.active #{op} ?", true, true
  82 + }
  83 +
  84 + scope :name_like, lambda { |name| where "from_products_products.name ILIKE ?", "%#{name}%" }
  85 + scope :with_product_category_id, lambda { |id| where 'from_products_products.product_category_id = ?', id }
  86 +
  87 + # prefer distributed_products has_many to use DistributedProduct scopes and eager loading
  88 + scope :distributed, -> { where type: 'SuppliersPlugin::DistributedProduct'}
  89 + scope :own, -> { where type: nil }
  90 + scope :supplied, -> {
  91 + where(type: [nil, 'SuppliersPlugin::DistributedProduct']).
  92 + # this allow duplicates and sorting on the fields
  93 + group('products.id')
  94 + }
  95 + scope :supplied_for_count, -> {
  96 + where(type: [nil, 'SuppliersPlugin::DistributedProduct']).uniq
  97 + }
  98 +
  99 + scope :from_supplier, lambda { |supplier| { conditions: ['suppliers_plugin_suppliers.id = ?', supplier.id] } }
  100 + scope :from_supplier_id, lambda { |supplier_id| { conditions: ['suppliers_plugin_suppliers.id = ?', supplier_id] } }
  101 +
  102 + after_create :distribute_to_consumers
  103 +
  104 + def own?
  105 + self.class == Product
  106 + end
  107 + def distributed?
  108 + self.class == SuppliersPlugin::DistributedProduct
  109 + end
  110 + def supplied?
  111 + self.own? or self.distributed?
  112 + end
  113 +
  114 + def supplier
  115 + # FIXME: use self.suppliers when rails support for nested preload comes
  116 + @supplier ||= self.sources_supplier_product.supplier rescue nil
  117 + @supplier ||= self.profile.self_supplier rescue nil
  118 + end
  119 + def supplier= value
  120 + @supplier = value
  121 + end
  122 + def supplier_id
  123 + self.supplier.id
  124 + end
  125 + def supplier_id= id
  126 + @supplier = profile.environment.profiles.find id
  127 + end
  128 +
  129 + def supplier_dummy?
  130 + self.supplier ? self.supplier.dummy? : self.profile.dummy?
  131 + end
  132 +
  133 + def distribute_to_consumer consumer, attrs = {}
  134 + distributed_product = consumer.distributed_products.where(profile_id: consumer.id, from_products_products: {id: self.id}).first
  135 + distributed_product ||= SuppliersPlugin::DistributedProduct.create! profile: consumer, from_products: [self]
  136 + distributed_product.update_attributes! attrs if attrs.present?
  137 + distributed_product
  138 + end
  139 +
  140 + def destroy_dependent
  141 + self.to_products.each do |to_product|
  142 + to_product.destroy if to_product.respond_to? :dependent? and to_product.dependent?
  143 + end
  144 + end
  145 +
  146 + # before_destroy and after_destroy don't work,
  147 + # see http://stackoverflow.com/questions/14175330/associations-not-loaded-in-before-destroy-callback
  148 + def destroy
  149 + self.class.transaction do
  150 + self.destroy_dependent
  151 + super
  152 + end
  153 + end
  154 +
  155 + def diff from = self.from_product
  156 + return @changed_attrs if @changed_attrs
  157 + @changed_attrs = []
  158 + SuppliersPlugin::BaseProduct::CORE_DEFAULT_ATTRIBUTES.each do |attr|
  159 + @changed_attrs << attr if self[attr].present? and self[attr] != from[attr]
  160 + end
  161 + @changed_attrs
  162 + end
  163 +
  164 + protected
  165 +
  166 + def distribute_to_consumers
  167 + # shopping_cart creates products without a profile...
  168 + return unless self.profile
  169 +
  170 + self.profile.consumers.except_people.except_self.each do |consumer|
  171 + self.distribute_to_consumer consumer.profile
  172 + end
  173 + end
  174 +
  175 +end
... ...
plugins/suppliers/lib/ext/profile.rb 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +require_dependency 'profile'
  2 +
  3 +# FIXME: The lines bellow should be on the core
  4 +class Profile
  5 +
  6 + has_many :products
  7 +
  8 + def create_product?
  9 + true
  10 + end
  11 +
  12 +end
  13 +
  14 +class Profile
  15 +
  16 + # use profile.products.supplied to include own products
  17 + has_many :distributed_products, class_name: 'SuppliersPlugin::DistributedProduct'
  18 +
  19 + has_many :from_products, through: :products
  20 + has_many :to_products, through: :products
  21 +
  22 + has_many :suppliers, class_name: 'SuppliersPlugin::Supplier', foreign_key: :consumer_id, dependent: :destroy,
  23 + include: [{profile: [:domains], consumer: [:domains]}], order: 'name ASC'
  24 + has_many :consumers, class_name: 'SuppliersPlugin::Consumer', foreign_key: :profile_id, dependent: :destroy,
  25 + include: [{profile: [:domains], consumer: [:domains]}], order: 'name ASC'
  26 +
  27 + def supplier_settings
  28 + @supplier_settings ||= Noosfero::Plugin::Settings.new self, SuppliersPlugin
  29 + end
  30 +
  31 + def dummy?
  32 + !self.visible
  33 + end
  34 +
  35 + def orgs_consumers
  36 + @orgs_consumers ||= self.consumers.except_people.except_self
  37 + end
  38 +
  39 + def self_supplier
  40 + @self_supplier ||= if new_record?
  41 + self.suppliers_without_self_supplier.build profile: self
  42 + else
  43 + suppliers_without_self_supplier.select{ |s| s.profile_id == s.consumer_id }.first || self.suppliers_without_self_supplier.create(profile: self)
  44 + end
  45 + end
  46 + def suppliers_with_self_supplier
  47 + self_supplier # guarantee that the self_supplier is created
  48 + suppliers_without_self_supplier
  49 + end
  50 + alias_method_chain :suppliers, :self_supplier
  51 +
  52 + def add_consumer consumer_profile
  53 + consumer = self.consumers.where(consumer_id: consumer_profile.id).first
  54 + consumer ||= self.consumers.create! profile: self, consumer: consumer_profile
  55 + end
  56 + def remove_consumer consumer_profile
  57 + consumer = self.consumers.of_consumer(consumer_profile).first
  58 + consumer.destroy if supplier
  59 + end
  60 +
  61 + def add_supplier supplier_profile, attrs={}
  62 + supplier = self.suppliers.where(profile_id: supplier_profile.id).first
  63 + supplier ||= self.suppliers.create! attrs.merge(profile: supplier_profile, consumer: self)
  64 + end
  65 + def remove_supplier supplier_profile
  66 + supplier_profile.remove_consumer self
  67 + end
  68 +
  69 + def not_distributed_products supplier
  70 + raise "'#{supplier.name}' is not a supplier of #{self.name}" if self.suppliers.of_profile(supplier).blank?
  71 +
  72 + # FIXME: only select all products if supplier is dummy
  73 + supplier.profile.products.unarchived.own - self.from_products.unarchived.by_profile(supplier.profile)
  74 + end
  75 +
  76 + delegate :margin_percentage, :margin_percentage=, to: :supplier_settings
  77 + extend CurrencyHelper::ClassMethods
  78 + has_number_with_locale :margin_percentage
  79 +
  80 + def supplier_products_default_margins
  81 + self.class.transaction do
  82 + self.distributed_products.unarchived.each do |product|
  83 + product.default_margin_percentage = true
  84 + product.save!
  85 + end
  86 + end
  87 + end
  88 +
  89 +end
... ...
plugins/suppliers/lib/suppliers_plugin.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +module SuppliersPlugin
  2 +
  3 + extend Noosfero::Plugin::ParentMethods
  4 +
  5 + def self.plugin_name
  6 + I18n.t'suppliers_plugin.lib.plugin.name'
  7 + end
  8 +
  9 + def self.plugin_description
  10 + I18n.t'suppliers_plugin.lib.plugin.description'
  11 + end
  12 +
  13 +end
  14 +
... ...
plugins/suppliers/lib/suppliers_plugin/base.rb 0 → 100644
... ... @@ -0,0 +1,70 @@
  1 +if defined? OrdersPlugin
  2 + require_dependency "#{File.dirname __FILE__}/../ext/orders_plugin/item"
  3 +end
  4 +
  5 +class SuppliersPlugin::Base < Noosfero::Plugin
  6 +
  7 + def stylesheet?
  8 + true
  9 + end
  10 +
  11 + def js_files
  12 + ['locale', 'toggle_edit', 'sortable-table', 'suppliers'].map{ |j| "javascripts/#{j}" }
  13 + end
  14 +
  15 + ProductTabs = {
  16 + distribution: {
  17 + id: 'product-distribution',
  18 + content: lambda do
  19 + render 'suppliers_plugin/manage_products/distribution_tab'
  20 + end
  21 + },
  22 + compare: {
  23 + id: 'product-compare-origin',
  24 + content: lambda do
  25 + render 'suppliers_plugin/manage_products/compare_tab'
  26 + end
  27 + },
  28 + basket: {
  29 + id: 'product-basket',
  30 + content: lambda do
  31 + render 'suppliers_plugin/manage_products/basket_tab'
  32 + end
  33 + },
  34 + }
  35 +
  36 + def product_tabs product
  37 + allowed_user = context.instance_variable_get :@allowed_user
  38 +
  39 + tabs = ProductTabs.dup
  40 + tabs.delete :distribution unless allowed_user and product.profile.orgs_consumers.present?
  41 + tabs.delete :compare unless allowed_user and product.from_products.size == 1
  42 + # for now, only support basket as a product of the profile
  43 + tabs.delete :basket unless product.own? and (allowed_user or product.from_products.size > 1)
  44 + tabs.each{ |t, op| op[:title] = I18n.t "suppliers_plugin.lib.plugin.#{t}_tab" }
  45 + tabs.values
  46 + end
  47 +
  48 + def control_panel_buttons
  49 + # FIXME: disable for now
  50 + return
  51 +
  52 + profile = context.profile
  53 + return unless profile.enterprise?
  54 + [
  55 + {title: I18n.t('suppliers_plugin.views.control_panel.suppliers'), icon: 'suppliers-manage-suppliers', url: {controller: :suppliers_plugin_myprofile, action: :index}},
  56 + {title: I18n.t('suppliers_plugin.views.control_panel.products'), icon: 'suppliers-manage-suppliers', url: {controller: 'suppliers_plugin/product', action: :index}},
  57 + ]
  58 + end
  59 +
  60 +end
  61 +
  62 +ActiveSupport.on_load :solr_product do
  63 + ::Product.class_eval do
  64 + def solr_supplied
  65 + self.supplied?
  66 + end
  67 + self.solr_extra_fields << :solr_supplied
  68 + end
  69 +end
  70 +
... ...
plugins/suppliers/lib/suppliers_plugin/display_helper.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +module SuppliersPlugin::DisplayHelper
  2 +
  3 + protected
  4 +
  5 + include SearchHelper
  6 +
  7 + include SuppliersPlugin::TableHelper
  8 + include SuppliersPlugin::FieldHelper
  9 + include SuppliersPlugin::ProductHelper
  10 + include SuppliersPlugin::ImageHelper
  11 + include SuppliersPlugin::JavascriptHelper
  12 +
  13 +end
... ...
plugins/suppliers/lib/suppliers_plugin/field_helper.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +module SuppliersPlugin::FieldHelper
  2 +
  3 + protected
  4 +
  5 + def labelled_field form, field, label, field_html, options = {}
  6 + help = options.delete(:help)
  7 +
  8 + field_label = (form ? form.label(field, label) : label_tag(field, label))
  9 + field_help = help.blank? ? '' : content_tag('div', help, class: 'field-help')
  10 + field_box = content_tag('div', field_html, class: 'field-box')
  11 +
  12 + content_tag('div', field_label + field_help + field_box,
  13 + options.merge(class: options[:class].to_s + ' field'))
  14 + end
  15 +
  16 +end
... ...
plugins/suppliers/lib/suppliers_plugin/image_helper.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +module SuppliersPlugin::ImageHelper
  2 +
  3 + # image that can be aligned, centered, and resized with aspect ratio
  4 + def profile_link_with_image profile, size=:portrait, options={}
  5 + options[:class] = "#{options[:class] || ''} inner"
  6 + options[:style] = "#{options[:style] || ''}; background-image: url(#{profile_icon profile, size})"
  7 +
  8 + link = link_to '', profile.url, options
  9 + content_tag 'div', link, :class => "profile-image #{size}"
  10 + end
  11 +
  12 +end
... ...
plugins/suppliers/lib/suppliers_plugin/import.rb 0 → 100644
... ... @@ -0,0 +1,138 @@
  1 +require 'csv'
  2 +require 'charlock_holmes'
  3 +
  4 +class SuppliersPlugin::Import
  5 +
  6 + def self.product_columns header
  7 + keys = I18n.t'suppliers_plugin.lib.import.keys'
  8 + columns = []
  9 + header.each do |name|
  10 + c = nil; keys.each do |key, regex|
  11 + if /#{regex}/i =~ name
  12 + c = key
  13 + break
  14 + end
  15 + end
  16 + raise "duplicate column match '#{name}' already added as :#{c}" if c and c.in? columns
  17 + columns << c
  18 + end
  19 + # check required fields
  20 + return if ([:supplier_name, :product_name, :price] - columns).present?
  21 + columns
  22 + end
  23 +
  24 + def self.products consumer, csv
  25 + default_product_category = consumer.environment.product_categories.find_by_name 'Produtos'
  26 +
  27 + detection = CharlockHolmes::EncodingDetector.detect csv
  28 + csv = CharlockHolmes::Converter.convert csv, detection[:encoding], 'UTF-8'
  29 + data = {}
  30 + rows = []
  31 + columns = []
  32 + quote_chars = %w[" | ~ ^ & *]
  33 + [",", ";", "\t"].each do |sep|
  34 + begin
  35 + rows = CSV.parse csv, quote_char: quote_chars.shift, col_sep: sep
  36 + columns = self.product_columns rows.first
  37 + rescue
  38 + if quote_chars.empty? then raise else retry end
  39 + ensure
  40 + break if columns.present?
  41 + end
  42 + end
  43 + rows.shift
  44 + raise "can't find required columns" if columns.blank?
  45 +
  46 + # extract and treat attributes
  47 + rows.each do |row|
  48 + attrs = {}; row.each.with_index do |value, i|
  49 + next unless c = columns[i]
  50 + value = value.to_s.squish
  51 + attrs[c] = if value.present? then value else nil end
  52 + end
  53 +
  54 + distributed = attrs[:distributed] = {}
  55 + distributed[:external_id] = attrs.delete :external_id
  56 + if supplier_price = attrs.delete(:supplier_price)
  57 + distributed[:price] = attrs[:price]
  58 + attrs[:price] = supplier_price
  59 + end
  60 +
  61 + attrs[:name] = attrs.delete :product_name
  62 + if product_category = attrs[:product_category]
  63 + attrs[:product_category] = ProductCategory.find_by_solr(product_category, query_fields: ['name']).first
  64 + end
  65 + attrs[:product_category] ||= default_product_category
  66 + if qualifiers = attrs[:qualifiers]
  67 + qualifiers = JSON.parse qualifiers
  68 + qualifiers.map! do |q|
  69 + next if q.blank?
  70 + Qualifier.find_by_solr(q, query_fields: ['name']).first
  71 + end.compact!
  72 + attrs[:qualifiers] = qualifiers
  73 + end
  74 + attrs[:unit] = consumer.environment.units.where(singular: attrs[:unit]).first || SuppliersPlugin::BaseProduct.default_unit
  75 + # FIXME
  76 + attrs.delete :stock
  77 +
  78 + if composition = attrs.delete(:composition)
  79 + composition = JSON.parse composition rescue nil
  80 + distributed[:price_details] = composition.map do |name, price|
  81 + production_cost = consumer.environment.production_costs.where(name: name).first
  82 + production_cost ||= consumer.production_costs.where(name: name).first
  83 + production_cost ||= consumer.production_costs.create! name: name, owner: profile
  84 + PriceDetail.new production_cost: production_cost, price: price
  85 + end
  86 + end
  87 +
  88 + # treat URLs
  89 + profile = nil
  90 + if product_url = attrs.delete(:product_url) and /manage_products\/show\/(\d+)/ =~ product_url
  91 + product = Product.where(id: $1).first
  92 + next if product.blank?
  93 + attrs[:record] = product
  94 + profile = product.profile
  95 + end
  96 + if supplier_url = attrs.delete(:supplier_url)
  97 + uri = URI.parse supplier_url
  98 + profile = Domain.where(name: uri.host).first.profile rescue nil
  99 + profile ||= Profile.where(identifier: Rails.application.routes.recognize_path(uri.path)[:profile]).first
  100 + next if profile.blank?
  101 + end
  102 + supplier_name = attrs.delete :supplier_name
  103 + supplier = profile || supplier_name
  104 +
  105 + data[supplier] ||= []
  106 + data[supplier] << attrs
  107 + end
  108 +
  109 + data.each do |supplier, products|
  110 + if supplier.is_a? Profile
  111 + supplier = consumer.add_supplier supplier, distribute_products_on_create: false
  112 + else
  113 + supplier_name = supplier
  114 + supplier = consumer.suppliers.where(name: supplier_name).first
  115 + supplier ||= SuppliersPlugin::Supplier.create_dummy consumer: consumer, name: supplier_name
  116 + end
  117 +
  118 + products.each do |attrs|
  119 + distributed_attrs = attrs.delete :distributed
  120 +
  121 + product = attrs.delete :record
  122 + product ||= supplier.profile.products.where(name: attrs[:name]).first
  123 + product ||= supplier.profile.products.build attrs
  124 + # let update happen only on dummy suppliers
  125 + if product.persisted? and supplier.dummy?
  126 + product.update_attributes! attrs
  127 + elsif product.new_record?
  128 + # create products as not available
  129 + attrs[:available] = false if not supplier.dummy?
  130 + product.update_attributes! attrs
  131 + end
  132 +
  133 + distributed_product = product.distribute_to_consumer consumer, distributed_attrs
  134 + end
  135 + end
  136 + end
  137 +
  138 +end
... ...
plugins/suppliers/lib/suppliers_plugin/javascript_helper.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +module SuppliersPlugin::JavascriptHelper
  2 +
  3 + def j *args
  4 + escape_javascript *args
  5 + end
  6 +
  7 +end
... ...
plugins/suppliers/lib/suppliers_plugin/product_helper.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +module SuppliersPlugin::ProductHelper
  2 +
  3 + protected
  4 +
  5 + def supplier_choices suppliers
  6 + @supplier_choices ||= suppliers.map do |s|
  7 + [s.abbreviation_or_name, s.id]
  8 + end.sort{ |a,b| a[0].downcase <=> b[0].downcase }
  9 + end
  10 +
  11 +end
... ...
plugins/suppliers/lib/suppliers_plugin/table_helper.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +module SuppliersPlugin::TableHelper
  2 +
  3 + protected
  4 +
  5 + include ToggleEdit::TableHelper
  6 +
  7 +end
... ...
plugins/suppliers/lib/suppliers_plugin/translation_helper.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +module SuppliersPlugin::TranslationHelper
  2 +
  3 + protected
  4 +
  5 + # included here to be used on controller's t calls
  6 + include TermsHelper
  7 +
  8 + def i18n_scope
  9 + ['suppliers_plugin']
  10 + end
  11 +
  12 +end
... ...
plugins/suppliers/lib/toggle_edit 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../orders/lib/toggle_edit
0 2 \ No newline at end of file
... ...
plugins/suppliers/locales/en-US.yml 0 → 100644
... ... @@ -0,0 +1,491 @@
  1 +en-US: &en-US
  2 +
  3 + suppliers_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "Suppliers"
  7 + description: "Suppliers and its distributed products registry"
  8 + distribution_tab: "Distribuição"
  9 + compare_tab: "Compare with the supplier"
  10 + basket_tab: "Cesta"
  11 + import:
  12 + keys:
  13 + # ordered by most specific
  14 + supplier_url: '(supplier|producer).+url'
  15 + product_url: 'product.+url'
  16 + supplier_price: '(supplier|producer).+price'
  17 + supplier: '(supplier|producer)'
  18 + product: 'product'
  19 + qualifiers: 'qualifier'
  20 + product_category: 'category'
  21 + price: 'price'
  22 + unit: 'unit'
  23 + composition: 'composition'
  24 + external_id: 'external'
  25 + stock: 'stock'
  26 +
  27 + terms: &suppliers_terms
  28 + profile: &suppliers_term_profile
  29 + singular: "organization"
  30 + plural: "organizations"
  31 + it:
  32 + singular: "it"
  33 + plural: "them"
  34 + article:
  35 + singular: "the organization"
  36 + plural: "the organizations"
  37 + undefined_article:
  38 + singular: "a organization"
  39 + by_article:
  40 + singular: "by the organization"
  41 + tO_it:
  42 + singular: "to it"
  43 + plural: "to them"
  44 + to_article:
  45 + singular: "to the organization"
  46 + plural: "to the organizations"
  47 + at_article:
  48 + singular: "at the organization"
  49 + from:
  50 + singular: "from an organization"
  51 + from_article:
  52 + singular: "from the organization"
  53 + from_which_article:
  54 + singular: "from which the organização"
  55 + of:
  56 + singular: "of organization"
  57 + plural: "of organizations"
  58 + of_article:
  59 + singular: "of the organization"
  60 + plural: "of the organizations"
  61 + of_this:
  62 + singular: "of this organization"
  63 + of_another:
  64 + singular: "of another organization"
  65 + this:
  66 + singular: "this organization"
  67 + with_which:
  68 + singular: "with which the organization"
  69 + consumer:
  70 + singular: "consumer"
  71 + plural: "consumers"
  72 + to_article:
  73 + singular: "to the consumer"
  74 + plural: "to the consumers"
  75 + from_article:
  76 + singular: "from consumer"
  77 + plural: "from consumers"
  78 + supplier:
  79 + singular: "supplier"
  80 + plural: "suppliers"
  81 + one: "one"
  82 + it:
  83 + singular: "it"
  84 + plural: "them"
  85 + article:
  86 + singular: "the supplier"
  87 + plural: "the suppliers"
  88 + undefined_article:
  89 + singular: "a supplier"
  90 + by_article:
  91 + singular: "by the supplier"
  92 + new_undefined_article:
  93 + singular: "a new supplier"
  94 + to:
  95 + singular: "to a supplier"
  96 + plural: "to suppliers"
  97 + tO_it:
  98 + singular: "to it"
  99 + plural: "to them"
  100 + to_article:
  101 + singular: "to the supplier"
  102 + plural: "to the suppliers"
  103 + none:
  104 + singular: "any supplier"
  105 + this:
  106 + singular: "this supplier"
  107 + from:
  108 + singular: "from supplier"
  109 + from_article:
  110 + singular: "from the supplier"
  111 + from_which_article:
  112 + singular: "from which the supplier"
  113 + of:
  114 + singular: "of supplier"
  115 + plural: "of suppliers"
  116 + of_article:
  117 + singular: "of the supplier"
  118 + plural: "of the suppliers"
  119 + of_this:
  120 + singular: "of this supplier"
  121 + of_another:
  122 + singular: "of another supplier"
  123 + your:
  124 + singular: "your supplier"
  125 + plural: "your suppliers"
  126 + on_your:
  127 + singular: "on your organization"
  128 + on_article:
  129 + singular: "on your organization"
  130 + on_undefined_article:
  131 + singular: "on an organization"
  132 + controllers:
  133 + myprofile:
  134 + supplier_created: "%{terms.supplier.singular.capitalize} created"
  135 + product:
  136 + import_in_progress: "The products are being imported. Wait a while and reload the page to view them."
  137 +
  138 + models:
  139 + base_product:
  140 + default_attributes:
  141 + name: 'Name'
  142 + description: 'Description'
  143 + price: 'Price'
  144 + unit_id: 'Unit'
  145 + product_category_id: 'Category'
  146 + image_id: 'Image'
  147 + distributed_product:
  148 + greater: " > "
  149 + product:
  150 + unit: unit
  151 + units: units
  152 + profile:
  153 + consumer: Consumer
  154 + views:
  155 + filter:
  156 + filter: Filter
  157 + filter_it: Filter
  158 + actions:
  159 +
  160 + manage_products:
  161 + compare_tab:
  162 + see_supplier: 'See original product at the page of the supplier "%{supplier}".'
  163 + see: 'See original product.'
  164 + no_diff: 'Este produto não tem nenhuma diferença com o que está na página do fornecedor".'
  165 + diff_count: "This product has <b>%{n}</b> differences compared to the same product on the supplier's page:"
  166 + distribution_tab:
  167 + title: "This product is distributed for the following consumers:"
  168 + no_consumers: "This profile isn't a suppliers of the network."
  169 + save: "Save"
  170 + basket_tab:
  171 + make_a_basket: 'Make this product a basket by adding another product!'
  172 + search: 'find and add your products'
  173 + component: 'Component'
  174 + quantity: 'Quantity'
  175 +
  176 + control_panel:
  177 + suppliers: "Manage suppliers"
  178 + products: "Manage distributed products"
  179 + myprofile:
  180 + margin_change:
  181 + ? "%"
  182 + : "%"
  183 + notice: "By changing the default margin of commercialization, the products that are \"use the default margin\" marked will automatically have their margin updated."
  184 + cancel: cancel
  185 + change_default_margin: "Change default margin of commercialization"
  186 + confirm: Confirm
  187 + apply_to_all: "Apply to the products with custom margins"
  188 + apply_to_open_cycles: "Apply to the products in open cycles"
  189 + new_margin: "New margin"
  190 + index:
  191 + redistribution_situation: "Product distribution"
  192 + name: "Name"
  193 +
  194 + product:
  195 + import:
  196 + action: "Import spreadsheet"
  197 + help: "Format you spreadsheet to have exactly the fields bellow. Then, save it choosing the CSV file type (.csv extension).<br><b>Products with equal names have their data (price/unit) updated.</b>"
  198 + field_supplier_name: "Supplier's name"
  199 + field_product_name: "Product's name"
  200 + field_product_unit: "Unit"
  201 + field_product_price: "Price per unit"
  202 + remove_all: 'Also remove all registered suppliers and their products (start a new list of products).'
  203 + confirm_remove_all: "CAUTION! This option will remove all registered suppliers and their products!"
  204 + unit_help: "Use as <b>unit</b> one of the following (put the name equal): %{units}"
  205 + send: "Send"
  206 + _edit:
  207 + distribution_setts: "Product's distribution settings"
  208 + margin_price: "Margin / price"
  209 + default_margin: "Use default"
  210 + stock: "Stock configuration"
  211 + default_stock: "Keep in stock"
  212 + current_stock: "Current stock"
  213 + distribution_state: "Distribution state"
  214 + available: Active
  215 + are_you_sure_you_want: "Are you sure you want to delete this product?"
  216 + remove_product: remove
  217 + cancel: cancel
  218 + save: save changes
  219 + view_on_supplier_page: "See product %{terms.supplier.at_article.singular}'s page"
  220 + product_registry: "Product's registry"
  221 + selling_unit: "Selling unit"
  222 + same_from_purchase: "Same from purchase"
  223 + unit: "Unit"
  224 + unit_detail: "Unit detail"
  225 + _edit_line:
  226 + active: Active
  227 + inactive: Inactive
  228 + _form_errors:
  229 + errors_found: "Errors found:"
  230 + _price_details:
  231 + commercialization_act: "Commercialization active"
  232 + minimum_order: "Minimum order"
  233 + price: Price
  234 + unit: Unit
  235 + unit_specification: "Unit specification"
  236 + _search:
  237 + status: Status
  238 + supplier: "%{terms.supplier.singular.capitalize}"
  239 + category: Category
  240 + product: Product
  241 + margin: Margin
  242 + price: Price
  243 + stock: Stock
  244 + unit: Unit
  245 + showing_pcount_produc: "Total: %{count} products"
  246 + we_don_t_have_product: "We don't have products to show."
  247 + add:
  248 + title: "Create product"
  249 + own_product: "Create own product"
  250 + or_from_a_supplier: 'or from a managed supplier:'
  251 + index:
  252 + active: active
  253 + add_product: "Create product"
  254 + all_the_categories: "all the categories"
  255 + all_the_suppliers: "all %{terms.supplier.article.plural}"
  256 + situation: "distribution situation"
  257 + anyone: anyone
  258 + bigger_than_the_stock: "bigger than the stock"
  259 + change: change
  260 + default_margin_of_com: "Default margin of commercialization"
  261 + default_margin_info: "This margin is automatically applied in all the products, but it is possible to stabilish specific margins for certain products."
  262 + in_any_state: "In any state"
  263 + inactive: inactive
  264 + margin: "%{margin} %"
  265 + no_margin_set: "no margin set"
  266 + products: Products
  267 + supplier: "%{terms.supplier.singular.capitalize}"
  268 + info: "This is list of all the products %{terms.profile.with_which.singular} distribute. The products are divided between active and inactive."
  269 + the_products: "The products"
  270 + name: "Nome"
  271 + whose_qty_available_i: "whose qty. available is"
  272 + category: "Category"
  273 + with_the_qualifiers: "with the qualifiers"
  274 + supplier:
  275 + _new:
  276 + title: "Add %{terms.supplier.singular} to %{profile}"
  277 + intro_help: "Begin to type the name %{terms.supplier.from_article.singular} that you wnat to add. We'll look if %{terms.supplier.article.singular} already exists at %{environment}. In case it doesn't exists, you'll be able to createa type of enterprise that will be visible ONLY %{terms.profile.on_your.singular}, and not at the %{environment}. If %{terms.supplier.this.singular} really wants to join the %{environment}, then it should send a message to %{contact_email} making the order."
  278 + query_placeholder: "find %{terms.supplier.undefined_article.singular}"
  279 + search: "Search"
  280 + none_found: "%{terms.supplier.none.singular} found. Please revise the search text."
  281 + add: "Add"
  282 + suggest_creation: "If you don't find %{terms.supplier.article.singular} you can create %{terms.supplier.new_undefined_article.singular} that will be managed %{terms.profile.by_article.singular}. But please find carefully to avoid duplicates!"
  283 + create_dummy: "Create %{terms.supplier.singular}"
  284 + _edit:
  285 + registry_help: "The registry %{terms.supplier.from_article.singular} will be managed by the %{terms.profile.by_article.singular} itself and will be private %{terms.profile.to_it.singular}."
  286 + abbreviated_name: "Abbreviated name"
  287 + add_supplier_managed_: "Add %{terms.supplier.singular} managed %{terms.profile.by_article.singular}"
  288 + additional_fields: "Additional fields"
  289 + basic_data: "Basic data"
  290 + cancel: cancel
  291 + description: Description
  292 + edit_supplier: "Edit %{terms.supplier.singular}"
  293 + estrategic_informatio: "Estrategic informations"
  294 + full_registration: "Full registration"
  295 + name: Name
  296 + register_new_supplier: "Register %{terms.supplier.new_undefined_article.singular}"
  297 + registry_form: "Registry form"
  298 + save: Save
  299 + _actions:
  300 + all: "all"
  301 + none: "none"
  302 + select: "select"
  303 + with_selection: "with selected"
  304 + activate: "activate"
  305 + deactivate: "deactivate"
  306 + _supplier:
  307 + edit: "Edit"
  308 + see_page: "See page"
  309 + see_products: "See products"
  310 + message: "Message"
  311 + abreviated_name: "Abreviated Name"
  312 + actions: Actions
  313 + among_them: ", among them"
  314 + any_registered_produc: "any registered product"
  315 + by_removing_this_supp: "By removing %{terms.supplier.article.singular} all its products won't be available for future cycles. Please confirm you choice"
  316 + deactivate: "hide %{terms.profile.from_article.singular}"
  317 + activate: activate
  318 + edit: edit
  319 + products_are_active: " produtos estão ativos"
  320 + products_distributed: " distributed products %{terms.profile.by_article.singular}"
  321 + products:
  322 + zero: "no products"
  323 + one: "1 product"
  324 + other: "%{count} products"
  325 + manage_products: "manage products"
  326 + add_product: "add product"
  327 + registered_supplier_i: "Registered %{terms.supplier.singular} in the network"
  328 + disassociate: "disassociate %{terms.profile.from_article.singular}"
  329 + supplier_profile_product: " registered products"
  330 + supplier_with_registr: "%{terms.supplier.singular.capitalize} with registry managed by the %{terms.profile.by_article.singular}"
  331 + this_supplier_has: "This %{terms.supplier.singular} has"
  332 + _suppliers_list:
  333 + this_search_didn_t_re: "This search didn't return %{terms.supplier.plural}."
  334 + any_registered: "%{terms.supplier.none.singular.capitalize} registered."
  335 + index:
  336 + add_supplier: "Add %{terms.supplier.singular}"
  337 + filter: Filter
  338 + filter_it: Filter
  339 + name: Name
  340 + suppliers: "%{terms.supplier.plural.capitalize}"
  341 + this_is_the_list_of_s: "This is the list of %{terms.supplier.plural} associated %{terms.profile.to_article.singular}. It is possible to associate with existent %{terms.supplier.plural} or register %{terms.supplier.one}."
  342 +
  343 + orders_cycle_plugin:
  344 + terms:
  345 + <<: *suppliers_terms
  346 +
  347 + consumers_coop_plugin:
  348 + terms:
  349 + <<: *suppliers_terms
  350 + profile: &consumers_coop_term_profile
  351 + singular: "consumers' coop"
  352 + plural: "consumers' coop"
  353 + it:
  354 + singular: "it"
  355 + plural: "them"
  356 + article:
  357 + singular: "the consumers' coop"
  358 + plural: "the consumers' coops"
  359 + undefined_article:
  360 + singular: "a consumers' coop"
  361 + by_article:
  362 + singular: "by the consumers' coop"
  363 + tO_it:
  364 + singular: "to it"
  365 + plural: "to them"
  366 + to_article:
  367 + singular: "to the consumers' coop"
  368 + at_article:
  369 + singular: "at the consumers' coop"
  370 + from:
  371 + singular: "from a consumers' coop"
  372 + from_article:
  373 + singular: "from the consumers' coop"
  374 + from_which_article:
  375 + singular: "from which the consumers' coop"
  376 + of:
  377 + singular: "of consumers' coop"
  378 + plural: "of consumers' coops"
  379 + of_article:
  380 + singular: "of the consumers' coop"
  381 + plural: "of the consumers' coops"
  382 + of_this:
  383 + singular: "of this consumers' coop"
  384 + of_another:
  385 + singular: "of another consumers' coop"
  386 + on_your:
  387 + singular: "on your consumers' coop"
  388 + this:
  389 + singular: "this consumers' coop"
  390 + with_which:
  391 + singular: "with which the consumers' coop"
  392 +
  393 + networks_plugin:
  394 + terms:
  395 + <<: *suppliers_terms
  396 + profile: &networks_term_profile
  397 + singular: "network"
  398 + plural: "networks"
  399 + it:
  400 + singular: "it"
  401 + plural: "them"
  402 + article:
  403 + singular: "the network"
  404 + plural: "the networks"
  405 + undefined_article:
  406 + singular: "a network"
  407 + by_article:
  408 + singular: "by the network"
  409 + tO_it:
  410 + singular: "to it"
  411 + plural: "to them"
  412 + to_article:
  413 + singular: "to the network"
  414 + at_article:
  415 + singular: "at the network"
  416 + from:
  417 + singular: "from a network"
  418 + from_article:
  419 + singular: "from the network"
  420 + from_which_article:
  421 + singular: "from which the rede"
  422 + of:
  423 + singular: "of the network"
  424 + plural: "of the networks"
  425 + of_article:
  426 + singular: "of the network"
  427 + plural: "of the networks"
  428 + of_this:
  429 + singular: "of this network"
  430 + of_another:
  431 + singular: "of another network"
  432 + this:
  433 + singular: "this network"
  434 + on_your:
  435 + singular: "on your network"
  436 + with_which:
  437 + singular: "with which the network"
  438 + supplier:
  439 + singular: "enterprise"
  440 + plural: "enterprises"
  441 + one: "one"
  442 + it:
  443 + singular: "it"
  444 + plural: "them"
  445 + article:
  446 + singular: "the enterprise"
  447 + plural: "the enterprises"
  448 + undefined_article:
  449 + singular: "a enterprise"
  450 + by_article:
  451 + singular: "by the enterprise"
  452 + new_undefined_article:
  453 + singular: "a new enterprise"
  454 + to:
  455 + singular: "to a enterprise"
  456 + plural: "to enterprises"
  457 + tO_it:
  458 + singular: "to it"
  459 + plural: "to them"
  460 + to_article:
  461 + singular: "to the enterprise"
  462 + plural: "to the enterprises"
  463 + your:
  464 + singular: "your enterprise"
  465 + plural: "your enterprises"
  466 + from:
  467 + singular: "from enterprise"
  468 + from_article:
  469 + singular: "from the enterprise"
  470 + from_which_article:
  471 + singular: "from which the enterprise"
  472 + of:
  473 + singular: "of enterprise"
  474 + plural: "of enterprises"
  475 + of_article:
  476 + singular: "of the enterprise"
  477 + plural: "of the enterprises"
  478 + of_this:
  479 + singular: "of this enterprise"
  480 + of_another:
  481 + singular: "of another enterprise"
  482 + none:
  483 + singular: "any enterprise"
  484 + this:
  485 + singular: "this enterprise"
  486 +
  487 +en_US:
  488 + <<: *en-US
  489 +en:
  490 + <<: *en-US
  491 +
... ...
plugins/suppliers/locales/pt-BR.yml 0 → 100644
... ... @@ -0,0 +1,536 @@
  1 +pt-BR: &pt-BR
  2 +
  3 + suppliers_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "Fornecedores"
  7 + description: "Registro de fornecedores e produtos distribuídos"
  8 + distribution_tab: "Distribuição"
  9 + compare_tab: "Compare com o fornecedor"
  10 + basket_tab: "Cesta"
  11 + import:
  12 + keys:
  13 + # ordered by most specific
  14 + supplier_url: 'url.+(fornecedor|produtor)'
  15 + product_url: 'url.+produto'
  16 + supplier_price: 'pre[[:alpha:]]o.+(fornecedor|produtor)'
  17 + supplier_name: '(fornecedor|produtor)'
  18 + product_name: 'produto'
  19 + qualifiers: 'qualificador'
  20 + product_category: 'categoria'
  21 + price: 'pre[[:alpha:]]o'
  22 + unit: 'unidade'
  23 + composition: 'composi[[:alpha:]]{2,2}o'
  24 + external_id: 'external'
  25 + stock: 'estoque'
  26 +
  27 + terms: &suppliers_terms
  28 + profile: &suppliers_term_profile
  29 + singular: "organização"
  30 + plural: "organizações"
  31 + it:
  32 + singular: "ela"
  33 + plural: "elas"
  34 + article:
  35 + singular: "a organização"
  36 + plural: "as organizações"
  37 + undefined_article:
  38 + singular: "uma organização"
  39 + by_article:
  40 + singular: "pela organização"
  41 + tO_it:
  42 + singular: "a ela"
  43 + plural: "a elas"
  44 + to_article:
  45 + singular: "a organização"
  46 + plural: "às organizações"
  47 + at_article:
  48 + singular: "na organização"
  49 + from:
  50 + singular: "de uma organização"
  51 + from_article:
  52 + singular: "da organização"
  53 + from_which_article:
  54 + singular: "com que a organização"
  55 + of:
  56 + singular: "de organização"
  57 + plural: "de organizações"
  58 + of_article:
  59 + singular: "da organização"
  60 + plural: "das organizações"
  61 + of_this:
  62 + singular: "desta organização"
  63 + of_another:
  64 + singular: "de outra organização"
  65 + with:
  66 + singular: "com organização"
  67 + plural: "com organizações"
  68 + with_article:
  69 + singular: "com a organização"
  70 + plural: "com as organizações"
  71 + your:
  72 + singular: "seu fornecedor"
  73 + plural: "seus fornecedores"
  74 + on_your:
  75 + singular: "na sua organização"
  76 + on_undefined_article:
  77 + singular: "em uma organização"
  78 + this:
  79 + singular: "esta organização"
  80 + consumer:
  81 + singular: "consumidor(a)"
  82 + plural: "consumidores(as)"
  83 + to_article:
  84 + singular: "ao(à) consumidor(a)"
  85 + plural: "aos(às) consumidores(as)"
  86 + from_article:
  87 + singular: "do(a) consumidor(a)"
  88 + plural: "dos(as) consumidores(as)"
  89 + supplier:
  90 + singular: "fornecedor"
  91 + plural: "fornecedores"
  92 + one: "um"
  93 + it:
  94 + singular: "ele"
  95 + plural: "eles"
  96 + to:
  97 + singular: "a um fornecedor"
  98 + plural: "a fornecedores"
  99 + to_it:
  100 + singular: "a ele"
  101 + plural: "a eles"
  102 + to_article:
  103 + singular: "ao fornecedor"
  104 + plural: "aos fornecedores"
  105 + undefined_article:
  106 + singular: "um fornecedor"
  107 + none:
  108 + singular: "nenhum fornecedor"
  109 + this:
  110 + singular: "este fornecedor"
  111 + from:
  112 + singular: "de fornecedor"
  113 + from_article:
  114 + singular: "do fornecedor"
  115 + from_which_article:
  116 + singular: "com que o fornecedor"
  117 + of:
  118 + singular: "de fornecedor"
  119 + plural: "de fornecedores"
  120 + of_article:
  121 + singular: "do fornecedor"
  122 + plural: "dos fornecedores"
  123 + of_this:
  124 + singular: "deste fornecedor"
  125 + of_another:
  126 + singular: "de outro fornecedor"
  127 + with:
  128 + singular: "com fornecedor"
  129 + plural: "com fornecedores"
  130 + with_article:
  131 + singular: "com o fornecedor"
  132 + plural: "com o fornecedores"
  133 + article:
  134 + singular: "o fornecedor"
  135 + plural: "os fornecedores"
  136 + by_article:
  137 + singular: "pelo fornecedor"
  138 + new_undefined_article:
  139 + singular: "um novo fornecedor"
  140 + controllers:
  141 + myprofile:
  142 + supplier_created: "%{terms.supplier.singular.capitalize} criado"
  143 + product:
  144 + import_in_progress: "Os produtos estão sendo importados. Aguarde um pouco e recarregue a página para visualizá-los."
  145 +
  146 + models:
  147 + base_product:
  148 + default_attributes:
  149 + name: 'Nome'
  150 + description: 'Descrição'
  151 + price: 'Preço'
  152 + unit_id: 'Unidade'
  153 + product_category_id: 'Categoria'
  154 + image_id: 'Imagem'
  155 + distributed_product:
  156 + greater: " > "
  157 + product:
  158 + unit: unidade
  159 + units: unidades
  160 + profile:
  161 + consumer: Consumidor(a)
  162 +
  163 + views:
  164 + filter:
  165 + filter: Filtro
  166 + filter_it: Filtrar
  167 + actions:
  168 +
  169 + manage_products:
  170 + compare_tab:
  171 + see_supplier: 'Ver produto original na página do fornecedor "%{supplier}".'
  172 + see: 'Ver produto original.'
  173 + no_diff: 'Este produto não tem nenhuma diferença com o que está na página do fornecedor.'
  174 + diff_count: 'Este produto está com <b>%{n}</b> diferenças com relação ao mesmo produto na página do fornecedor:'
  175 + distribution_tab:
  176 + title: "Esse produto é distribuído para os seguintes consumidores(as):"
  177 + save: "Salvar"
  178 + basket_tab:
  179 + make_a_basket: 'Faça deste produto uma cesta adicionando mais um produto!'
  180 + search: 'encontre e adicione seus produtos'
  181 + component: 'Componente'
  182 + quantity: 'Quantidade'
  183 +
  184 + control_panel:
  185 + suppliers: "Gerenciar fornecedores"
  186 + products: "Gerenciar produtos distribuídos"
  187 +
  188 + myprofile:
  189 + margin_change:
  190 + ? "%"
  191 + : "%"
  192 + notice: "Ao mudar a margem padrão de comercialização, os produtos que são marcados como \"use a margem padrão\" automaticamente terão sua margem atualizada."
  193 + cancel: Cancelar
  194 + change_default_margin: "Mudar a margem padrão de comercialização"
  195 + confirm: Confirmar
  196 + apply_to_all: "Aplicar para os produtos com margens personalizadas"
  197 + apply_to_open_cycles: "Aplicar para os produtos nos ciclos abertos"
  198 + new_margin: "Nova margem"
  199 + index:
  200 + redistribution_situation: "Distribuição dos produtos"
  201 + name: "Nome"
  202 +
  203 + product:
  204 + import:
  205 + action: "Importar planilha"
  206 + help: "Formate sua planilha para que tenha exatamente os campos abaixo. Depois, salve-a escolhendo o tipo de arquivo CSV (extensão .csv).<br><b>Produtos com nomes iguais tem seus dados (preço/unidade) atualizados.</b>"
  207 + field_supplier_name: "Nome do fornecedor"
  208 + field_product_name: "Nome do produto"
  209 + field_product_unit: "Unidade"
  210 + field_product_price: "Preço por unidade"
  211 + remove_all: 'Remover todos os atuais fornecedores cadastrados e todos os seus produtos (começar uma nova lista de produtos).'
  212 + confirm_remove_all: "ATENÇÃO! Essa opção removerá todos os fornecedores cadastrados e todos os seus produtos!"
  213 + unit_help: "Use como <b>unidade</b> uma entre (coloque o nome igual): %{units}"
  214 + send: "Enviar"
  215 + _edit:
  216 + distribution_setts: "Configurações de distribuição do produto"
  217 + margin_price: "Margem / preço"
  218 + default_margin: "Usar padrão"
  219 + stock: "Configuração de estoque"
  220 + default_stock: "Manter estoque"
  221 + current_stock: "Estoque atual"
  222 + distribution_state: "Estado da distribuição"
  223 + available: Ativa
  224 + are_you_sure_you_want: "Tem certeza de que deseja remover este produto?"
  225 + remove_product: remover
  226 + cancel: cancelar
  227 + save: salvar mudanças
  228 + view_on_supplier_page: "Ver produto na página %{terms.supplier.from_article.singular}"
  229 + product_registry: "Cadastro do produto"
  230 + selling_unit: "Unidade de venda"
  231 + same_from_purchase: "Mesma da compra"
  232 + unit: "Unidade"
  233 + unit_detail: "Especificação da unidade"
  234 + _edit_line:
  235 + active: Ativo
  236 + inactive: Inativo
  237 + _form_errors:
  238 + errors_found: "Erros encontrados:"
  239 + _price_details:
  240 + commercialization_act: "Comercialização ativa"
  241 + minimum_order: "Pedido mínimo"
  242 + price: Preço
  243 + unit: Unidade
  244 + unit_specification: "Especificações de unidade"
  245 + _search:
  246 + status: Estado
  247 + category: Categoria
  248 + supplier: "%{terms.supplier.singular.capitalize}"
  249 + product: Produto
  250 + margin: Margem
  251 + price: Preço
  252 + stock: Estoque
  253 + unit: Unidade
  254 + showing_pcount_produc: "Total: %{count} produtos"
  255 + we_don_t_have_product: "Não temos produtos a mostrar."
  256 + add:
  257 + title: "Criar produto"
  258 + own_product: "Criar produto próprio"
  259 + or_from_a_supplier: 'ou de um fornecedor gerido pelo coletivo:'
  260 + index:
  261 + active: ativa
  262 + add_product: "Criar produto"
  263 + all_the_categories: "todas as categorias"
  264 + all_the_suppliers: "todos %{terms.supplier.article.plural}"
  265 + situation: "Situação de distribuição"
  266 + anyone: anyone
  267 + bigger_than_the_stock: "maior do que o estoque"
  268 + change: mudar
  269 + default_margin_of_com: "Margem padrão de comercialização"
  270 + default_margin_info: "Esta margem é automaticamente aplicada em todos os produtos, mas é possível estabelecer margens específicas para certos produtos."
  271 + in_any_state: "Em qualquer estado"
  272 + inactive: inativa
  273 + margin: "%{margin} %"
  274 + no_margin_set: "Sem margem definida"
  275 + products: Produtos
  276 + supplier: "%{terms.supplier.singular.capitalize}"
  277 + info: "Esta é a lista de produtos %{terms.profile.from_which_article.singular} distribuí. Os produtos se dividem entre ativos e inativos."
  278 + the_products: "Os produtos"
  279 + name: "Nome"
  280 + whose_qty_available_i: "cuja qtd disponível é"
  281 + category: "Categoria"
  282 + with_the_qualifiers: "com os qualificadores"
  283 +
  284 + supplier:
  285 + _new:
  286 + title: "Adicionar %{terms.supplier.singular} a %{profile}"
  287 + intro_help: "Comece a digitar o nome %{terms.supplier.from_article.singular} que deseja adicionar. Procuraremos para ver se %{terms.supplier.article.singular} já existe no %{environment}. Caso ele não exista, você poderá criar um tipo de empreendimento que ficará visível APENAS %{terms.profile.on_your.singular}, e não no %{environment} como um todo. Se %{terms.supplier.this.singular} quiser entrar realmente no %{environment}, deve enviar uma mensagem para %{contact_email} fazendo o pedido."
  288 + query_placeholder: "busque %{terms.supplier.undefined_article.singular}"
  289 + search: "Buscar"
  290 + none_found: "%{terms.supplier.none.singular} foi encontrado. Por favor revise o texto da busca."
  291 + add: "Adicionar"
  292 + suggest_creation: "Se você não encontrar %{terms.supplier.article.singular}, pode criar %{terms.supplier.new_undefined_article.singular} que será gerenciado %{terms.profile.by_article.singular}. Mas procure bem para evitar duplicatas!"
  293 + create_dummy: "Criar %{terms.supplier.singular}"
  294 + _edit:
  295 + registry_help: "O registro %{terms.supplier.from_article.singular} será administrado %{terms.profile.by_article.singular} e será privado %{terms.profile.to_it.singular}."
  296 + abbreviated_name: "Nome Abreviado"
  297 + add_supplier_managed_: "Adicionar %{terms.supplier.singular} com registro administrado %{terms.profile.by_article.singular}"
  298 + additional_fields: "Campos adicionais"
  299 + basic_data: "Informações básicas"
  300 + cancel: Cancelar
  301 + description: Descrição
  302 + edit_supplier: "Editar %{terms.supplier.singular}"
  303 + estrategic_informatio: "Informações estratégicas"
  304 + full_registration: "Registro completo"
  305 + name: Nome
  306 + register_new_supplier: "Registrar %{terms.supplier.new_undefined_article.singular}"
  307 + registry_form: "Formulário de registro"
  308 + save: Salvar
  309 + _actions:
  310 + all: "todos"
  311 + none: "nenhum"
  312 + select: "selecionar"
  313 + with_selection: "com selecionados"
  314 + activate: "activar"
  315 + deactivate: "desativar"
  316 + _supplier:
  317 + edit: "Editar"
  318 + cancel: "Cancelar"
  319 + see_page: "Ver página"
  320 + see_products: "Ver produtos"
  321 + message: "Messagem"
  322 + abreviated_name: "Nome Abreviado"
  323 + actions: Ações
  324 + among_them: ", entre eles"
  325 + any_registered_produc: "nenhum produto registrado"
  326 + by_removing_this_supp: "Ao remover %{terms.supplier.article.singular}, todos os produtos ficarão indisponíveis em futuros ciclos. Por favor, confirme sua opção"
  327 + indirect: "indireta"
  328 + direct: "direta"
  329 + active: "ativa"
  330 + inactive: "inativa"
  331 + deactivate: "desativar"
  332 + activate: ativar
  333 + edit: editar
  334 + contact_status: "contato <b>%{status}</b>"
  335 + contact: "contato"
  336 + qualifiers: qualificadores
  337 + tags: "Marcadores"
  338 + minidescription: "Mini descrição"
  339 + cycles: "Ciclos abertos"
  340 + register: "Ficha cadastral"
  341 + geolocalization: "Geolocalização"
  342 + strategic_info: "Informações Estratégicas"
  343 + register: "Ficha cadastral"
  344 + distribution_status: "redistribuição <b>%{status}</b>"
  345 + management_type: "gerência <b>%{type}</b>"
  346 + collective: coletivo
  347 + active_products: "produtos <b>%{count} ativos</b>"
  348 + situation: "Situação geral"
  349 + products:
  350 + zero: "nenhum produto"
  351 + one: "1 produto"
  352 + other: "%{count} produtos"
  353 + manage_products: "gerenciar produtos"
  354 + add_product: "adicionar produto"
  355 + registered_supplier_i: "%{terms.supplier.singular.capitalize} registrado na rede"
  356 + disassociate: "disassociar"
  357 + supplier_profile_product: "produtos registrados"
  358 + supplier_with_registr: "%{terms.supplier.singular.capitalize} com registro administrado %{terms.profile.by_article.singular}"
  359 + this_supplier_has: "Este %{terms.supplier.singular} tem"
  360 + _suppliers_list:
  361 + this_search_didn_t_re: "Esta busca não retornou %{terms.supplier.plural}."
  362 + any_registered: "%{terms.supplier.none.singular.capitalize} registrado."
  363 + index:
  364 + add_supplier: "Adicionar %{terms.supplier.singular}"
  365 + filter: Filtro
  366 + name: Nome
  367 + suppliers: "%{terms.supplier.plural.capitalize}"
  368 + this_is_the_list_of_s: "Esta é a lista de %{terms.supplier.plural} associados %{terms.profile.to_article.singular}. É possível associar %{terms.supplier.to.plural} existentes ou registrar %{terms.supplier.one}."
  369 +
  370 + orders_cycle_plugin:
  371 + terms:
  372 + <<: *suppliers_terms
  373 +
  374 + consumers_coop_plugin:
  375 + terms:
  376 + <<: *suppliers_terms
  377 + profile: &consumers_coop_term_profile
  378 + singular: "coletivo de consumo"
  379 + plural: "coletivos de consumo"
  380 + it:
  381 + singular: "ele"
  382 + plural: "eles"
  383 + article:
  384 + singular: "o coletivo de consumo"
  385 + plural: "os coletivos de consumo"
  386 + undefined_article:
  387 + singular: "um coletivo de consumo"
  388 + by_article:
  389 + singular: "pelo coletivo de consumo"
  390 + to_it:
  391 + singular: "a ele"
  392 + plural: "a eles"
  393 + to_article:
  394 + singular: "ao coletivo de consumo"
  395 + at_article:
  396 + singular: "no coletivo de consumo"
  397 + from:
  398 + singular: "de um coletivo de consumo"
  399 + from_article:
  400 + singular: "do coletivo de consumo"
  401 + from_which_article:
  402 + singular: "com que o coletivo de consumo"
  403 + of:
  404 + singular: "de coletivo de consumo"
  405 + plural: "de coletivos de consumo"
  406 + of_article:
  407 + singular: "do coletivo de consumo"
  408 + plural: "dos coletivos de consumo"
  409 + of_this:
  410 + singular: "deste coletivo de consumo"
  411 + of_another:
  412 + singular: "de outro coletivo de consumo"
  413 + with:
  414 + singular: "com coletivo de consumo"
  415 + plural: "com coletivos de consumo"
  416 + with_article:
  417 + singular: "com o coletivo de consumo"
  418 + plural: "com os coletivos de consumo"
  419 + on_your:
  420 + singular: "em seu coletivo de consumo"
  421 + this:
  422 + singular: "este coletivo de consumo"
  423 +
  424 + networks_plugin:
  425 + terms:
  426 + <<: *suppliers_terms
  427 + profile: &networks_term_profile
  428 + singular: "rede"
  429 + plural: "redes"
  430 + it:
  431 + singular: "ela"
  432 + plural: "elas"
  433 + to_it:
  434 + singular: "a ela"
  435 + plural: "a elas"
  436 + article:
  437 + singular: "a rede"
  438 + plural: "as redes"
  439 + undefined_article:
  440 + singular: "uma rede"
  441 + to_article:
  442 + singular: "a rede"
  443 + at_article:
  444 + singular: "na rede"
  445 + from:
  446 + singular: "de uma rede"
  447 + from_article:
  448 + singular: "da rede"
  449 + from_which_article:
  450 + singular: "com que a rede"
  451 + of:
  452 + singular: "de rede"
  453 + plural: "de redes"
  454 + of_article:
  455 + singular: "da rede"
  456 + plural: "das redes"
  457 + of_this:
  458 + singular: "desta rede"
  459 + of_another:
  460 + singular: "de outra rede"
  461 + with:
  462 + singular: "com rede"
  463 + plural: "com redes"
  464 + with_article:
  465 + singular: "com a rede"
  466 + plural: "com a organizações"
  467 + this:
  468 + singular: "esta rede"
  469 + on_your:
  470 + singular: "na sua rede"
  471 + supplier:
  472 + singular: "empreendimento"
  473 + plural: "empreendimentos"
  474 + one: "um"
  475 + it:
  476 + singular: "ele"
  477 + plural: "eles"
  478 + to:
  479 + singular: "a um empreendimento"
  480 + plural: "a empreendimentos"
  481 + to_it:
  482 + singular: "a ele"
  483 + plural: "a eles"
  484 + to_article:
  485 + singular: "ao empreendimento"
  486 + plural: "aos empreendimentos"
  487 + article:
  488 + singular: "o empreendimento"
  489 + plural: "os empreendimentos"
  490 + by_article:
  491 + singular: "pelo empreendimento"
  492 + undefined_article:
  493 + singular: "um empreendimento"
  494 + new_undefined_article:
  495 + singular: "um novo empreendimento"
  496 + to_it:
  497 + singular: "a ele"
  498 + plural: "a eles"
  499 + to_article:
  500 + singular: "ao empreendimento"
  501 + plural: "aos empreendimentos"
  502 + none:
  503 + singular: "nenhum empreendimento"
  504 + this:
  505 + singular: "este empreendimento"
  506 + your:
  507 + singular: "seu empreendimento"
  508 + plural: "seus empreendimentos"
  509 + from:
  510 + singular: "de empreendimento"
  511 + from_article:
  512 + singular: "do empreendimento"
  513 + from_which_article:
  514 + singular: "com que o empreendimento"
  515 + of:
  516 + singular: "de empreendimento"
  517 + plural: "de empreendimentos"
  518 + of_article:
  519 + singular: "do empreendimento"
  520 + plural: "dos empreendimentos"
  521 + of_this:
  522 + singular: "deste empreendimento"
  523 + of_another:
  524 + singular: "de outro empreendimento"
  525 + with:
  526 + singular: "com empreendimento"
  527 + plural: "com empreendimentos"
  528 + with_article:
  529 + singular: "com o empreendimento"
  530 + plural: "com os empreendimentos"
  531 +
  532 +pt_BR:
  533 + <<: *pt-BR
  534 +pt:
  535 + <<: *pt-BR
  536 +
... ...
plugins/suppliers/models/suppliers_plugin/base_product.rb 0 → 100644
... ... @@ -0,0 +1,192 @@
  1 +# for some unknown reason, if this is named SuppliersPlugin::Product then
  2 +# cycle.products will go to an infinite loop
  3 +class SuppliersPlugin::BaseProduct < Product
  4 +
  5 + attr_accessible :default_margin_percentage, :margin_percentage, :default_unit, :unit_detail,
  6 + :supplier_product_attributes
  7 +
  8 + accepts_nested_attributes_for :supplier_product
  9 +
  10 + default_scope include: [
  11 + # from_products is required for products.available
  12 + :from_products,
  13 + # FIXME: move use cases to a scope called 'includes_for_links'
  14 + {
  15 + suppliers: [{ profile: [:domains, {environment: :domains}] }]
  16 + },
  17 + {
  18 + profile: [:domains, {environment: :domains}]
  19 + }
  20 + ]
  21 +
  22 + self.abstract_class = true
  23 +
  24 + settings_items :minimum_selleable, type: Float, default: nil
  25 + settings_items :margin_percentage, type: Float, default: nil
  26 + settings_items :quantity, type: Float, default: nil
  27 + settings_items :unit_detail, type: String, default: nil
  28 +
  29 + CORE_DEFAULT_ATTRIBUTES = [
  30 + :name, :description, :price, :unit_id, :product_category_id, :image_id,
  31 + ]
  32 + DEFAULT_ATTRIBUTES = CORE_DEFAULT_ATTRIBUTES + [
  33 + :margin_percentage, :stored, :minimum_selleable, :unit_detail,
  34 + ]
  35 +
  36 + extend DefaultDelegate::ClassMethods
  37 + default_delegate_setting :name, to: :supplier_product
  38 + default_delegate_setting :description, to: :supplier_product
  39 +
  40 + default_delegate_setting :qualifiers, to: :supplier_product
  41 + default_delegate :product_qualifiers, default_setting: :default_qualifiers, to: :supplier_product
  42 +
  43 + default_delegate_setting :product_category, to: :supplier_product
  44 + default_delegate :product_category_id, default_setting: :default_product_category, to: :supplier_product
  45 +
  46 + default_delegate_setting :image, to: :supplier_product, prefix: :_default
  47 + default_delegate :image_id, default_setting: :_default_image, to: :supplier_product
  48 +
  49 + default_delegate_setting :unit, to: :supplier_product
  50 + default_delegate :unit_id, default_setting: :default_unit, to: :supplier_product
  51 +
  52 + default_delegate_setting :margin_percentage, to: :profile,
  53 + default_if: -> { self.own_margin_percentage.blank? or self.own_margin_percentage.zero? }
  54 + default_delegate :price, default_setting: :default_margin_percentage, default_if: :equal?,
  55 + to: -> { self.supplier_product.price_with_discount if self.supplier_product }
  56 +
  57 + default_delegate :unit_detail, default_setting: :default_unit, to: :supplier_product
  58 + default_delegate_setting :minimum_selleable, to: :supplier_product
  59 +
  60 + extend CurrencyHelper::ClassMethods
  61 + has_currency :own_price
  62 + has_currency :original_price
  63 + has_number_with_locale :minimum_selleable
  64 + has_number_with_locale :own_minimum_selleable
  65 + has_number_with_locale :original_minimum_selleable
  66 + has_number_with_locale :quantity
  67 + has_number_with_locale :margin_percentage
  68 + has_number_with_locale :own_margin_percentage
  69 + has_number_with_locale :original_margin_percentage
  70 +
  71 + def self.default_product_category environment
  72 + ProductCategory.top_level_for(environment).order('name ASC').first
  73 + end
  74 + def self.default_unit
  75 + Unit.new(singular: I18n.t('suppliers_plugin.models.product.unit'), plural: I18n.t('suppliers_plugin.models.product.units'))
  76 + end
  77 +
  78 + def self.search_scope scope, params
  79 + scope = scope.from_supplier_id params[:supplier_id] if params[:supplier_id].present?
  80 + scope = scope.with_available(if params[:available] == 'true' then true else false end) if params[:available].present?
  81 + scope = scope.name_like params[:name] if params[:name].present?
  82 + scope = scope.with_product_category_id params[:category_id] if params[:category_id].present?
  83 + scope
  84 + end
  85 +
  86 + def self.orphans_ids
  87 + # FIXME: need references from rails4 to do it without raw query
  88 + result = self.connection.execute <<-SQL
  89 +SELECT products.id FROM products
  90 +LEFT OUTER JOIN suppliers_plugin_source_products ON suppliers_plugin_source_products.to_product_id = products.id
  91 +LEFT OUTER JOIN products from_products_products ON from_products_products.id = suppliers_plugin_source_products.from_product_id
  92 +WHERE products.type IN (#{(self.descendants << self).map{ |d| "'#{d}'" }.join(',')})
  93 +GROUP BY products.id HAVING count(from_products_products.id) = 0;
  94 +SQL
  95 + result.values
  96 + end
  97 +
  98 + def self.archive_orphans
  99 + self.where(id: self.orphans_ids).find_each batch_size: 50 do |product|
  100 + # need full save to trigger search index
  101 + product.update_attributes archived: true
  102 + end
  103 + end
  104 +
  105 + def buy_price
  106 + self.supplier_products.inject(0){ |sum, p| sum += p.price || 0 }
  107 + end
  108 + def buy_unit
  109 + #TODO: handle multiple products
  110 + (self.supplier_product.unit rescue nil) || self.class.default_unit
  111 + end
  112 +
  113 + def available
  114 + self[:available]
  115 + end
  116 +
  117 + def available_with_supplier
  118 + return self.available_without_supplier unless self.supplier
  119 + self.available_without_supplier and self.supplier.active rescue false
  120 + end
  121 + def chained_available
  122 + return self.available_without_supplier unless self.supplier_product
  123 + self.available_without_supplier and self.supplier_product.available and self.supplier.active rescue false
  124 + end
  125 + alias_method_chain :available, :supplier
  126 +
  127 + def dependent?
  128 + self.from_products.length >= 1
  129 + end
  130 + def orphan?
  131 + !self.dependent?
  132 + end
  133 +
  134 + def minimum_selleable
  135 + self[:minimum_selleable] || 0.1
  136 + end
  137 +
  138 + def price_with_margins base_price = nil, margin_source = nil
  139 + margin_source ||= self
  140 + margin_percentage = margin_source.margin_percentage
  141 + margin_percentage ||= self.profile.margin_percentage if self.profile
  142 +
  143 + base_price ||= 0
  144 + price = if margin_percentage and not base_price.zero?
  145 + base_price.to_f + (margin_percentage.to_f / 100) * base_price.to_f
  146 + else
  147 + self.price_with_default
  148 + end
  149 +
  150 + price
  151 + end
  152 +
  153 + def price_without_margins
  154 + self[:price] / (1 + self.margin_percentage/100)
  155 + end
  156 +
  157 + # FIXME: move to core
  158 + # just in case the from_products is nil
  159 + def product_category_with_default
  160 + self.product_category_without_default or self.class.default_product_category(self.environment)
  161 + end
  162 + def product_category_id_with_default
  163 + self.product_category_id_without_default or self.product_category_with_default.id
  164 + end
  165 + alias_method_chain :product_category, :default
  166 + alias_method_chain :product_category_id, :default
  167 +
  168 + # FIXME: move to core
  169 + def unit_with_default
  170 + self.unit_without_default or self.class.default_unit
  171 + end
  172 + alias_method_chain :unit, :default
  173 +
  174 + # FIXME: move to core
  175 + def archive
  176 + self.update_attributes! archived: true
  177 + end
  178 + def unarchive
  179 + self.update_attributes! archived: false
  180 + end
  181 +
  182 + protected
  183 +
  184 + def validate_uniqueness_of_column_name?
  185 + false
  186 + end
  187 +
  188 + # overhide Product's after_create callback to avoid infinite loop
  189 + def distribute_to_consumers
  190 + end
  191 +
  192 +end
... ...
plugins/suppliers/models/suppliers_plugin/consumer.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class SuppliersPlugin::Consumer < SuppliersPlugin::Supplier
  2 +
  3 + self.table_name = :suppliers_plugin_suppliers
  4 +
  5 + belongs_to :profile, foreign_key: :consumer_id
  6 + belongs_to :supplier, foreign_key: :profile_id
  7 + alias_method :consumer, :profile
  8 +
  9 + def name
  10 + self.profile.name
  11 + end
  12 +
  13 +end
... ...
plugins/suppliers/models/suppliers_plugin/distributed_product.rb 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +class SuppliersPlugin::DistributedProduct < SuppliersPlugin::BaseProduct
  2 +
  3 + attr_accessible :from_products
  4 +
  5 + # missed from lib/ext/product.rb because of STI
  6 + attr_accessible :external_id, :price_details
  7 +
  8 + validates_presence_of :supplier
  9 +
  10 + def supplier_price
  11 + self.supplier_product.price if self.supplier_product
  12 + end
  13 +
  14 + # Automatic set/get price chaging/applying margins
  15 + # FIXME: this won't work if we have other params, like fixed margin, delivery cost, etc
  16 + def price
  17 + base_price = self.supplier_price
  18 + return super if base_price.blank?
  19 +
  20 + self.price_with_margins base_price
  21 + end
  22 + def price= value
  23 + return super if value.blank?
  24 + value = value.to_f
  25 + base_price = self.supplier_price
  26 + return super if base_price.blank?
  27 +
  28 + self.margin_percentage = 100 * (value - base_price) / base_price
  29 + super
  30 + end
  31 +
  32 + protected
  33 +
  34 +end
... ...
plugins/suppliers/models/suppliers_plugin/source_product.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +class SuppliersPlugin::SourceProduct < ActiveRecord::Base
  2 +
  3 + attr_accessible :from_product, :to_product, :quantity
  4 +
  5 + default_scope include: [:from_product, :to_product]
  6 +
  7 + belongs_to :from_product, class_name: 'Product'
  8 + belongs_to :to_product, class_name: 'Product'
  9 + belongs_to :supplier, class_name: 'SuppliersPlugin::Supplier'
  10 +
  11 + has_many :sources_from_products, through: :from_product
  12 + has_many :sources_to_products, through: :to_product
  13 +
  14 + has_one :supplier_profile, through: :supplier, source: :profile
  15 +
  16 + before_validation :find_supplier
  17 +
  18 + validates_presence_of :from_product
  19 + validates_presence_of :to_product
  20 + validates_presence_of :supplier
  21 + validates_numericality_of :quantity, allow_nil: true
  22 +
  23 + protected
  24 +
  25 + def find_supplier
  26 + self.supplier = SuppliersPlugin::Supplier.where(profile_id: self.from_product.profile_id, consumer_id: self.to_product.profile_id).first
  27 + raise "Can't find supplier" unless self.supplier
  28 + self.supplier
  29 + end
  30 +
  31 +end
... ...
plugins/suppliers/models/suppliers_plugin/supplier.rb 0 → 100644
... ... @@ -0,0 +1,158 @@
  1 +class SuppliersPlugin::Supplier < ActiveRecord::Base
  2 +
  3 + attr_accessor :distribute_products_on_create, :dont_destroy_dummy, :identifier_from_name
  4 +
  5 + attr_accessible :profile_id, :profile, :consumer, :consumer_id, :name, :name_abbreviation, :description
  6 +
  7 + belongs_to :profile
  8 + belongs_to :consumer, class_name: 'Profile'
  9 + alias_method :supplier, :profile
  10 +
  11 + validates_presence_of :name, if: :dummy?
  12 + validates_associated :profile, if: :dummy?
  13 + validates_presence_of :profile
  14 + validates_presence_of :consumer
  15 + validates_uniqueness_of :consumer_id, scope: :profile_id, if: :profile_id
  16 +
  17 + scope :alphabetical, -> { order 'name ASC' }
  18 +
  19 + scope :active, conditions: {active: true}
  20 + scope :dummy, -> { joins(:profile).where profiles: {visible: false} }
  21 +
  22 + scope :of_profile, lambda { |p| { conditions: {profile_id: p.id} } }
  23 + scope :of_profile_id, lambda { |id| { conditions: {profile_id: id} } }
  24 + scope :of_consumer, lambda { |c| { conditions: {consumer_id: c.id} } }
  25 + scope :of_consumer_id, lambda { |id| { conditions: {consumer_id: id} } }
  26 +
  27 + scope :from_supplier_id, lambda { |supplier_id| { conditions: ['suppliers_plugin_suppliers.id = ?', supplier_id] } }
  28 +
  29 + scope :with_name, lambda { |name| if name then {conditions: ["LOWER(suppliers_plugin_suppliers.name) LIKE ?","%#{name.downcase}%"]} else {} end }
  30 + scope :by_active, lambda { |active| if active then {conditions: {active: active}} else {} end }
  31 +
  32 + scope :except_people, { conditions: ['profiles.type <> ?', Person.name], joins: [:consumer] }
  33 + scope :except_self, { conditions: 'profile_id <> consumer_id' }
  34 +
  35 + after_create :add_admins, if: :dummy?
  36 + after_create :save_profile, if: :dummy?
  37 + after_create :distribute_products_to_consumer
  38 + before_validation :fill_identifier, if: :dummy?
  39 + before_destroy :destroy_consumer_products
  40 +
  41 + def self.new_dummy attributes
  42 + environment = attributes[:consumer].environment
  43 + profile = environment.enterprises.build
  44 + profile.enabled = false
  45 + profile.visible = false
  46 + profile.public_profile = false
  47 +
  48 + supplier = self.new
  49 + supplier.profile = profile
  50 + supplier.consumer = attributes.delete :consumer
  51 + supplier.attributes = attributes
  52 + supplier
  53 + end
  54 + def self.create_dummy attributes
  55 + s = new_dummy attributes
  56 + s.save!
  57 + s
  58 + end
  59 +
  60 + def self?
  61 + self.profile_id == self.consumer_id
  62 + end
  63 + def person?
  64 + self.consumer.person?
  65 + end
  66 + def dummy?
  67 + !self.supplier.visible rescue false
  68 + end
  69 + def active?
  70 + self.active
  71 + end
  72 +
  73 + def name
  74 + self.attributes['name'] || self.profile.name
  75 + end
  76 + def name= value
  77 + self['name'] = value
  78 + self.supplier.name = value if self.dummy? and not self.supplier.frozen?
  79 + end
  80 + def description
  81 + self.attributes['description'] || self.profile.description
  82 + end
  83 + def description= value
  84 + self['description'] = value
  85 + self.supplier.description = value if self.dummy? and not self.supplier.frozen?
  86 + end
  87 +
  88 + def abbreviation_or_name
  89 + return self.profile.nickname || self.name if self.self?
  90 + self.name_abbreviation.blank? ? self.name : self.name_abbreviation
  91 + end
  92 +
  93 + def destroy_with_dummy
  94 + if not self.self? and not self.dont_destroy_dummy and self.supplier and self.supplier.dummy?
  95 + self.supplier.destroy
  96 + end
  97 + self.destroy_without_dummy
  98 + end
  99 + alias_method_chain :destroy, :dummy
  100 +
  101 + protected
  102 +
  103 + def set_identifier
  104 + if self.identifier_from_name
  105 + identifier = self.profile.identifier = self.profile.name.to_slug
  106 + i = 0
  107 + self.profile.identifier = "#{identifier}#{i += 1}" while Profile[self.profile.identifier].present?
  108 + else
  109 + self.profile.identifier = Digest::MD5.hexdigest rand.to_s
  110 + end
  111 + end
  112 +
  113 + def fill_identifier
  114 + return if self.profile.identifier.present?
  115 + set_identifier
  116 + end
  117 +
  118 + def add_admins
  119 + self.consumer.admins.to_a.each{ |a| self.supplier.add_admin a }
  120 + end
  121 +
  122 + # sync name, description, etc
  123 + def save_profile
  124 + self.supplier.save
  125 + end
  126 +
  127 + def distribute_products_to_consumer
  128 + self.distribute_products_on_create = true if self.distribute_products_on_create.nil?
  129 + return if self.self? or self.consumer.person? or not self.distribute_products_on_create
  130 +
  131 + already_supplied = self.consumer.distributed_products.unarchived.from_supplier_id(self.id).all
  132 +
  133 + self.profile.products.unarchived.each do |source_product|
  134 + next if already_supplied.find{ |f| f.supplier_product == source_product }
  135 +
  136 + source_product.distribute_to_consumer self.consumer
  137 + end
  138 + end
  139 + handle_asynchronously :distribute_products_to_consumer
  140 +
  141 + def destroy_consumer_products
  142 + self.consumer.products.joins(:suppliers).from_supplier(self).destroy_all
  143 + end
  144 +
  145 + # delegate missing methods to profile
  146 + def method_missing method, *args, &block
  147 + if self.profile.respond_to? method
  148 + self.profile.send method, *args, &block
  149 + else
  150 + super method, *args, &block
  151 + end
  152 + end
  153 + def respond_to_with_profile? method, include_private=false
  154 + respond_to_without_profile? method, include_private or Profile.new.respond_to? method, include_private
  155 + end
  156 + alias_method_chain :respond_to?, :profile
  157 +
  158 +end
... ...
plugins/suppliers/public/images/control-panel/manage-suppliers.jpg 0 → 100644

17.3 KB

plugins/suppliers/public/images/control-panel/manage-suppliers.png 0 → 100644

4.32 KB

plugins/suppliers/public/images/minus.png 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/images/minus.png
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/images/plus.png 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/images/plus.png
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/javascripts/locale.js 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/javascripts/locale.js
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/javascripts/sortable-table.js 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/javascripts/sortable-table.js
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/javascripts/suppliers.js 0 → 100644
... ... @@ -0,0 +1,194 @@
  1 +
  2 +suppliers = {
  3 +
  4 + filter: {
  5 + form: function() {
  6 + return table_filter.form()
  7 + },
  8 +
  9 + apply: function() {
  10 + this.form().submit()
  11 + },
  12 +
  13 + },
  14 +
  15 + add_link: function () {
  16 + if (toggle_edit.isEditing())
  17 + toggle_edit.value_row.toggle_edit();
  18 +
  19 + var supplier_add = $('#supplier-add');
  20 + toggle_edit.setEditing(supplier_add);
  21 + toggle_edit.value_row.toggle_edit();
  22 + },
  23 +
  24 + toggle_edit: function () {
  25 + if (toggle_edit.editing().is('#supplier-add'))
  26 + toggle_edit.editing().toggle(toggle_edit.isEditing());
  27 + toggle_edit.editing().find('.box-view').toggle(!toggle_edit.isEditing());
  28 + toggle_edit.editing().find('.box-edit').toggle(toggle_edit.isEditing());
  29 + },
  30 +
  31 + add: {
  32 + supplier_added: function () {
  33 + $('#results').html('');
  34 + },
  35 + create_dummy: function () {
  36 + $('#find-enterprise, #create-dummy, #create-dummy .box-edit').toggle();
  37 + },
  38 +
  39 + search: function (input) {
  40 + query = $(input).val();
  41 + if (query.length < 3)
  42 + return;
  43 + input.form.onsubmit();
  44 + },
  45 + },
  46 +
  47 + // core uses (future product plugin)
  48 + product: {
  49 +
  50 + pmsync: function (to_price) {
  51 + var margin_input = $('#product_margin_percentage')
  52 + var price_input = $('#product_price')
  53 + var base_price_input = $('#product_supplier_product_attributes_price')
  54 +
  55 + if (to_price)
  56 + suppliers.price.calculate(price_input, margin_input, base_price_input)
  57 + else
  58 + suppliers.margin.calculate(margin_input, price_input, base_price_input)
  59 + },
  60 +
  61 + updateBasePrice: function () {
  62 + this.pmsync(this)
  63 + },
  64 + },
  65 +
  66 + our_product: {
  67 +
  68 + toggle_edit: function () {
  69 + toggle_edit.editing().find('.box-edit').toggle(toggle_edit.isEditing())
  70 + },
  71 +
  72 + default_change: function (event) {
  73 + var block = $(this).parents('.block')
  74 + var nonDefaults = block.find('div[data-non-defaults]')
  75 + nonDefaults.toggle(!this.checked)
  76 + nonDefaults.find('input,select,textarea').prop('disabled', this.checked)
  77 + },
  78 +
  79 + load: function(id) {
  80 + $('#our-product-'+id+' div[data-default-toggle] input').change(suppliers.our_product.default_change).change();
  81 + },
  82 +
  83 + pmsync: function (context, to_price) {
  84 + var p = $(context).parents('.our-product')
  85 + var margin_input = p.find('.product-margin-percentage')
  86 + var price_input = p.find('.product-price')
  87 + var buy_price_input = p.find('.product-base-price')
  88 +
  89 + if (to_price)
  90 + suppliers.price.calculate(price_input, margin_input, buy_price_input)
  91 + else
  92 + suppliers.margin.calculate(margin_input, price_input, buy_price_input)
  93 + },
  94 +
  95 + select: {
  96 + all: function() {
  97 + $('.our-product #product_ids_').attr('checked', true)
  98 + },
  99 + none: function() {
  100 + $('.our-product #product_ids_').attr('checked', false)
  101 + },
  102 +
  103 + activate: function(state) {
  104 + var selection = $('.our-product #product_ids_:checked').parents('.our-product')
  105 + selection.find('.available input[type=checkbox]').each(function() {
  106 + this.checked = state
  107 + $(this.form).submit()
  108 + });
  109 + },
  110 +
  111 + },
  112 +
  113 + import: {
  114 + confirmRemoveAll: function(checkbox, text) {
  115 + if (checkbox.checked && !confirm(text))
  116 + checkbox.checked = false
  117 + },
  118 + },
  119 + },
  120 +
  121 + basket: {
  122 + searchUrl: null,
  123 + addUrl: null,
  124 + removeUrl: null,
  125 +
  126 + remove: function (id) {
  127 + var self = this
  128 + $.ajax(self.removeUrl, {method: 'DELETE',
  129 + data: {aggregate_id: id},
  130 + success: function(data) {
  131 + self.reload(data)
  132 + },
  133 + })
  134 + },
  135 +
  136 + reload: function (data) {
  137 + $('#product-basket').html(data)
  138 + $('#basket-add').focus()
  139 + },
  140 +
  141 + load: function () {
  142 + var self = this
  143 + var input = $('#basket-add')
  144 +
  145 + self.source = new Bloodhound({
  146 + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
  147 + queryTokenizer: Bloodhound.tokenizers.whitespace,
  148 + remote: this.searchUrl+'?query=%QUERY',
  149 + })
  150 + self.source.initialize()
  151 +
  152 + input.typeahead({
  153 + minLength: 2, highlight: true,
  154 + }, {
  155 + displayKey: 'label',
  156 + source: this.source.ttAdapter(),
  157 + }).on('typeahead:selected', function(e, item) {
  158 + input.val('')
  159 + $.post(self.addUrl, {aggregate_id: item.value}, function(data) {
  160 + self.reload(data)
  161 + })
  162 + })
  163 + },
  164 + },
  165 +
  166 + price: {
  167 +
  168 + calculate: function (price_input, margin_input, base_price_input) {
  169 + var price = unlocalize_currency($(price_input).val());
  170 + var base_price = unlocalize_currency($(base_price_input).val());
  171 + var margin = unlocalize_currency($(margin_input).val());
  172 +
  173 + var value = base_price + (margin / 100) * base_price;
  174 + if (isNaN(value))
  175 + value = unlocalize_currency(base_price_input.val());
  176 + $(price_input).val(value);
  177 + },
  178 + },
  179 +
  180 + margin: {
  181 +
  182 + calculate: function (margin_input, price_input, base_price_input) {
  183 + var price = unlocalize_currency($(price_input).val());
  184 + var base_price = unlocalize_currency($(base_price_input).val());
  185 + var margin = unlocalize_currency($(margin_input).val());
  186 +
  187 + var value = ((price - base_price) / base_price ) * 100;
  188 + value = !isFinite(value) ? 0.0 : value;
  189 + $(margin_input).val(value);
  190 + },
  191 + },
  192 +
  193 +};
  194 +
... ...
plugins/suppliers/public/javascripts/table_filter.js 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/javascripts/table_filter.js
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/javascripts/toggle_edit.js 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/javascripts/toggle_edit.js
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/style.scss 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +@import 'stylesheets/suppliers'
  2 +
... ...
plugins/suppliers/public/stylesheets/_actions.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_actions.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/_base.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_base.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/_field.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_field.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/_filter.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_filter.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/_popin.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_popin.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/_product.scss 0 → 100644
... ... @@ -0,0 +1,129 @@
  1 +@import 'base';
  2 +
  3 +#supplier-our-products {
  4 +
  5 + .sortable-table {
  6 +
  7 + .box-field {
  8 + &.status {
  9 + width: $module01 - $base;
  10 + }
  11 + &.category {
  12 + width: $module02 - $base;
  13 + }
  14 + &.supplier {
  15 + width: $module02;
  16 + }
  17 + &.product {
  18 + width: $module04;
  19 + }
  20 + &.margin {
  21 + width: $module01;
  22 + }
  23 + &.price {
  24 + width: $module01;
  25 + }
  26 + &.stock {
  27 + width: $module01 - $base;
  28 + }
  29 + &.unit {
  30 + width: $module01 + 2*$base;
  31 + }
  32 + }
  33 +
  34 + form {
  35 + width: $module10 + $intercolumn;
  36 + float: left;
  37 + @extend .container-clean;
  38 +
  39 + .field-secondary {
  40 + float: left;
  41 + display: block;
  42 +
  43 + &, input, select {
  44 + width: $module02 - $intercolumn;
  45 + }
  46 +
  47 + &.with-unit {
  48 + @extend .container-clean;
  49 +
  50 + .input-group {
  51 + float: left;
  52 + width: $module02 - 2*$intercolumn;
  53 + &, input, select {
  54 + width: 100%;
  55 + }
  56 + }
  57 + }
  58 + }
  59 +
  60 + .internal-table {
  61 +
  62 + .row {
  63 + @extend .container-clean;
  64 +
  65 + &.first {
  66 + .block {
  67 + //height: 12*$height;
  68 + }
  69 + }
  70 +
  71 + .block {
  72 + float: left;
  73 + border-top: 2*$border solid rgba(255,255,255,0.5);
  74 + border-right: 2*$border solid rgba(255,255,255,0.5);
  75 + padding: $padding;
  76 + padding-top: $padding - 2*$border;
  77 + padding-right: $padding - 2*$border;
  78 + margin: 0;
  79 +
  80 + &:last-child {
  81 + border-right: none;
  82 + }
  83 +
  84 + &.margin-price {
  85 + width: $module04;
  86 + }
  87 + &.stock {
  88 + width: $module04;
  89 + }
  90 + &.available {
  91 + width: $module02;
  92 + color: black; //overwrite stupid application.css style
  93 + }
  94 +
  95 + .field-secondary {
  96 + margin-right: $margin;
  97 + }
  98 + }
  99 + }
  100 + }
  101 +
  102 + .action-button-container {
  103 + float: left;
  104 + width: $module10 - 2*$intercolumn;
  105 + margin-top: $half-margin;
  106 +
  107 + .action-button {
  108 +
  109 + &.save {
  110 + float: right;
  111 + }
  112 + &.remove, &.cancel {
  113 + margin-right: $margin;
  114 + float: left;
  115 + }
  116 + }
  117 + }
  118 + }
  119 + }
  120 +
  121 + .links {
  122 + float: right;
  123 + width: $module02;
  124 +
  125 + .action-button {
  126 + margin-bottom: $half-margin;
  127 + }
  128 + }
  129 +}
... ...
plugins/suppliers/public/stylesheets/_sortable-table.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../orders/public/stylesheets/_sortable-table.scss
0 2 \ No newline at end of file
... ...
plugins/suppliers/public/stylesheets/suppliers.scss 0 → 100644
... ... @@ -0,0 +1,160 @@
  1 +@import 'base';
  2 +@import 'popin';
  3 +@import 'sortable-table';
  4 +
  5 +@import 'filter';
  6 +@import 'actions';
  7 +
  8 +@import 'product';
  9 +
  10 +.controller-profile_editor a.control-panel-suppliers-manage-suppliers {
  11 + background-image: url("/plugins/suppliers/images/control-panel/manage-suppliers.png")
  12 +}
  13 +
  14 +#suppliers-page {
  15 +
  16 + /* should be moved to core */
  17 + input.small-loading {
  18 + background: transparent url(/images/loading-small.gif) no-repeat scroll right center;
  19 + }
  20 +
  21 + #search-results {
  22 +
  23 + > strong {
  24 + display: block;
  25 + margin-top: 3*$margin;
  26 + border-top: $border solid black;
  27 + }
  28 + }
  29 +
  30 + .supplier {
  31 + border-top: $border solid black;
  32 + border-bottom: none;
  33 +
  34 + &.supplier-inactive {
  35 + background-color: #faeeee;
  36 + }
  37 +
  38 + &#supplier-add {
  39 + display: none;
  40 + &.edit {
  41 + display: block;
  42 + }
  43 + }
  44 + &:last-child {
  45 + border-bottom: $border solid black;
  46 + }
  47 +
  48 + .supplier-add-link {
  49 + margin: 20px 0;
  50 + }
  51 +
  52 + #create-dummy {
  53 + display: none; //default
  54 + }
  55 +
  56 + .box-view {
  57 + padding: $padding;
  58 + padding-right: 0;
  59 + @extend .container-clean;
  60 +
  61 + .supplier-type {
  62 + font-style: italic;
  63 + }
  64 +
  65 + .supplier-logo {
  66 + float: left;
  67 + width: 60px;
  68 + min-height: 60px;
  69 + }
  70 +
  71 + .supplier-body {
  72 + width: $module04;
  73 + float: left;
  74 + }
  75 +
  76 + .supplier-name {
  77 + text-transform: uppercase;
  78 + }
  79 +
  80 + .supplier-actions {
  81 + float: right;
  82 + width: $module03;
  83 + text-align: right;
  84 + .hidden {
  85 + display: none;
  86 + }
  87 + }
  88 + }
  89 +
  90 + .box-edit {
  91 + display: none; //default
  92 + padding: $padding;
  93 + background-color: #FDF2A3;
  94 +
  95 + form {
  96 + width: 640px;
  97 + @import 'field';
  98 +
  99 + // disable them until they are ready
  100 + .full-registration-check,
  101 + .full-data {
  102 + display: none !important;
  103 + }
  104 +
  105 + .data {
  106 +
  107 + textarea,
  108 + input {
  109 + width: 100%;
  110 + }
  111 +
  112 + .basic-data,
  113 + .full-data {
  114 + float: left;
  115 + @extend .container-clean;
  116 + width: 290px;
  117 + margin-right: $wireframe-padding;
  118 + }
  119 + .full-data {
  120 + &.estrategic-info {
  121 + float: right;
  122 + }
  123 + &.additional-fields {
  124 + width: 100%;
  125 + }
  126 + }
  127 + }
  128 +
  129 + input[type=submit] {
  130 + margin-top: $half-margin;
  131 + margin-right: $half-margin;
  132 + }
  133 + }
  134 + }
  135 + }
  136 +
  137 + .filter-box {
  138 +
  139 + .box-field {
  140 +
  141 + &, input {
  142 + width: 100%;
  143 + margin-left: 0;
  144 + }
  145 + }
  146 + }
  147 +}
  148 +
  149 +#suppliers-products-import {
  150 + padding:
  151 +
  152 + table, th, td {
  153 + border: $border solid black;
  154 + }
  155 + table {
  156 + th {
  157 + font-weight: bold;
  158 + }
  159 + }
  160 +}
... ...
plugins/suppliers/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
... ...
plugins/suppliers/test/unit/code_numbering_test.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class CodeNumberingTest < ActiveSupport::TestCase
  4 +
  5 +
  6 +end
  7 +
... ...
plugins/suppliers/test/unit/default_delegate_test.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +require 'test_helper'
  2 +
  3 +class DefaultDelegateTest < ActiveSupport::TestCase
  4 +
  5 +
  6 +end
... ...
plugins/suppliers/test/unit/suppliers_plugin/distributed_product_test.rb 0 → 100644
... ... @@ -0,0 +1,108 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class SuppliersPlugin::DistributedProductTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @product_category = create(ProductCategory, :name => 'parent')
  7 + @profile = build(Enterprise)
  8 + @invisible_profile = build(Enterprise, :visible => false)
  9 + @other_profile = build(Enterprise)
  10 + @self_supplier = build(SuppliersPlugin::Supplier, :consumer => @profile, :profile => @profile)
  11 + @dummy_supplier = build(SuppliersPlugin::Supplier, :consumer => @profile, :profile => @dummy_profile)
  12 + @other_supplier = build(SuppliersPlugin::Supplier, :consumer => @profile, :profile => @other_profile)
  13 + end
  14 +
  15 + attr_accessor :product_category,
  16 + :profile, :invisible_profile, :other_profile,
  17 + :profile, :dummy_profile, :other_profile, :self_supplier, :dummy_supplier, :other_supplier
  18 +
  19 + should 'return default settings considering dummy supplier' do
  20 + product = build(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @dummy_supplier)
  21 + assert_equal nil, product.default_name
  22 + assert_equal nil, product.default_description
  23 + product = build(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @other_supplier)
  24 + assert_equal true, product.default_name
  25 + assert_equal true, product.default_description
  26 + end
  27 +
  28 + should 'return price without margins if it is own product' do
  29 + product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :profile => @profile, :supplier => @self_supplier)
  30 + assert_equal 10.0, product.price.to_f
  31 + end
  32 +
  33 + should 'return price without margins if supplier product has no price' do
  34 + supplier_product = build(SuppliersPlugin::DistributedProduct, :profile => @other_profile, :supplier => @other_profile.self_supplier)
  35 + product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :profile => @profile, :supplier => @other_supplier)
  36 + assert_equal 10.0, product.price.to_f
  37 + end
  38 +
  39 + should 'return price with margins' do
  40 + supplier_product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :profile => @other_profile, :supplier => @other_profile.self_supplier)
  41 + product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :supplier_product => supplier_product, :profile => @profile, :supplier => @other_supplier)
  42 +
  43 + product.default_margin_percentage = false
  44 + assert_equal 11.0, product.price.to_f
  45 + profile.margin_percentage = 20
  46 + product.default_margin_percentage = true
  47 + assert_equal 12.0, product.price.to_f
  48 + end
  49 +
  50 + should 'allow set of supplier product' do
  51 + product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :profile => @profile, :supplier => @self_supplier)
  52 +
  53 + product.from_product = build(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @self_supplier)
  54 + assert_nothing_raised do
  55 + product.supplier_product = {:price => 10, :margin_percentage => 10}
  56 + product.supplier_product = SuppliersPlugin::DistributedProduct.new :price => 5
  57 + end
  58 + end
  59 +
  60 + should 'create a supplier product for a dummy supplier' do
  61 + product = build(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @dummy_supplier)
  62 + assert product.supplier_product
  63 + # negative assertion
  64 + product = build(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @other_supplier)
  65 + assert !product.supplier_product
  66 + end
  67 +
  68 + should 'respond to supplier_product_id setter and getter' do
  69 + product = create(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @dummy_supplier)
  70 + assert_equal product.supplier_product.id, product.supplier_product_id
  71 + product.expects(:distribute_from)
  72 + product.supplier_product_id = 1
  73 + end
  74 +
  75 + should 'respond to distribute_from' do
  76 + product = create(SuppliersPlugin::DistributedProduct, :profile => @profile, :supplier => @profile.self_supplier)
  77 +
  78 + assert_raise RuntimeError do
  79 + supplier_product = build(SuppliersPlugin::DistributedProduct, :profile => @other_profile)
  80 + product.distribute_from(supplier_product)
  81 + end
  82 +
  83 + supplier_product = create(SuppliersPlugin::DistributedProduct, :profile => @other_profile)
  84 + product.profile.add_supplier @other_profile
  85 + product.distribute_from supplier_product
  86 + assert_equal product.supplier.profile, supplier_product.profile
  87 + assert_equal product.supplier.profile, supplier_product.profile
  88 + end
  89 +
  90 + should 'return json for category hierarchy' do
  91 + grandparent = create(ProductCategory, :name => 'grand parent')
  92 + parent = create(ProductCategory, :name => 'parent')
  93 + child = product_category
  94 +
  95 + product = SuppliersPlugin::DistributedProduct.new :category => parent
  96 + hash = {:own_name => "parent", :id => "2", :subcats => [], :name => "parent",
  97 + :hierarchy => [{:own_name => "parent", :subcats => [], :name => "parent", :id => "2"}]}
  98 + assert_equal hash, product.json_for_category
  99 + end
  100 +
  101 + should 'block own product distribution' do
  102 + product = Product.create :enterprise => @profile, :product_category => product_category
  103 + distributed = SuppliersPlugin::DistributedProduct.new :enterprise => @profile, :from_products => [product]
  104 +
  105 + assert distributed.invalid?
  106 + end
  107 +
  108 +end
... ...
plugins/suppliers/test/unit/suppliers_plugin/product_test.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +require "test_helper"
  2 +
  3 +class SuppliersPlugin::ProductTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @product = build(SuppliersPlugin::BaseProduct)
  7 + end
  8 +
  9 + should 'return first from product as supplier product' do
  10 + fp = build(SuppliersPlugin::BaseProduct, :profile => @product.profile)
  11 + @product.from_products = [fp]
  12 + assert_equal fp, @product.from_product
  13 + assert_equal fp, @product.supplier_product
  14 + end
  15 +
  16 + should 'respond to dummy and own' do
  17 + assert !@product.dummy?
  18 + assert @product.own?
  19 + end
  20 +
  21 + should 'return price with margins' do
  22 + supplier_product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :profile => @product.profile, :supplier => @product.profile.self_supplier)
  23 + product = build(SuppliersPlugin::DistributedProduct, :price => 10, :margin_percentage => 10, :supplier_product => supplier_product, :profile => @product.profile, :supplier => @product.profile.self_supplier)
  24 +
  25 + product.default_margin_percentage = false
  26 + assert_equal 11.0, product.price_with_margins
  27 + @product.profile.margin_percentage = 20
  28 + product.default_margin_percentage = true
  29 + assert_equal 12.0, product.price_with_margins
  30 + end
  31 +
  32 + should 'build default unit if none exists' do
  33 + assert_equal 0, Unit.count
  34 + assert 'unit', @product.unit.singular
  35 + end
  36 +
  37 + should 'avoid destroy by raising an exception' do
  38 + assert_raise RuntimeError do
  39 + @product.destroy
  40 + end
  41 + end
  42 +
  43 + should 'accept price in different formats' do
  44 + @product.price = '2,45'
  45 + assert_equal 2.45, @product.price
  46 + end
  47 +
  48 +
  49 +end
... ...
plugins/suppliers/test/unit/suppliers_plugin/source_product_test.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class SuppliersPlugin::SourceProductTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + end
  7 +
  8 +end
... ...
plugins/suppliers/test/unit/suppliers_plugin/supplier_test.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require "#{File.dirname(__FILE__)}/../../test_helper"
  2 +
  3 +class SuppliersPlugin::SupplierTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + end
  7 +
  8 +end
... ...
plugins/suppliers/views/suppliers_plugin/manage_products/_basket_add.html.slim 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 += content_for :head do
  2 + = javascript_include_tag 'typeahead.bundle.js'
  3 + = stylesheet_link_tag 'typeahead'
  4 +
  5 += text_field_tag :query, nil, id: 'basket-add', placeholder: t('suppliers_plugin.views.manage_products.basket_tab.search')
  6 +
... ...
plugins/suppliers/views/suppliers_plugin/manage_products/_basket_tab.html.slim 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 += t'suppliers_plugin.views.manage_products.basket_tab.make_a_basket' if @product.from_products.size <= 1
  2 +
  3 += render 'suppliers_plugin/manage_products/basket_add'
  4 +
  5 +table
  6 + thead
  7 + th= t'suppliers_plugin.views.manage_products.basket_tab.component'
  8 + th= t'suppliers_plugin.views.manage_products.basket_tab.quantity'
  9 + th
  10 +
  11 + tbody
  12 + - @product.sources_from_products.each do |sp|
  13 + tr
  14 + td
  15 + = link_to_product sp.from_product
  16 + |&emsp;
  17 + = "(#{sp.from_product.unit.singular})" if sp.from_product.unit
  18 + td= sp.quantity
  19 + td= button_to_function :remove, _('Remove'), "suppliers.basket.remove(#{sp.from_product.id.to_json})" if @allowed_user
  20 +
  21 +javascript:
  22 + suppliers.basket.removeUrl = #{url_for(controller: 'suppliers_plugin/basket', action: :remove, id: @product.id).to_json}
  23 + suppliers.basket.searchUrl = #{url_for(controller: 'suppliers_plugin/basket', action: :search, id: @product.id).to_json}
  24 + suppliers.basket.addUrl = #{url_for(controller: 'suppliers_plugin/basket', action: :add, id: @product.id).to_json}
  25 + suppliers.basket.load()
  26 +
... ...
plugins/suppliers/views/suppliers_plugin/manage_products/_compare_tab.html.slim 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +- if @product.diff.empty?
  2 + = t'suppliers_plugin.views.manage_products.compare_tab.no_diff'
  3 +- else
  4 + = t'suppliers_plugin.views.manage_products.compare_tab.diff_count', n: @product.diff.size
  5 + ul
  6 + - @product.diff.each do |attr|
  7 + li= t"suppliers_plugin.models.base_product.default_attributes.#{attr}"
  8 +
  9 +div
  10 + - if @product.profile != @product.from_product.profile
  11 + = button nil, t('suppliers_plugin.views.manage_products.compare_tab.see_supplier', supplier: @product.from_product.profile.name),
  12 + {controller: :manage_products, profile: @product.from_product.profile.identifier, action: :show, id: @product.from_product.id},
  13 + target: '_blank'
  14 + - else
  15 + = button nil, t('suppliers_plugin.views.manage_products.compare_tab.see'),
  16 + {controller: :manage_products, action: :show, id: @product.from_product.id},
  17 + target: '_blank'
  18 +
... ...
plugins/suppliers/views/suppliers_plugin/manage_products/_distribution_tab.html.slim 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +
  2 += t'suppliers_plugin.views.manage_products.distribution_tab.title'
  3 +
  4 += form_for :consumers, remote: true, url: {controller: 'suppliers_plugin/product', action: :distribute_to_consumers, id: @product.id},
  5 + html: {data: {loading: "#product-distribution"}} do |f|
  6 +
  7 + - distributed_consumers = @product.consumers.all.to_set
  8 + - @product.profile.orgs_consumers.each do |consumer|
  9 + div
  10 + = check_box_tag "consumers[#{consumer.id}]", '1', distributed_consumers.include?(consumer.profile)
  11 + = label_tag "consumers[#{consumer.id}]", consumer.name
  12 +
  13 + - button_bar do
  14 + = submit_button :save, _('Save')
... ...
plugins/suppliers/views/suppliers_plugin/product/_actions.html.slim 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +#actions-box.wireframe-size
  2 + .labels
  3 + span.select.col-lg-4.col-md-4.col-sm-4
  4 + span.with-selection.col-lg-4.col-md-4.col-sm-4
  5 + = t'views.actions.with_selection'
  6 + .clean
  7 +
  8 + .buttons
  9 + .select.btn-group.col-lg-4.col-md-4.col-sm-4
  10 + button.btn.btn-default.btn-xs.dropdown-toggle aria-expanded="false" data-toggle="dropdown" type="button"
  11 + = t'views.actions.select'
  12 + span.caret
  13 +
  14 + ul.dropdown-menu role="menu"
  15 + li= link_to_function t('views.actions.all'), 'suppliers.our_product.select.all()'
  16 + li= link_to_function t('views.actions.none'), 'suppliers.our_product.select.none()'
  17 +
  18 + .with-selection.col-lg-4.col-md-4.col-sm-4
  19 + = link_to_function t('views.actions.activate'), 'suppliers.our_product.select.activate(true)', class: 'btn btn-default btn-xs'
  20 + |&nbsp;
  21 + = link_to_function t('views.actions.deactivate'), 'suppliers.our_product.select.activate(false)', class: 'btn btn-default btn-xs'
  22 +
  23 + .other.col-lg-4.col-md-4.col-sm-4
  24 + = modal_link_to t('views.product.import.action'), {controller: 'suppliers_plugin/product', action: :import}, class: 'btn btn-default btn-xs'
  25 +
  26 + .clean
... ...
plugins/suppliers/views/suppliers_plugin/product/_edit.html.slim 0 → 100644
... ... @@ -0,0 +1,79 @@
  1 +.box-edit
  2 + = form_for product, as: "product_#{product.id}", remote: true, html: {data: {loading: true}},
  3 + url: {controller: 'suppliers_plugin/product', action: :edit, id: product ? product.id : nil} do |f|
  4 + .internal-table
  5 + .title
  6 + = t'views.product._edit.distribution_setts'
  7 +
  8 + .row.first
  9 +
  10 + - unless product.own?
  11 + .block.margin-price
  12 + .field
  13 + = f.label :margin_percentage, t('views.product._edit.margin_price')
  14 + div.checkbox data-default-toggle=""
  15 + label name='default_margin_percentage'
  16 + = f.check_box :default_margin_percentage
  17 + = t'views.product._edit.default_margin'
  18 + div data-non-defaults=""
  19 + span.field-secondary.with-unit
  20 + = input_group_addon('%'){ f.number_field :margin_percentage, class: 'product-margin-percentage', step: 'any', onkeyup: 'suppliers.our_product.pmsync(this, true);', oninput: 'this.onkeyup()' }
  21 +
  22 + span.field-secondary.with-unit
  23 + = hidden_field_tag :base_price, product.from_product.price, class: 'product-base-price', id: 'product_base_price',
  24 + value: product.from_product.price
  25 + = input_group_addon(environment.currency_unit){ f.number_field :price, value: product.price, step: 'any', class: 'product-price', onkeyup: 'suppliers.our_product.pmsync(this, false);', oninput: 'this.onkeyup()' }
  26 +
  27 + - if defined? StockPlugin
  28 + .block.stock
  29 + .field
  30 + = f.label :stock, t('views.product._edit.stock')
  31 + div.checkbox data-default-toggle=""
  32 + label name='default_stored'
  33 + = f.check_box :default_stored
  34 + = t'views.product._edit.default_stock'
  35 + div data-non-defaults=""
  36 + span.field-secondary
  37 + = f.label :stored, t('views.product._edit.current_stock'), class: 'field-bellow'
  38 + = f.number_field :stored, step: 'any'
  39 +
  40 + .block.available
  41 + .field
  42 + = f.label :available, t('views.product._edit.distribution_state')
  43 + div.checkbox
  44 + label name='available'
  45 + = f.check_box :available
  46 + = t'views.product._edit.available'
  47 +
  48 + .row.second
  49 + - unless product.own?
  50 + .block.unit
  51 + .field
  52 + = f.label :unit_id, t('views.product._edit.selling_unit')
  53 + div.checkbox data-default-toggle=""
  54 + label name='default_unit'
  55 + = f.check_box :default_unit
  56 + = t'views.product._edit.same_from_purchase'
  57 + div data-non-defaults=""
  58 + span.field-secondary
  59 + = f.label :unit_id, t('views.product._edit.unit'), class: 'field-bellow'
  60 + = f.select :unit_id, options_for_select(@units.map{ |u| [u.name, u.id]})
  61 + span.field-secondary
  62 + = f.label :unit_detail, t('views.product._edit.unit_detail'), class: 'field-bellow'
  63 + = f.text_field :unit_detail
  64 +
  65 + .action-button-container
  66 + = f.submit t('views.product._edit.save'), class: 'save action-button'
  67 + = link_to_function t('views.product._edit.cancel'), '', class: 'action-button cancel', 'toggle-edit' => ''
  68 + = link_to_remote t('views.product._edit.remove_product'), remote: true, confirm: t('views.product._edit.are_you_sure_you_want'),
  69 + url: {controller: 'suppliers_plugin/product', action: 'destroy', id: product.id }, class: 'action-button remove',
  70 + html: {data: {loading: "#our-product-#{product.id}"}}
  71 +
  72 + .links
  73 + = link_to t('views.product._edit.product_registry'),
  74 + {controller: :manage_products, action: :show, id: product.id},
  75 + target: '_blank', class: 'action-button registry'
  76 +
  77 +javascript:
  78 + suppliers.our_product.load(#{product.id});
  79 +
... ...
plugins/suppliers/views/suppliers_plugin/product/_edit_line.html.slim 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +.box-view.with-inner
  2 + .box-field.select toggle-ignore="" = check_box_tag "product_ids[]", product.id
  3 +
  4 + .box-view-inner
  5 + .box-field.status= t"views.product._edit_line.#{if product.available then :active else :inactive end}"
  6 + .box-field.category= product.category_name
  7 + .box-field.supplier= product.supplier.abbreviation_or_name
  8 + .box-field.product.box-edit-link= product.name
  9 + / own products doesn't have these fields, rescue
  10 + .box-field.margin= "#{product.margin_percentage_localized}%" unless product.own?
  11 + .box-field.price= price_span(product.price_as_currency_number || '-') unless product.own?
  12 + - if defined? StockPlugin
  13 + .box-field.stock= product.stored_localized || '∞' unless product.own?
  14 + .box-field.unit= product.unit.name if product.unit
  15 +
  16 + = edit_arrow "#our-product-#{product.id}", false
  17 + .clean
  18 +
  19 += render 'suppliers_plugin/product/edit', product: product
... ...
plugins/suppliers/views/suppliers_plugin/product/_filter_fields.html.slim 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 += hidden_field_tag :page
  2 +
  3 +.field.state
  4 + label= t'suppliers_plugin.views.product.index.situation'
  5 + div= select_tag :available, options_for_select([[t('suppliers_plugin.views.product.index.in_any_state'), ""], [ t('suppliers_plugin.views.product.index.active'), 'true'], [ t('suppliers_plugin.views.product.index.inactive'), 'false']], params[:available])
  6 +
  7 +.field.category
  8 + label= t'suppliers_plugin.views.product.index.category'
  9 + div= select_tag :category_id, options_for_select(@product_categories.map{ |pc| [pc.name, pc.id]}.insert(0,[t('suppliers_plugin.views.product.index.all_the_categories'), ""]))
  10 +
  11 +.field.supplier
  12 + label= t'suppliers_plugin.views.product.index.supplier'
  13 + div= select_tag :supplier_id, options_for_select([[t('suppliers_plugin.views.product.index.all_the_suppliers'), ""]] + supplier_choices(profile.suppliers), params[:supplier_id].to_i)
  14 +
  15 +/ .field.qualifier
  16 + label= t'suppliers_plugin.views.product.index.with_the_qualifiers'
  17 + div= select_tag :qualifier_id, options_for_select([t('suppliers_plugin.views.product.index.anyone'), ''])
  18 +/ .field.stock
  19 + label= t'suppliers_plugin.views.product.index.whose_qty_available_i'
  20 + div= select_tag :stock, options_for_select([t('suppliers_plugin.views.product.index.bigger_than_the_stock'), ''])
  21 +
  22 +.field.name
  23 + label= t'suppliers_plugin.views.product.index.name'
  24 + div= text_field_tag :name, params[:name]
... ...
plugins/suppliers/views/suppliers_plugin/product/_form_errors.html.erb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<% if form_errors.errors.any? %>
  2 + <div>
  3 + <strong><%= t('suppliers_plugin.views.product._form_errors.errors_found') %></strong>
  4 + <ul>
  5 + <%= form_errors.errors.each do |attr,message| %>
  6 + <li><%= "#{_(attr.humanize)} #{message}" %></li>
  7 + <% end %>
  8 + </ul>
  9 + </div>
  10 +<% end %>
  11 +
... ...
plugins/suppliers/views/suppliers_plugin/product/_price_details.html.erb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<div class="properties-block price-block <%= "disabled-fields" if not product.supplier_dummy? %>">
  2 + <% options = {class: "small-input"}.merge((product.supplier_dummy? or not supplier_product) ? {} : {disabled: 'disabled'}) %>
  3 + <% if supplier_product %>
  4 + <% options = options.merge(onkeyup: 'suppliers.our_product.sync_referred(this)', oninput: 'this.onkeyup()') %>
  5 + <% else %>
  6 + <% options = options.merge(onkeyup: 'suppliers.our_product.pmsync(this, false)', oninput: 'this.onkeyup()') %>
  7 + <% end %>
  8 +
  9 + <%= labelled_field f, :price, t('views.product._price_details.price'),
  10 + f.number_field(:price, options.merge(step: 'any', oldvalue: product.own_price)), class: 'left-column' %>
  11 + <%= labelled_field f, :unit_id, t('views.product._price_details.unit'),
  12 + f.select(:unit_id, Unit.all.map{ |u| [u.singular, u.id] }, {}, options.merge(oldvalue: product.own_unit_id)) %>
  13 + <%= labelled_field f, :minimum_selleable, t('views.product._price_details.minimum_order'),
  14 + f.number_field(:minimum_selleable, options.merge(step: 'any', oldvalue: product.own_minimum_selleable)), class: 'left-column' %>
  15 + <%= labelled_field f, :unit_detail, t('views.product._price_details.unit_specification'),
  16 + f.number_field(:unit_detail, options.merge(step: 'any', oldvalue: product.own_unit_detail)) %>
  17 +
  18 + <%# put here due to css align, see distribution.js %>
  19 + <% if supplier_product and not product.supplier_dummy? %>
  20 + <div class="clean">
  21 + <%= check_box_tag :product_supplier_product_available, 1, false, style: 'float: left', disabled: 'disabled' %>
  22 + <%= f.label :available, t('views.product._price_details.commercialization_act'), class: 'line-label' %>
  23 + </div>
  24 + <% end %>
  25 +
  26 + <div class="clean"></div>
  27 +</div>
... ...
plugins/suppliers/views/suppliers_plugin/product/_results.html.slim 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +- @products.each do |product|
  2 + .our-product.value-row id="our-product-#{product.id}" toggle-edit="suppliers.our_product.toggle_edit()"
  3 + = render "suppliers_plugin/product/edit_line", product: product
  4 +
  5 += pagination_links @products
  6 +javascript:
  7 + table_filter.pagination.init(#{_('loading...').to_json})
  8 +
... ...
plugins/suppliers/views/suppliers_plugin/product/_search.html.slim 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +#result-count
  2 + small= t'views.product._search.showing_pcount_produc', count: @products_count
  3 +
  4 +#results.sortable-table.wireframe-size.checkable
  5 + .table-header style="#{"display: none" if @products.empty?}"
  6 + .box-field.status= t'views.product._search.status'
  7 + .box-field.category= t'views.product._search.category'
  8 + .box-field.supplier= t'views.product._search.supplier'
  9 + .box-field.product= t'views.product._search.product'
  10 + .box-field.margin= t'views.product._search.margin'
  11 + .box-field.price= t'views.product._search.price'
  12 + - if defined? StockPlugin
  13 + .box-field.stock= t'views.product._search.stock'
  14 + .box-field.unit= t'views.product._search.unit'
  15 +
  16 + .table-content
  17 + - if @products.empty?
  18 + = t'views.product._search.we_don_t_have_product'
  19 + - else
  20 + = render 'results'
  21 + .clean
... ...
plugins/suppliers/views/suppliers_plugin/product/add.html.slim 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +div.popin
  2 + h1= t'views.product.add.title'
  3 +
  4 + div
  5 + = button :new, t('views.product.add.own_product'), {controller: :manage_products, profile: profile.identifier, action: :new},
  6 + target: '_blank', onclick: 'noosfero.modal.close()'
  7 + |&nbsp;
  8 + = t'views.product.add.or_from_a_supplier'
  9 + br
  10 +
  11 + div
  12 + - profile.suppliers.dummy.except_self.alphabetical.each do |supplier|
  13 + = link_to({controller: :manage_products, profile: supplier.profile.identifier, action: :new},
  14 + target: '_blank', onclick: 'noosfero.modal.close()') do
  15 + = profile_image supplier, :icon
  16 + = supplier.abbreviation_or_name
  17 + |&emsp;
  18 +
... ...
plugins/suppliers/views/suppliers_plugin/product/destroy.rjs 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +page.remove 'our-product-'+@product.id.to_s
  2 +page << "jQuery('#filter-form').submit();"
... ...
plugins/suppliers/views/suppliers_plugin/product/distribute_to_consumers.js.erb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +$('#product-distribution').html('<%= j render('suppliers_plugin/manage_products/distribution_tab') %>');
  2 +
... ...
plugins/suppliers/views/suppliers_plugin/product/edit.rjs 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +page.replace_html "our-product-#{@product.id}", partial: 'suppliers_plugin/product/edit_line', locals: {product: @product}
  2 +page << "toggle_edit.value_row.reload()"
  3 +
... ...
plugins/suppliers/views/suppliers_plugin/product/import.html.slim 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +#suppliers-products-import.popin
  2 + h1= t'views.product.import.action'
  3 +
  4 + = form_tag({controller: 'suppliers_plugin/product', action: :import}, onsubmit: '$(this).ajaxSubmit(); return false') do
  5 + p= t'views.product.import.help'
  6 + table
  7 + thead
  8 + th= t'views.product.import.field_supplier_name'
  9 + th= t'views.product.import.field_product_name'
  10 + th= t'views.product.import.field_product_unit'
  11 + th= t'views.product.import.field_product_price'
  12 + tbody
  13 + tr
  14 + td
  15 + td
  16 + td
  17 + td
  18 + p= t'views.product.import.unit_help', units: environment.units.reorder('singular ASC').map(&:singular).join(' - ')
  19 +
  20 + .checkbox
  21 + label name='remove_all_suppliers'
  22 + = check_box_tag :remove_all_suppliers, 'true', false, onchange: "suppliers.our_product.import.confirmRemoveAll(this, '#{j t'views.product.import.confirm_remove_all'}')"
  23 + = t'views.product.import.remove_all'
  24 +
  25 + = file_field_tag :csv, accept: 'text/csv'
  26 +
  27 + = submit_button :save, t('views.product.import.send')
  28 + = modal_close_button _'Cancel'
  29 +
... ...
plugins/suppliers/views/suppliers_plugin/product/import.js.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +noosfero.modal.close()
  2 +
  3 +display_notice(<%= @notice.to_json %>)
  4 +
... ...
plugins/suppliers/views/suppliers_plugin/product/index.html.slim 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +#supplier-our-products
  2 + h2= t'views.product.index.products'
  3 + div
  4 + .subtitle= t'views.product.index.info'
  5 +
  6 + h3= t'views.product.index.default_margin_of_com'
  7 + .subtitle= t'views.product.index.default_margin_info'
  8 + span#profile-margin-percentage
  9 + span class="#{profile.margin_percentage.blank? ? 'none' : 'set'}"
  10 + = (if profile.margin_percentage_localized then t'views.product.index.margin', margin: profile.margin_percentage_localized else t'views.product.index.no_margin_set' end)
  11 + |&nbsp;
  12 + = modal_link_to t('views.product.index.change'), {controller: :suppliers_plugin_myprofile, action: :margin_change}
  13 +
  14 + br
  15 + = link_to t('views.product.index.add_product'), {controller: 'suppliers_plugin/product', action: :add}, class: 'btn btn-default btn-xs modal-toggle'
  16 +
  17 + = render 'suppliers_plugin/shared/filter', type: :product
  18 + = render 'suppliers_plugin/product/actions'
  19 +
  20 + #search-results
  21 + = render 'suppliers_plugin/product/search'
  22 +
... ...
plugins/suppliers/views/suppliers_plugin/product/search.rjs 0 → 100644
... ... @@ -0,0 +1 @@
  1 +page.replace_html 'search-results', partial: 'suppliers_plugin/product/search'
... ...
plugins/suppliers/views/suppliers_plugin/shared/_filter.html.slim 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +#filter class="wireframe-size #{'with-actions' if type == :product}"
  2 + .title.filter-box= t'views.filter.filter'
  3 + - action = if type == :product then {action: :search} else {action: :index} end
  4 + = form_tag action, remote: true, id: 'filter-form', data: {loading: '#filter', update: '#search-results', type: "html"}, method: :get do
  5 + .fields.filter-box
  6 + - if type == :supplier
  7 + = render partial: 'suppliers_plugin_myprofile/filter_fields'
  8 + - elsif type == :product
  9 + = render 'suppliers_plugin/product/filter_fields'
  10 + .submit
  11 + = submit_tag t('views.filter.filter_it'), class: 'filter-submit'
  12 +
  13 +- if type == :product
  14 + = javascript_include_tag '/assets/plugins/suppliers/javascripts/table_filter'
  15 +
... ...
plugins/suppliers/views/suppliers_plugin/shared/_pagereload.js.erb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../../orders/views/orders_plugin_shared/_pagereload.js.erb
0 2 \ No newline at end of file
... ...
plugins/suppliers/views/suppliers_plugin/shared/_pagereload.rjs 0 → 100644
plugins/suppliers/views/suppliers_plugin_myprofile/_edit.html.erb 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +<% supplier_label = (supplier.new_record? ? "add" : supplier.id) %>
  2 +
  3 +<div class='box-edit'>
  4 +
  5 + <div class="title">
  6 + <% if supplier.new_record? %>
  7 + <% if supplier.dummy? %>
  8 + <%= t('views.supplier._edit.add_supplier_managed_') %>
  9 + <% else %>
  10 + <%= t('views.supplier._edit.register_new_supplier') %>
  11 + <% end %>
  12 + <% else %>
  13 + <%= t('views.supplier._edit.edit_supplier') %>
  14 + <% end %>
  15 + </div>
  16 +
  17 + <% if supplier.dummy? %>
  18 + <div class="subtitle">
  19 + <%= t('views.supplier._edit.registry_help') %>
  20 + </div>
  21 +
  22 + <% end %>
  23 +
  24 + <%= form_for supplier, as: :supplier, url: {action: supplier.new_record? ? :new : :edit, id: supplier.id}, remote: true,
  25 + html: {data: {loading: "#supplier-#{supplier_label}"}, class: 'full-registration'} do |f| %>
  26 +
  27 + <%= error_messages_for :supplier %>
  28 +
  29 + <div class="full-registration-check">
  30 + <%= check_box_tag :full_registration, '1', false,
  31 + onchange: "jQuery(this).parents('.supplier').find('.data').toggleClass('full-registration', this.checked)" %>
  32 + <%= label_tag :full_registration, t('views.supplier._edit.full_registration') %>
  33 + </div>
  34 +
  35 + <div class="data">
  36 + <div class="basic-data">
  37 + <h3><%= t('views.supplier._edit.basic_data') %></h3>
  38 +
  39 + <%= labelled_field f, :name, t('views.supplier._edit.name'), f.text_field(:name) %>
  40 + <%= labelled_field f, :name_abbreviation, t('views.supplier._edit.abbreviated_name'), f.text_field(:name_abbreviation) %>
  41 + <%= labelled_field f, :description, t('views.supplier._edit.description'), f.text_area(:description, maxlength: '550') %>
  42 +
  43 + </div>
  44 + <div class="estrategic-info full-data">
  45 + <h3><%= t('views.supplier._edit.estrategic_informatio') %></h3>
  46 + </div>
  47 + <div class="full-data">
  48 + <h3><%= t('views.supplier._edit.registry_form') %></h3>
  49 + </div>
  50 + <div class="additional-fields full-data">
  51 + <h3><%= t('views.supplier._edit.additional_fields') %></h3>
  52 + </div>
  53 +
  54 + <div class="clean"></div>
  55 + </div>
  56 +
  57 + <%= f.submit t('views.supplier._edit.save') %>
  58 + <%= link_to_function t('views.supplier._edit.cancel'), '', 'toggle-edit' => '' %>
  59 +
  60 + <% end %>
  61 +</div>
  62 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_filter_fields.html.erb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<div class="field state">
  2 + <label><%= t('suppliers_plugin.views.myprofile.index.redistribution_situation') %></label>
  3 + <div>
  4 + <%= select_tag :active, options_for_select([[t('suppliers_plugin.views.product.index.in_any_state'), ""], [ t('suppliers_plugin.views.product.index.active'), 'true'], [ t('suppliers_plugin.views.product.index.inactive'), 'false']], params[:active]) %>
  5 + </div>
  6 +</div>
  7 +
  8 +<div class="field name">
  9 + <label><%= t('suppliers_plugin.views.myprofile.index.name') %></label>
  10 + <div><%= text_field_tag :name, params[:name] %></div>
  11 +</div>
  12 +
  13 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_index.html.erb 0 → 120000
... ... @@ -0,0 +1 @@
  1 +index.html.erb
0 2 \ No newline at end of file
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_new.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div id="find-enterprise">
  2 + <%= render :partial => 'suppliers_plugin_myprofile/search', :locals => {:profile => profile} %>
  3 +</div>
  4 +
  5 +<div id="create-dummy">
  6 + <%= render :partial => 'suppliers_plugin_myprofile/edit', :locals => {:supplier => supplier, :profile => profile} %>
  7 +</div>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_result.html.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<tr>
  2 + <td>
  3 + <%= profile_link_with_image enterprise, :thumb, :target => '_blank' %>
  4 + </td>
  5 + <td>
  6 + <div class="name">
  7 + <%= link_to enterprise.name, enterprise.url, :target => '_blank' %>
  8 + </div>
  9 + <div class="location">
  10 + <%= city_with_state enterprise.region %>
  11 + </div>
  12 + </td>
  13 + <td>
  14 + <%= link_to_remote t('views.supplier._new.add'),
  15 + :url => {:action => :add, :profile => profile.identifier, :id => enterprise.id},
  16 + :update => "search-results", :html => {:class => 'action-button', data: {loading: '#supplier-add'}} %>
  17 + </td>
  18 +</tr>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_results.html.erb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<% if @enterprises.empty? %>
  2 + <%= t('views.supplier._new.none_found') %>
  3 +<% else %>
  4 + <table>
  5 + <% @enterprises.each do |enterprise| %>
  6 + <%= render 'result', :enterprise => enterprise %>
  7 + <% end %>
  8 + </table>
  9 +
  10 + <div id="suggest-creation">
  11 + <div class="subtitle">
  12 + <%= t('views.supplier._new.suggest_creation') %>
  13 + </div>
  14 +
  15 + <%= link_to_function t('views.supplier._new.create_dummy'), 'suppliers.add.create_dummy();' %>
  16 + </div>
  17 +<% end %>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_search.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<h2> <%= t('views.supplier._new.title') % {profile: profile.name} %> </h2>
  2 +
  3 +<p> <%= t('views.supplier._new.intro_help') % {environment: environment.name, contact_email: environment.contact_email} %> </p>
  4 +
  5 +<%= form_tag({action: :search, profile: profile.identifier}, remote: true, method: :get, id: 'enterprise-search-form',
  6 + data: {loading: '#results'}) do %>
  7 +
  8 + <%= text_field_tag 'query', nil, placeholder: t('views.supplier._new.query_placeholder'),
  9 + onkeyup: 'suppliers.add.search(this)', autocomplete: 'off', autofill: 'off' %>
  10 +
  11 + <div id="results">
  12 + </div>
  13 +
  14 +<% end %>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_supplier.html.erb 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +<div class='box-view'>
  2 + <div class='supplier-logo'>
  3 + <%= link_to profile_image(supplier.profile, :minor), supplier.profile.url, :target => '_blank' %>
  4 + </div>
  5 +
  6 + <div class='supplier-body'>
  7 + <div class="supplier-type subtitle">
  8 + <%= if supplier.dummy? then t('views.supplier._supplier.supplier_with_registr') else t('views.supplier._supplier.registered_supplier_i') end %>
  9 + </div>
  10 +
  11 + <div class="supplier-name">
  12 + <%= link_to supplier.name, supplier.profile.url, :class => 'supplier-name', :target => '_blank' %>
  13 + </div>
  14 + <% if supplier.name_abbreviation %>
  15 + <div class="subtitle"><strong><%= t('views.supplier._supplier.abreviated_name') + ' ' %></strong><%= supplier.name_abbreviation %></div>
  16 + <% end %>
  17 + <p class="subtitle"><%= supplier.description %></p>
  18 +
  19 + <div class="subtitle"><strong><%= t('views.supplier._supplier.this_supplier_has') %></strong></div>
  20 + <div class='supplier-products-count subtitle'>
  21 + <% products = supplier.consumer.distributed_products.unarchived.from_supplier_id(supplier.id) %>
  22 + <% if products.size > 0 %>
  23 + <%= link_to t('views.supplier._supplier.products', count: products.size),
  24 + {controller: 'suppliers_plugin/product', action: :index, supplier_id: supplier.id}, target: '_blank' %>
  25 +
  26 + <%= t('views.supplier._supplier.among_them') %>
  27 +
  28 + <% if supplier.dummy? %>
  29 + <% msg = products.available.size.to_s +
  30 + t('views.supplier._supplier.products_are_active') %>
  31 + <% else %>
  32 + <% msg = products.size.to_s + t('views.supplier._supplier.products_distributed') %>
  33 + <% end %>
  34 + <%= link_to msg, {controller: 'suppliers_plugin/product', action: :index, supplier_id: supplier.id}, target: '_blank', class: 'supplier-distributed-products-count' %>
  35 + <% else %>
  36 + <%= t('views.supplier._supplier.any_registered_produc') %>
  37 + <% end %>
  38 + </div>
  39 + </div>
  40 +
  41 + <div class='supplier-actions subtitle'>
  42 + <div><strong><%= t('views.supplier._supplier.actions') %></strong></div>
  43 + <div><%= link_to_function t('views.supplier._supplier.edit'), '', 'toggle-edit' => '' %></div>
  44 +
  45 + <div class='deactivate <%= 'hidden' unless supplier.active %>'><%= link_to_remote t('views.supplier._supplier.deactivate'), :url => {:action => :toggle_active, :id => supplier.id} %></div>
  46 + <div class='activate <%= 'hidden' if supplier.active %>'><%= link_to_remote t('views.supplier._supplier.activate'), :url => {:action => :toggle_active, :id => supplier.id} %></div>
  47 +
  48 + <div><%= link_to_remote t('views.supplier._supplier.disassociate'), :url => {:action => :destroy, :id => supplier.id},
  49 + :confirm => t('views.supplier._supplier.by_removing_this_supp'), :class => 'supplier-remove' %></div>
  50 +
  51 + <div><%= link_to t('views.supplier._supplier.manage_products'), {controller: 'suppliers_plugin/product', action: :index, supplier_id: supplier.id}, target: '_blank' %></div>
  52 + <div><%= link_to t('views.supplier._supplier.add_product'), {controller: :manage_products, profile: supplier.profile.identifier, action: :new}, target: '_blank' %></div>
  53 + </div>
  54 +</div>
  55 +
  56 +<%= render :partial => 'suppliers_plugin_myprofile/edit', :locals => {:supplier => supplier} %>
  57 +
  58 +<div class="clean"></div>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/_suppliers_list.html.erb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +<%= pagination_links suppliers %>
  2 +
  3 +<% if suppliers.empty? %>
  4 + <% if @is_search %>
  5 + <strong><%= t('views.supplier._suppliers_list.this_search_didn_t_re') %></strong>
  6 + <% else %>
  7 + <strong><%= t('views.supplier._suppliers_list.any_registered') %></strong>
  8 + <% end %>
  9 +<% else %>
  10 + <% suppliers.each do |supplier| %>
  11 + <div id="supplier-<%=supplier.id%>" class="supplier value-row <%= 'supplier-inactive' unless supplier.active %>" without-hover without-click-edit toggle-edit='suppliers.toggle_edit();'>
  12 + <%= render :partial => 'suppliers_plugin_myprofile/supplier', :object => supplier %>
  13 + </div>
  14 + <% end %>
  15 +<% end %>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/add.js.erb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +<%= render 'suppliers_plugin/shared/pagereload' %>
  2 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/destroy.js.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +jQuery("#supplier-<%= @supplier.id %>").remove();
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/edit.js.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +jQuery("#supplier-<%= @supplier.id %>").html("<%= j render(:partial => 'suppliers_plugin_myprofile/supplier', :object => @supplier) %>");
  2 +<% unless @supplier.valid? %>
  3 + toggle_edit.value_row.reload();
  4 +<% end %>
  5 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/index.html.erb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<div id="suppliers-page" class="admin-page">
  2 + <h2><%= t('views.supplier.index.suppliers') %></h2>
  3 +
  4 + <div class="subtitle text-wrap">
  5 + <%= t('views.supplier.index.this_is_the_list_of_s') %>
  6 + </div>
  7 +
  8 + <div class="subtitle supplier-add-link" toggle-ignore><%= link_to_function t('views.supplier.index.add_supplier'), onclick: 'suppliers.add_link();' %></div>
  9 +
  10 + <%= render 'suppliers_plugin/shared/filter', type: :supplier %>
  11 +
  12 + <div class="supplier-listing">
  13 + <div id="supplier-add" class="supplier value-row" toggle-edit='suppliers.toggle_edit();' >
  14 + <%= render 'suppliers_plugin_myprofile/new', supplier: @new_supplier %>
  15 + </div>
  16 +
  17 + <div id="search-results">
  18 + <%= render 'suppliers_plugin_myprofile/suppliers_list', suppliers: @suppliers %>
  19 + </div>
  20 + </div>
  21 +</div>
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/margin_change.html.erb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +<div id="suppliers-plugin-margin-change" class="popin">
  2 + <h1><%= t('views.myprofile.margin_change.change_default_margin') %></h1>
  3 +
  4 + <%= form_for @profile, as: :profile_data, remote: true, url: {controller: :suppliers_plugin_myprofile, action: :margin_change} do |f| %>
  5 +
  6 + <div>
  7 + <%= t('views.myprofile.margin_change.notice') %>
  8 + </div>
  9 +
  10 + <%= labelled_field f, :margin_percentage, t('views.myprofile.margin_change.new_margin'),
  11 + f.number_field(:margin_percentage, step: 'any') + ' ' + t('views.myprofile.margin_change.%') %>
  12 +
  13 + <div>
  14 + <%= check_box_tag :apply_to_all, 1, false, style: 'float: left' %>
  15 + <%= label_tag :apply_to_all, t('views.myprofile.margin_change.apply_to_all'), class: 'line-label' %>
  16 + </div>
  17 +
  18 + <div class="clean"></div>
  19 +
  20 + <%= submit_tag t('views.myprofile.margin_change.confirm') %>
  21 + <%= modal_close_link t('views.myprofile.margin_change.cancel') %>
  22 + <% end %>
  23 +</div>
  24 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/new.js.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<% if @supplier.valid? %>
  2 + window.location = '<%= url_for :action => :index %>';
  3 +<% else %>
  4 + jQuery("#supplier-add").html("<%= j render(:partial => 'suppliers_plugin_myprofile/supplier', :object => @supplier) %>");
  5 + toggle_edit.value_row.reload();
  6 +<% end %>
  7 +
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/search.js.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +jQuery('#results').html("<%= j render('results') %>");
... ...
plugins/suppliers/views/suppliers_plugin_myprofile/toggle_active.js.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +jQuery('#supplier-<%=@supplier.id%>').toggleClass('supplier-inactive');
  2 +jQuery('#supplier-<%=@supplier.id%> .supplier-actions .deactivate').toggleClass('hidden');
  3 +jQuery('#supplier-<%=@supplier.id%> .supplier-actions .activate').toggleClass('hidden');
... ...
plugins/volunteers/controllers/myprofile/volunteers_plugin_myprofile_controller.rb 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +class VolunteersPluginMyprofileController < MyProfileController
  2 +
  3 + no_design_blocks
  4 +
  5 + # remove fake dependency
  6 + helper OrdersPlugin::DateHelper
  7 +
  8 + def index
  9 +
  10 + end
  11 +
  12 + def toggle_assign
  13 + @owner_id = params[:owner_id]
  14 + @owner_type = params[:owner_type]
  15 + @owner = @owner_type.constantize.find @owner_id
  16 + @period = @owner.volunteers_periods.find params[:id]
  17 +
  18 + if profile.members.include? user
  19 + @assignment = @period.assignments.where(profile_id: user.id).first
  20 + if @assignment
  21 + @assignment.destroy
  22 + else
  23 + @period.assignments.create! profile_id: user.id
  24 + end
  25 + @period.assignments.reload
  26 + end
  27 +
  28 + render partial: 'volunteering', locals: {period: @period}
  29 + end
  30 +
  31 + protected
  32 +
  33 +end
... ...
plugins/volunteers/db/migrate/20141012005319_create_volunteers_plugin_periods.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class CreateVolunteersPluginPeriods < ActiveRecord::Migration
  2 + def up
  3 + create_table :volunteers_plugin_periods do |t|
  4 + t.integer :owner_id
  5 + t.string :owner_type
  6 + t.text :name
  7 + t.datetime :start
  8 + t.datetime :end
  9 + t.integer :minimum_assigments
  10 + t.integer :maximum_assigments
  11 +
  12 + t.timestamps
  13 + end
  14 + add_index :volunteers_plugin_periods, [:owner_type]
  15 + add_index :volunteers_plugin_periods, [:owner_id, :owner_type]
  16 + end
  17 +
  18 + def down
  19 + drop_table :volunteers_plugin_periods
  20 + end
  21 +end
... ...
plugins/volunteers/db/migrate/20141012005354_create_volunteers_plugin_assignments.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class CreateVolunteersPluginAssignments < ActiveRecord::Migration
  2 + def up
  3 + create_table :volunteers_plugin_assignments do |t|
  4 + t.integer :profile_id
  5 + t.integer :period_id
  6 +
  7 + t.timestamps
  8 + end
  9 + add_index :volunteers_plugin_assignments, [:period_id]
  10 + add_index :volunteers_plugin_assignments, [:profile_id]
  11 + add_index :volunteers_plugin_assignments, [:profile_id, :period_id]
  12 + end
  13 +
  14 + def down
  15 + drop_table :volunteers_plugin_assignments
  16 + end
  17 +end
... ...
plugins/volunteers/lib/ext/profile.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +require_dependency 'profile'
  2 +
  3 +# attr_accessible must be defined on subclasses
  4 +Profile.descendants.each do |subclass|
  5 + subclass.class_eval do
  6 + attr_accessible :volunteers_settings
  7 + end
  8 +end
  9 +
  10 +class Profile
  11 +
  12 + def volunteers_settings attrs = {}
  13 + @volunteers_settings ||= Noosfero::Plugin::Settings.new self, VolunteersPlugin, attrs
  14 + attrs.each{ |a, v| @volunteers_settings.send "#{a}=", v }
  15 + @volunteers_settings
  16 + end
  17 + alias_method :volunteers_settings=, :volunteers_settings
  18 +
  19 +end
... ...
plugins/volunteers/lib/split_datetime.rb 0 → 100644
... ... @@ -0,0 +1,72 @@
  1 +
  2 +module SplitDatetime
  3 +
  4 + class << self
  5 + def nil_time
  6 + Time.parse "#{Time.now.hour}:0:0"
  7 + end
  8 + def nil_date
  9 + Date.today
  10 + end
  11 +
  12 + def to_time datetime
  13 + datetime = self.nil_time if datetime.blank?
  14 + datetime.to_formatted_s :time
  15 + end
  16 + def to_date datetime
  17 + datetime = self.nil_date if datetime.blank?
  18 + datetime.strftime '%d/%m/%Y'
  19 + end
  20 + def set_time datetime, value
  21 + value = if value.blank?
  22 + self.nil_time
  23 + elsif value.kind_of? String
  24 + Time.parse value
  25 + else
  26 + value.to_time
  27 + end
  28 + datetime = self.nil_date if datetime.blank?
  29 +
  30 + Time.mktime(datetime.year, datetime.month, datetime.day, value.hour, value.min, value.sec).to_datetime
  31 + end
  32 + def set_date datetime, value
  33 + value = if value.blank?
  34 + self.nil_date
  35 + elsif value.kind_of? String
  36 + DateTime.strptime value, '%d/%m/%Y'
  37 + else
  38 + value.to_time
  39 + end
  40 + datetime = nil_time if datetime.blank?
  41 +
  42 + Time.mktime(value.year, value.month, value.day, datetime.hour, datetime.min, datetime.sec).to_datetime
  43 + end
  44 + end
  45 +
  46 + module SplitMethods
  47 +
  48 + def split_datetime attr
  49 + define_method "#{attr}_time" do
  50 + datetime = send attr
  51 + SplitDatetime.to_time datetime
  52 + end
  53 + define_method "#{attr}_date" do
  54 + datetime = send attr
  55 + SplitDatetime.to_date datetime
  56 + end
  57 + define_method "#{attr}_time=" do |value|
  58 + datetime = send attr
  59 + send "#{attr}=", SplitDatetime.set_time(datetime, value)
  60 + end
  61 + define_method "#{attr}_date=" do |value|
  62 + datetime = send attr
  63 + send "#{attr}=", SplitDatetime.set_date(datetime, value)
  64 + end
  65 + end
  66 +
  67 + end
  68 +
  69 +end
  70 +
  71 +Class.extend SplitDatetime::SplitMethods
  72 +ActiveRecord::Base.extend SplitDatetime::SplitMethods
... ...
plugins/volunteers/lib/volunteers_plugin.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +
  2 +class VolunteersPlugin < Noosfero::Plugin
  3 +
  4 + def self.plugin_name
  5 + I18n.t('volunteers_plugin.lib.plugin.name')
  6 + end
  7 +
  8 + def self.plugin_description
  9 + I18n.t('volunteers_plugin.lib.plugin.description')
  10 + end
  11 +
  12 + def stylesheet?
  13 + true
  14 + end
  15 +
  16 + def js_files
  17 + ['volunteers.js'].map{ |j| "javascripts/#{j}" }
  18 + end
  19 +
  20 +end
... ...
plugins/volunteers/locales/en-US.yml 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +# encoding: UTF-8
  2 +
  3 +"en-US": &en-US
  4 +
  5 + volunteers_plugin:
  6 + lib:
  7 + plugin:
  8 + name: "Work periods and volunteers"
  9 + description: "Do management of periods of voluteers work"
  10 + models:
  11 + period:
  12 + name: 'Nome'
  13 + start_date: 'Start day'
  14 + start_time: 'Time of start'
  15 + end_date: 'End day'
  16 + end_time: 'Time of end'
  17 + minimum_assigments: 'Minimum number required'
  18 + views:
  19 + index:
  20 + volunteer: "volunteer"
  21 + myprofile:
  22 + volunteers_periods: "Volunteers' period"
  23 + add_period: "Add"
  24 + period:
  25 + label: 'Turno'
  26 + remove: 'Remove'
  27 + minimum: 'have minimum?'
  28 + maximum: 'have maximum?'
  29 + no: 'No'
  30 +
  31 +'en_US':
  32 + <<: *en-US
  33 +'en':
  34 + <<: *en-US
  35 +
... ...
plugins/volunteers/locales/pt-BR.yml 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +# encoding: UTF-8
  2 +
  3 +"pt-BR": &pt-BR
  4 +
  5 + volunteers_plugin:
  6 + lib:
  7 + plugin:
  8 + name: "Períodos de trabalhos e voluntários"
  9 + description: "Faz a gestão dos períodos de trabalho voluntário"
  10 + attributes:
  11 + period:
  12 + name: 'Nome'
  13 + start_date: 'Dia de início'
  14 + start_time: 'Horário de início'
  15 + end_date: 'Dia de fim'
  16 + end_time: 'Horário de fim'
  17 + minimum_assigments: 'Número mínimo requerido'
  18 + views:
  19 + index:
  20 + volunteer: "voluntariar"
  21 + myprofile:
  22 + volunteers_periods: "Turnos de voluntários"
  23 + add_period: "Adicionar turno"
  24 + period:
  25 + label: 'Turno'
  26 + remove: 'Remover'
  27 + minimum: 'tem mínimo?'
  28 + noo: 'Não'
  29 + maximum: 'tem maximo?'
  30 +
  31 +'pt_BR':
  32 + <<: *pt-BR
  33 +'pt':
  34 + <<: *pt-BR
  35 +
... ...
plugins/volunteers/models/volunteers_plugin/assignment.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class VolunteersPlugin::Assignment < ActiveRecord::Base
  2 +
  3 + attr_accessible :profile_id
  4 +
  5 + belongs_to :profile
  6 + belongs_to :period, class_name: 'VolunteersPlugin::Period'
  7 +
  8 + validates_presence_of :profile
  9 + validates_presence_of :period
  10 + validates_uniqueness_of :profile_id, scope: :period_id
  11 +
  12 +end
... ...
plugins/volunteers/models/volunteers_plugin/period.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +class VolunteersPlugin::Period < ActiveRecord::Base
  2 +
  3 + attr_accessible :name
  4 + attr_accessible :start, :end
  5 + attr_accessible :owner_type
  6 + attr_accessible :minimum_assigments
  7 + attr_accessible :maximum_assigments
  8 +
  9 + belongs_to :owner, polymorphic: true
  10 +
  11 + has_many :assignments, class_name: 'VolunteersPlugin::Assignment', foreign_key: :period_id, include: [:profile], dependent: :destroy
  12 +
  13 + validates_presence_of :owner
  14 + validates_presence_of :name
  15 + validates_presence_of :start, :end
  16 +
  17 + extend OrdersPlugin::DateRangeAttr::ClassMethods
  18 + date_range_attr :start, :end
  19 +
  20 + extend SplitDatetime::SplitMethods
  21 + split_datetime :start
  22 + split_datetime :end
  23 +
  24 +end
... ...
plugins/volunteers/public/javascripts/volunteers.js 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +volunteers = {
  2 +
  3 + periods: {
  4 + load: function() {
  5 + $('#volunteers-periods .period').each(function() {
  6 + volunteers.periods.applyDaterangepicker(this)
  7 + })
  8 + $('#period-new input').prop('disabled', true)
  9 + },
  10 +
  11 + new: function() {
  12 + var period = $('#volunteers-periods-template').html()
  13 + period = period.replace(/_new_/g, new Date().getTime())
  14 + period = $(period)
  15 + period.find('input').prop('disabled', false)
  16 + this.applyDaterangepicker(period)
  17 + return period
  18 + },
  19 +
  20 + add: function() {
  21 + $('.periods').append(this.new())
  22 + },
  23 +
  24 + remove: function(link) {
  25 + link = $(link)
  26 + var period = link.parents('.period')
  27 + period.find('input[name*=_destroy]').prop('value', '1')
  28 + period.hide()
  29 + },
  30 +
  31 + applyDaterangepicker: function(period) {
  32 + orders.daterangepicker.init($(period).find('.daterangepicker-field'))
  33 + },
  34 +
  35 + },
  36 +
  37 + assignments: {
  38 + toggle: function(period) {
  39 + period = $(period)
  40 + $.get(period.attr('data-toggle-url'), function(data) {
  41 + $(period).replaceWith(data)
  42 + })
  43 + },
  44 +
  45 + },
  46 +
  47 +};
... ...
plugins/volunteers/public/style.scss 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +@import 'stylesheets/style'
  2 +
... ...
plugins/volunteers/public/stylesheets/_base.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../../../suppliers/public/stylesheets/_base.scss
0 2 \ No newline at end of file
... ...
plugins/volunteers/public/stylesheets/style.scss 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +@import 'base';
  2 +
  3 +.volunteer-to-period {
  4 + width: $module03;
  5 + margin-right: $margin;
  6 + float: left;
  7 + cursor: pointer;
  8 +
  9 + background: #F8FA83;
  10 + text-align: left;
  11 +
  12 + &:hover {
  13 + background: lighten(yellow, 20%);
  14 + }
  15 +
  16 + .name {
  17 + background-color: #E1F5C4;
  18 + width: 100%;
  19 + line-height: 20px;
  20 + font-size: 15px;
  21 + font-weight: bold;
  22 + text-transform: capitalize;
  23 + padding: $padding;
  24 + box-sizing: border-box;
  25 + }
  26 + .body {
  27 + padding: $padding;
  28 +
  29 + .period {
  30 + margin-bottom: $margin;
  31 + }
  32 + }
  33 +}
  34 +
  35 +#cycle-admin-page #volunteers-periods {
  36 + margin-bottom: $margin*2;
  37 +
  38 + .body {
  39 + margin-bottom: $half-margin;
  40 + }
  41 + input.minimum {
  42 + display: block;
  43 + }
  44 + input.time-select {
  45 + width: 50px;
  46 + }
  47 + select.minimum {
  48 + float: left;
  49 + margin-right: 20px;
  50 + }
  51 + select.maximum {
  52 + }
  53 + a.remove {
  54 + clear: both;
  55 + display: block;
  56 + margin-bottom: $margin;
  57 + }
  58 +}
... ...
plugins/volunteers/test/unit/split_datetime_test.rb 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class ModelWithDate
  4 +
  5 + def initialize
  6 + @delivery = DateTime.now
  7 + end
  8 +
  9 + attr_accessor :delivery
  10 +
  11 + extend SplitDatetime::SplitMethods
  12 + split_datetime :delivery
  13 +
  14 +end
  15 +
  16 +class SplitDatetimeTest < ActiveSupport::TestCase
  17 +
  18 + def setup
  19 + @m = ModelWithDate.new
  20 + @m.delivery = (Time.mktime(2011) + 2.hours + 2.minutes + 2.seconds).to_datetime
  21 + end
  22 +
  23 + should 'return get splitted times' do
  24 + assert_equal @m.delivery_date, '2011-01-01'
  25 + assert_equal @m.delivery_time, '02:02'
  26 + end
  27 +
  28 + should 'return set splitted times by Date' do
  29 + @m.delivery_date = (Time.mktime(2011, 3, 5) + 3.hours + 3.minutes + 3.seconds).to_datetime
  30 + assert_equal @m.delivery_date, '2011-03-05'
  31 + assert_equal @m.delivery_time, '02:02'
  32 + end
  33 +
  34 + should 'return set splitted times by Time' do
  35 + @m.delivery_time = (Time.mktime(2011, 3, 5) + 3.hours + 3.minutes + 3.seconds).to_datetime
  36 + assert_equal @m.delivery_date, '2011-01-01'
  37 + assert_equal @m.delivery_time, '03:03'
  38 + end
  39 +
  40 + should 'return set splitted times by Date String' do
  41 + @m.delivery_date = "2011-11-11"
  42 + assert_equal @m.delivery_date, '2011-11-11'
  43 + assert_equal @m.delivery_time, '02:02'
  44 + end
  45 +
  46 + should 'return set splitted times by Time String' do
  47 + @m.delivery_time = "15:43"
  48 + assert_equal @m.delivery_date, '2011-01-01'
  49 + assert_equal @m.delivery_time, '15:43'
  50 + end
  51 +
  52 +end
  53 +
... ...
plugins/volunteers/views/volunteers_plugin_myprofile/_edit_periods.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<div id='volunteers-periods' class="field">
  2 +
  3 + <%= f.label :volunteers_periods, t('volunteers_plugin.views.myprofile.volunteers_periods') %>
  4 + <div class=clear></div>
  5 +
  6 + <div class="periods">
  7 + <% owner.volunteers_periods.each do |period| %>
  8 + <%= f.fields_for :volunteers_periods, period do |ff| %>
  9 + <div id="period-<%=period.id%>" class="period">
  10 + <%= render 'volunteers_plugin_myprofile/period', f: ff, period: period, owner: owner %>
  11 + </div>
  12 + <% end %>
  13 + <% end %>
  14 + </div>
  15 +
  16 + <div id="volunteers-periods-template" style="display: none">
  17 + <% period = VolunteersPlugin::Period.new %>
  18 + <%= f.fields_for :volunteers_periods, period, child_index: '_new_' do |ff| %>
  19 + <div id="period-new" class="period">
  20 + <%= render 'volunteers_plugin_myprofile/period', f: ff, period: period, owner: owner %>
  21 + </div>
  22 + <% end %>
  23 + </div>
  24 + <%= link_to_function t('volunteers_plugin.views.myprofile.add_period'), 'volunteers.periods.add()', class: 'action-button' %>
  25 +
  26 + <%= javascript_tag do %>
  27 + volunteers.periods.load();
  28 + <% end %>
  29 +
  30 +</div>
... ...
plugins/volunteers/views/volunteers_plugin_myprofile/_period.html.erb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<%= f.hidden_field :id %>
  2 +<%= f.hidden_field :_destroy %>
  3 +
  4 +<% here = 'volunteers_plugin.views.myprofile.period.' %>
  5 +
  6 +<div class="field">
  7 + <%= f.text_field :name, placeholder: t('volunteers_plugin.attributes.period.name') %>
  8 + <div class=body>
  9 + <%= datetime_range_field f, :start, :end %>
  10 + <div class=clear></div>
  11 +
  12 + <%# f.label :minimum_assigments, t(here+'minimum') %>
  13 + <%# f.select :minimum_assigments, options_for_select((0..10).to_a), {prompt: t(here+'noo')}, class: "minimum" %>
  14 +
  15 + <%# f.label :maximum_assigments, t(here+'maximum') %>
  16 + <%# f.select :maximum_assigments, options_for_select((1..10).to_a), {prompt: t(here+'noo')}, class: "maximum" %>
  17 +
  18 + <%= link_to_function t(here+'remove'), 'volunteers.periods.remove(this)', class: 'remove' %>
  19 + </div>
  20 +</div>
  21 +
... ...
plugins/volunteers/views/volunteers_plugin_myprofile/_volunteering.html.erb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +<div class="volunteer-to-period" data-toggle-url="<%= url_for action: :toggle_assign, id: period.id, owner_id: period.owner_id, owner_type: period.owner_type %>" onclick='volunteers.assignments.toggle(this)'>
  2 + <div class="name">
  3 + <%= period.name %>
  4 + </div>
  5 + <div class=body>
  6 + <div class="period">
  7 + <%= day_time_period period.start, period.end %>
  8 + </div>
  9 + <div class="assignments">
  10 + <% period.assignments.each do |assig| %>
  11 + <div class="volunteer">
  12 + <%= profile_image assig.profile, :icon %>
  13 + <%= assig.profile.short_name %>
  14 + </div>
  15 + <% end %>
  16 + <%# link_to t('volunteers_plugin.views.index.volunteer'), url: '' %>
  17 + </div>
  18 + </div>
  19 +
  20 +</div>
... ...
plugins/volunteers/views/volunteers_plugin_myprofile/index.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<% profile.orders_cycle.has_volunteers_periods.each do |cycle| %>
  2 + <% cycle %>
  3 + <% cycle.volunteers-periods.each do |period| %>
  4 + <% end %>
  5 +<% end %>
... ...