diff --git a/CHANGELOG.md b/CHANGELOG.md index 446e40cd2..a9371208b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Procfile b/Procfile index e68a73938..bbda6f44f 100644 --- a/Procfile +++ b/Procfile @@ -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 diff --git a/app/controllers/api/payments_controller.rb b/app/controllers/api/payments_controller.rb index 0dcdef361..241f5942b 100644 --- a/app/controllers/api/payments_controller.rb +++ b/app/controllers/api/payments_controller.rb @@ -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] diff --git a/app/controllers/api/prices_controller.rb b/app/controllers/api/prices_controller.rb index af2dafe0e..d3fb8ef2c 100644 --- a/app/controllers/api/prices_controller.rb +++ b/app/controllers/api/prices_controller.rb @@ -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 diff --git a/app/controllers/api/reservations_controller.rb b/app/controllers/api/reservations_controller.rb index e5460fe1c..549ee2a07 100644 --- a/app/controllers/api/reservations_controller.rb +++ b/app/controllers/api/reservations_controller.rb @@ -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] diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index de406730b..2e1b1d6db 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -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] diff --git a/app/frontend/src/javascript/controllers/plans.js b/app/frontend/src/javascript/controllers/plans.js index c4af2a91b..6237290d7 100644 --- a/app/frontend/src/javascript/controllers/plans.js +++ b/app/frontend/src/javascript/controllers/plans.js @@ -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, 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; diff --git a/app/frontend/templates/plans/index.html b/app/frontend/templates/plans/index.html index fd3338d3f..3aab02673 100644 --- a/app/frontend/templates/plans/index.html +++ b/app/frontend/templates/plans/index.html @@ -118,10 +118,24 @@ + + + diff --git a/app/models/price.rb b/app/models/price.rb index 023c981d6..c6b993a8e 100644 --- a/app/models/price.rb +++ b/app/models/price.rb @@ -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} 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} 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, 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} 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 diff --git a/app/services/payment_schedule_service.rb b/app/services/payment_schedule_service.rb new file mode 100644 index 000000000..8251837ef --- /dev/null +++ b/app/services/payment_schedule_service.rb @@ -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 diff --git a/app/views/api/prices/compute.json.jbuilder b/app/views/api/prices/compute.json.jbuilder index 0a268bfd3..2f599976c 100644 --- a/app/views/api/prices/compute.json.jbuilder +++ b/app/views/api/prices/compute.json.jbuilder @@ -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 diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 060f06b54..5db4eeba7 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -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 subscription:" + 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 subscription:" 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" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index ee0c79735..b0cc61a76 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -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 abonnement :" + 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 l'abonnement :" thank_you_your_subscription_is_successful: "Merci. Votre abonnement a bien été pris en compte !" diff --git a/db/structure.sql b/db/structure.sql index 7712c8733..e767ca742 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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'),