Angularising Order Cycles Index

This commit is contained in:
Rob Harrington
2016-06-24 10:37:19 +10:00
parent bf69ed0008
commit 2a5f598fb0
24 changed files with 437 additions and 182 deletions

View File

@@ -8,4 +8,4 @@ angular.module("ofn.admin").directive "datetimepicker", ->
onSelect: (dateText, inst) ->
scope.$apply (scope) ->
# Fires ngModel.$parsers
ngModel.$setViewValue dateText
ngModel.$setViewValue dateText

View File

@@ -29,9 +29,9 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
RequestMonitor.load $scope.lineItems = LineItems.index("q[order][state_not_eq]": "canceled", "q[order][completed_at_not_null]": "true", "q[order][completed_at_gt]": "#{parseDate($scope.startDate)}", "q[order][completed_at_lt]": "#{parseDate($scope.endDate)}")
unless $scope.initialized
RequestMonitor.load $scope.distributors = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{daysFromToday(-90)}")
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise]).then ->
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID

View File

@@ -0,0 +1,34 @@
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, StatusMessage, RequestMonitor, OrderCycles, Enterprises) ->
$scope.RequestMonitor = RequestMonitor
$scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form)
$scope.ordersCloseAtLimit = -31 # days
compileDataFor = (orderCycles) ->
for orderCycle in orderCycles
OrderCycles.linkToEnterprises(orderCycle)
orderCycle.producerNames = orderCycle.producers.map((producer) -> producer.name).join(", ")
orderCycle.shopNames = orderCycle.shops.map((shop) -> shop.name).join(", ")
# NOTE: this is using the Enterprises service from the admin.enterprises module
RequestMonitor.load ($scope.enterprises = Enterprises.index(includeBlank: true, action: "visible", ams_prefix: "basic")).$promise
RequestMonitor.load ($scope.orderCycles = OrderCycles.index(ams_prefix: "index", "q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit)}")).$promise
RequestMonitor.load $q.all([$scope.enterprises.$promise, $scope.orderCycles.$promise]).then -> compileDataFor($scope.orderCycles)
$scope.$watch 'order_cycles_form.$dirty', (newVal, oldVal) ->
StatusMessage.display 'notice', "You have unsaved changes" if newVal
$scope.showMore = (days) ->
$scope.ordersCloseAtLimit -= days
existingIDs = Object.keys(OrderCycles.orderCyclesByID)
RequestMonitor.load (orderCycles = OrderCycles.index(ams_prefix: "index", "q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit)}", "q[id_not_in][]": existingIDs)).$promise
orderCycles.$promise.then ->
compileDataFor(orderCycles)
$scope.orderCycles.push(orderCycle) for orderCycle in orderCycles
daysFromToday = (days) ->
now = new Date
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
now.setDate( now.getDate() + days )
now

View File

@@ -1,23 +1,28 @@
angular.module('admin.orderCycles', ['admin.utils', 'admin.indexUtils', 'ngTagsInput'])
angular.module('admin.orderCycles', ['admin.utils', 'admin.indexUtils', 'admin.enterprises', 'ngTagsInput'])
.config ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
.directive 'datetimepicker', ($parse) ->
(scope, element, attrs) ->
# using $parse instead of scope[attrs.datetimepicker] for cases
# where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker
dateFormat: 'yy-mm-dd'
timeFormat: 'HH:mm:ss'
showOn: "button"
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
buttonImageOnly: true
stepMinute: 15
onSelect: (dateText, inst) ->
scope.$apply ->
parsed = $parse(attrs.datetimepicker)
parsed.assign(scope, dateText)
.directive 'datetimepicker', ($timeout, $parse) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
$timeout ->
# using $parse instead of scope[attrs.datetimepicker] for cases
# where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker
dateFormat: 'yy-mm-dd'
timeFormat: 'HH:mm:ss'
showOn: "button"
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
buttonImageOnly: true
stepMinute: 15
onSelect: (dateText, inst) ->
scope.$apply(->
element.val(dateText)
parsed = $parse(attrs.datetimepicker)
parsed.assign(scope, dateText)
)
.directive 'ofnOnChange', ->
(scope, element, attrs) ->

View File

@@ -5,4 +5,9 @@ angular.module("admin.resources").factory 'OrderCycleResource', ($resource) ->
isArray: true
'update':
method: 'PUT'
'bulkUpdate':
method: 'POST'
isArray: true
params:
action: 'bulk_update'
})

View File

@@ -1,4 +1,4 @@
angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCycleResource) ->
angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCycleResource, StatusMessage, Enterprises, blankOption) ->
new class OrderCycles
all: []
byID: {}
@@ -30,14 +30,53 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
deferred.reject(response)
deferred.promise
saveChanges: (form) ->
changed = {}
for id, orderCycle of @orderCyclesByID when not @saved(orderCycle)
changed[Object.keys(changed).length] = @changesFor(orderCycle)
if Object.keys(changed).length > 0
StatusMessage.display('progress', "Saving...")
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed } }, (data) =>
for orderCycle in data
angular.extend(@orderCyclesByID[orderCycle.id], orderCycle)
angular.extend(@pristineByID[orderCycle.id], orderCycle)
@linkToEnterprises(orderCycle)
form.$setPristine() if form?
StatusMessage.display('success', "Order cycles have been updated.")
, (response) =>
StatusMessage.display('failure', "Oh no! I was unable to save your changes.")
saved: (order_cycle) ->
@diff(order_cycle).length == 0
diff: (order_cycle) ->
changed = []
for attr, value of order_cycle when not angular.equals(value, @pristineByID[order_cycle.id][attr])
changed.push attr unless attr is "$$hashKey"
changed.push attr if attr in @attrsToSave()
changed
changesFor: (orderCycle) ->
changes = { id: orderCycle.id }
for attr, value of orderCycle when not angular.equals(value, @pristineByID[orderCycle.id][attr])
changes[attr] = orderCycle[attr] if attr in @attrsToSave()
changes
attrsToSave: ->
['orders_open_at','orders_close_at']
resetAttribute: (order_cycle, attribute) ->
order_cycle[attribute] = @pristineByID[order_cycle.id][attribute]
linkAllToEnterprises: ->
for id, orderCycle of @orderCyclesByID
@linkToEnterprises(orderCycle)
linkToEnterprises: (orderCycle) ->
coordinator = Enterprises.enterprisesByID[orderCycle.coordinator.id]
orderCycle.coordinator = coordinator if coordinator?
for producer, i in orderCycle.producers
producer = Enterprises.enterprisesByID[producer.id]
orderCycle.producers[i] = producer if producer?
for shop, i in orderCycle.shops
shop = Enterprises.enterprisesByID[shop.id]
orderCycle.shops[i] = shop if shop?

View File

@@ -106,10 +106,10 @@ module Admin
end
end
def for_line_items
def visible
respond_to do |format|
format.json do
render_as_json @collection, ams_prefix: 'basic', spree_current_user: spree_current_user
render_as_json @collection, ams_prefix: params[:ams_prefix] || 'basic', spree_current_user: spree_current_user
end
end
end
@@ -157,7 +157,7 @@ module Admin
else
Enterprise.where("1=0")
end
when :for_line_items
when :visible
OpenFoodNetwork::Permissions.new(spree_current_user).visible_enterprises.ransack(params[:q]).result
else
# TODO was ordered with is_distributor DESC as well, not sure why or how we want to sort this now
@@ -168,7 +168,7 @@ module Admin
end
def collection_actions
[:index, :for_order_cycle, :for_line_items, :bulk_update]
[:index, :for_order_cycle, :visible, :bulk_update]
end
def load_methods_and_fees

View File

@@ -76,9 +76,14 @@ module Admin
def bulk_update
@order_cycle_set = params[:order_cycle_set] && OrderCycleSet.new(params[:order_cycle_set])
if @order_cycle_set.andand.save
redirect_to main_app.admin_order_cycles_path, notice: I18n.t(:order_cycles_bulk_update_notice)
respond_to do |format|
order_cycles = OrderCycle.where(id: params[:order_cycle_set][:collection_attributes].map{ |k,v| v[:id] })
format.json { render_as_json order_cycles, ams_prefix: 'index', current_user: spree_current_user }
end
else
render :index
respond_to do |format|
format.json { render :json => {:success => false} }
end
end
end
@@ -98,6 +103,7 @@ module Admin
protected
def collection
return Enterprise.where("1=0") unless json_request?
ocs = if params[:as] == "distributor"
OrderCycle.ransack(params[:q]).result.
involving_managed_distributors_of(spree_current_user).order('updated_at DESC')
@@ -120,14 +126,14 @@ module Admin
private
def load_data_for_index
@show_more = !!params[:show_more]
unless @show_more || params[:q].andand[:orders_close_at_gt].present?
if json_request?
# Split ransack params into all those that currently exist and new ones to limit returned ocs to recent or undated
orders_close_at_gt = params[:q].andand.delete(:orders_close_at_gt) || 31.days.ago
params[:q] = {
g: [ params.delete(:q) || {}, { m: 'or', orders_close_at_gt: 31.days.ago, orders_close_at_null: true } ]
g: [ params.delete(:q) || {}, { m: 'or', orders_close_at_gt: orders_close_at_gt, orders_close_at_null: true } ]
}
@order_cycle_set = OrderCycleSet.new :collection => (@collection = collection)
end
@order_cycle_set = OrderCycleSet.new :collection => (@collection = collection)
end
def require_coordinator
@@ -177,7 +183,7 @@ module Admin
end
def ams_prefix_whitelist
[:basic]
[:basic, :index]
end
end
end

View File

@@ -58,8 +58,8 @@ module OrderCyclesHelper
OrderCycle.active.with_distributor(@distributor).present?
end
def order_cycles_simple_index
@order_cycles_simple_index ||= !OpenFoodNetwork::Permissions.new(spree_current_user).can_manage_complex_order_cycles?
def simple_index
@simple_index ||= !OpenFoodNetwork::Permissions.new(spree_current_user).can_manage_complex_order_cycles?
end
def order_cycles_simple_form

View File

@@ -201,7 +201,7 @@ class AbilityDecorator
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
end
can [:admin, :bulk_management, :managed], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor)
can [:admin , :for_line_items], Enterprise
can [:admin, :visible], Enterprise
can [:admin, :index, :create, :update, :destroy], :line_item
can [:admin, :index, :create], Spree::LineItem
can [:destroy, :update], Spree::LineItem do |item|

View File

@@ -0,0 +1,65 @@
require 'open_food_network/order_cycle_permissions'
class Api::Admin::IndexOrderCycleSerializer < ActiveModel::Serializer
include OrderCyclesHelper
attributes :id, :name, :orders_open_at, :orders_close_at, :status, :variant_count, :deletable
attributes :coordinator, :producers, :shops, :viewing_as_coordinator
attributes :edit_path, :clone_path, :delete_path
def deletable
can_delete?(object)
end
def variant_count
object.variants.count
end
def status
order_cycle_status_class object
end
def orders_open_at
object.orders_open_at.to_s
end
def orders_close_at
object.orders_close_at.to_s
end
def viewing_as_coordinator
Enterprise.managed_by(options[:current_user]).include? object.coordinator
end
def coordinator
Api::Admin::IdNameSerializer.new(object.coordinator).serializable_hash
end
def producers
producers = object.suppliers.merge(visible_enterprises)
ActiveModel::ArraySerializer.new(producers, {each_serializer: Api::Admin::IdNameSerializer})
end
def shops
shops = object.distributors.merge(visible_enterprises)
ActiveModel::ArraySerializer.new(shops, {each_serializer: Api::Admin::IdNameSerializer})
end
def edit_path
edit_admin_order_cycle_path(object)
end
def clone_path
clone_admin_order_cycle_path(object)
end
def delete_path
admin_order_cycle_path(object)
end
private
def visible_enterprises
@visible_enterprises ||= OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object).visible_enterprises
end
end

View File

@@ -0,0 +1,33 @@
%colgroup
%col
%col{'style' => 'width: 20%;'}
%col{'style' => 'width: 20%;'}
- unless simple_index
%col
%col
%col
%col
%col
%col
%col
%thead
%tr
%th
=t :name
%th
=t :open
%th
=t :close
- unless simple_index
%th
=t :label_producers
%th
=t :coordinator
%th
=t :label_shops
%th
=t :products
%th.actions
%th.actions
%th.actions

View File

@@ -0,0 +1,4 @@
%div.sixteen.columns.alpha.omega#loading{ ng: { cloak: true, if: 'RequestMonitor.loading' } }
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1{ ng: { hide: 'orderCycles.length > 0' } } LOADING ORDER CYCLES
%h1{ ng: { show: 'orderCycles.length > 0' } } LOADING...

View File

@@ -1,39 +1,36 @@
- order_cycle = order_cycle_form.object
- klass = "order-cycle-#{order_cycle.id} #{order_cycle_status_class order_cycle}"
%tr{ class: "order-cycle-{{orderCycle.id}} {{orderCycle.status}}", ng: { repeat: 'orderCycle in orderCycles track by orderCycle.id' } }
%td
%a{ ng: { href: '{{orderCycle.edit_path}}' } }
{{ orderCycle.name }}
%td
%input.datetimepicker{ id: 'oc{{::orderCycle.id}}_orders_open_at', name: 'oc{{::orderCycle.id}}[orders_open_at]', type: 'text', ng: { if: 'orderCycle.viewing_as_coordinator', model: 'orderCycle.orders_open_at' }, datetimepicker: 'orderCycle.orders_open_at' }
%input{ id: 'oc{{::orderCycle.id}}_orders_open_at', name: 'oc{{::orderCycle.id}}[orders_open_at]', type: 'text', ng: { if: '!orderCycle.viewing_as_coordinator', model: 'orderCycle.orders_open_at'}, disabled: true }
%td
%input.datetimepicker{ id: 'oc{{::orderCycle.id}}_orders_close_at', name: 'oc{{::orderCycle.id}}[orders_close_at]', type: 'text', ng: { if: 'orderCycle.viewing_as_coordinator', model: 'orderCycle.orders_close_at' }, datetimepicker: 'orderCycle.orders_close_at' }
%input{ id: 'oc{{::orderCycle.id}}_orders_close_at', name: 'oc{{::orderCycle.id}}[orders_close_at]', type: 'text', ng: { if: '!orderCycle.viewing_as_coordinator', model: 'orderCycle.orders_close_at'}, disabled: true }
%tr{class: klass}
%td= link_to order_cycle.name, main_app.edit_admin_order_cycle_path(order_cycle)
%td= order_cycle_form.text_field :orders_open_at, :class => "#{viewing_as_coordinator_of?(order_cycle) ? 'datetimepicker' : ''}", :value => order_cycle.orders_open_at, :disabled => !viewing_as_coordinator_of?(order_cycle)
%td= order_cycle_form.text_field :orders_close_at, :class => "#{viewing_as_coordinator_of?(order_cycle) ? 'datetimepicker' : ''}", :value => order_cycle.orders_close_at, :disabled => !viewing_as_coordinator_of?(order_cycle)
- unless order_cycles_simple_index
%td.suppliers
- suppliers = order_cycle.suppliers.merge(OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, order_cycle).visible_enterprises)
- supplier_list = suppliers.map(&:name).sort.join ', '
- if suppliers.count > 3
%span{'ofn-with-tip' => supplier_list}
= suppliers.count
= t('.suppliers')
- else
= supplier_list
%td= order_cycle.coordinator.name
%td.distributors
- distributors = order_cycle.distributors.merge(OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, order_cycle).visible_enterprises)
- distributor_list = distributors.map(&:name).sort.join ', '
- if distributors.count > 3
%span{'ofn-with-tip' => distributor_list}
= distributors.count
= t('.distributors')
- else
= distributor_list
- unless simple_index
%td.producers
%span{'ofn-with-tip' => '{{ orderCycle.producerNames }}', ng: { show: 'orderCycle.producers.length > 3' } }
{{ orderCycle.producers.length }}
= t('.suppliers')
%span{ ng: { hide: 'orderCycle.producers.length > 3', bind: 'orderCycle.producerNames' } }
%td.coordinator
{{ orderCycle.coordinator.name }}
%td.shops
%span{'ofn-with-tip' => '{{ orderCycle.shopNames }}', ng: { show: 'orderCycle.shops.length > 3' } }
{{ orderCycle.shops.length }}
= t('.distributors')
%span{ ng: { hide: 'orderCycle.shops.length > 3', bind: 'orderCycle.shopNames' } }
%td.products
%span= "#{order_cycle.variants.count} #{t('.variants')}"
%span
{{orderCycle.variant_count}}
= t('.variants')
%td.actions
= link_to '', main_app.edit_admin_order_cycle_path(order_cycle), class: 'edit-order-cycle icon-edit no-text'
%td.actions
= link_to '', main_app.clone_admin_order_cycle_path(order_cycle), class: 'clone-order-cycle icon-copy no-text'
- if can_delete?(order_cycle)
%td.actions
= link_to '', main_app.admin_order_cycle_path(order_cycle), class: 'delete-order-cycle icon-trash no-text', :method => :delete, data: { confirm: t(:are_you_sure) }
%a.edit-order-cycle.icon-edit.no-text{ ng: { href: '{{orderCycle.edit_path}}'} }
%td.actions{ ng: { if: 'orderCycle.viewing_as_coordinator' } }
%a.clone-order-cycle.icon-copy.no-text{ ng: { href: '{{orderCycle.clone_path}}'} }
%td.actions{ ng: { if: 'orderCycle.deletable && orderCycle.viewing_as_coordinator' } }
%a.delete-order-cycle.icon-trash.no-text{ ng: { href: '{{orderCycle.delete_path}}'}, data: { method: 'delete', confirm: t(:are_you_sure) } }

View File

@@ -0,0 +1,4 @@
.text-center.margin-bottom-50{ ng: { hide: "RequestMonitor.loading" } }
%input{ type: 'button', value: "#{t('admin.show_n_more', num: '30')} #{t(:days)}", ng: { click: 'showMore(30)' } }
or
%input{ type: 'button', value: "#{t('admin.show_n_more', num: '90')} #{t(:days)}", ng: { click: 'showMore(90)' } }

View File

@@ -4,51 +4,14 @@
= content_for :page_actions do
%li#new_order_cycle_link
= button_link_to t(:new_order_cycle), main_app.new_admin_order_cycle_path, icon: 'icon-plus', id: 'admin_new_order_cycle_link'
- if @show_more
%li
= button_link_to t(:label_less), main_app.admin_order_cycles_path
- else
%li
= button_link_to t(:label_more), main_app.admin_order_cycles_path(params: { show_more: true })
= form_for @order_cycle_set, url: main_app.bulk_update_admin_order_cycles_path, html: {"ng-app" => "admin.orderCycles"} do |f|
%table.index#listing_order_cycles
%colgroup
%col
%col{'style' => 'width: 20%;'}
%col{'style' => 'width: 20%;'}
- unless order_cycles_simple_index
%col
%col
%col
%col
%col
%col
%col
%thead
%tr
%th
=t :name
%th
=t :open
%th
=t :close
- unless order_cycles_simple_index
%th
=t :supplier
%th
=t :coordinator
%th
=t :distributors
%th
=t :products
%th.actions
%th.actions
%th.actions
%form{ name: 'order_cycles_form', ng: { app: "admin.orderCycles", controller: 'OrderCyclesCtrl' } }
%save-bar{ dirty: "order_cycles_form.$dirty", persist: "false" }
%input.red{ type: "button", value: t(:save_changes), ng: { click: "saveAll()", disabled: "!order_cycles_form.$dirty" } }
%table.index#listing_order_cycles{ ng: { show: 'orderCycles.length > 0' } }
= render 'admin/order_cycles/header' #, simple_index: simple_index
%tbody
= f.fields_for :collection do |order_cycle_form|
= render 'admin/order_cycles/row', order_cycle_form: order_cycle_form
= f.submit t :update
= render 'admin/order_cycles/row' #, simple_index: simple_index
= render 'admin/order_cycles/loading_flash'
= render 'admin/order_cycles/show_more'

View File

@@ -219,6 +219,8 @@ en:
form_invalid: "Form contains missing or invalid fields"
clear_filters: Clear Filters
clear: Clear
show_more: Show more
show_n_more: Show %{num} more
columns: Columns
actions: Actions
@@ -898,9 +900,13 @@ en:
no_payment: no payment methods
no_shipping_or_payment: no shipping or payment methods
unconfirmed: unconfirmed
days: days
label_shop: "Shop"
label_shops: "Shops"
label_map: "Map"
label_producer: "Producer"
label_producers: "Producers"
label_groups: "Groups"
label_about: "About"

View File

@@ -97,7 +97,7 @@ Openfoodnetwork::Application.routes.draw do
resources :enterprises do
collection do
get :for_order_cycle
get :for_line_items
get :visible
post :bulk_update, as: :bulk_update
end

View File

@@ -562,18 +562,22 @@ module Admin
end
end
describe "for_line_items" do
let!(:user) { create(:user) }
let!(:enterprise) { create(:enterprise, sells: 'any', owner: user) }
describe "visible" do
let!(:user) { create(:user, enterprise_limit: 10) }
let!(:visible_enterprise) { create(:enterprise, sells: 'any', owner: user) }
let!(:not_visible_enterprise) { create(:enterprise, sells: 'any') }
before do
# As a user with permission
controller.stub spree_current_user: user
# :create_variant_overrides does not affect visiblity (at time of writing)
create(:enterprise_relationship, parent: not_visible_enterprise, child: visible_enterprise, permissions_list: [:create_variant_overrides])
end
it "initializes permissions with the existing OrderCycle" do
# expect(controller).to receive(:render_as_json).with([enterprise], {ams_prefix: 'basic', spree_current_user: user})
spree_get :for_line_items, format: :json
it "uses permissions to determine which enterprises are visible and should be rendered" do
expect(controller).to receive(:render_as_json).with([visible_enterprise], {ams_prefix: 'basic', spree_current_user: user}).and_call_original
spree_get :visible, format: :json
end
end

View File

@@ -16,28 +16,41 @@ module Admin
let!(:oc1) { create(:simple_order_cycle, orders_close_at: 60.days.ago ) }
let!(:oc2) { create(:simple_order_cycle, orders_close_at: 40.days.ago ) }
let!(:oc3) { create(:simple_order_cycle, orders_close_at: 20.days.ago ) }
let!(:oc4) { create(:simple_order_cycle, orders_close_at: nil ) }
context "where show_more is set to true" do
it "loads all order cycles" do
spree_get :index, show_more: true
expect(assigns(:collection)).to include oc1, oc2, oc3
context "html" do
it "doesn't load any data" do
spree_get :index, format: :html
expect(assigns(:collection)).to be_empty
end
end
context "where show_more is not set" do
context "and q[orders_close_at_gt] is set" do
it "loads order cycles that closed within the past month" do
spree_get :index, q: { orders_close_at_gt: 45.days.ago }
expect(assigns(:collection)).to_not include oc1
expect(assigns(:collection)).to include oc2, oc3
context "json" do
context "where ransack conditions are specified" do
it "loads order cycles that closed within the past month, and orders without a close_at date" do
spree_get :index, format: :json
expect(assigns(:collection)).to_not include oc1, oc2
expect(assigns(:collection)).to include oc3, oc4
end
end
context "and q[orders_close_at_gt] is not set" do
it "loads order cycles that closed within the past month" do
spree_get :index
expect(assigns(:collection)).to_not include oc1, oc2
expect(assigns(:collection)).to include oc3
context "where q[orders_close_at_gt] is set" do
let(:q) { { orders_close_at_gt: 45.days.ago } }
it "loads order cycles that closed after the specified date, and orders without a close_at date" do
spree_get :index, format: :json, q: q
expect(assigns(:collection)).to_not include oc1
expect(assigns(:collection)).to include oc2, oc3, oc4
end
context "and other conditions are specified" do
before { q.merge!(id_not_in: [oc2.id, oc4.id]) }
it "loads order cycles that meet all conditions" do
spree_get :index, format: :json, q: q
expect(assigns(:collection)).to_not include oc1, oc2, oc4
expect(assigns(:collection)).to include oc3
end
end
end
end

View File

@@ -21,12 +21,15 @@ feature %q{
oc1 = create(:order_cycle, name: '1')
oc0 = create(:simple_order_cycle, name: '0',
orders_open_at: nil, orders_close_at: nil)
oc7 = create(:simple_order_cycle, name: '0',
orders_open_at: 2.months.ago, orders_close_at: 5.weeks.ago)
# When I go to the admin order cycles page
login_to_admin_section
click_link 'Order Cycles'
# Then the order cycles should be ordered correctly
expect(page).to have_selector "#listing_order_cycles tr td:first-child", count: 7
page.all('#listing_order_cycles tr td:first-child').map(&:text).should ==
['0', '1', '2', '3', '4', '5', '6']
@@ -40,13 +43,12 @@ feature %q{
page.should have_selector "#listing_order_cycles tr.order-cycle-#{oc6.id}.closed"
# And I should see all the details for an order cycle
# (the table includes a hidden field between each row, making this nth-child(3) instead of 2)
within('table#listing_order_cycles tbody tr:nth-child(3)') do
within('table#listing_order_cycles tbody tr:nth-child(2)') do
# Then I should see the basic fields
page.should have_selector 'a', text: oc1.name
page.should have_selector "input[value='#{oc1.orders_open_at}']"
page.should have_selector "input[value='#{oc1.orders_close_at}']"
page.should have_input "oc#{oc1.id}[orders_open_at]", value: oc1.orders_open_at
page.should have_input "oc#{oc1.id}[orders_close_at]", value: oc1.orders_close_at
page.should have_content oc1.coordinator.name
# And I should see the suppliers and distributors
@@ -56,6 +58,11 @@ feature %q{
# And I should see the number of variants
page.should have_selector 'td.products', text: '2 variants'
end
# I can load more order_cycles
page.should have_no_selector "#listing_order_cycles tr.order-cycle-#{oc7.id}"
click_button "Show 30 more days"
page.should have_selector "#listing_order_cycles tr.order-cycle-#{oc7.id}"
end
describe 'listing order cycles with other locales' do
@@ -85,15 +92,15 @@ feature %q{
end
within("tr.order-cycle-#{oc_de.id}") do
expect(find('input.datetimepicker', match: :first).value).to eq '2012-01-30 00:00'
expect(find('input.datetimepicker', match: :first).value).to eq '2012-01-30 00:00:00'
end
end
end
end
context "with specific time" do
let(:order_cycle_opening_time) { Time.zone.local(2040, 11, 06, 06, 00, 00) }
let(:order_cycle_closing_time) { Time.zone.local(2040, 11, 13, 17, 00, 00) }
let(:order_cycle_opening_time) { Time.zone.local(2040, 11, 06, 06, 00, 00).strftime("%F %T %z") }
let(:order_cycle_closing_time) { Time.zone.local(2040, 11, 13, 17, 00, 00).strftime("%F %T %z") }
scenario "creating an order cycle", js: true do
page.driver.resize(1280, 2000)
@@ -183,16 +190,17 @@ feature %q{
# Then my order cycle should have been created
page.should have_content 'Your order cycle has been created.'
oc = OrderCycle.last
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='#{order_cycle_opening_time}']"
page.should have_selector "input[value='#{order_cycle_closing_time}']"
page.should have_input "oc#{oc.id}[orders_open_at]", value: order_cycle_opening_time
page.should have_input "oc#{oc.id}[orders_close_at]", value: order_cycle_closing_time
page.should have_content 'My coordinator'
page.should have_selector 'td.suppliers', text: 'My supplier'
page.should have_selector 'td.distributors', text: 'My distributor'
page.should have_selector 'td.producers', text: 'My supplier'
page.should have_selector 'td.shops', text: 'My distributor'
# And it should have some fees
oc = OrderCycle.last
oc.exchanges.incoming.first.enterprise_fees.should == [supplier_fee]
oc.coordinator_fees.should == [coordinator_fee]
oc.exchanges.outgoing.first.enterprise_fees.should == [distributor_fee]
@@ -317,34 +325,35 @@ feature %q{
# Then my order cycle should have been updated
page.should have_content 'Your order cycle has been updated.'
page.should have_selector 'a', text: 'Plums & Avos'
oc = OrderCycle.last
page.should have_selector "input[value='#{order_cycle_opening_time}']"
page.should have_selector "input[value='#{order_cycle_closing_time}']"
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_input "oc#{oc.id}[orders_open_at]", value: order_cycle_opening_time
page.should have_input "oc#{oc.id}[orders_close_at]", value: order_cycle_closing_time
page.should have_content coordinator.name
page.should have_selector 'td.suppliers', text: 'My supplier'
page.should have_selector 'td.distributors', text: 'My distributor'
page.should have_selector 'td.producers', text: 'My supplier'
page.should have_selector 'td.shops', text: 'My distributor'
# And my coordinator fees should have been configured
OrderCycle.last.coordinator_fee_ids.should match_array [coordinator_fee1.id, coordinator_fee2.id]
oc.coordinator_fee_ids.should match_array [coordinator_fee1.id, coordinator_fee2.id]
# And my supplier fees should have been configured
OrderCycle.last.exchanges.incoming.last.enterprise_fee_ids.should == [supplier_fee2.id]
oc.exchanges.incoming.last.enterprise_fee_ids.should == [supplier_fee2.id]
# And my distributor fees should have been configured
OrderCycle.last.exchanges.outgoing.last.enterprise_fee_ids.should == [distributor_fee2.id]
oc.exchanges.outgoing.last.enterprise_fee_ids.should == [distributor_fee2.id]
# And my tags should have been save
OrderCycle.last.exchanges.outgoing.last.tag_list.should == ['wholesale']
oc.exchanges.outgoing.last.tag_list.should == ['wholesale']
# And it should have some variants selected
selected_initial_variants = initial_variants.take initial_variants.size - 1
OrderCycle.last.variants.map(&:id).should match_array(selected_initial_variants.map(&:id) + [v1.id, v2.id])
oc.variants.map(&:id).should match_array (selected_initial_variants.map(&:id) + [v1.id, v2.id])
# And the collection details should have been updated
OrderCycle.last.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present
OrderCycle.last.exchanges.where(pickup_time: 'New time 1', pickup_instructions: 'New instructions 1').should be_present
oc.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present
oc.exchanges.where(pickup_time: 'New time 1', pickup_instructions: 'New instructions 1').should be_present
end
end
@@ -492,10 +501,10 @@ feature %q{
all('input').last.set '2040-12-01 12:00:05'
end
click_button 'Update'
click_button 'Save Changes'
# Then my times should have been saved
flash_message.should == 'Order cycles have been updated.'
expect(page).to have_selector "#save-bar", text: "Order cycles have been updated."
OrderCycle.order('id ASC').map { |oc| oc.orders_open_at.sec }.should == [0, 2, 4]
OrderCycle.order('id ASC').map { |oc| oc.orders_close_at.sec }.should == [1, 3, 5]
end
@@ -507,12 +516,14 @@ feature %q{
# When I clone it
login_to_admin_section
click_link 'Order Cycles'
first('a.clone-order-cycle').click
flash_message.should == "Your order cycle #{oc.name} has been cloned."
within "tr.order-cycle-#{oc.id}" do
find('a.clone-order-cycle').click
end
expect(flash_message).to eq "Your order cycle #{oc.name} has been cloned."
# Then I should have clone of the order cycle
occ = OrderCycle.last
occ.name.should == "COPY OF #{oc.name}"
expect(occ.name).to eq "COPY OF #{oc.name}"
end
@@ -619,10 +630,10 @@ feature %q{
page.should_not have_content oc_for_other_user.name
# The order cycle should show all enterprises in the order cycle
page.should have_selector 'td.suppliers', text: supplier_managed.name
page.should have_selector 'td.distributors', text: distributor_managed.name
page.should have_selector 'td.suppliers', text: supplier_unmanaged.name
page.should have_selector 'td.distributors', text: distributor_unmanaged.name
page.should have_selector 'td.producers', text: supplier_managed.name
page.should have_selector 'td.shops', text: distributor_managed.name
page.should have_selector 'td.producers', text: supplier_unmanaged.name
page.should have_selector 'td.shops', text: distributor_unmanaged.name
end
scenario "creating a new order cycle" do
@@ -740,8 +751,10 @@ feature %q{
oc = create(:simple_order_cycle, coordinator: distributor_managed)
click_link "Order Cycles"
first('a.clone-order-cycle').click
flash_message.should == "Your order cycle #{oc.name} has been cloned."
within "tr.order-cycle-#{oc.id}" do
find('a.clone-order-cycle').click
end
expect(flash_message).to eq "Your order cycle #{oc.name} has been cloned."
# Then I should have clone of the order cycle
occ = OrderCycle.last
@@ -940,12 +953,14 @@ feature %q{
# Then my order cycle should have been created
page.should have_content 'Your order cycle has been created.'
oc = OrderCycle.last
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='#{Time.zone.local(2040, 10, 17, 06, 00, 00)}']"
page.should have_selector "input[value='#{Time.zone.local(2040, 10, 24, 17, 00, 00)}']"
page.should have_input "oc#{oc.id}[orders_open_at]", value: Time.zone.local(2040, 10, 17, 06, 00, 00).strftime("%F %T %z")
page.should have_input "oc#{oc.id}[orders_close_at]", value: Time.zone.local(2040, 10, 24, 17, 00, 00).strftime("%F %T %z")
# And it should have some variants selected
oc = OrderCycle.last
oc.exchanges.incoming.first.variants.count.should == 2
oc.exchanges.outgoing.first.variants.count.should == 2
@@ -1028,12 +1043,13 @@ feature %q{
# Then my order cycle should have been updated
page.should have_content 'Your order cycle has been updated.'
oc = OrderCycle.last
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='#{Time.zone.local(2040, 10, 17, 06, 00, 00)}']"
page.should have_selector "input[value='#{Time.zone.local(2040, 10, 24, 17, 00, 00)}']"
page.should have_input "oc#{oc.id}[orders_open_at]", value: Time.zone.local(2040, 10, 17, 06, 00, 00).strftime("%F %T %z")
page.should have_input "oc#{oc.id}[orders_close_at]", value: Time.zone.local(2040, 10, 24, 17, 00, 00).strftime("%F %T %z")
# And it should have a variant selected
oc = OrderCycle.last
oc.exchanges.incoming.first.variants.should == [v2]
oc.exchanges.outgoing.first.variants.should == [v2]

View File

@@ -35,9 +35,9 @@ describe "LineItemsCtrl", ->
httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_gt%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bstate_not_eq%5D=canceled").respond [order]
httpBackend.expectGET("/admin/bulk_line_items.json?q%5Border%5D%5Bcompleted_at_gt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_lt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem]
httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor]
httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor]
httpBackend.expectGET("/admin/order_cycles.json?ams_prefix=basic&as=distributor&q%5Borders_close_at_gt%5D=SomeDate").respond [orderCycle]
httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bis_primary_producer_eq%5D=true").respond [supplier]
httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic&q%5Bis_primary_producer_eq%5D=true").respond [supplier]
scope.bulk_order_form = jasmine.createSpyObj('bulk_order_form', ['$setPristine'])

View File

@@ -0,0 +1,60 @@
describe "OrderCyclesCtrl", ->
ctrl = scope = httpBackend = Enterprises = OrderCycles = null
coordinator = producer = shop = orderCycle = null
beforeEach ->
module "admin.orderCycles"
module ($provide) ->
$provide.value 'columns', []
null
jasmine.addMatchers
toDeepEqual: (util, customEqualityTesters) ->
compare: (actual, expected) ->
{ pass: angular.equals(actual, expected) }
beforeEach inject(($controller, $rootScope, $httpBackend, _OrderCycles_, _Enterprises_) ->
scope = $rootScope.$new()
ctrl = $controller
httpBackend = $httpBackend
Enterprises = _Enterprises_
OrderCycles = _OrderCycles_
spyOn(window, "daysFromToday").and.returnValue "SomeDate"
coordinator = { id: 3, name: "Coordinator" }
producer = { id: 1, name: "Producer" }
shop = { id: 5, name: "Shop" }
orderCycle = { id: 4, name: "OC1", coordinator: {id: 3}, shops: [{id: 3},{id: 5}], producers: [{id: 1}] }
httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic").respond [coordinator, producer, shop]
httpBackend.expectGET("/admin/order_cycles.json?ams_prefix=index&q%5Borders_close_at_gt%5D=SomeDate").respond [orderCycle]
ctrl "OrderCyclesCtrl", {$scope: scope, Enterprises: Enterprises, OrderCycles: OrderCycles}
)
describe "before data is returned", ->
it "the RequestMonitor will have a state of loading", ->
expect(scope.RequestMonitor.loading).toBe true
describe "after data is returned", ->
beforeEach ->
httpBackend.flush()
describe "initialisation", ->
it "gets suppliers, adds a blank option as the first in the list", ->
expect(scope.enterprises).toDeepEqual [ { id : '0', name : 'All' }, coordinator, producer, shop ]
it "stores enterprises in an list that is accessible by id", ->
expect(Enterprises.enterprisesByID["5"]).toDeepEqual shop
it "gets order cycles, with dereferenced coordinator, shops and producers", ->
oc = OrderCycles.orderCyclesByID["4"]
expect(scope.orderCycles).toDeepEqual [oc]
expect(oc.coordinator).toDeepEqual coordinator
expect(oc.shops).toDeepEqual [coordinator,shop]
expect(oc.producers).toDeepEqual [producer]
expect(oc.shopNames).toEqual "Coordinator, Shop"
expect(oc.producerNames).toEqual "Producer"
it "the RequestMonitor will not longer have a state of loading", ->
expect(scope.RequestMonitor.loading).toBe false

View File

@@ -103,10 +103,11 @@ describe "OrderCycles service", ->
describe "diff", ->
beforeEach ->
OrderCycles.pristineByID = { 23: { id: 23, name: "ent1", is_primary_producer: true } }
OrderCycles.pristineByID = { 23: { id: 23, name: "orderCycle321", orders_open_at: '123' } }
it "returns a list of properties that have been altered", ->
expect(OrderCycles.diff({ id: 23, name: "orderCycle123", is_primary_producer: true })).toEqual ["name"]
it "returns a list of properties that have been altered, if they are in attrsToSave()", ->
spyOn(OrderCycles, "attrsToSave").and.returnValue(["orders_open_at"])
expect(OrderCycles.diff({ id: 23, name: "orderCycle123", orders_open_at: '321' })).toEqual ["orders_open_at"]
describe "resetAttribute", ->