Enterprise User can create and update schedules via OC index

This commit is contained in:
Rob Harrington
2016-07-27 15:39:29 +10:00
parent 920f52e112
commit 357040f67a
23 changed files with 391 additions and 100 deletions

View File

@@ -1,4 +1,4 @@
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules) ->
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) ->
$scope.RequestMonitor = RequestMonitor
$scope.columns = Columns.columns
$scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form)
@@ -7,10 +7,13 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, C
compileData = ->
for schedule in $scope.schedules
Schedules.linkToOrderCycles(schedule)
Dereferencer.dereference(schedule.order_cycles, OrderCycles.orderCyclesByID)
for orderCycle in $scope.orderCycles
OrderCycles.linkToEnterprises(orderCycle)
OrderCycles.linkToSchedules(orderCycle)
coordinator = Enterprises.byID[orderCycle.coordinator.id]
orderCycle.coordinator = coordinator if coordinator?
Dereferencer.dereference(orderCycle.producers, Enterprises.byID)
Dereferencer.dereference(orderCycle.shops, Enterprises.byID)
Dereferencer.dereference(orderCycle.schedules, Schedules.byID)
orderCycle.involvedEnterpriseIDs = [orderCycle.coordinator.id]
orderCycle.producerNames = orderCycle.producers.map((producer) -> orderCycle.involvedEnterpriseIDs.push(producer.id); producer.name).join(", ")
orderCycle.shopNames = orderCycle.shops.map((shop) -> orderCycle.involvedEnterpriseIDs.push(shop.id); shop.name).join(", ")
@@ -29,8 +32,8 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, C
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 ->
compileData()
$scope.orderCycles.push(orderCycle) for orderCycle in orderCycles
compileData()
daysFromToday = (days) ->
now = new Date

View File

@@ -2,14 +2,8 @@ angular.module("admin.orderCycles").directive 'orderCyclesSelector', (OrderCycle
restrict: 'C'
templateUrl: 'admin/order_cycles_selector.html'
link: (scope, element, attr) ->
if scope.scheduleID?
scope.selectedOrderCycles = Schedules.byID[scope.scheduleID].orderCycles
scope.orderCycleIDs = scope.selectedOrderCycles.map (i, orderCycle) -> orderCycle.id
else
scope.selectedOrderCycles = []
scope.availableOrderCycles = (orderCycle for id, orderCycle of OrderCycles.orderCyclesByID when orderCycle not in scope.selectedOrderCycles)
scope.selectedOrderCycles = (orderCycle for id, orderCycle of OrderCycles.orderCyclesByID when orderCycle.id in scope.schedule.order_cycle_ids)
scope.availableOrderCycles = (orderCycle for id, orderCycle of OrderCycles.orderCyclesByID when orderCycle.id not in scope.schedule.order_cycle_ids)
element.find('#available-order-cycles .order-cycles').sortable
connectWith: '#selected-order-cycles .order-cycles'
@@ -17,6 +11,6 @@ angular.module("admin.orderCycles").directive 'orderCyclesSelector', (OrderCycle
element.find('#selected-order-cycles .order-cycles').sortable
connectWith: '#available-order-cycles .order-cycles'
receive: (event, ui) ->
scope.orderCycleIDs = $('#selected-order-cycles .order-cycles').children('.order-cycle').map((i, element) -> $(element).scope().orderCycle.id).get()
scope.schedule.order_cycle_ids = $('#selected-order-cycles .order-cycles').children('.order-cycle').map((i, element) -> $(element).scope().orderCycle.id).get()
remove: (event, ui) ->
scope.orderCycleIDs = $('#selected-order-cycles .order-cycles').children('.order-cycle').map((i, element) -> $(element).scope().orderCycle.id).get()
scope.schedule.order_cycle_ids = $('#selected-order-cycles .order-cycles').children('.order-cycle').map((i, element) -> $(element).scope().orderCycle.id).get()

View File

@@ -1,40 +1,43 @@
angular.module("admin.orderCycles").directive 'scheduleDialog', ($compile, $injector, $templateCache, DialogDefaults, Schedules) ->
angular.module("admin.orderCycles").directive 'scheduleDialog', ($window, $compile, $injector, $templateCache, DialogDefaults, Schedules) ->
restrict: 'A'
scope:
scheduleID: '@'
scheduleId: '@'
link: (scope, element, attr) ->
scope.submitted = false
scope.name = ""
scope.orderCycleIDs = []
scope.errors = []
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
existing = Schedules.byID[scope.scheduleId]
scope.schedule =
id: existing?.id
name: existing?.name || ''
order_cycle_ids: existing?.order_cycle_ids || []
scope.submitted = false
scope.errors = []
# Compile modal template
scope.template = $compile($templateCache.get('admin/schedule_dialog.html'))(scope)
# Set Dialog options
settings = angular.copy(DialogDefaults)
scope.template.dialog(angular.extend(settings,{width: $window.innerWidth * 0.6}))
scope.template.dialog(close: -> scope.template.remove())
scope.template.dialog('open')
scope.close = ->
scope.template.dialog('close')
return
scope.addSchedule = ->
scope.submit = ->
scope.schedule_form.$setPristine()
scope.submitted = true
scope.errors = []
return scope.errors.push("Please select at least one order cycle") unless scope.schedule.order_cycle_ids.length > 0
if scope.schedule_form.$valid
Schedules.add({name: scope.name, order_cycle_ids: scope.orderCycleIDs}).$promise.then (data) ->
method = if scope.schedule.id? then Schedules.update else Schedules.add
method(scope.schedule).$promise.then (data) ->
if data.id
scope.name = ""
scope.orderCycleIDs = ""
scope.submitted = false
template.dialog('close')
scope.template.dialog('close')
, (response) ->
if response.data.errors
scope.errors.push(error) for error in response.data.errors
else
scope.errors.push("Sorry! Could not create '#{scope.name}'")
return
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
# Compile modal template
scope.template = $compile($templateCache.get('admin/schedule_dialog.html'))(scope)
# Set Dialog options
scope.template.dialog(DialogDefaults)
scope.template.dialog(close: -> scope.template.remove())
scope.template.dialog('open')

View File

@@ -7,4 +7,6 @@ angular.module("admin.orderCycles").factory 'ScheduleResource', ($resource) ->
method: 'POST'
'update':
method: 'PUT'
params:
id: '@id'
})

View File

@@ -1,11 +1,23 @@
angular.module("admin.orderCycles").factory "Schedules", ($q, RequestMonitor, ScheduleResource) ->
angular.module("admin.orderCycles").factory "Schedules", ($q, RequestMonitor, ScheduleResource, OrderCycles, Dereferencer) ->
new class Schedules
byID: {}
# all: []
add: (params) ->
add: (params) =>
ScheduleResource.create params, (schedule) =>
@byID[schedule.id] = schedule if schedule.id
@byID[schedule.id] = schedule if schedule.id?
Dereferencer.dereference(schedule.order_cycles, OrderCycles.orderCyclesByID)
orderCycle.schedules.push(schedule) for orderCycle in schedule.order_cycles
update: (params) =>
ScheduleResource.update params, (schedule) =>
if schedule.id?
Dereferencer.dereference(schedule.order_cycles, OrderCycles.orderCyclesByID)
for orderCycle in @byID[schedule.id].order_cycles when orderCycle.id not in schedule.order_cycle_ids
orderCycle.schedules.splice(i, 1) for s, i in orderCycle.schedules by -1 when s.id == schedule.id
for orderCycle in schedule.order_cycles when orderCycle.id not in @byID[schedule.id].order_cycle_ids
orderCycle.schedules.push(@byID[schedule.id])
angular.extend(@byID[schedule.id], schedule)
# remove: (schedule) ->
# params = id: schedule.id
@@ -26,8 +38,3 @@ angular.module("admin.orderCycles").factory "Schedules", ($q, RequestMonitor, Sc
# @all = data
RequestMonitor.load(request.$promise)
request
linkToOrderCycles: (schedule) ->
for orderCycle, i in schedule.orderCycles
orderCycle = OrderCycles.orderCyclesByID[orderCycle.id]
schedule.orderCycles[i] = orderCycle if orderCycle?

View File

@@ -66,22 +66,3 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
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?
linkToSchedules: (orderCycle) ->
for schedule, i in orderCycle.schedules
schedule = Schedules.byID[schedule.id]
orderCycle.schedules[i] = schedule if schedule?

View File

@@ -3,7 +3,8 @@ angular.module("admin.utils").factory "DialogDefaults", ($window) ->
hide: { effect: "fade", duration: 300 }
autoOpen: false
resizable: false
width: $window.innerWidth * 0.4;
width: $window.innerWidth * 0.4
position: ['middle', 100]
modal: true
open: (event, ui) ->
$('.ui-widget-overlay').bind 'click', ->

View File

@@ -8,3 +8,4 @@
.order-cycles
.order-cycle{ ng: { repeat: 'orderCycle in selectedOrderCycles' } }
{{ orderCycle.name }}
.error{ ng: { repeat: "error in errors", bind: "error" } }

View File

@@ -1,19 +1,20 @@
#schedule-dialog
.text-normal.margin-bottom-30.text-center
= t('admin.order_cycles.index.add_a_new_schedule')
%span{ ng: { hide: 'schedule.id' } }= t('admin.order_cycles.index.adding_a_new_schedule')
%span{ ng: { show: 'schedule.id' } }= t('admin.order_cycles.index.updating_a_schedule')
%form{ name: 'schedule_form', novalidate: true, ng: { submit: "addSchedule()" }}
%form{ name: 'schedule_form', novalidate: true, ng: { submit: "submit()" }}
.text-center.margin-bottom-20
%input.fullwidth{ type: 'text', name: 'name', required: true, placeholder: t('admin.order_cycles.index.schedule_name_placeholder'), ng: { model: "name" } }
%div{ ng: { show: "submitted && new_schedule_form.$pristine" } }
.error{ ng: { show: "(new_schedule_form.name.$error.required)" } }
%input.fullwidth{ type: 'text', name: 'name', required: true, placeholder: t('admin.order_cycles.index.schedule_name_placeholder'), ng: { model: "schedule.name" } }
%div{ ng: { show: "submitted && schedule_form.$pristine" } }
.error{ ng: { show: "(schedule_form.name.$error.required)" } }
= t('admin.order_cycles.index.name_required_error')
.error{ ng: { repeat: "error in errors", bind: "error" } }
.order-cycles-selector.text-center.margin-bottom-30
.text-center
%input.button.icon-plus{ type: 'submit', value: t('admin.order_cycles.index.create_schedule') }
%input.button.icon-plus{ type: 'submit', value: t('admin.order_cycles.index.create_schedule'), ng: { hide: 'schedule.id' } }
%input.button.icon-plus{ type: 'submit', value: t('admin.order_cycles.index.update_schedule'), ng: { show: 'schedule.id' } }
or
%input.button.red.icon-remove{ type: 'button', value: t('actions.cancel'), ng: { click: 'close()' } }

View File

@@ -66,6 +66,9 @@ input.search {
background-color: #fa787e;
}
a {
cursor:pointer;
}
form.order_cycle {
h2 {

View File

@@ -2,12 +2,16 @@ require 'open_food_network/permissions'
module Admin
class SchedulesController < ResourceController
before_filter :check_editable_order_cycle_ids, only: [:create, :update]
respond_to :json
respond_override create: { json: {
success: lambda {
binding.pry
render_as_json @schedule, editable_schedule_ids: permissions.editable_schedules.pluck(:id)
},
success: lambda { render_as_json @schedule, editable_schedule_ids: permissions.editable_schedules.pluck(:id) },
failure: lambda { render json: { errors: @schedule.errors.full_messages }, status: :unprocessable_entity }
} }
respond_override update: { json: {
success: lambda { render_as_json @schedule, editable_schedule_ids: permissions.editable_schedules.pluck(:id) },
failure: lambda { render json: { errors: @schedule.errors.full_messages }, status: :unprocessable_entity }
} }
@@ -30,6 +34,15 @@ module Admin
[:index]
end
def check_editable_order_cycle_ids
return unless params[:schedule][:order_cycle_ids]
requested = params[:schedule][:order_cycle_ids]
existing = @schedule.order_cycle_ids
permitted = OrderCycle.where(id: params[:schedule][:order_cycle_ids] + existing).merge(OrderCycle.managed_by(spree_current_user)).pluck(:id)
params[:schedule][:order_cycle_ids] |= (existing - permitted)
params[:schedule][:order_cycle_ids] -= (requested - permitted)
end
def permissions
return @permissions unless @permission.nil?
@permissions = OpenFoodNetwork::Permissions.new(spree_current_user)

View File

@@ -2,4 +2,6 @@ class Schedule < ActiveRecord::Base
has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules'
attr_accessible :name, :order_cycle_ids
validates :order_cycles, presence: true
end

View File

@@ -186,6 +186,9 @@ class AbilityDecorator
OrderCycle.accessible_by(user).include? order_cycle
end
can [:admin, :index, :create], Schedule
can [:admin, :update], Schedule do |schedule|
OpenFoodNetwork::Permissions.new(user).editable_schedules.include? schedule
end
can [:bulk_update, :clone, :destroy, :notify_producers], OrderCycle do |order_cycle|
user.enterprises.include? order_cycle.coordinator
end

View File

@@ -1,6 +1,6 @@
%colgroup
%col{ ng: { show: 'columns.name.visible' } }
%col{ ng: { show: 'columns.schedule.visible' } }
%col{ ng: { show: 'columns.schedules.visible' } }
%col{ ng: { show: 'columns.open.visible' }, style: 'width: 20%;' }
%col{ ng: { show: 'columns.close.visible' }, style: 'width: 20%;' }
- unless simple_index
@@ -16,7 +16,7 @@
%tr
%th{ ng: { show: 'columns.name.visible' } }
=t :name
%th{ ng: { show: 'columns.schedule.visible' } }
%th{ ng: { show: 'columns.schedules.visible' } }
=t('admin.order_cycles.index.schedules')
%th{ ng: { show: 'columns.open.visible' } }
=t :open

View File

@@ -1,14 +1,16 @@
%tr{ class: "order-cycle-{{orderCycle.id}} {{orderCycle.status}}", ng: { repeat: 'orderCycle in orderCycles | involving:involvingFilter | filter:{name: query} track by orderCycle.id' } }
%td{ ng: { show: 'columns.name.visible' } }
%td.name{ ng: { show: 'columns.name.visible' } }
%a{ ng: { href: '{{orderCycle.edit_path}}' } }
{{ orderCycle.name }}
%td{ ng: { show: 'columns.schedule.visible' } }
%a{ href: '#', 'schedule-dialog' => true, 'schedule-id' => 'schedule.id', ng: { repeat: 'schedule in orderCycle.schedules'} }
{{ schedule.name }}
%td{ ng: { show: 'columns.open.visible' } }
%td.schedules{ ng: { show: 'columns.schedules.visible' } }
%span{ ng: { repeat: 'schedule in orderCycle.schedules'} }
%a{ 'schedule-dialog' => true, 'schedule-id' => '{{schedule.id}}' }
{{ schedule.name }}
%span{ ng: { show: 'orderCycle.schedules.length == 0'}} None
%td.orders_open_at{ ng: { show: 'columns.open.visible' } }
%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{ ng: { show: 'columns.close.visible' } }
%td.orders_close_at{ ng: { show: 'columns.close.visible' } }
%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 }

View File

@@ -6,7 +6,7 @@
= content_for :page_actions do
%li
%a.button.icon-plus#new-schedule{ href: "#", "schedule-dialog" => true }
%a.button.icon-plus#new-schedule{ "schedule-dialog" => true }
= t('admin.order_cycles.index.new_schedule')
%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'

View File

@@ -677,8 +677,11 @@ en:
index:
involving: Involving
schedules: Schedules
add_a_new_schedule: Add a new schedule
adding_a_new_schedule: Adding A New Schedule
updating_a_schedule: Updating A Schedule
new_schedule: New Schedule
create_schedule: Create Schedule
update_schedule: Update Schedule
schedule_name_placeholder: Schedule Name
name_required_error: Please enter a name for this schedule
name_and_timing_form:

View File

@@ -175,7 +175,7 @@ Openfoodnetwork::Application.routes.draw do
get :status, on: :collection
end
resources :schedules, only: [:index, :create], format: :json
resources :schedules, only: [:index, :create, :update], format: :json
end
namespace :api do

View File

@@ -128,14 +128,11 @@ module OpenFoodNetwork
end
def editable_schedules
Schedule.joins(:order_cycles).where(coordinator_id: managed_enterprises.pluck(:id)).select("DISTINCT schedules.*")
Schedule.joins(:order_cycles).where(order_cycles: { id: OrderCycle.managed_by(@user).pluck(:id) }).select("DISTINCT schedules.*")
end
def visible_schedules
managed_enterprise_ids = managed_enterprises.pluck(:id)
Schedule.joins(order_cycles: :exchanges)
.where('exchanges.sender_id IN (?) OR exchanges.receiver_id IN (?)', managed_enterprise_ids, managed_enterprise_ids)
.select("DISTINCT schedules.*")
Schedule.joins(:order_cycles).where(order_cycles: { id: OrderCycle.accessible_by(@user).pluck(:id) }).select("DISTINCT schedules.*")
end

View File

@@ -0,0 +1,167 @@
require 'spec_helper'
describe Admin::SchedulesController, type: :controller do
include AuthenticationWorkflow
describe "index" do
let!(:coordinated_order_cycle) { create(:simple_order_cycle) }
let!(:managed_coordinator) { coordinated_order_cycle.coordinator }
let!(:other_order_cycle) { create(:simple_order_cycle, coordinator: create(:enterprise)) }
let!(:coordinated_schedule) { create(:schedule, order_cycles: [coordinated_order_cycle] ) }
let!(:uncoordinated_schedule) { create(:schedule, order_cycles: [other_order_cycle] ) }
context "html" do
context "where I manage an order cycle coordinator" do
before do
controller.stub spree_current_user: managed_coordinator.owner
end
it "returns an empty @collection" do
spree_get :index, format: :html
expect(assigns(:collection)).to eq []
end
end
end
context "json" do
context "where I manage an order cycle coordinator" do
before do
controller.stub spree_current_user: managed_coordinator.owner
end
let(:params) { { format: :json } }
it "scopes @collection to schedules containing order_cycles coordinated by enterprises I manage" do
spree_get :index, params
expect(assigns(:collection)).to eq [coordinated_schedule]
end
it "serializes the data" do
expect(ActiveModel::ArraySerializer).to receive(:new)
spree_get :index, params
end
end
context "where I manage an order cycle coordinator" do
it "returns an empty collection" do
spree_get :index, format: :json
expect(assigns(:collection)).to be_nil
end
end
end
end
describe "update" do
let(:user) { create(:user, enterprise_limit: 10) }
let!(:managed_coordinator) { create(:enterprise, owner: user) }
let!(:managed_enterprise) { create(:enterprise, owner: user) }
let!(:coordinated_order_cycle) { create(:simple_order_cycle, coordinator: managed_coordinator ) }
let!(:coordinated_order_cycle2) { create(:simple_order_cycle, coordinator: managed_enterprise ) }
let!(:uncoordinated_order_cycle) { create(:simple_order_cycle, coordinator: create(:enterprise) ) }
let!(:uncoordinated_order_cycle2) { create(:simple_order_cycle, coordinator: create(:enterprise)) }
let!(:coordinated_schedule) { create(:schedule, order_cycles: [coordinated_order_cycle, uncoordinated_order_cycle] ) }
let!(:uncoordinated_schedule) { create(:schedule, order_cycles: [uncoordinated_order_cycle] ) }
context "json" do
context "where I manage at least one of the schedule's coordinators" do
render_views
before do
controller.stub spree_current_user: user
end
it "allows me to update basic information" do
spree_put :update, format: :json, id: coordinated_schedule.id, schedule: { name: "my awesome schedule" }
expect(JSON.parse(response.body)["id"]).to eq coordinated_schedule.id
expect(JSON.parse(response.body)["name"]).to eq "my awesome schedule"
expect(assigns(:schedule)).to eq coordinated_schedule
expect(coordinated_schedule.reload.name).to eq 'my awesome schedule'
end
it "allows me to add/remove only order cycles I coordinate to/from the schedule" do
order_cycle_ids = [coordinated_order_cycle2.id, uncoordinated_order_cycle2.id ]
spree_put :update, format: :json, id: coordinated_schedule.id, schedule: { order_cycle_ids: order_cycle_ids }
expect(assigns(:schedule)).to eq coordinated_schedule
# coordinated_order_cycle2 is added, uncoordinated_order_cycle is NOT removed
expect(coordinated_schedule.reload.order_cycles).to include coordinated_order_cycle2, uncoordinated_order_cycle
# coordinated_order_cycle is removed, uncoordinated_order_cycle2 is NOT added
expect(coordinated_schedule.reload.order_cycles).to_not include coordinated_order_cycle, uncoordinated_order_cycle2
end
end
context "where I don't manage any of the schedule's coordinators" do
before do
controller.stub spree_current_user: uncoordinated_order_cycle2.coordinator.owner
end
it "prevents me from updating the schedule" do
spree_put :update, format: :json, id: coordinated_schedule.id, schedule: { name: "my awesome schedule" }
expect(response).to redirect_to spree.unauthorized_path
expect(assigns(:schedule)).to eq nil
expect(coordinated_schedule.name).to_not eq "my awesome schedule"
end
end
end
end
describe "create" do
let(:user) { create(:user) }
let!(:managed_coordinator) { create(:enterprise, owner: user) }
let!(:coordinated_order_cycle) { create(:simple_order_cycle, coordinator: managed_coordinator ) }
let!(:uncoordinated_order_cycle) { create(:simple_order_cycle, coordinator: create(:enterprise)) }
def create_schedule(params)
spree_put :create, params
end
context "json" do
let(:params) { { format: :json, schedule: { name: 'new schedule' } } }
context 'as an enterprise user' do
before { allow(controller).to receive(:spree_current_user) { user } }
context "where no order cycles ids are provided" do
it "does not allow me to create the schedule" do
expect { create_schedule params }.to_not change(Schedule, :count)
end
end
context "where I manage at least one of the order cycles to be added to the schedules" do
before do
params[:schedule].merge!( order_cycle_ids: [coordinated_order_cycle.id, uncoordinated_order_cycle.id] )
end
it "allows me to create the schedule, adding only order cycles that I manage" do
expect { create_schedule params }.to change(Schedule, :count).by(1)
schedule = Schedule.last
expect(schedule.order_cycles).to include coordinated_order_cycle
expect(schedule.order_cycles).to_not include uncoordinated_order_cycle
end
end
context "where I don't manage any of the order cycles to be added to the schedules" do
before do
params[:schedule].merge!( order_cycle_ids: [uncoordinated_order_cycle.id] )
end
it "prevents me from creating the schedule" do
expect { create_schedule params }.to_not change(Schedule, :count)
end
end
end
context 'as an admin user' do
before do
allow(controller).to receive(:spree_current_user) { create(:admin_user) }
params[:schedule].merge!( order_cycle_ids: [coordinated_order_cycle.id, uncoordinated_order_cycle.id] )
end
it "allows me to create a schedule" do
expect { create_schedule params }.to change(Schedule, :count).by(1)
schedule = Schedule.last
expect(schedule.order_cycles).to include coordinated_order_cycle, uncoordinated_order_cycle
end
end
end
end
end

View File

@@ -133,6 +133,10 @@ FactoryGirl.define do
receiver { incoming ? order_cycle.coordinator : FactoryGirl.create(:enterprise) }
end
factory :schedule, class: Schedule do
sequence(:name) { |n| "Schedule #{n}" }
end
factory :variant_override, :class => VariantOverride do
price 77.77
count_on_hand 11111

View File

@@ -0,0 +1,90 @@
require 'spec_helper'
feature 'Schedules', js: true do
include AuthenticationWorkflow
include WebHelper
context "as an enterprise user" do
let(:user) { create(:user) }
let(:managed_enterprise) { create(:distributor_enterprise, owner: user) }
let(:unmanaged_enterprise) { create(:distributor_enterprise) }
let!(:weekly_schedule) { create(:schedule, name: 'Weekly') }
let!(:fortnightly_schedule) { create(:schedule, name: 'Fortnightly') }
let!(:oc1) { create(:order_cycle, coordinator: managed_enterprise, name: 'oc1', schedules: [weekly_schedule]) }
let!(:oc2) { create(:order_cycle, coordinator: managed_enterprise, name: 'oc2', schedules: [weekly_schedule]) }
let!(:oc3) { create(:order_cycle, coordinator: managed_enterprise, name: 'oc3', schedules: [weekly_schedule]) }
let!(:oc4) { create(:order_cycle, coordinator: unmanaged_enterprise, name: 'oc4', schedules: [weekly_schedule]) }
before { login_to_admin_as user }
describe "Adding a new Schedule" do
it "immediately shows the schedule in the order cycle list once created" do
click_link 'Order Cycles'
expect(page).to have_selector ".order-cycle-#{oc1.id}"
find('a', text: 'NEW SCHEDULE').click
within "#schedule-dialog" do
expect(page).to have_selector '#available-order-cycles .order-cycle', text: oc1.name
expect(page).to have_selector '#available-order-cycles .order-cycle', text: oc2.name
expect(page).to have_selector '#available-order-cycles .order-cycle', text: oc3.name
expect(page).to have_no_selector '#available-order-cycles .order-cycle', text: oc4.name
fill_in 'name', with: "Fortnightly"
find("#available-order-cycles .order-cycle", text: oc1.name).drag_to find("#selected-order-cycles")
find("#available-order-cycles .order-cycle", text: oc3.name).drag_to find("#selected-order-cycles")
click_button "Create Schedule"
end
within ".order-cycle-#{oc1.id} td.schedules" do
expect(page).to have_selector "a", text: "Weekly"
expect(page).to have_selector "a", text: "Fortnightly"
end
within ".order-cycle-#{oc2.id} td.schedules" do
expect(page).to have_selector "a", text: "Weekly"
expect(page).to have_no_selector "a", text: "Fortnightly"
end
within ".order-cycle-#{oc3.id} td.schedules" do
expect(page).to have_selector "a", text: "Weekly"
expect(page).to have_selector "a", text: "Fortnightly"
end
end
end
describe "updating existing schedules" do
use_short_wait
before do
oc1.update_attributes(schedule_ids: [weekly_schedule.id, fortnightly_schedule.id])
oc3.update_attributes(schedule_ids: [weekly_schedule.id, fortnightly_schedule.id])
end
it "immediately shows updated schedule lists for order cycles" do
click_link 'Order Cycles'
within ".order-cycle-#{oc1.id} td.schedules" do
find('a', text: "Weekly").click
end
within "#schedule-dialog" do
find("#selected-order-cycles .order-cycle", text: oc3.name).drag_to find("#available-order-cycles")
click_button "Update Schedule"
end
within ".order-cycle-#{oc1.id} td.schedules" do
expect(page).to have_selector "a", text: "Weekly"
expect(page).to have_selector "a", text: "Fortnightly"
end
within ".order-cycle-#{oc2.id} td.schedules" do
expect(page).to have_selector "a", text: "Weekly"
expect(page).to have_no_selector "a", text: "Fortnightly"
end
within ".order-cycle-#{oc3.id} td.schedules" do
expect(page).to have_no_selector "a", text: "Weekly"
expect(page).to have_selector "a", text: "Fortnightly"
end
end
end
end
end

View File

@@ -1,6 +1,6 @@
describe "OrderCyclesCtrl", ->
ctrl = scope = httpBackend = Enterprises = OrderCycles = null
coordinator = producer = shop = orderCycle = null
ctrl = scope = httpBackend = Enterprises = OrderCycles = Schedules = null
coordinator = producer = shop = orderCycle = schedule = null
beforeEach ->
module "admin.orderCycles"
@@ -13,48 +13,62 @@ describe "OrderCyclesCtrl", ->
compare: (actual, expected) ->
{ pass: angular.equals(actual, expected) }
beforeEach inject(($controller, $rootScope, $httpBackend, _OrderCycles_, _Enterprises_) ->
beforeEach inject(($controller, $rootScope, $httpBackend, _OrderCycles_, _Enterprises_, _Schedules_) ->
scope = $rootScope.$new()
ctrl = $controller
httpBackend = $httpBackend
Enterprises = _Enterprises_
OrderCycles = _OrderCycles_
Schedules = _Schedules_
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}] }
schedule = { id: 7, name: 'Weekly', order_cycles: [{id: 4}]}
orderCycle = { id: 4, schedules: [{id: 7}], 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/schedules.json").respond [schedule]
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}
ctrl "OrderCyclesCtrl", {$scope: scope, Enterprises: Enterprises, OrderCycles: OrderCycles, Schedules: Schedules}
)
describe "before data is returned", ->
it "the RequestMonitor will have a state of loading", ->
expect(scope.RequestMonitor.loading).toBe true
it "has not received/stored any data yet", ->
expect(Enterprises.byID["5"]).toBeUndefined()
expect(OrderCycles.orderCyclesByID["4"]).toBeUndefined()
expect(Schedules.byID["7"]).toBeUndefined()
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 "gets enterprises", ->
expect(scope.enterprises).toDeepEqual [ coordinator, producer, shop ]
it "stores enterprises in an list that is accessible by id", ->
expect(Enterprises.enterprisesByID["5"]).toDeepEqual shop
it "stores enterprises, order cycle and schedules in a list that is accessible by id", ->
expect(Enterprises.byID["5"]).toBeDefined()
expect(OrderCycles.orderCyclesByID["4"]).toBeDefined()
expect(Schedules.byID["7"]).toBeDefined()
it "gets order cycles, with dereferenced coordinator, shops and producers", ->
it "gets order cycles, with dereferenced coordinator, shops and producers, schedules", ->
oc = OrderCycles.orderCyclesByID["4"]
s = Schedules.byID["7"]
expect(scope.orderCycles).toDeepEqual [oc]
expect(oc.coordinator).toDeepEqual coordinator
expect(oc.shops).toDeepEqual [coordinator,shop]
expect(oc.producers).toDeepEqual [producer]
expect(oc.schedules).toEqual [s]
expect(s.order_cycles).toEqual [oc]
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