diff --git a/app/controllers/api/payment_schedules_controller.rb b/app/controllers/api/payment_schedules_controller.rb index fceb0da50..901bb73b2 100644 --- a/app/controllers/api/payment_schedules_controller.rb +++ b/app/controllers/api/payment_schedules_controller.rb @@ -6,6 +6,15 @@ class API::PaymentSchedulesController < API::ApiController before_action :set_payment_schedule, only: %i[download cancel] before_action :set_payment_schedule_item, only: %i[cash_check refresh_item pay_item] + def index + @payment_schedules = PaymentSchedule.where('invoicing_profile_id = ?', current_user.invoicing_profile.id) + .includes(:invoicing_profile, :payment_schedule_items, :subscription) + .joins(:invoicing_profile) + .order('payment_schedules.created_at DESC') + .page(params[:page]) + .per(params[:size]) + end + def list authorize PaymentSchedule diff --git a/app/frontend/src/javascript/api/api-client.ts b/app/frontend/src/javascript/api/api-client.ts index ab6d49c86..a1f9c1fe4 100644 --- a/app/frontend/src/javascript/api/api-client.ts +++ b/app/frontend/src/javascript/api/api-client.ts @@ -33,9 +33,13 @@ function extractHumanReadableMessage(error: any): string { return error; } - // parse Rails errors (as JSON) + // parse Rails errors (as JSON) or API errors let message = ''; if (error instanceof Object) { + // API errors + if (error.hasOwnProperty('error') && typeof error.error === 'string') { + return error.error; + } // iterate through all the keys to build the message for (const key in error) { if (Object.prototype.hasOwnProperty.call(error, key)) { diff --git a/app/frontend/src/javascript/api/payment-schedule.ts b/app/frontend/src/javascript/api/payment-schedule.ts index 33a0acc2a..21d56f359 100644 --- a/app/frontend/src/javascript/api/payment-schedule.ts +++ b/app/frontend/src/javascript/api/payment-schedule.ts @@ -14,6 +14,11 @@ export default class PaymentScheduleAPI { return res?.data; } + async index (query: PaymentScheduleIndexRequest): Promise> { + const res: AxiosResponse = await apiClient.get(`/api/payment_schedules?page=${query.query.page}&size=${query.query.size}`); + return res?.data; + } + async cashCheck(paymentScheduleItemId: number): Promise { const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/cash_check`); return res?.data; @@ -33,10 +38,5 @@ export default class PaymentScheduleAPI { const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`); return res?.data; } - - static list (query: PaymentScheduleIndexRequest): IWrapPromise> { - const api = new PaymentScheduleAPI(); - return wrapPromise(api.list(query)); - } } diff --git a/app/frontend/src/javascript/components/payment-schedules-dashboard.tsx b/app/frontend/src/javascript/components/payment-schedules-dashboard.tsx new file mode 100644 index 000000000..cfe580ef4 --- /dev/null +++ b/app/frontend/src/javascript/components/payment-schedules-dashboard.tsx @@ -0,0 +1,94 @@ +/** + * This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices + * for the currentUser + */ + +import React, { useEffect, useState } from 'react'; +import { IApplication } from '../models/application'; +import { useTranslation } from 'react-i18next'; +import { Loader } from './loader'; +import { react2angular } from 'react2angular'; +import PaymentScheduleAPI from '../api/payment-schedule'; +import { PaymentSchedulesTable } from './payment-schedules-table'; +import { FabButton } from './fab-button'; +import { User } from '../models/user'; +import { PaymentSchedule } from '../models/payment-schedule'; + +declare var Application: IApplication; + +interface PaymentSchedulesDashboardProps { + currentUser: User +} + +const PAGE_SIZE = 20; + +const PaymentSchedulesDashboard: React.FC = ({ currentUser }) => { + const { t } = useTranslation('logged'); + + const [paymentSchedules, setPaymentSchedules] = useState>([]); + const [pageNumber, setPageNumber] = useState(1); + + useEffect(() => { + handleRefreshList(); + }, []); + + /** + * Fetch from the API the next payment schedules to display, for the current filters, and append them to the current results table. + */ + const handleLoadMore = (): void => { + setPageNumber(pageNumber + 1); + + const api = new PaymentScheduleAPI(); + api.index({ query: { page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => { + const list = paymentSchedules.concat(res); + setPaymentSchedules(list); + }); + } + + /** + * Reload from te API all the currently displayed payment schedules + */ + const handleRefreshList = (onError?: (msg: any) => void): void => { + const api = new PaymentScheduleAPI(); + api.index({ query: { page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => { + setPaymentSchedules(res); + }).catch((err) => { + if (typeof onError === 'function') { onError(err.message); } + }); + } + + /** + * Check if the current collection of payment schedules is empty or not. + */ + const hasSchedules = (): boolean => { + return paymentSchedules.length > 0; + } + + /** + * Check if there are some results for the current filters that aren't currently shown. + */ + const hasMoreSchedules = (): boolean => { + return hasSchedules() && paymentSchedules.length < paymentSchedules[0].max_length; + } + + return ( +
+ {!hasSchedules() &&
{t('app.logged.dashboard.payment_schedules.no_payment_schedules')}
} + {hasSchedules() &&
+ + {hasMoreSchedules() && {t('app.logged.dashboard.payment_schedules.load_more')}} +
} +
+ ); +} + + +const PaymentSchedulesDashboardWrapper: React.FC = ({ currentUser }) => { + return ( + + + + ); +} + +Application.Components.component('paymentSchedulesDashboard', react2angular(PaymentSchedulesDashboardWrapper, ['currentUser'])); diff --git a/app/frontend/src/javascript/components/payment-schedules-list.tsx b/app/frontend/src/javascript/components/payment-schedules-list.tsx index b2868a19c..01c381e28 100644 --- a/app/frontend/src/javascript/components/payment-schedules-list.tsx +++ b/app/frontend/src/javascript/components/payment-schedules-list.tsx @@ -2,7 +2,7 @@ * This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { IApplication } from '../models/application'; import { useTranslation } from 'react-i18next'; import { Loader } from './loader'; @@ -12,6 +12,7 @@ import { DocumentFilters } from './document-filters'; import { PaymentSchedulesTable } from './payment-schedules-table'; import { FabButton } from './fab-button'; import { User } from '../models/user'; +import { PaymentSchedule } from '../models/payment-schedule'; declare var Application: IApplication; @@ -20,16 +21,19 @@ interface PaymentSchedulesListProps { } const PAGE_SIZE = 20; -const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } }); const PaymentSchedulesList: React.FC = ({ currentUser }) => { const { t } = useTranslation('admin'); - const [paymentSchedules, setPaymentSchedules] = useState(paymentSchedulesList.read()); - const [pageNumber, setPageNumber] = useState(1); - const [referenceFilter, setReferenceFilter] = useState(null); - const [customerFilter, setCustomerFilter] = useState(null); - const [dateFilter, setDateFilter] = useState(null); + const [paymentSchedules, setPaymentSchedules] = useState>([]); + const [pageNumber, setPageNumber] = useState(1); + const [referenceFilter, setReferenceFilter] = useState(null); + const [customerFilter, setCustomerFilter] = useState(null); + const [dateFilter, setDateFilter] = useState(null); + + useEffect(() => { + handleRefreshList(); + }, []); /** * Fetch from the API the payments schedules matching the given filters and reset the results table with the new schedules. @@ -61,10 +65,12 @@ const PaymentSchedulesList: React.FC = ({ currentUser /** * Reload from te API all the currently displayed payment schedules */ - const handleRefreshList = (): void => { + const handleRefreshList = (onError?: (msg: any) => void): void => { const api = new PaymentScheduleAPI(); api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => { setPaymentSchedules(res); + }).catch((err) => { + if (typeof onError === 'function') { onError(err.message); } }); } diff --git a/app/frontend/src/javascript/components/payment-schedules-table.tsx b/app/frontend/src/javascript/components/payment-schedules-table.tsx index fbc15a8fa..e40109cf9 100644 --- a/app/frontend/src/javascript/components/payment-schedules-table.tsx +++ b/app/frontend/src/javascript/components/payment-schedules-table.tsx @@ -25,12 +25,12 @@ declare var Fablab: IFablab; interface PaymentSchedulesTableProps { paymentSchedules: Array, showCustomer?: boolean, - refreshList: () => void, + refreshList: (onError: (msg: any) => void) => void, operator: User, } const PaymentSchedulesTableComponent: React.FC = ({ paymentSchedules, showCustomer, refreshList, operator }) => { - const { t } = useTranslation('admin'); + const { t } = useTranslation('shared'); const [showExpanded, setShowExpanded] = useState>(new Map()); const [showConfirmCashing, setShowConfirmCashing] = useState(false); @@ -114,7 +114,7 @@ const PaymentSchedulesTableComponent: React.FC = ({ return ( - {t('app.admin.invoices.schedules_table.download')} + {t('app.shared.schedules_table.download')} ); } @@ -123,9 +123,9 @@ const PaymentSchedulesTableComponent: React.FC = ({ * Return the human-readable string for the status of the provided deadline. */ const formatState = (item: PaymentScheduleItem): JSX.Element => { - let res = t(`app.admin.invoices.schedules_table.state_${item.state}`); + let res = t(`app.shared.schedules_table.state_${item.state}`); if (item.state === PaymentScheduleItemState.Paid) { - const key = `app.admin.invoices.schedules_table.method_${item.payment_method}` + const key = `app.shared.schedules_table.method_${item.payment_method}` res += ` (${t(key)})`; } return {res}; @@ -150,24 +150,24 @@ const PaymentSchedulesTableComponent: React.FC = ({ return ( }> - {t('app.admin.invoices.schedules_table.confirm_payment')} + {t('app.shared.schedules_table.confirm_payment')} ); } else { - return {t('app.admin.invoices.schedules_table.please_ask_reception')} + return {t('app.shared.schedules_table.please_ask_reception')} } case PaymentScheduleItemState.RequireAction: return ( }> - {t('app.admin.invoices.schedules_table.solve')} + {t('app.shared.schedules_table.solve')} ); case PaymentScheduleItemState.RequirePaymentMethod: return ( }> - {t('app.admin.invoices.schedules_table.update_card')} + {t('app.shared.schedules_table.update_card')} ); case PaymentScheduleItemState.Error: @@ -175,11 +175,11 @@ const PaymentSchedulesTableComponent: React.FC = ({ return ( }> - {t('app.admin.invoices.schedules_table.cancel_subscription')} + {t('app.shared.schedules_table.cancel_subscription')} ) } else { - return {t('app.admin.invoices.schedules_table.please_ask_reception')} + return {t('app.shared.schedules_table.please_ask_reception')} } default: return @@ -203,12 +203,19 @@ const PaymentSchedulesTableComponent: React.FC = ({ const api = new PaymentScheduleAPI(); api.cashCheck(tempDeadline.id).then((res) => { if (res.state === PaymentScheduleItemState.Paid) { - refreshList(); + refreshSchedulesTable(); toggleConfirmCashingModal(); } }); } + /** + * Refresh all payment schedules in the table + */ + const refreshSchedulesTable = (): void => { + refreshList(setErrors); + } + /** * Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline. */ @@ -240,7 +247,7 @@ const PaymentSchedulesTableComponent: React.FC = ({ toggleConfirmActionButton(); const api = new PaymentScheduleAPI(); api.refreshItem(tempDeadline.id).then(() => { - refreshList(); + refreshSchedulesTable(); toggleResolveActionModal(); }); } @@ -297,10 +304,10 @@ const PaymentSchedulesTableComponent: React.FC = ({ const handleCardUpdateSuccess = (): void => { const api = new PaymentScheduleAPI(); api.payItem(tempDeadline.id).then(() => { - refreshList(); + refreshSchedulesTable(); toggleUpdateCardModal(); }).catch((err) => { - handleCardUpdateError(err.error); + handleCardUpdateError(err); }); } @@ -335,7 +342,7 @@ const PaymentSchedulesTableComponent: React.FC = ({ const onCancelSubscriptionConfirmed = (): void => { const api = new PaymentScheduleAPI(); api.cancel(tempSchedule.id).then(() => { - refreshList(); + refreshSchedulesTable(); toggleCancelSubscriptionModal(); }); } @@ -346,16 +353,16 @@ const PaymentSchedulesTableComponent: React.FC = ({ - {t('app.admin.invoices.schedules_table.schedule_num')} - {t('app.admin.invoices.schedules_table.date')} - {t('app.admin.invoices.schedules_table.price')} - {showCustomer && {t('app.admin.invoices.schedules_table.customer')}} + {t('app.shared.schedules_table.schedule_num')} + {t('app.shared.schedules_table.date')} + {t('app.shared.schedules_table.price')} + {showCustomer && {t('app.shared.schedules_table.customer')}} {paymentSchedules.map(p => - + @@ -368,14 +375,14 @@ const PaymentSchedulesTableComponent: React.FC = ({
- +
- - - + + + @@ -398,37 +405,37 @@ const PaymentSchedulesTableComponent: React.FC = ({
{t('app.admin.invoices.schedules_table.deadline')}{t('app.admin.invoices.schedules_table.amount')}{t('app.admin.invoices.schedules_table.state')}{t('app.shared.schedules_table.deadline')}{t('app.shared.schedules_table.amount')}{t('app.shared.schedules_table.state')}
- + confirmButton={t('app.shared.schedules_table.confirm_button')}> {tempDeadline && - {t('app.admin.invoices.schedules_table.confirm_check_cashing_body', { + {t('app.shared.schedules_table.confirm_check_cashing_body', { AMOUNT: formatPrice(tempDeadline.amount), DATE: formatDate(tempDeadline.due_date) })} } - - {t('app.admin.invoices.schedules_table.confirm_cancel_subscription')} + confirmButton={t('app.shared.schedules_table.confirm_button')}> + {t('app.shared.schedules_table.confirm_cancel_subscription')} - {tempDeadline && } - = ({
} }
- {canSubmitUpdateCard && } + {canSubmitUpdateCard && } {!canSubmitUpdateCard &&
diff --git a/app/frontend/src/javascript/controllers/dashboard.js b/app/frontend/src/javascript/controllers/dashboard.js index 0f627cab5..6f2741898 100644 --- a/app/frontend/src/javascript/controllers/dashboard.js +++ b/app/frontend/src/javascript/controllers/dashboard.js @@ -30,13 +30,13 @@ Application.Controllers.controller('DashboardController', ['$scope', 'memberProm const initialize = () => $scope.social.networks = filterNetworks(); /** - * Filter social network or website that are associated with the profile of the user provided in promise + * Filter the social networks or websites that are associated with the profile of the user provided in promise * and return the filtered networks * @return {Array} */ - var filterNetworks = function () { + const filterNetworks = function () { const networks = []; - for (let network of Array.from(SocialNetworks)) { + for (const network of Array.from(SocialNetworks)) { if ($scope.user.profile[network] && ($scope.user.profile[network].length > 0)) { networks.push(network); } diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index e0d7b82fe..38dc021b9 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -205,6 +205,15 @@ angular.module('application.router', ['ui.router']) } } }) + .state('app.logged.dashboard.payment_schedules', { + url: '/payment_schedules', + views: { + 'main@': { + templateUrl: '/dashboard/payment_schedules.html', + controller: 'DashboardController' + } + } + }) .state('app.logged.dashboard.wallet', { url: '/wallet', abstract: !Fablab.walletModule, diff --git a/app/frontend/src/stylesheets/app.components.scss b/app/frontend/src/stylesheets/app.components.scss index 4f00ab404..7c1a082e4 100644 --- a/app/frontend/src/stylesheets/app.components.scss +++ b/app/frontend/src/stylesheets/app.components.scss @@ -291,9 +291,9 @@ padding: 15px 0; background-color: $bg-gray; - .wrap { - width: 100px; - height: 100px; + .wrap, .wrap-monthly { + width: 130px; + height: 130px; display: inline-block; background: white; @@ -302,38 +302,39 @@ border: 3px solid; .price { - width: 84px; + width: 114px; + display: flex; + flex-direction: column; + justify-content: center; @include border-radius(50%, 50%, 50%, 50%); } } .wrap-monthly { - height: 100px; - width: 180px; - display: inline-block; - background: white; - border: 3px solid; - @include border-radius(25px, 25px, 25px, 25px); + & > .price { + & > .amount { + padding-top: 4px; + line-height: 1.2em; + } - .price { - width: 164px; - @include border-radius(20px, 20px, 20px, 20px); + & > .period { + padding-top: 4px; + } } } .price { position: relative; top: 5px; left: 5px; - height: 84px; + height: 114px; background-color: black; .amount { - padding-top: 16px; padding-left: 4px; padding-right: 4px; font-weight: bold; - font-size: rem-calc(18); + font-size: rem-calc(17); color: white; } diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 2c16f03eb..fac4aacf6 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -31,5 +31,6 @@ @import "modules/payment-schedules-table"; @import "modules/payment-schedules-list"; @import "modules/stripe-confirm"; +@import "modules/payment-schedule-dashboard"; @import "app.responsive"; diff --git a/app/frontend/src/stylesheets/modules/payment-schedule-dashboard.scss b/app/frontend/src/stylesheets/modules/payment-schedule-dashboard.scss new file mode 100644 index 000000000..50c979c62 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/payment-schedule-dashboard.scss @@ -0,0 +1,12 @@ +.payment-schedules-dashboard { + + margin: 30px 15px 15px; + + .schedules-list { + text-align: center; + + .load-more { + margin-top: 2em; + } + } +} diff --git a/app/frontend/src/stylesheets/modules/payment-schedules-table.scss b/app/frontend/src/stylesheets/modules/payment-schedules-table.scss index 1b4ab3130..734485223 100644 --- a/app/frontend/src/stylesheets/modules/payment-schedules-table.scss +++ b/app/frontend/src/stylesheets/modules/payment-schedules-table.scss @@ -134,6 +134,7 @@ .stripe-errors { padding: 4px 0; color: #9e2146; + overflow: auto; } } .submit-card { diff --git a/app/frontend/src/stylesheets/modules/stripe-modal.scss b/app/frontend/src/stylesheets/modules/stripe-modal.scss index ae47ee783..6e1a370c6 100644 --- a/app/frontend/src/stylesheets/modules/stripe-modal.scss +++ b/app/frontend/src/stylesheets/modules/stripe-modal.scss @@ -12,6 +12,7 @@ .stripe-errors { padding: 4px 0; color: #9e2146; + overflow: auto; margin-bottom: 1.2em; } } diff --git a/app/frontend/templates/dashboard/nav.html b/app/frontend/templates/dashboard/nav.html index 70898f726..b288b6ca5 100644 --- a/app/frontend/templates/dashboard/nav.html +++ b/app/frontend/templates/dashboard/nav.html @@ -16,6 +16,7 @@
  • {{ 'app.public.common.my_trainings' }}
  • {{ 'app.public.common.my_events' }}
  • {{ 'app.public.common.my_invoices' }}
  • +
  • {{ 'app.public.common.my_payment_schedules' }}
  • {{ 'app.public.common.my_wallet' }}
  • diff --git a/app/frontend/templates/dashboard/payment_schedules.html b/app/frontend/templates/dashboard/payment_schedules.html new file mode 100644 index 000000000..bea0c0280 --- /dev/null +++ b/app/frontend/templates/dashboard/payment_schedules.html @@ -0,0 +1,11 @@ +
    + +
    +
    + +
    + +
    + + +
    diff --git a/app/frontend/templates/home/members.html b/app/frontend/templates/home/members.html index 030de6ea1..ca72477d5 100644 --- a/app/frontend/templates/home/members.html +++ b/app/frontend/templates/home/members.html @@ -10,7 +10,7 @@ - {{member.name}} + {{member.name}}
    diff --git a/app/frontend/templates/shared/header.html.erb b/app/frontend/templates/shared/header.html.erb index b00acbe8e..bf4d74bb1 100644 --- a/app/frontend/templates/shared/header.html.erb +++ b/app/frontend/templates/shared/header.html.erb @@ -40,6 +40,7 @@
  • {{ 'app.public.common.my_trainings' }}
  • {{ 'app.public.common.my_events' }}
  • {{ 'app.public.common.my_invoices' }}
  • +
  • {{ 'app.public.common.my_payment_schedules' }}
  • {{ 'app.public.common.my_wallet' }}
  • {{ 'app.public.common.help' }}
  • diff --git a/app/views/api/payment_schedules/index.json.jbuilder b/app/views/api/payment_schedules/index.json.jbuilder new file mode 100644 index 000000000..45e5db54f --- /dev/null +++ b/app/views/api/payment_schedules/index.json.jbuilder @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +max_schedules = @payment_schedules.except(:offset, :limit, :order).count + +json.array! @payment_schedules do |ps| + json.max_length max_schedules + json.partial! 'api/payment_schedules/payment_schedule', payment_schedule: ps +end diff --git a/app/views/notifications_mailer/notify_member_payment_schedule_failed.html.erb b/app/views/notifications_mailer/notify_member_payment_schedule_failed.html.erb index a1c6639e5..584de552c 100644 --- a/app/views/notifications_mailer/notify_member_payment_schedule_failed.html.erb +++ b/app/views/notifications_mailer/notify_member_payment_schedule_failed.html.erb @@ -7,4 +7,4 @@ DATE: I18n.l(@attached_object.due_date, format: :long)) %> <%= t('.body.error') %>

    -

    <%= t('.body.action', DASHBOARD: link_to(t('.body.your_dashboard'), "#{root_url}#!/dashboard/invoices")) %>

    +

    <%= t('.body.action_html', DASHBOARD: link_to(t('.body.your_dashboard'), "#{root_url}#!/dashboard/payment_schedules")) %>

    diff --git a/app/views/notifications_mailer/notify_member_payment_schedule_ready.html.erb b/app/views/notifications_mailer/notify_member_payment_schedule_ready.html.erb index a3f8cd132..574cec43f 100644 --- a/app/views/notifications_mailer/notify_member_payment_schedule_ready.html.erb +++ b/app/views/notifications_mailer/notify_member_payment_schedule_ready.html.erb @@ -14,7 +14,7 @@

    <%= t('.body.schedule_in_your_dashboard_html', DASHBOARD: link_to( t('.body.your_dashboard'), - "#{root_url}#!/dashboard/invoices" + "#{root_url}#!/dashboard/payment_schedules" ) ) %>

    diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 60070bb40..695fa063d 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -644,36 +644,6 @@ en: filter_schedules: "Filter schedules" no_payment_schedules: "No payment schedules to display" load_more: "Load more" - schedules_table: - schedule_num: "Schedule #" - date: "Date" - price: "Price" - customer: "Customer" - deadline: "Deadline" - amount: "Amount" - state: "State" - download: "Download" - state_new: "Not yet due" - state_pending: "Waiting for the cashing of the check" - state_requires_payment_method: "The credit card must be updated" - state_requires_action: "Action required" - state_paid: "Paid" - state_error: "Error" - state_canceled: "Canceled" - method_stripe: "by card" - method_check: "by check" - confirm_payment: "Confirm payment" - solve: "Solve" - update_card: "Update the card" - confirm_check_cashing: "Confirm the cashing of the check" - confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date." - confirm_button: "Confirm" - resolve_action: "Resolve the action" - ok_button: "OK" - validate_button: "Validate the new card" - cancel_subscription: "Cancel the subscription" - confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?" - please_ask_reception: "For any questions, please contact the FabLab's reception." document_filters: reference: "Reference" customer: "Customer" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 166d59aee..6fbe957e2 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -644,36 +644,6 @@ fr: filter_schedules: "Filtrer les échéanciers" no_payment_schedules: "Pas d'échéancier à afficher" load_more: "Voir plus" - schedules_table: - schedule_num: "Échéancier n°" - date: "Date" - price: "Prix" - customer: "Client" - deadline: "Échéance" - amount: "Montant" - state: "État" - download: "Télécharger" - state_new: "Pas encore à l'échéance" - state_pending: "En attente de l'encaissement du chèque" - state_requires_payment_method: "La carte bancaire doit être mise à jour" - state_requires_action: "Action requise" - state_paid: "Payée" - state_error: "Erreur" - state_canceled: "Annulée" - method_stripe: "par carte" - method_check: "par chèque" - confirm_payment: "Confirmer l'encaissement" - solve: "Résoudre" - update_card: "Mettre à jour la carte" - confirm_check_cashing: "Confirmer l'encaissement du chèque" - confirm_check_cashing_body: "Vous devez encaisser un chèque de {AMOUNT} pour l'échéance du {DATE}. En confirmant l'encaissement du chèque, une facture sera générée pour cette échéance." - confirm_button: "Confirmer" - resolve_action: "Résoudre l'action" - ok_button: "OK" - validate_button: "Valider la nouvelle carte" - cancel_subscription: "Annuler l'abonnement" - confirm_cancel_subscription: "Vous êtes sur le point d'annuler cet échéancier de paiement ainsi que l'abonnement lié. Êtes-vous sur ?" - please_ask_reception: "Pour toute question, merci de contacter l'accueil du FabLab." document_filters: reference: "Référence" customer: "Client" diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml index 8348486bb..6607f18e6 100644 --- a/config/locales/app.logged.en.yml +++ b/config/locales/app.logged.en.yml @@ -125,6 +125,9 @@ en: download_the_invoice: "Download the invoice" download_the_credit_note: "Download the refund invoice" no_invoices_for_now: "No invoices for now." + payment_schedules: + no_payment_schedules: "No payment schedules to display" + load_more: "Load more" #public profil of a member members_show: members_list: "Members list" diff --git a/config/locales/app.logged.fr.yml b/config/locales/app.logged.fr.yml index eda735ac4..22aab44ff 100644 --- a/config/locales/app.logged.fr.yml +++ b/config/locales/app.logged.fr.yml @@ -125,6 +125,9 @@ fr: download_the_invoice: "Télécharger la facture" download_the_credit_note: "Télécharger l'avoir" no_invoices_for_now: "Aucune facture pour le moment." + payment_schedules: + no_payment_schedules: "Pas d'échéancier à afficher" + load_more: "Voir plus" #public profil of a member members_show: members_list: "Liste des membres" diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 9b6832797..656f2cce3 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -19,6 +19,7 @@ en: my_trainings: "My Trainings" my_events: "My Events" my_invoices: "My Invoices" + my_payment_schedules: "My payment schedules" my_wallet: "My Wallet" #contextual help help: "Help" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index ee5c43709..b5703f919 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -19,6 +19,7 @@ fr: my_trainings: "Mes formations" my_events: "Mes événements" my_invoices: "Mes factures" + my_payment_schedules: "Mes échéanciers de paiement" my_wallet: "Mon porte-monnaie" #contextual help help: "Aide" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 758b955ce..d3774ae6b 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -478,3 +478,34 @@ en: stripe_confirm: pending: "Pending for action..." success: "Thank you, your card setup is complete. The payment will be proceeded shortly." + # the summary table of all payment schedules + schedules_table: + schedule_num: "Schedule #" + date: "Date" + price: "Price" + customer: "Customer" + deadline: "Deadline" + amount: "Amount" + state: "State" + download: "Download" + state_new: "Not yet due" + state_pending: "Waiting for the cashing of the check" + state_requires_payment_method: "The credit card must be updated" + state_requires_action: "Action required" + state_paid: "Paid" + state_error: "Error" + state_canceled: "Canceled" + method_stripe: "by card" + method_check: "by check" + confirm_payment: "Confirm payment" + solve: "Solve" + update_card: "Update the card" + confirm_check_cashing: "Confirm the cashing of the check" + confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date." + confirm_button: "Confirm" + resolve_action: "Resolve the action" + ok_button: "OK" + validate_button: "Validate the new card" + cancel_subscription: "Cancel the subscription" + confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?" + please_ask_reception: "For any questions, please contact the FabLab's reception." diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index b4ab925f2..69d567210 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -478,3 +478,34 @@ fr: stripe_confirm: pending: "En attente de l'action ..." success: "Merci, la configuration de votre carte est terminée. Le paiement sera effectué sous peu." + # the summary table of all payment schedules + schedules_table: + schedule_num: "Échéancier n°" + date: "Date" + price: "Prix" + customer: "Client" + deadline: "Échéance" + amount: "Montant" + state: "État" + download: "Télécharger" + state_new: "Pas encore à l'échéance" + state_pending: "En attente de l'encaissement du chèque" + state_requires_payment_method: "La carte bancaire doit être mise à jour" + state_requires_action: "Action requise" + state_paid: "Payée" + state_error: "Erreur" + state_canceled: "Annulée" + method_stripe: "par carte" + method_check: "par chèque" + confirm_payment: "Confirmer l'encaissement" + solve: "Résoudre" + update_card: "Mettre à jour la carte" + confirm_check_cashing: "Confirmer l'encaissement du chèque" + confirm_check_cashing_body: "Vous devez encaisser un chèque de {AMOUNT} pour l'échéance du {DATE}. En confirmant l'encaissement du chèque, une facture sera générée pour cette échéance." + confirm_button: "Confirmer" + resolve_action: "Résoudre l'action" + ok_button: "OK" + validate_button: "Valider la nouvelle carte" + cancel_subscription: "Annuler l'abonnement" + confirm_cancel_subscription: "Vous êtes sur le point d'annuler cet échéancier de paiement ainsi que l'abonnement lié. Êtes-vous sur ?" + please_ask_reception: "Pour toute question, merci de contacter l'accueil du FabLab." diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index d4183a4d6..bca9a14a6 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -305,7 +305,7 @@ en: body: remember: "In accordance with your %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}." error: "Unfortunately, this card debit was unable to complete successfully." - action: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted." + action_html: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted." your_dashboard: "your dashboard" notify_admin_payment_schedule_check_deadline: subject: "Payment deadline" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index 7460d3c08..210d8cda2 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -305,7 +305,7 @@ fr: body: remember: "Conformément à votre échéancier de paiement %{REFERENCE}, un prélèvement par carte de %{AMOUNT} était prévu le %{DATE}." error: "Malheureusement, ce prélèvement n'a pas pu être effectué correctement." - action: "Veuillez vous rendre dans votre %{DASHBOARD} ou prendre contact avec un gestionnaire sous 24 heures, faute de quoi votre abonnement risque d'être interrompu." + action_html: "Veuillez vous rendre dans %{DASHBOARD} ou prendre contact avec un gestionnaire sous 24 heures, faute de quoi votre abonnement risque d'être interrompu." your_dashboard: "votre tableau de bord" notify_admin_payment_schedule_check_deadline: subject: "Échéance d'encaissement" diff --git a/config/routes.rb b/config/routes.rb index b320ba45d..7f47c4d4d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -111,7 +111,7 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end - resources :payment_schedules, only: %i[show] do + resources :payment_schedules, only: %i[index show] do post 'list', action: 'list', on: :collection put 'cancel', on: :member get 'download', on: :member