1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-29 18:52:22 +01:00

compute payment schedule + basic display

This commit is contained in:
Sylvain 2020-11-03 16:50:11 +01:00
parent 506d9dd5fb
commit 21bd1312bc
14 changed files with 214 additions and 103 deletions

View File

@ -5,6 +5,7 @@
- Fix a bug: unable to access embedded plan views
- Fix a bug: warning message overflow in credit wallet modal
- [TODO DEPLOY] `rails fablab:stripe:plans_prices`
- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet`
## v4.6.3 2020 October 28

View File

@ -1,4 +1,4 @@
web: bundle exec rails server puma -p $PORT
#web: bundle exec rails server puma -p $PORT
worker: bundle exec sidekiq -C ./config/sidekiq.yml
wp-client: bin/webpack-dev-server
wp-server: SERVER_BUNDLE_ONLY=yes bin/webpack --watch

View File

@ -155,10 +155,10 @@ class API::PaymentsController < API::ApiController
current_user,
reservable,
slots,
plan_id,
nb_places,
tickets,
coupon_params[:coupon_code])
plan_id: plan_id,
nb_places: nb_places,
tickets: tickets,
coupon_code: coupon_params[:coupon_code])
# Subtract wallet amount from total
total = price_details[:total]

View File

@ -41,18 +41,23 @@ class API::PricesController < API::ApiController
# user
user = User.find(price_parameters[:user_id])
# reservable
if [nil, ''].include? price_parameters[:reservable_id]
if [nil, ''].include?(price_parameters[:reservable_id]) && ['', nil].include?(price_parameters[:plan_id])
@amount = { elements: nil, total: 0, before_coupon: 0 }
else
reservable = price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
reservable = if [nil, ''].include?(price_parameters[:reservable_id])
nil
else
price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
end
@amount = Price.compute(current_user.admin? || (current_user.manager? && current_user.id != user.id),
user,
reservable,
price_parameters[:slots_attributes] || [],
price_parameters[:plan_id],
price_parameters[:nb_reserve_places],
price_parameters[:tickets_attributes],
coupon_params[:coupon_code])
plan_id: price_parameters[:plan_id],
nb_places: price_parameters[:nb_reserve_places],
tickets: price_parameters[:tickets_attributes],
coupon_code: coupon_params[:coupon_code],
payment_schedule: price_parameters[:payment_schedule])
end
@ -70,7 +75,7 @@ class API::PricesController < API::ApiController
end
def compute_price_params
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places,
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places, :payment_schedule,
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end

View File

@ -65,10 +65,10 @@ class API::ReservationsController < API::ApiController
user,
reservation_params[:reservable_type].constantize.find(reservation_params[:reservable_id]),
reservation_params[:slots_attributes] || [],
reservation_params[:plan_id],
reservation_params[:nb_reserve_places],
reservation_params[:tickets_attributes],
coupon_params[:coupon_code])
plan_id: reservation_params[:plan_id],
nb_places: reservation_params[:nb_reserve_places],
tickets: reservation_params[:tickets_attributes],
coupon_code: coupon_params[:coupon_code])
# Subtract wallet amount from total
total = price_details[:total]

View File

@ -54,10 +54,10 @@ class API::SubscriptionsController < API::ApiController
user,
nil,
[],
subscription_params[:plan_id],
nil,
nil,
coupon_params[:coupon_code])
plan_id: subscription_params[:plan_id],
nb_places: nil,
tickets: nil,
coupon_code: coupon_params[:coupon_code])
# Subtract wallet amount from total
total = price_details[:total]

View File

@ -12,8 +12,8 @@
*/
'use strict';
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', 'settingsPromise',
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise) {
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', 'settingsPromise', 'Price',
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise, Price) {
/* PUBLIC SCOPE */
// list of groups
@ -46,9 +46,12 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
$scope.coupon =
{ applied: null };
// Storage for the total price (plan price + coupon, if any)
$scope.cart =
{ total: null };
// Storage for the total price (plan price + coupon, if any) and of the payment schedule
$scope.cart = {
total: null,
payment_schedule: false,
schedule: null
};
// text that appears in the bottom-right box of the page (subscriptions rules details)
$scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value;
@ -76,6 +79,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
if ($scope.isAuthenticated()) {
if ($scope.selectedPlan !== plan) {
$scope.selectedPlan = plan;
$scope.cart.payment_schedule = plan.monthly_payment;
updateCartPrice();
} else {
$scope.selectedPlan = null;
@ -87,6 +91,18 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
}, 50);
};
/**
* This will update the payment_schedule setting when the user toggles the switch button
* @param checked {Boolean}
*/
$scope.togglePaymentSchedule = (checked) => {
setTimeout(() => {
$scope.cart.payment_schedule = checked;
updateCartPrice();
$scope.$apply();
}, 50);
};
/**
* Check if the provided plan is currently selected
* @param plan {Object} Resource plan
@ -101,17 +117,17 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
$scope.openSubscribePlanModal = function () {
Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
if ((AuthService.isAuthorized('member') && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if ((AuthService.isAuthorized('member') && amountToPay > 0) ||
(AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
if (settingsPromise.online_payment_module !== 'true') {
growl.error(_t('app.public.plans.online_payment_disabled'));
} else {
return payByStripe();
}
} else {
if (AuthService.isAuthorized('admin')
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
if (AuthService.isAuthorized('admin') ||
(AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id) ||
amountToPay === 0) {
return payOnSite();
}
}
@ -191,7 +207,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
// group all plans by Group
for (const group of $scope.groups) {
const groupObj = { id: group.id, name: group.name, plans: [], actives: 0 };
for (let plan of plansPromise) {
for (const plan of plansPromise) {
if (plan.group_id === group.id) {
groupObj.plans.push(plan);
if (!plan.disabled) { groupObj.actives++; }
@ -225,22 +241,44 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
const updateCartPrice = function () {
// first we check the selection of a user
if (Object.keys($scope.ctrl.member).length > 0 && $scope.selectedPlan) {
$scope.cart.total = $scope.selectedPlan.amount;
// apply the coupon if any
if ($scope.coupon.applied) {
let discount;
if ($scope.coupon.applied.type === 'percent_off') {
discount = ($scope.cart.total * $scope.coupon.applied.percent_off) / 100;
} else if ($scope.coupon.applied.type === 'amount_off') {
discount = $scope.coupon.applied.amount_off;
}
return $scope.cart.total -= discount;
}
const r = mkReservation($scope.ctrl.member, $scope.selectedPlan);
Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
$scope.cart.total = res.price;
$scope.cart.schedule = res.schedule;
});
} else {
return $scope.reserve.amountTotal = null;
}
};
/**
* Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
* @param reservation {Object} as returned by mkReservation()
* @param coupon {Object} Coupon as returned from the API
* @return {{reservation:Object, coupon_code:string}}
*/
const mkRequestParams = function (reservation, coupon) {
return {
reservation,
coupon_code: ((coupon ? coupon.code : undefined))
};
};
/**
* Create an hash map implementing the Reservation specs
* @param member {Object} User as retrieved from the API: current user / selected user if current is admin
* @param [plan] {Object} Plan as retrieved from the API: plan to buy with the current reservation
* @return {{user_id:Number, slots_attributes:Array<Object>, plan_id:Number|null}}
*/
const mkReservation = function (member, plan) {
return {
user_id: member.id,
slots_attributes: [],
plan_id: ((plan ? plan.id : undefined)),
payment_schedule: $scope.cart.payment_schedule
};
};
/**
* Open a modal window which trigger the stripe payment process
*/
@ -296,7 +334,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
};
}
]
}).result['finally'](null).then(function (subscription) {
}).result.finally(null).then(function (subscription) {
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
$scope.paid.plan = angular.copy($scope.selectedPlan);
@ -383,7 +421,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}
]
}).result['finally'](null).then(function (subscription) {
}).result.finally(null).then(function (subscription) {
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
$scope.ctrl.member = null;

View File

@ -118,10 +118,24 @@
</div>
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" total="selectedPlan.amount" user-id="{{ctrl.member.id}}"></coupon>
<label for="payment_schedule" translate>{{ 'app.public.plans.monthly_payment' }}</label>
<switch checked="cart.payment_schedule" id="payment_schedule" on-change="togglePaymentSchedule" class-name="'v-middle'" ng-if="selectedPlan.monthly_payment"></switch>
</div>
<div class="widget-footer">
<button class="btn btn-valid btn-info btn-block p-l text-u-c r-b" ng-click="openSubscribePlanModal()" ng-if="!ctrl.member.subscribed_plan">{{ 'app.public.plans.confirm_and_pay' | translate }} {{cart.total | currency}}</button>
<div ng-if="cart.schedule">
<h4 translate>{{ 'app.public.plans.your_payment_schedule' }}</h4>
<ul>
<li ng-repeat="item in cart.schedule.items">
<span>{{item.due_date | amDateFormat:'L'}}</span>
<span>{{item.price | currency}}</span>
</li>
</ul>
</div>
<button class="btn btn-valid btn-info btn-block p-l text-u-c r-b" ng-click="openSubscribePlanModal()" ng-if="!ctrl.member.subscribed_plan">
{{ 'app.public.plans.confirm_and_pay' | translate }} {{cart.total | currency}}
</button>
</div>
</section>

View File

@ -20,13 +20,15 @@ class Price < ApplicationRecord
# @param user {User} The user who's reserving (or selected if an admin is reserving)
# @param reservable {Machine|Training|Event} what the reservation is targeting
# @param slots {Array<Slot>} when did the reservation will occur
# @param [plan_id] {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here
# @param [nb_places] {Number} for _reservable_ of type Event, pass here the number of booked places
# @param [tickets] {Array<Ticket>} for _reservable_ of type Event, mapping of the number of seats booked per price's category
# @param [coupon_code] {String} Code of the coupon to apply to the total price
# @param options {plan_id:Number, nb_places:Number, tickets:Array<Ticket>, coupon_code:String, payment_schedule:Boolean}
# - plan_id {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here
# - nb_places {Number} for _reservable_ of type Event, pass here the number of booked places
# - tickets {Array<Ticket>} for _reservable_ of type Event, mapping of the number of seats booked per price's category
# - coupon_code {String} Code of the coupon to apply to the total price
# - payment_schedule {Boolean} if the user is requesting a payment schedule for his subscription
# @return {Hash} total and price detail
##
def compute(admin, user, reservable, slots, plan_id = nil, nb_places = nil, tickets = nil, coupon_code = nil)
def compute(admin, user, reservable, slots, options = {})
total_amount = 0
all_elements = {}
all_elements[:slots] = []
@ -35,9 +37,9 @@ class Price < ApplicationRecord
plan = if user.subscribed_plan
new_plan_being_bought = false
user.subscribed_plan
elsif plan_id
elsif options[:plan_id]
new_plan_being_bought = true
Plan.find(plan_id)
Plan.find(options[:plan_id])
else
new_plan_being_bought = false
nil
@ -91,8 +93,8 @@ class Price < ApplicationRecord
# Event reservation
when Event
amount = reservable.amount * nb_places
tickets&.each do |ticket|
amount = reservable.amount * options[:nb_places]
options[:tickets]&.each do |ticket|
amount += ticket[:booked] * EventPriceCategory.find(ticket[:event_price_category_id]).amount
end
slots.each do |slot|
@ -130,8 +132,8 @@ class Price < ApplicationRecord
raise NotImplementedError
end
# === compute Plan price if any ===
unless plan_id.nil?
# === compute Plan price (if any) ===
unless options[:plan_id].nil?
all_elements[:plan] = plan.amount
total_amount += plan.amount
end
@ -139,11 +141,29 @@ class Price < ApplicationRecord
# === apply Coupon if any ===
_amount_no_coupon = total_amount
cs = CouponService.new
cp = cs.validate(coupon_code, user.id)
cp = cs.validate(options[:coupon_code], user.id)
total_amount = cs.apply(total_amount, cp)
# == generate PaymentSchedule ()if applicable) ===
schedule = if options[:payment_schedule] && plan.monthly_payment
pss = PaymentScheduleService.new
pss.compute(plan, _amount_no_coupon, cp)
else
nil
end
if schedule
total_amount -= schedule[:payment_schedule].total
total_amount += schedule[:items][0].amount
end
# return result
{ elements: all_elements, total: total_amount.to_i, before_coupon: _amount_no_coupon.to_i, coupon: cp }
{
elements: all_elements,
total: total_amount.to_i,
before_coupon: _amount_no_coupon.to_i,
coupon: cp,
schedule: schedule
}
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
# create PaymentSchedules for various items
class PaymentScheduleService
##
# Compute a payment schedule for a new subscription to the provided plan
# @param plan {Plan}
# @param total {Number} Total amount of the current shopping cart (which includes this plan)
# @param coupon {Coupon} apply this coupon, if any
##
def compute(plan, total, coupon = nil)
price = if coupon
cs = CouponService.new
cs.ventilate(total, plan.amount, coupon)
else
plan.amount
end
ps = PaymentSchedule.new(scheduled: plan, total: price, coupon: coupon)
deadlines = plan.duration / 1.month
per_month = price / deadlines
adjustment = if per_month * deadlines != price
price - (per_month * deadlines)
else
0
end
items = []
(0..deadlines - 1).each do |i|
date = DateTime.current + i.months
items.push PaymentScheduleItem.new(
amount: per_month + adjustment,
due_date: date,
payment_schedule: ps
)
adjustment = 0
end
{ payment_schedule: ps, items: items }
end
end

View File

@ -1,10 +1,20 @@
json.price @amount[:total] / 100.00
json.price_without_coupon @amount[:before_coupon] / 100.00
json.details do
json.slots @amount[:elements][:slots] do |slot|
json.start_at slot[:start_at]
json.price slot[:price] / 100.00
json.promo slot[:promo]
if @amount[:elements]
json.details do
json.slots @amount[:elements][:slots] do |slot|
json.start_at slot[:start_at]
json.price slot[:price] / 100.00
json.promo slot[:promo]
end
json.plan @amount[:elements][:plan] / 100.00 if @amount[:elements][:plan]
end
json.plan @amount[:elements][:plan] / 100.00 if @amount[:elements][:plan]
end if @amount[:elements]
end
if @amount[:schedule]
json.schedule do
json.items @amount[:schedule][:items] do |item|
json.price item.amount / 100.00
json.due_date item.due_date
end
end
end

View File

@ -250,7 +250,9 @@ en:
your_subscription_has_expired_on_the_DATE: "Your subscription has expired on the {DATE}"
subscription_price: "Subscription price"
you_ve_just_selected_a_subscription_html: "You've just selected a <strong>subscription</strong>:"
monthly_payment: "Pay by monthly schedule"
confirm_and_pay: "Confirm and pay"
your_payment_schedule: "Your payment schedule"
you_ve_just_payed_the_subscription_html: "You've just paid the <strong>subscription</strong>:"
thank_you_your_subscription_is_successful: "Thank you. Your subscription is successful!"
your_invoice_will_be_available_soon_from_your_dashboard: "Your invoice will be available soon from your dashboard"

View File

@ -250,6 +250,8 @@ fr:
your_subscription_has_expired_on_the_DATE: "Votre abonnement a expiré au {DATE}"
subscription_price: "Coût de l'abonnement"
you_ve_just_selected_a_subscription_html: "Vous venez de sélectionner un <strong>abonnement</strong> :"
monthly_payment: "Payer par échéancier mensuel"
your_payment_schedule: "Votre échéancier de paiement"
confirm_and_pay: "Valider et payer"
you_ve_just_payed_the_subscription_html: "Vous venez de régler <strong>l'abonnement</strong> :"
thank_you_your_subscription_is_successful: "Merci. Votre abonnement a bien été pris en compte !"

View File

@ -108,8 +108,8 @@ SET default_tablespace = '';
CREATE TABLE public.abuses (
id integer NOT NULL,
signaled_id integer,
signaled_type character varying,
signaled_id integer,
first_name character varying,
last_name character varying,
email character varying,
@ -187,8 +187,8 @@ CREATE TABLE public.addresses (
locality character varying,
country character varying,
postal_code character varying,
placeable_id integer,
placeable_type character varying,
placeable_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
@ -263,8 +263,8 @@ CREATE TABLE public.ar_internal_metadata (
CREATE TABLE public.assets (
id integer NOT NULL,
viewable_id integer,
viewable_type character varying,
viewable_id integer,
attachment character varying,
type character varying,
created_at timestamp without time zone,
@ -504,8 +504,8 @@ ALTER SEQUENCE public.coupons_id_seq OWNED BY public.coupons.id;
CREATE TABLE public.credits (
id integer NOT NULL,
creditable_id integer,
creditable_type character varying,
creditable_id integer,
plan_id integer,
hours integer,
created_at timestamp without time zone,
@ -1046,8 +1046,8 @@ ALTER SEQUENCE public.invoice_items_id_seq OWNED BY public.invoice_items.id;
CREATE TABLE public.invoices (
id integer NOT NULL,
invoiced_id integer,
invoiced_type character varying,
invoiced_id integer,
stp_invoice_id character varying,
total integer,
created_at timestamp without time zone,
@ -1226,15 +1226,15 @@ ALTER SEQUENCE public.machines_id_seq OWNED BY public.machines.id;
CREATE TABLE public.notifications (
id integer NOT NULL,
receiver_id integer,
attached_object_id integer,
attached_object_type character varying,
attached_object_id integer,
notification_type_id integer,
is_read boolean DEFAULT false,
created_at timestamp without time zone,
updated_at timestamp without time zone,
receiver_type character varying,
is_send boolean DEFAULT false,
meta_data jsonb DEFAULT '{}'::jsonb
meta_data jsonb DEFAULT '"{}"'::jsonb
);
@ -1653,8 +1653,8 @@ CREATE TABLE public.prices (
id integer NOT NULL,
group_id integer,
plan_id integer,
priceable_id integer,
priceable_type character varying,
priceable_id integer,
amount integer,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
@ -1969,8 +1969,8 @@ CREATE TABLE public.reservations (
message text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
reservable_id integer,
reservable_type character varying,
reservable_id integer,
nb_reserve_places integer,
statistic_profile_id integer
);
@ -2002,8 +2002,8 @@ ALTER SEQUENCE public.reservations_id_seq OWNED BY public.reservations.id;
CREATE TABLE public.roles (
id integer NOT NULL,
name character varying,
resource_id integer,
resource_type character varying,
resource_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
@ -2937,8 +2937,8 @@ CREATE TABLE public.users_roles (
CREATE TABLE public.wallet_transactions (
id integer NOT NULL,
wallet_id integer,
transactable_id integer,
transactable_type character varying,
transactable_id integer,
transaction_type character varying,
amount integer,
created_at timestamp without time zone NOT NULL,
@ -4027,6 +4027,14 @@ ALTER TABLE ONLY public.roles
ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -5084,29 +5092,6 @@ CREATE INDEX profiles_lower_unaccent_last_name_trgm_idx ON public.profiles USING
CREATE INDEX projects_search_vector_idx ON public.projects USING gin (search_vector);
--
-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
--
-- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: -
--
CREATE RULE accounting_periods_del_protect AS
ON DELETE TO public.accounting_periods DO INSTEAD NOTHING;
--
-- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: -
--
CREATE RULE accounting_periods_upd_protect AS
ON UPDATE TO public.accounting_periods DO INSTEAD NOTHING;
--
-- Name: projects projects_search_content_trigger; Type: TRIGGER; Schema: public; Owner: -
--
@ -5633,7 +5618,6 @@ INSERT INTO "schema_migrations" (version) VALUES
('20140605125131'),
('20140605142133'),
('20140605151442'),
('20140606133116'),
('20140609092700'),
('20140609092827'),
('20140610153123'),
@ -5702,14 +5686,12 @@ INSERT INTO "schema_migrations" (version) VALUES
('20150507075620'),
('20150512123546'),
('20150520132030'),
('20150520133409'),
('20150526130729'),
('20150527153312'),
('20150529113555'),
('20150601125944'),
('20150603104502'),
('20150603104658'),
('20150603133050'),
('20150604081757'),
('20150604131525'),
('20150608142234'),
@ -5791,7 +5773,6 @@ INSERT INTO "schema_migrations" (version) VALUES
('20160905142700'),
('20160906094739'),
('20160906094847'),
('20160906145713'),
('20160915105234'),
('20161123104604'),
('20170109085345'),