From 6f283430fb18276d607f07857b5af0ca3275d326 Mon Sep 17 00:00:00 2001 From: Himali Malvawala Date: Wed, 2 Sep 2026 09:57:56 +0530 Subject: [PATCH 1/2] update: /kpis to reflect different currencies --- .../giving/controllers/DonationController.ts | 29 ++++++++++--------- .../giving/repositories/DonationRepo.ts | 23 ++++++++++----- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/modules/giving/controllers/DonationController.ts b/src/modules/giving/controllers/DonationController.ts index 320cd608..7927f96a 100644 --- a/src/modules/giving/controllers/DonationController.ts +++ b/src/modules/giving/controllers/DonationController.ts @@ -6,19 +6,6 @@ import { Permissions } from "../../../shared/helpers/Permissions.js"; @controller("/giving/donations") export class DonationController extends GivingBaseController { - @httpGet("/kpis") - public async getKpis(req: express.Request<{}, {}, null>, res: express.Response): Promise { - return this.actionWrapper(req, res, async (au) => { - if (!au.checkAccess(Permissions.donations.viewSummary)) return this.json({}, 401); - else { - const startDate = req.query.startDate ? new Date(req.query.startDate.toString()) : new Date(2000, 1, 1); - const endDate = req.query.endDate ? new Date(req.query.endDate.toString()) : new Date(); - const fundId = req.query.fundId?.toString() || ""; - const result = await this.repos.donation.loadDashboardKpis(au.churchId, startDate, endDate, fundId || undefined); - return result || { totalGiving: 0, avgGift: 0, donorCount: 0, donationCount: 0 }; - } - }); - } @httpGet("/summary") public async getSummary(req: express.Request<{}, {}, null>, res: express.Response): Promise { @@ -73,6 +60,22 @@ export class DonationController extends GivingBaseController { }); } + @httpPost("/kpis") + public async sendkpis (req: express.Request<{}, {}, any>, res: express.Response): Promise { + return this.actionWrapper(req, res, async (au) => { + if (!au.checkAccess(Permissions.donations.viewSummary)) return this.json({}, 401); + else { + const startDate = req.body.startDate ? new Date(req.body.startDate.toString()) : new Date(2000, 1, 1); + const endDate = req.body.endDate ? new Date(req.body.endDate.toString()) : new Date(); + const fundId = req.body?.fundId ? req.body.fundId.toString() : ""; + const currency = req.body?.currency ? req.body.currency.toString() : "usd"; + const rates = req.body.rates ? req.body.rates: {}; + const result = await this.repos.donation.loadDashboardKpis(au.churchId, startDate, endDate, fundId || undefined, currency, rates); + return result || { totalGiving: 0, avgGift: 0, donorCount: 0, donationCount: 0 }; + } + }); + } + @httpPost("/") public async save(req: express.Request<{}, {}, Donation[]>, res: express.Response): Promise { return this.actionWrapper(req, res, async (au) => { diff --git a/src/modules/giving/repositories/DonationRepo.ts b/src/modules/giving/repositories/DonationRepo.ts index 6d3d177a..262acc3c 100644 --- a/src/modules/giving/repositories/DonationRepo.ts +++ b/src/modules/giving/repositories/DonationRepo.ts @@ -2,6 +2,7 @@ import { injectable } from "inversify"; import { sql } from "kysely"; import { getDb } from "../db/index.js"; import { UniqueIdHelper, DateHelper, ArrayHelper } from "@churchapps/apihelper"; +import { CurrencyHelper } from "@churchapps/helpers"; import { DateHelper as LocalDateHelper } from "../../../shared/helpers/DateHelper.js"; import { Donation, DonationSummary } from "../models/index.js"; import { WebhookDispatcher } from "../../../shared/webhooks/index.js"; @@ -136,28 +137,34 @@ export class DonationRepo { return row ? this.rowToModel(row) : null; } - public async loadDashboardKpis(churchId: string, startDate: Date, endDate: Date, fundId?: string) { + public async loadDashboardKpis(churchId: string, startDate: Date, endDate: Date, fundId?: string, currency: string = "usd", rates?: any) { const sDate = DateHelper.toMysqlDate(startDate); const eDate = DateHelper.toMysqlDate(endDate); + let result; if (fundId) { - const result = await sql` - SELECT SUM(fd.amount) as totalGiving, AVG(d.amount) as avgGift, COUNT(DISTINCT d.personId) as donorCount, COUNT(DISTINCT d.id) as donationCount + result = await sql` + SELECT fd.amount as fdAmount, d.amount as dAmount, d.personId, d.id, d.currency FROM donations d INNER JOIN fundDonations fd on fd.donationId = d.id INNER JOIN funds f on f.id = fd.fundId WHERE d.churchId = ${churchId} AND d.donationDate BETWEEN ${sDate} AND ${eDate} AND fd.fundId = ${fundId}`.execute(getDb()); - return result.rows[0] ?? null; } else { - const result = await sql` - SELECT SUM(fd.amount) as totalGiving, AVG(d.amount) as avgGift, COUNT(DISTINCT d.personId) as donorCount, COUNT(DISTINCT d.id) as donationCount + result = await sql` + SELECT fd.amount as fdAmount, d.amount as dAmount, d.personId, d.id, d.currency FROM donations d INNER JOIN fundDonations fd on fd.donationId = d.id INNER JOIN funds f on f.id = fd.fundId WHERE d.churchId = ${churchId} - AND d.donationDate BETWEEN ${sDate} AND ${eDate}`.execute(getDb()); - return result.rows[0] ?? null; + AND d.donationDate BETWEEN ${sDate} AND ${eDate}`.execute(getDb()); + } + if (result.rows) { + const donationCount = ArrayHelper.getIds(result.rows, "id").length; + const donorCount = ArrayHelper.getUniqueValues(result.rows, "personId").filter(i => i !== null).length; + const totalGiving = result.rows.reduce((sum, item) => sum + CurrencyHelper.convertAmount(item.fdAmount, item.currency, currency, rates), 0); + const avgGift = totalGiving / donationCount; + return { totalGiving, avgGift, donorCount, donationCount }; } } From a2912b94e272bb74e5b3a041db703f6e5a4737f9 Mon Sep 17 00:00:00 2001 From: Himali Malvawala Date: Thu, 3 Sep 2026 23:17:53 +0530 Subject: [PATCH 2/2] update helpers --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f006c0a9..b417ec47 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@aws-sdk/s3-request-presigner": "3.1057.0", "@churchapps/apihelper": "^1.1.3", "@churchapps/content-providers": "^0.9.4", - "@churchapps/helpers": "^2.2.2", + "@churchapps/helpers": "^2.2.3", "@churchapps/texting": "^0.4.0", "@codegenie/serverless-express": "^4.15.0", "@hubspot/api-client": "^13.1.0", diff --git a/yarn.lock b/yarn.lock index 1fab1161..f1086d51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1364,9 +1364,9 @@ __metadata: languageName: node linkType: hard -"@churchapps/helpers@npm:^2.2.2": - version: 2.2.2 - resolution: "@churchapps/helpers@npm:2.2.2" +"@churchapps/helpers@npm:^2.2.3": + version: 2.2.3 + resolution: "@churchapps/helpers@npm:2.2.3" dependencies: dayjs: "npm:^1.11.20" peerDependencies: @@ -1374,7 +1374,7 @@ __metadata: peerDependenciesMeta: rrule: optional: true - checksum: 10c0/47fe3071cddffd365d5dbe23c7dcdb56048dbc0760fbcb25bb247bddb1fb6de491cb1f91c70f90f75af6585e8a26d3b037b3fb9faf35d3bf2e54ef900fd6045f + checksum: 10c0/45a35d694ba8a517a398582f36ca2fcdd75a4c2ca8a916349838bcd3e8221695d0f47168ace18e6fe835aa307ffe8278d492d34ee7cefcec40c14fb3223b96c7 languageName: node linkType: hard @@ -3863,7 +3863,7 @@ __metadata: "@aws-sdk/s3-request-presigner": "npm:3.1057.0" "@churchapps/apihelper": "npm:^1.1.3" "@churchapps/content-providers": "npm:^0.9.4" - "@churchapps/helpers": "npm:^2.2.2" + "@churchapps/helpers": "npm:^2.2.3" "@churchapps/texting": "npm:^0.4.0" "@codegenie/serverless-express": "npm:^4.15.0" "@hubspot/api-client": "npm:^13.1.0"