From 162a96c05e202d980255392d4432dc42199bc93b Mon Sep 17 00:00:00 2001 From: brennerthomas Date: Sat, 10 Aug 2024 13:26:45 +0200 Subject: [PATCH] feature(admin): payout forecast (#855) --- admin/src/App.tsx | 2 + .../actions/CreatePaymentForecastAction.tsx | 31 +++ admin/src/actions/PaymentProcessAction.tsx | 2 +- admin/src/collections/PaymentForecast.ts | 53 ++++++ firebase.json | 1 + .../webhooks/admin/payment-forecast/index.ts | 101 ++++++++++ .../payment-process/tasks/PaymentCSVTask.ts | 4 +- .../tasks/UpdateDatabaseEntriesTask.ts | 17 +- .../scripts/BatchAddCHFToPayments.test.ts | 2 +- functions/src/webhooks/index.ts | 2 + shared/src/types/currency.ts | 177 +++++++++++++++++- shared/src/types/exchange-rates.ts | 4 +- shared/src/types/payment-forecast.ts | 9 + shared/src/types/payment.ts | 3 +- .../finances/[currency]/section-4.tsx | 6 +- 15 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 admin/src/actions/CreatePaymentForecastAction.tsx create mode 100644 admin/src/collections/PaymentForecast.ts create mode 100644 functions/src/webhooks/admin/payment-forecast/index.ts create mode 100644 shared/src/types/payment-forecast.ts diff --git a/admin/src/App.tsx b/admin/src/App.tsx index 2ac59aae6..7c89e18ad 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -12,6 +12,7 @@ import { campaignsCollection } from './collections/Campaigns'; import { buildContributionsCollection } from './collections/Contributions'; import { expensesCollection } from './collections/Expenses'; import { buildPartnerOrganisationsCollection } from './collections/PartnerOrganisations'; +import { buildPaymentForecastCollection } from './collections/PaymentForecast'; import { usersCollection } from './collections/Users'; import { buildRecipientsCollection } from './collections/recipients/Recipients'; import { buildRecipientsPaymentsCollection } from './collections/recipients/RecipientsPayments'; @@ -52,6 +53,7 @@ export default function App() { buildSurveysCollection({ collectionGroup: true }), adminsCollection, expensesCollection, + buildPaymentForecastCollection(), usersCollection, campaignsCollection, buildContributionsCollection({ collectionGroup: true }), diff --git a/admin/src/actions/CreatePaymentForecastAction.tsx b/admin/src/actions/CreatePaymentForecastAction.tsx new file mode 100644 index 000000000..94095d23a --- /dev/null +++ b/admin/src/actions/CreatePaymentForecastAction.tsx @@ -0,0 +1,31 @@ +import { Button } from '@mui/material'; +import { DEFAULT_REGION } from '@socialincome/shared/src/firebase'; +import { getFunctions, httpsCallable } from 'firebase/functions'; +import { useSnackbarController } from 'firecms'; + +export function CreatePaymentForecastAction() { + const snackbarController = useSnackbarController(); + + const createPaymentForecast = () => { + const runPaymentForecastTask = httpsCallable(getFunctions(undefined, DEFAULT_REGION), 'runPaymentForecastTask'); + runPaymentForecastTask() + .then((result) => { + snackbarController.open({ type: 'success', message: 'Payment forecast updated successfully' }); + }) + .catch((reason: Error) => { + snackbarController.open({ type: 'error', message: reason.message }); + }); + }; + + return ( +
+ +
+ ); +} + +function setIsFunctionRunning(arg0: boolean) { + throw new Error('Function not implemented.'); +} diff --git a/admin/src/actions/PaymentProcessAction.tsx b/admin/src/actions/PaymentProcessAction.tsx index cd2a7ce75..edf8a2ff6 100644 --- a/admin/src/actions/PaymentProcessAction.tsx +++ b/admin/src/actions/PaymentProcessAction.tsx @@ -84,7 +84,7 @@ export function PaymentProcessAction() { { if (value) setPaymentDate(toPaymentDate(DateTime.fromJSDate(value))); }} diff --git a/admin/src/collections/PaymentForecast.ts b/admin/src/collections/PaymentForecast.ts new file mode 100644 index 000000000..c11e9ffaa --- /dev/null +++ b/admin/src/collections/PaymentForecast.ts @@ -0,0 +1,53 @@ +import { PAYMENT_FORECAST_FIRESTORE_PATH, PaymentForecastEntry } from '@socialincome/shared/src/types/payment-forecast'; +import { buildProperties, useSnackbarController } from 'firecms'; +import { EntityCollection } from 'firecms/dist/types/collections'; +import { CreatePaymentForecastAction } from '../actions/CreatePaymentForecastAction'; +import { buildAuditedCollection } from './shared'; + +export const buildPaymentForecastCollection = () => { + const snackbarController = useSnackbarController(); + + const collection: EntityCollection = { + name: 'Payout Forecast', + group: 'Finances', + path: PAYMENT_FORECAST_FIRESTORE_PATH, + textSearchEnabled: false, + initialSort: ['order', 'asc'], + icon: 'LocalConvenienceStore', + description: 'Projected payout forecast for the next six months', + Actions: CreatePaymentForecastAction, + permissions: { + edit: false, + create: false, + delete: false, + }, + properties: buildProperties({ + order: { + dataType: 'number', + name: 'Order', + validation: { required: true }, + }, + month: { + dataType: 'string', + name: 'Month', + validation: { required: true }, + }, + numberOfRecipients: { + dataType: 'number', + name: 'Number of Recipients', + validation: { required: true }, + }, + amount_usd: { + dataType: 'number', + name: 'Total Amount USD', + validation: { required: true }, + }, + amount_sle: { + dataType: 'number', + name: 'Total Amount SLE', + validation: { required: true }, + }, + }), + }; + return buildAuditedCollection(collection); +}; diff --git a/firebase.json b/firebase.json index e0db8d8d8..eea55112c 100644 --- a/firebase.json +++ b/firebase.json @@ -24,6 +24,7 @@ "rules": "firestore.rules" }, "emulators": { + "singleProjectMode": false, "auth": { "port": 9099, "host": "0.0.0.0" diff --git a/functions/src/webhooks/admin/payment-forecast/index.ts b/functions/src/webhooks/admin/payment-forecast/index.ts new file mode 100644 index 000000000..5553d804b --- /dev/null +++ b/functions/src/webhooks/admin/payment-forecast/index.ts @@ -0,0 +1,101 @@ +import { onCall } from 'firebase-functions/v2/https'; +import { DateTime } from 'luxon'; +import { FirestoreAdmin } from '../../../../../shared/src/firebase/admin/FirestoreAdmin'; +import { PAYMENT_AMOUNT_SLE } from '../../../../../shared/src/types/payment'; +import { PAYMENT_FORECAST_FIRESTORE_PATH } from '../../../../../shared/src/types/payment-forecast'; +import { + calcFinalPaymentDate, + calcPaymentsLeft, + RECIPIENT_FIRESTORE_PATH, + RecipientProgramStatus, +} from '../../../../../shared/src/types/recipient'; +import { getLatestExchangeRate } from '../../../../../shared/src/utils/exchangeRates'; + +function prepareNextSixMonths(): Map { + const nextSixMonths: Map = new Map(); + const now: DateTime = DateTime.now(); + for (let i = 1; i < 7; ++i) { + const nextMonthDateTime = now.plus({ months: i }); + nextSixMonths.set(nextMonthDateTime.toFormat('LLLL yyyy'), 0); + } + return nextSixMonths; +} + +function addRecipient(nextSixMonths: Map, paymentsLeft: number) { + nextSixMonths.forEach((value, key) => { + if (paymentsLeft > 0) { + nextSixMonths.set(key, ++value); + paymentsLeft -= 1; + } + }); +} + +async function calculateUSDAmount(firestoreAdmin: FirestoreAdmin): Promise { + const exchangeRateUSD = await getLatestExchangeRate(firestoreAdmin, 'USD'); + const exchangeRateSLE = await getLatestExchangeRate(firestoreAdmin, 'SLE'); + const monthlyAllowanceInUSD = (PAYMENT_AMOUNT_SLE / exchangeRateSLE) * exchangeRateUSD; + return parseFloat(monthlyAllowanceInUSD.toFixed(2)); +} + +async function deleteAllDocuments(firestoreAdmin: FirestoreAdmin): Promise { + const batch = firestoreAdmin.firestore.batch(); + const snapshot = await firestoreAdmin.firestore.collection(PAYMENT_FORECAST_FIRESTORE_PATH).get(); + snapshot.forEach((doc) => { + batch.delete(doc.ref); + }); + await batch.commit(); +} + +async function fillNextSixMonths( + firestoreAdmin: FirestoreAdmin, + nextSixMonthsList: Map, +): Promise { + const batch = firestoreAdmin.firestore.batch(); + const monthlyAllowanceInUSD = await calculateUSDAmount(firestoreAdmin); + let count = 1; + nextSixMonthsList.forEach((value, key) => { + const newDocRef = firestoreAdmin.firestore.collection(PAYMENT_FORECAST_FIRESTORE_PATH).doc(); + batch.set(newDocRef, { + order: count, + month: key, + numberOfRecipients: value, + amount_usd: value * monthlyAllowanceInUSD, + amount_sle: value * PAYMENT_AMOUNT_SLE, + }); + ++count; + }); + await batch.commit(); +} + +export default onCall>({ memory: '2GiB' }, async (request) => { + const firestoreAdmin = new FirestoreAdmin(); + try { + await firestoreAdmin.assertGlobalAdmin(request.auth?.token?.email); + const nextSixMonthsList = prepareNextSixMonths(); + const recipientsSnapshot = await firestoreAdmin + .collection(RECIPIENT_FIRESTORE_PATH) + .where('progr_status', 'in', [RecipientProgramStatus.Active, RecipientProgramStatus.Designated]) + .get(); + recipientsSnapshot.docs.map((doc) => { + const recipient = doc.data(); + if (recipient.si_start_date && recipient.progr_status === RecipientProgramStatus.Active) { + addRecipient( + nextSixMonthsList, + calcPaymentsLeft( + calcFinalPaymentDate(DateTime.fromSeconds(recipient.si_start_date._seconds, { zone: 'utc' })), + ), + ); + } else if (recipient.progr_status === RecipientProgramStatus.Designated) { + addRecipient(nextSixMonthsList, 6); + } + }); + + await deleteAllDocuments(firestoreAdmin); + await fillNextSixMonths(firestoreAdmin, nextSixMonthsList); + + return 'Function executed successfully.'; + } catch (error) { + console.error('Error during function execution:', error); + throw new Error('An error occurred while processing your request.'); + } +}); diff --git a/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.ts b/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.ts index e5fe320a4..ce0e57ca3 100644 --- a/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.ts +++ b/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.ts @@ -1,5 +1,5 @@ import { DateTime } from 'luxon'; -import { PAYMENT_AMOUNT } from '../../../../../../shared/src/types/payment'; +import { PAYMENT_AMOUNT_SLE } from '../../../../../../shared/src/types/payment'; import { PaymentTask } from './PaymentTask'; export class PaymentCSVTask extends PaymentTask { @@ -12,7 +12,7 @@ export class PaymentCSVTask extends PaymentTask { recipients.map(async (recipient) => { csvRows.push([ recipient.get('mobile_money_phone').phone.toString().slice(-8), - PAYMENT_AMOUNT.toString(), + PAYMENT_AMOUNT_SLE.toString(), recipient.get('first_name'), recipient.get('last_name'), recipient.get('om_uid').toString(), diff --git a/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.ts b/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.ts index 491d7a954..9a6e4d206 100644 --- a/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.ts +++ b/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.ts @@ -1,11 +1,10 @@ import { DateTime } from 'luxon'; import { toFirebaseAdminTimestamp } from '../../../../../../shared/src/firebase/admin/utils'; import { - PAYMENTS_COUNT, - PAYMENT_AMOUNT, - PAYMENT_CURRENCY, - PAYMENT_FIRESTORE_PATH, Payment, + PAYMENT_AMOUNT_SLE, + PAYMENT_FIRESTORE_PATH, + PAYMENTS_COUNT, PaymentStatus, } from '../../../../../../shared/src/types/payment'; import { RECIPIENT_FIRESTORE_PATH, RecipientProgramStatus } from '../../../../../../shared/src/types/recipient'; @@ -17,7 +16,7 @@ export class UpdateDatabaseEntriesTask extends PaymentTask { let [paymentsPaid, paymentsCreated, setToActiveCount, setToFormerCount] = [0, 0, 0, 0]; const nextMonthPaymentDate = paymentDate.plus({ months: 1 }); const exchangeRates = await new ExchangeRateImporter().getExchangeRates(paymentDate); - const amountChf = Math.round((PAYMENT_AMOUNT / exchangeRates[PAYMENT_CURRENCY]) * 100) / 100; + const amountChf = Math.round((PAYMENT_AMOUNT_SLE / exchangeRates!['SLE']!) * 100) / 100; const recipients = await this.getRecipients(); await Promise.all( @@ -31,9 +30,9 @@ export class UpdateDatabaseEntriesTask extends PaymentTask { if (!currentMonthPaymentDoc.exists || currentMonthPaymentDoc.get('status') === PaymentStatus.Created) { // Payments are set to paid if they have status set to created or if the document doesn't exist yet await currentMonthPaymentRef.set({ - amount: PAYMENT_AMOUNT, + amount: PAYMENT_AMOUNT_SLE, amount_chf: amountChf, - currency: PAYMENT_CURRENCY, + currency: 'SLE', payment_at: toFirebaseAdminTimestamp(paymentDate), status: PaymentStatus.Paid, phone_number: recipient.get('mobile_money_phone').phone, @@ -61,8 +60,8 @@ export class UpdateDatabaseEntriesTask extends PaymentTask { nextMonthPaymentDate.toFormat('yyyy-MM'), ) .set({ - amount: PAYMENT_AMOUNT, - currency: PAYMENT_CURRENCY, + amount: PAYMENT_AMOUNT_SLE, + currency: 'SLE', payment_at: toFirebaseAdminTimestamp(nextMonthPaymentDate), status: PaymentStatus.Created, }); diff --git a/functions/src/webhooks/admin/scripts/BatchAddCHFToPayments.test.ts b/functions/src/webhooks/admin/scripts/BatchAddCHFToPayments.test.ts index b7f2cf162..061cd6969 100644 --- a/functions/src/webhooks/admin/scripts/BatchAddCHFToPayments.test.ts +++ b/functions/src/webhooks/admin/scripts/BatchAddCHFToPayments.test.ts @@ -42,7 +42,7 @@ test('BatchAddCHFToPayments', async () => { const exchangeRatesWithoutSLEAndSLL: Map = new Map([ [ 1682640000, // 2023-04-28 00:00:00 - { XYZ: 25000 }, + { BTC: 25000 }, ], ]); expect(PaymentsManager.calcAmountChf(exchangeRatesWithoutSLEAndSLL, paymentSLE)).toBe(null); diff --git a/functions/src/webhooks/index.ts b/functions/src/webhooks/index.ts index f0e64f135..d5fb7cc0c 100644 --- a/functions/src/webhooks/index.ts +++ b/functions/src/webhooks/index.ts @@ -1,4 +1,5 @@ import createDonationCertificatesFunction from './admin/donation-certificates'; +import paymentForecastFunction from './admin/payment-forecast'; import paymentProcessFunction from './admin/payment-process'; import { addMissingAmountChfFunction, @@ -10,6 +11,7 @@ import surveyLoginFunction from './website/survey-login'; export const createDonationCertificates = createDonationCertificatesFunction; export const runPaymentProcessTask = paymentProcessFunction; +export const runPaymentForecastTask = paymentForecastFunction; export const batchImportStripeCharges = batchImportStripeChargesFunction; export const addMissingAmountChf = addMissingAmountChfFunction; diff --git a/shared/src/types/currency.ts b/shared/src/types/currency.ts index 68a8d7d82..fc7903793 100644 --- a/shared/src/types/currency.ts +++ b/shared/src/types/currency.ts @@ -1,6 +1,181 @@ import { CountryCode } from './country'; -export const CURRENCIES = ['CHF', 'EUR', 'USD', 'SLE'] as const; +// ISO 4217 currency codes +export const CURRENCIES = [ + 'AED', // United Arab Emirates + 'AFN', // Afghanistan + 'ALL', // Albania + 'AMD', // Armenia + 'ANG', // Netherlands Antilles + 'AOA', // Angola + 'ARS', // Argentina + 'AUD', // Australia + 'AWG', // Aruba + 'AZN', // Azerbaijan + 'BAM', // Bosnia and Herzegovina + 'BBD', // Barbados + 'BDT', // Bangladesh + 'BGN', // Bulgaria + 'BHD', // Bahrain + 'BIF', // Burundi + 'BMD', // Bermuda + 'BND', // Brunei + 'BOB', // Bolivia + 'BRL', // Brazil + 'BSD', // Bahamas + 'BTC', // Bitcoin + 'BTN', // Bhutan + 'BWP', // Botswana + 'BYN', // Belarus + 'BYR', // Belarus + 'BZD', // Belize + 'CAD', // Canada + 'CDF', // Congo + 'CHF', // Switzerland + 'CLF', // Chile + 'CLP', // Chile + 'CNY', // China + 'COP', // Colombia + 'CRC', // Costa Rica + 'CUC', // Cuba + 'CUP', // Cuba + 'CVE', // Cape Verde + 'CZK', // Czech Republic + 'DJF', // Djibouti + 'DKK', // Denmark + 'DOP', // Dominican Republic + 'DZD', // Algeria + 'EGP', // Egypt + 'ERN', // Eritrea + 'ETB', // Ethiopia + 'EUR', // Eurozone + 'FJD', // Fiji + 'FKP', // Falkland Islands + 'FOK', // Faroe Islands + 'GBP', // United Kingdom + 'GEL', // Georgia + 'GGP', // Guernsey + 'GHS', // Ghana + 'GIP', // Gibraltar + 'GMD', // Gambia + 'GNF', // Guinea + 'GTQ', // Guatemala + 'GYD', // Guyana + 'HKD', // Hong Kong + 'HNL', // Honduras + 'HRK', // Croatia + 'HTG', // Haiti + 'HUF', // Hungary + 'IDR', // Indonesia + 'ILS', // Israel + 'IMP', // Isle of Man + 'INR', // India + 'IQD', // Iraq + 'IRR', // Iran + 'ISK', // Iceland + 'JEP', // Jersey + 'JMD', // Jamaica + 'JOD', // Jordan + 'JPY', // Japan + 'KES', // Kenya + 'KGS', // Kyrgyzstan + 'KHR', // Cambodia + 'KID', // Kiribati + 'KMF', // Comoros + 'KPW', // North Korea + 'KRW', // South Korea + 'KWD', // Kuwait + 'KYD', // Cayman Islands + 'KZT', // Kazakhstan + 'LAK', // Laos + 'LBP', // Lebanon + 'LKR', // Sri Lanka + 'LRD', // Liberia + 'LSL', // Lesotho + 'LTL', // Lithuania + 'LYD', // Libya + 'LVL', // Latvia + 'MAD', // Morocco + 'MDL', // Moldova + 'MGA', // Madagascar + 'MKD', // North Macedonia + 'MMK', // Myanmar + 'MNT', // Mongolia + 'MOP', // Macau + 'MRO', // Mauritania + 'MUR', // Mauritius + 'MVR', // Maldives + 'MWK', // Malawi + 'MXN', // Mexico + 'MYR', // Malaysia + 'MZN', // Mozambique + 'NAD', // Namibia + 'NGN', // Nigeria + 'NIO', // Nicaragua + 'NOK', // Norway + 'NPR', // Nepal + 'NZD', // New Zealand + 'OMR', // Oman + 'PAB', // Panama + 'PEN', // Peru + 'PGK', // Papua New Guinea + 'PHP', // Philippines + 'PKR', // Pakistan + 'PLN', // Poland + 'PYG', // Paraguay + 'QAR', // Qatar + 'RON', // Romania + 'RSD', // Serbia + 'RUB', // Russia + 'RWF', // Rwanda + 'SAR', // Saudi Arabia + 'SBD', // Solomon Islands + 'SCR', // Seychelles + 'SDG', // Sudan + 'SEK', // Sweden + 'SGD', // Singapore + 'SHP', // Saint Helena + 'SLE', // Sierra Leone + 'SLL', // Sierra Leone + 'SOS', // Somalia + 'SRD', // Suriname + 'SSP', // South Sudan + 'STD', // Sao Tome and Principe + 'SVC', // El Salvador + 'SYP', // Syria + 'SZL', // Eswatini + 'THB', // Thailand + 'TJS', // Tajikistan + 'TMT', // Turkmenistan + 'TND', // Tunisia + 'TOP', // Tonga + 'TRY', // Turkey + 'TTD', // Trinidad and Tobago + 'TWD', // Taiwan + 'TZS', // Tanzania + 'UAH', // Ukraine + 'UGX', // Uganda + 'USD', // United States + 'UYU', // Uruguay + 'UZS', // Uzbekistan + 'VEF', // Venezuela + 'VES', // Venezuela + 'VND', // Vietnam + 'VUV', // Vanuatu + 'WST', // Samoa + 'XAF', // Central African CFA franc + 'XAU', // Gold + 'XAG', // Silver + 'XCD', // East Caribbean dollar + 'XDR', // Special Drawing Rights + 'XOF', // West African CFA franc + 'XPF', // CFP franc + 'YER', // Yemen + 'ZAR', // South Africa + 'ZMK', // Zambia + 'ZMW', // Zambia + 'ZWL', // Zimbabwe +] as const; export type Currency = (typeof CURRENCIES)[number]; export const FALLBACK_CURRENCY: Currency = 'USD'; diff --git a/shared/src/types/exchange-rates.ts b/shared/src/types/exchange-rates.ts index aea4e2571..695f03ad1 100644 --- a/shared/src/types/exchange-rates.ts +++ b/shared/src/types/exchange-rates.ts @@ -1,3 +1,5 @@ +import { Currency } from './currency'; + export const EXCHANGE_RATES_PATH = 'exchange-rates'; export type ExchangeRatesEntry = { @@ -6,4 +8,4 @@ export type ExchangeRatesEntry = { rates: ExchangeRates; }; -export type ExchangeRates = Record; +export type ExchangeRates = Partial>; diff --git a/shared/src/types/payment-forecast.ts b/shared/src/types/payment-forecast.ts new file mode 100644 index 000000000..5a9316acb --- /dev/null +++ b/shared/src/types/payment-forecast.ts @@ -0,0 +1,9 @@ +export const PAYMENT_FORECAST_FIRESTORE_PATH = 'payment-forecast'; + +export type PaymentForecastEntry = { + order: number; + month: string; + numberOfRecipients: number; + amount_usd: number; + amount_sle: number; +}; diff --git a/shared/src/types/payment.ts b/shared/src/types/payment.ts index aa66e1112..8b3f1589f 100644 --- a/shared/src/types/payment.ts +++ b/shared/src/types/payment.ts @@ -30,6 +30,5 @@ export enum PaymentProcessTaskType { SendNotifications = 'SendNotifications', } -export const PAYMENT_AMOUNT = 700; +export const PAYMENT_AMOUNT_SLE = 700; export const PAYMENTS_COUNT = 36; -export const PAYMENT_CURRENCY = 'SLE'; diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx index a1d94dc5a..fe4d6ac4f 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx @@ -1,7 +1,7 @@ import { firestoreAdmin } from '@/firebase-admin'; import { InformationCircleIcon } from '@heroicons/react/24/outline'; import { BanknotesIcon, BuildingLibraryIcon, DevicePhoneMobileIcon } from '@heroicons/react/24/solid'; -import { PAYMENT_AMOUNT } from '@socialincome/shared/src/types/payment'; +import { PAYMENT_AMOUNT_SLE } from '@socialincome/shared/src/types/payment'; import { getLatestExchangeRate } from '@socialincome/shared/src/utils/exchangeRates'; import { Translator } from '@socialincome/shared/src/utils/i18n'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typography } from '@socialincome/ui'; @@ -209,7 +209,7 @@ export async function Section4({ params, expensesStats, paymentStats, contributi {translator.t('section-4.covers-payments-1', { context: { - recipientsCount: (reservesTotal / (PAYMENT_AMOUNT / exchangeRateSLE) / 36).toFixed(0), + recipientsCount: (reservesTotal / (PAYMENT_AMOUNT_SLE / exchangeRateSLE) / 36).toFixed(0), monthsCount: 36, }, })} @@ -217,7 +217,7 @@ export async function Section4({ params, expensesStats, paymentStats, contributi {translator.t('section-4.covers-payments-2', { context: { - recipientsCount: (reservesTotal / (PAYMENT_AMOUNT / exchangeRateSLE)).toFixed(0), + recipientsCount: (reservesTotal / (PAYMENT_AMOUNT_SLE / exchangeRateSLE)).toFixed(0), }, })}