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

Too many changes.

To preserve performance only 100 of 237 files displayed.

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]
... ...