Skip to content

Commit

Permalink
Merge pull request #619 from code4romania/feat/438
Browse files Browse the repository at this point in the history
feat:[438] Financial Information
  • Loading branch information
radulescuandrew authored Aug 27, 2024
2 parents aba3eed + 1c503b0 commit 0656ebe
Show file tree
Hide file tree
Showing 51 changed files with 1,731 additions and 380 deletions.
40 changes: 40 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@nestjs/event-emitter": "2.0.4",
"@nestjs/passport": "10.0.3",
"@nestjs/platform-express": "10.3.10",
"@nestjs/schedule": "^4.1.0",
"@nestjs/swagger": "7.3.1",
"@nestjs/throttler": "5.2.0",
"@nestjs/typeorm": "10.0.2",
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { PracticeProgramModule } from './modules/practice-program/practice-progr
import { CivicCenterModule } from './modules/civic-center-service/civic-center.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { NotificationsModule } from './modules/notifications/notifications.module';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
imports: [
Expand All @@ -37,6 +38,7 @@ import { NotificationsModule } from './modules/notifications/notifications.modul
useClass: RateLimiterConfigService,
}),
EventEmitterModule.forRoot(),
ScheduleModule.forRoot(),

// Providers
DatabaseProviderModule,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/common/config/email-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class EmailConfigService {
defaults: {
from: '"No Reply" <no-reply@localhost>',
},
preview: true,
preview: false,
template: {
dir: __dirname + '/../../mail/templates',
adapter: new HandlebarsAdapter({ asset_url: this.createAssetUrl }),
Expand Down
105 changes: 100 additions & 5 deletions backend/src/mail/constants/template.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ export const MAIL_OPTIONS: Record<string, IMailOptions> = {
template: ORGANIZATION_REQUEST,
subject: 'NGO Hub - Solicitare creare cont organizație',
context: {
title:
'Ești doar la un pas de a primi accesul în NGO Hub.',
title: 'Ești doar la un pas de a primi accesul în NGO Hub.',
subtitle: () =>
`Solicitarea de a crea un cont pentru organizația ta în NGO Hub a fost trimisă cu succes. Echipa NGO Hub va verifica informațiile primite și imediat ce este aprobată vei primi o notificare cu detalii despre accesarea ecosistemului de soluții NGO Hub. Dacă ai întrebări, ne poți contacta la ${process.env.MAIL_CONTACT}`,
},
Expand Down Expand Up @@ -97,10 +96,106 @@ export const MAIL_OPTIONS: Record<string, IMailOptions> = {
subtitle: (organizationName, applicationName) =>
`O nouă cerere de instalare a aplicației ${applicationName} a fost creată în ONG Hub pentru organizația ${organizationName}.`,
cta: {
link: () =>
`${process.env.ONGHUB_URL}/applications/requests`,
link: () => `${process.env.ONGHUB_URL}/applications/requests`,
label: 'Vezi cererea',
},
},
},
};
REMIND_TO_UPDATE_ORGANIZATION_REPORTS: {
template: ORGANIZATION_REQUEST,
subject:
'Actualizați profilul organizației până pe 30 iunie pentru a evita suspendarea contului',
context: {
title:
'Actualizați profilul organizației până pe 30 iunie pentru a evita suspendarea contului',
subtitle: () => `
<p>Bună,</p>
<p>Ne bucurăm că ești parte din comunitatea NGO Hub!</p>
<p>Vrem să îți reamintim că este important să îți actualizezi datele din profilul tău din NGO Hub până la data de <b>30 iunie ${new Date().getFullYear()}</b>.</p>
<p>Dacă nu reușești să faci această actualizare până la termenul limită, contul tău va fi suspendat temporar.</p>
<p>Aceștia sunt pașii pe care trebuie să îi urmezi pentru actualizare:</p>
<ol>
<li>Conectează-te la contul tău NGO Hub</li>
<li>Mergi la secțiunea „Organizația mea"</li>
<li>Verifică și actualizează toate informațiile din secțiunea „Informații financiare" și „ONG-ul în numere".</li>
<li>Salvează modificările</li>
</ol>
<p>Dacă ai nevoie de ajutor sau ai orice fel de întrebare, ne poți contacta oricând la <a href="mailto:[email protected]">[email protected]</a> sau poți programa o sesiune Civic Tech 911 direct din contul NGO Hub al organizației.</p>
`,
cta: {
link: () => `${process.env.ONGHUB_URL}/organization`,
label: 'Intra in cont',
},
},
},
WEEKLY_REMINDER_TO_UPDATE_ORGANIZATION_REPORTS: {
template: ORGANIZATION_REQUEST,
subject:
'Actualizați profilul organizației până pe 30 iunie pentru a evita suspendarea contului',
context: {
title:
'Actualizați profilul organizației până pe 30 iunie pentru a evita suspendarea contului',
subtitle: () => `
<p>Bună,</p>
<p>Ne bucurăm că ești parte din comunitatea NGO Hub!</p>
<p>Îți reamintim că termenul limită pentru actualizarea datelor din profilul tău este 30 iunie ${new Date().getFullYear()}. Pentru a-ți păstra contul activ, te rugăm să îți actualizezi informațiile cât mai curând posibil.</p>
<p>Aceștia sunt pașii pe care trebuie să îi urmezi pentru actualizare:</p>
<ol>
<li>Conectează-te la contul tău NGO Hub</li>
<li>Mergi la secțiunea „Organizația mea"</li>
<li>Verifică și actualizează toate informațiile din secțiunea „Informații financiare" și „ONG-ul în numere".</li>
<li>Salvează modificările</li>
</ol>
<p>Dacă ai nevoie de ajutor sau ai orice fel de întrebare, ne poți contacta oricând la <a href="mailto:[email protected]">[email protected]</a> sau poți programa o sesiune Civic Tech 911 direct din contul NGO Hub al organizației.</p>
`,
cta: {
link: () => `${process.env.ONGHUB_URL}/organization`,
label: 'Intra in cont',
},
},
},
NOTIFY_FOR_UNAVAILABLE_OR_INVALID_FINANCIAL_INFORMATION: {
template: ORGANIZATION_REQUEST,
subject:
'Acțiune necesară: Te rugăm să corectezi datele din profilul organizației din NGO Hub',
context: {
title:
'Acțiune necesară: Te rugăm să corectezi datele din profilul organizației din NGO Hub',
subtitle: () => `
<p>Bună,</p>
<p>Ne bucurăm că ești parte din comunitatea NGO Hub!</p>
<p>Am observat că unele dintre informațiile recent actualizate din profilul tău de pe NGO Hub par a fi invalide sau incomplete. Pentru a ne asigura că ai acces neîntrerupt la contul tău, te rugăm să îți actualizezi și corectezi datele cât mai curând posibil.</p>
<p>Aceștia sunt pașii pe care trebuie să îi urmezi pentru actualizare:</p>
<ol>
<li>Conectează-te la contul tău NGO Hub</li>
<li>Mergi la secțiunea „Organizația mea"</li>
<li>Verifică și actualizează toate informațiile din secțiunea „Informații financiare" și „ONG-ul în numere".</li>
<li>Salvează modificările</li>
</ol>
<p>Este important să faci aceste actualizări înainte de <b>30 iunie 2024</b> pentru a evita suspendarea contului tău NGO Hub.</p>
<p>Dacă ai nevoie de ajutor sau ai orice fel de întrebare, ne poți contacta oricând la <a href="mailto:[email protected]">[email protected]</a> sau poți programa o sesiune Civic Tech 911 direct din contul NGO Hub al organizației.</p>
`,
cta: {
link: () => `${process.env.ONGHUB_URL}/organization`,
label: 'Intra in cont',
},
},
},
};
5 changes: 4 additions & 1 deletion backend/src/mail/templates/mail-template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,15 @@
{{> header}}
<div id="content">
<h1>{{title}}</h1>
<p class="p1">{{subtitle}}</p>
<p class="p1">{{{subtitle}}}</p>
{{#if cta}}
<a href={{cta.link}} target="_blank">
<button type="button">{{cta.label}}</button>
</a>
{{/if}}

<p class="p1">Cu drag, </p>
<p class="p1">Echipa NGO Hub</p>
</div>
{{> footer}}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddNewFinancialReportStatus1724056332367
implements MigrationInterface
{
name = 'AddNewFinancialReportStatus1724056332367';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."organization_financial_status_enum" AS ENUM('Not Completed', 'Pending', 'Completed', 'Invalid')`,
);
await queryRunner.query(
`ALTER TABLE "organization_financial" ADD "status" "public"."organization_financial_status_enum" NOT NULL DEFAULT 'Not Completed'`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "organization_financial" DROP COLUMN "status"`,
);
await queryRunner.query(
`DROP TYPE "public"."organization_financial_status_enum"`,
);
}
}
104 changes: 104 additions & 0 deletions backend/src/migrations/1724232066999-RemoveCompletionStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class RemoveCompletionStatus1724232066999 implements MigrationInterface {
name = 'RemoveCompletionStatus1724232066999';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`,
['VIEW', 'OrganizationView', 'public'],
);
await queryRunner.query(`DROP VIEW "OrganizationView"`);
await queryRunner.query(
`ALTER TABLE "organization" DROP COLUMN "synced_on"`,
);
await queryRunner.query(
`ALTER TABLE "organization" DROP COLUMN "completion_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."organization_completion_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "organization_history" DROP COLUMN "synced_on"`,
);
await queryRunner.query(`CREATE VIEW "OrganizationView" AS
SELECT
"organization".id AS "id",
"organization".status AS "status",
"organization".created_on AS "createdOn",
CASE WHEN MIN(COALESCE("organization_financial".status, 'Not Completed')) != 'Completed'
OR MIN(COALESCE("report".status, 'Not Completed')) != 'Completed'
OR MIN(COALESCE("partner".status, 'Not Completed')) != 'Completed'
OR MIN(COALESCE("investor".status, 'Not Completed')) != 'Completed' THEN
'Not completed'
ELSE
'Completed'
END AS "completionStatus",
"organization_general".name AS "name",
"organization_general".alias AS "alias",
COUNT("user".id) AS "userCount",
"organization_general".logo AS "logo",
MAX(GREATEST(COALESCE("organization_financial".updated_on, '1970-01-01'), COALESCE("report".updated_on, '1970-01-01'), COALESCE("partner".updated_on, '1970-01-01'), COALESCE("investor".updated_on, '1970-01-01'))) AS "updatedOn"
FROM
"organization" "organization"
LEFT JOIN "organization_general" "organization_general" ON "organization".organization_general_id = "organization_general".id
LEFT JOIN "user" "user" ON "user".organization_id = "organization".id
AND "user".deleted_on IS NULL
AND "user".ROLE = 'employee'
AND "user".status != 'pending'
LEFT JOIN "organization_financial" "organization_financial" ON "organization".id = "organization_financial"."organizationId"
LEFT JOIN "organization_report" "organization_report" ON "organization".organization_report_id = "organization_report".id
LEFT JOIN "report" "report" ON "organization_report".id = "report"."organizationReportId"
LEFT JOIN "partner" "partner" ON "organization_report".id = "partner"."organizationReportId"
LEFT JOIN "investor" "investor" ON "organization_report".id = "investor"."organizationReportId"
WHERE
"organization".status != 'pending'
GROUP BY
"organization".id,
"organization_general".id
`);
await queryRunner.query(
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`,
[
'public',
'VIEW',
'OrganizationView',
'SELECT\n "organization".id AS "id",\n "organization".status AS "status",\n "organization".created_on AS "createdOn",\n CASE WHEN MIN(COALESCE("organization_financial".status, \'Not Completed\')) != \'Completed\'\n OR MIN(COALESCE("report".status, \'Not Completed\')) != \'Completed\'\n OR MIN(COALESCE("partner".status, \'Not Completed\')) != \'Completed\'\n OR MIN(COALESCE("investor".status, \'Not Completed\')) != \'Completed\' THEN\n \'Not completed\'\n ELSE\n \'Completed\'\n END AS "completionStatus",\n "organization_general".name AS "name",\n "organization_general".alias AS "alias",\n COUNT("user".id) AS "userCount",\n "organization_general".logo AS "logo",\n MAX(GREATEST(COALESCE("organization_financial".updated_on, \'1970-01-01\'), COALESCE("report".updated_on, \'1970-01-01\'), COALESCE("partner".updated_on, \'1970-01-01\'), COALESCE("investor".updated_on, \'1970-01-01\'))) AS "updatedOn"\n FROM\n "organization" "organization"\n LEFT JOIN "organization_general" "organization_general" ON "organization".organization_general_id = "organization_general".id\n LEFT JOIN "user" "user" ON "user".organization_id = "organization".id\n AND "user".deleted_on IS NULL\n AND "user".ROLE = \'employee\'\n AND "user".status != \'pending\'\n LEFT JOIN "organization_financial" "organization_financial" ON "organization".id = "organization_financial"."organizationId"\n LEFT JOIN "organization_report" "organization_report" ON "organization".organization_report_id = "organization_report".id\n LEFT JOIN "report" "report" ON "organization_report".id = "report"."organizationReportId"\n LEFT JOIN "partner" "partner" ON "organization_report".id = "partner"."organizationReportId"\n LEFT JOIN "investor" "investor" ON "organization_report".id = "investor"."organizationReportId"\n WHERE\n "organization".status != \'pending\'\n GROUP BY\n "organization".id,\n "organization_general".id',
],
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`,
['VIEW', 'OrganizationView', 'public'],
);
await queryRunner.query(`DROP VIEW "OrganizationView"`);
await queryRunner.query(
`ALTER TABLE "organization_history" ADD "synced_on" TIMESTAMP WITH TIME ZONE NOT NULL`,
);
await queryRunner.query(
`CREATE TYPE "public"."organization_completion_status_enum" AS ENUM('Completed', 'Not Completed')`,
);
await queryRunner.query(
`ALTER TABLE "organization" ADD "completion_status" "public"."organization_completion_status_enum" NOT NULL DEFAULT 'Not Completed'`,
);
await queryRunner.query(
`ALTER TABLE "organization" ADD "synced_on" TIMESTAMP WITH TIME ZONE NOT NULL`,
);
await queryRunner.query(`CREATE VIEW "OrganizationView" AS SELECT "organization".id as "id", "organization".status AS "status", "organization".created_on as "createdOn", "organization".updated_on as "updatedOn", "organization".completion_status as "completionStatus", "organization_general".name as "name","organization_general".alias as "alias", COUNT("user".id) as "userCount", "organization_general".logo as "logo" FROM "organization" "organization"
LEFT JOIN "organization_general" "organization_general" ON "organization".organization_general_id = "organization_general".id
LEFT JOIN "user" "user" ON "user".organization_id = "organization".id AND "user".deleted_on IS NULL AND "user".role = 'employee' and "user".status != 'pending'
WHERE "organization".status != 'pending'
GROUP BY "organization".id, "organization_general".id`);
await queryRunner.query(
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`,
[
'public',
'VIEW',
'OrganizationView',
'SELECT "organization".id as "id", "organization".status AS "status", "organization".created_on as "createdOn", "organization".updated_on as "updatedOn", "organization".completion_status as "completionStatus", "organization_general".name as "name","organization_general".alias as "alias", COUNT("user".id) as "userCount", "organization_general".logo as "logo" FROM "organization" "organization"\n LEFT JOIN "organization_general" "organization_general" ON "organization".organization_general_id = "organization_general".id\n LEFT JOIN "user" "user" ON "user".organization_id = "organization".id AND "user".deleted_on IS NULL AND "user".role = \'employee\' and "user".status != \'pending\'\n WHERE "organization".status != \'pending\'\n GROUP BY "organization".id, "organization_general".id',
],
);
}
}
Loading

0 comments on commit 0656ebe

Please sign in to comment.