From b33f639c8169be1ea579cd2b049e8324fc021dbd Mon Sep 17 00:00:00 2001 From: Gbuntu Date: Tue, 7 Nov 2023 13:32:28 +0330 Subject: [PATCH] mail servide and more --- nest-cli.json | 2 +- src/app.module.ts | 5 +- src/db/migrations/1698779896533-new.ts | 14 - src/db/migrations/1699105112698-new.ts | 44 - src/db/migrations/1699180499824-new.ts | 14 + ...9105844419-new.ts => 1699181005168-new.ts} | 14 +- src/db/migrations/1699181702531-new.ts | 24 + src/entities/campaign.entity.ts | 21 +- src/entities/flaskEntities/receipt.entity.ts | 3 + src/entities/need.entity.ts | 3 - src/entities/signature.entity.ts | 3 - src/entities/user.entity.ts | 4 +- src/features/analytic/analytic.module.ts | 15 +- src/features/analytic/analytic.service.ts | 16 +- src/features/campaign/campaign.controller.ts | 34 + .../campaign.module.ts} | 23 +- src/features/campaign/campaign.service.ts | 301 ++ .../templates/expandFamilyNoChild.hbs | 478 +++ .../campaign/templates/monthlyCampaign.hbs | 1144 +++++++ .../campaign/templates/swRemindNoNeeds.hbs | 500 +++ src/features/children/children.module.ts | 9 +- src/features/children/children.service.ts | 4 +- src/features/comment/comment.module.ts | 4 +- src/features/family/family.controller.ts | 22 + src/features/family/family.module.ts | 14 +- src/features/ipfs/ipfs.module.ts | 14 +- src/features/mail/mail.service.ts | 220 -- .../mail/templates/monthlySummary copy.hbs | 2885 ----------------- .../mail/templates/monthlySummary.hbs | 2722 ---------------- src/features/midjourney/midjourney.module.ts | 13 +- src/features/milestone/milestone.module.ts | 16 +- src/features/mine/mine.module.ts | 7 +- src/features/need/need.module.ts | 4 + src/features/need/need.service.ts | 17 +- src/features/ngo/ngo.module.ts | 4 + src/features/payment/payment.module.ts | 14 +- src/features/payment/payment.service.ts | 15 +- src/features/receipt/receipt.module.ts | 4 +- src/features/schedule/schedule.module.ts | 15 +- src/features/schedule/schedule.service.ts | 54 +- src/features/sync/sync.module.ts | 4 + src/features/ticket/ticket.module.ts | 4 + src/features/user/user.module.ts | 15 +- src/features/user/user.service.ts | 10 +- src/features/wallet/wallet.module.ts | 4 + src/types/interfaces/interface.ts | 4 +- src/utils/dataCache.ts | 14 +- src/utils/helpers.ts | 36 +- 48 files changed, 2836 insertions(+), 5974 deletions(-) delete mode 100644 src/db/migrations/1698779896533-new.ts delete mode 100644 src/db/migrations/1699105112698-new.ts create mode 100644 src/db/migrations/1699180499824-new.ts rename src/db/migrations/{1699105844419-new.ts => 1699181005168-new.ts} (50%) create mode 100644 src/db/migrations/1699181702531-new.ts create mode 100644 src/features/campaign/campaign.controller.ts rename src/features/{mail/mail.module.ts => campaign/campaign.module.ts} (84%) create mode 100644 src/features/campaign/campaign.service.ts create mode 100644 src/features/campaign/templates/expandFamilyNoChild.hbs create mode 100644 src/features/campaign/templates/monthlyCampaign.hbs create mode 100644 src/features/campaign/templates/swRemindNoNeeds.hbs delete mode 100644 src/features/mail/mail.service.ts delete mode 100644 src/features/mail/templates/monthlySummary copy.hbs delete mode 100644 src/features/mail/templates/monthlySummary.hbs diff --git a/nest-cli.json b/nest-cli.json index 29518c8ad..2229a44c1 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -4,7 +4,7 @@ "sourceRoot": "src", "compilerOptions": { "assets": [ - "features/mail/templates/**/*" + "features/campaign/templates/**/*" ], "watchAssets": true } diff --git a/src/app.module.ts b/src/app.module.ts index 8a4950a7c..b5d30e5a5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -43,7 +43,7 @@ import { MineModule } from './features/mine/mine.module'; import { ContributionModule } from './features/contribution/contribution.module'; import { ThrottlerModule } from '@nestjs/throttler'; import { postgresDataSourceOptions } from './db/data-source'; -import { MailModule } from './features/mail/mail.module'; +import { CampaignModule } from './features/campaign/campaign.module'; import { Countries } from './entities/flaskEntities/countries.entity'; const imports = [ @@ -80,7 +80,7 @@ const imports = [ // ServeStaticModule.forRoot({ // rootPath: join(__dirname, '..', 'files'), // }), - MailModule, + CampaignModule, ScheduleTaskModule, GatewayModule, LocationModule, @@ -105,6 +105,7 @@ const imports = [ CommentModule, MineModule, ContributionModule, + CampaignModule, ]; @Module({ diff --git a/src/db/migrations/1698779896533-new.ts b/src/db/migrations/1698779896533-new.ts deleted file mode 100644 index 7973b236b..000000000 --- a/src/db/migrations/1698779896533-new.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class New1698779896533 implements MigrationInterface { - name = 'New1698779896533' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "all_user_entity" ADD "monthlyEmail" boolean NOT NULL DEFAULT false`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "all_user_entity" DROP COLUMN "monthlyEmail"`); - } - -} diff --git a/src/db/migrations/1699105112698-new.ts b/src/db/migrations/1699105112698-new.ts deleted file mode 100644 index b28e493c0..000000000 --- a/src/db/migrations/1699105112698-new.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class New1699105112698 implements MigrationInterface { - name = 'New1699105112698' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "campaign_entity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "title" character varying NOT NULL, "campaign" integer NOT NULL, "type" integer NOT NULL, CONSTRAINT "PK_9e02b1f09bf92b8ce7b80e4fb7a" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "campaign_entity_receivers_all_user_entity" ("campaignEntityId" uuid NOT NULL, "allUserEntityId" uuid NOT NULL, CONSTRAINT "PK_147af3299a0c9fe8859ce754aa0" PRIMARY KEY ("campaignEntityId", "allUserEntityId"))`); - await queryRunner.query(`CREATE INDEX "IDX_c7f9a0ab87070fb14e116587a8" ON "campaign_entity_receivers_all_user_entity" ("campaignEntityId") `); - await queryRunner.query(`CREATE INDEX "IDX_47beff96e36228ba71b277e509" ON "campaign_entity_receivers_all_user_entity" ("allUserEntityId") `); - await queryRunner.query(`CREATE TABLE "campaign_entity_content_signatures_signature_entity" ("campaignEntityId" uuid NOT NULL, "signatureEntityId" uuid NOT NULL, CONSTRAINT "PK_2a6739114fed6e9d81369e83453" PRIMARY KEY ("campaignEntityId", "signatureEntityId"))`); - await queryRunner.query(`CREATE INDEX "IDX_edca9c9d4686a3cb99d20ae497" ON "campaign_entity_content_signatures_signature_entity" ("campaignEntityId") `); - await queryRunner.query(`CREATE INDEX "IDX_e34cd832c86a0366db8a8b9d06" ON "campaign_entity_content_signatures_signature_entity" ("signatureEntityId") `); - await queryRunner.query(`CREATE TABLE "campaign_entity_content_needs_need_entity" ("campaignEntityId" uuid NOT NULL, "needEntityId" uuid NOT NULL, CONSTRAINT "PK_98884604dc03c841798c07824a0" PRIMARY KEY ("campaignEntityId", "needEntityId"))`); - await queryRunner.query(`CREATE INDEX "IDX_65b8cbeda0c8f14d9a97500a1b" ON "campaign_entity_content_needs_need_entity" ("campaignEntityId") `); - await queryRunner.query(`CREATE INDEX "IDX_0863220483cce26ae11a4e9f88" ON "campaign_entity_content_needs_need_entity" ("needEntityId") `); - await queryRunner.query(`ALTER TABLE "campaign_entity_receivers_all_user_entity" ADD CONSTRAINT "FK_c7f9a0ab87070fb14e116587a8d" FOREIGN KEY ("campaignEntityId") REFERENCES "campaign_entity"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "campaign_entity_receivers_all_user_entity" ADD CONSTRAINT "FK_47beff96e36228ba71b277e509b" FOREIGN KEY ("allUserEntityId") REFERENCES "all_user_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_signatures_signature_entity" ADD CONSTRAINT "FK_edca9c9d4686a3cb99d20ae4978" FOREIGN KEY ("campaignEntityId") REFERENCES "campaign_entity"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_signatures_signature_entity" ADD CONSTRAINT "FK_e34cd832c86a0366db8a8b9d06c" FOREIGN KEY ("signatureEntityId") REFERENCES "signature_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_needs_need_entity" ADD CONSTRAINT "FK_65b8cbeda0c8f14d9a97500a1ba" FOREIGN KEY ("campaignEntityId") REFERENCES "campaign_entity"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_needs_need_entity" ADD CONSTRAINT "FK_0863220483cce26ae11a4e9f88d" FOREIGN KEY ("needEntityId") REFERENCES "need_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "campaign_entity_content_needs_need_entity" DROP CONSTRAINT "FK_0863220483cce26ae11a4e9f88d"`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_needs_need_entity" DROP CONSTRAINT "FK_65b8cbeda0c8f14d9a97500a1ba"`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_signatures_signature_entity" DROP CONSTRAINT "FK_e34cd832c86a0366db8a8b9d06c"`); - await queryRunner.query(`ALTER TABLE "campaign_entity_content_signatures_signature_entity" DROP CONSTRAINT "FK_edca9c9d4686a3cb99d20ae4978"`); - await queryRunner.query(`ALTER TABLE "campaign_entity_receivers_all_user_entity" DROP CONSTRAINT "FK_47beff96e36228ba71b277e509b"`); - await queryRunner.query(`ALTER TABLE "campaign_entity_receivers_all_user_entity" DROP CONSTRAINT "FK_c7f9a0ab87070fb14e116587a8d"`); - await queryRunner.query(`DROP INDEX "public"."IDX_0863220483cce26ae11a4e9f88"`); - await queryRunner.query(`DROP INDEX "public"."IDX_65b8cbeda0c8f14d9a97500a1b"`); - await queryRunner.query(`DROP TABLE "campaign_entity_content_needs_need_entity"`); - await queryRunner.query(`DROP INDEX "public"."IDX_e34cd832c86a0366db8a8b9d06"`); - await queryRunner.query(`DROP INDEX "public"."IDX_edca9c9d4686a3cb99d20ae497"`); - await queryRunner.query(`DROP TABLE "campaign_entity_content_signatures_signature_entity"`); - await queryRunner.query(`DROP INDEX "public"."IDX_47beff96e36228ba71b277e509"`); - await queryRunner.query(`DROP INDEX "public"."IDX_c7f9a0ab87070fb14e116587a8"`); - await queryRunner.query(`DROP TABLE "campaign_entity_receivers_all_user_entity"`); - await queryRunner.query(`DROP TABLE "campaign_entity"`); - } - -} diff --git a/src/db/migrations/1699180499824-new.ts b/src/db/migrations/1699180499824-new.ts new file mode 100644 index 000000000..72d142782 --- /dev/null +++ b/src/db/migrations/1699180499824-new.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class New1699180499824 implements MigrationInterface { + name = 'New1699180499824' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME COLUMN "campaign" TO "campaignName"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME COLUMN "campaignName" TO "campaign"`); + } + +} diff --git a/src/db/migrations/1699105844419-new.ts b/src/db/migrations/1699181005168-new.ts similarity index 50% rename from src/db/migrations/1699105844419-new.ts rename to src/db/migrations/1699181005168-new.ts index 98070a7ef..a2c3f982f 100644 --- a/src/db/migrations/1699105844419-new.ts +++ b/src/db/migrations/1699181005168-new.ts @@ -1,16 +1,18 @@ import { MigrationInterface, QueryRunner } from "typeorm"; -export class New1699105844419 implements MigrationInterface { - name = 'New1699105844419' +export class New1699181005168 implements MigrationInterface { + name = 'New1699181005168' public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "campaign_entity" ADD "campaignNumber" integer NOT NULL DEFAULT '0'`); - await queryRunner.query(`ALTER TABLE "campaign_entity" ADD CONSTRAINT "UQ_77f8819f4ce0b24cffce96c2906" UNIQUE ("campaignNumber")`); + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP COLUMN "campaignName"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD "campaignName" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "all_user_entity" ALTER COLUMN "monthlyEmail" SET DEFAULT true`); } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "campaign_entity" DROP CONSTRAINT "UQ_77f8819f4ce0b24cffce96c2906"`); - await queryRunner.query(`ALTER TABLE "campaign_entity" DROP COLUMN "campaignNumber"`); + await queryRunner.query(`ALTER TABLE "all_user_entity" ALTER COLUMN "monthlyEmail" SET DEFAULT false`); + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP COLUMN "campaignName"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD "campaignName" integer NOT NULL`); } } diff --git a/src/db/migrations/1699181702531-new.ts b/src/db/migrations/1699181702531-new.ts new file mode 100644 index 000000000..a5254a445 --- /dev/null +++ b/src/db/migrations/1699181702531-new.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class New1699181702531 implements MigrationInterface { + name = 'New1699181702531' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME COLUMN "campaignNumber" TO "campaignCode"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME CONSTRAINT "UQ_77f8819f4ce0b24cffce96c2906" TO "UQ_b9e68834e43af5477e94c2796ae"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP CONSTRAINT "UQ_b9e68834e43af5477e94c2796ae"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP COLUMN "campaignCode"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD "campaignCode" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD CONSTRAINT "UQ_b9e68834e43af5477e94c2796ae" UNIQUE ("campaignCode")`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP CONSTRAINT "UQ_b9e68834e43af5477e94c2796ae"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" DROP COLUMN "campaignCode"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD "campaignCode" integer NOT NULL`); + await queryRunner.query(`ALTER TABLE "campaign_entity" ADD CONSTRAINT "UQ_b9e68834e43af5477e94c2796ae" UNIQUE ("campaignCode")`); + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME CONSTRAINT "UQ_b9e68834e43af5477e94c2796ae" TO "UQ_77f8819f4ce0b24cffce96c2906"`); + await queryRunner.query(`ALTER TABLE "campaign_entity" RENAME COLUMN "campaignCode" TO "campaignNumber"`); + } + +} diff --git a/src/entities/campaign.entity.ts b/src/entities/campaign.entity.ts index d1f4c6358..bee90b05f 100644 --- a/src/entities/campaign.entity.ts +++ b/src/entities/campaign.entity.ts @@ -1,7 +1,10 @@ import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm'; import { BaseEntity } from './BaseEntity'; import { AllUserEntity } from './user.entity'; -import { CampaignEnum, CampaignTypeEnum } from 'src/types/interfaces/interface'; +import { + CampaignNameEnum, + CampaignTypeEnum, +} from 'src/types/interfaces/interface'; import { SignatureEntity } from './signature.entity'; import { NeedEntity } from './need.entity'; @@ -11,29 +14,17 @@ export class CampaignEntity extends BaseEntity { title: string; @Column({ nullable: false }) - campaign: CampaignEnum; + campaignName: CampaignNameEnum; @Column({ nullable: false }) type: CampaignTypeEnum; @Column({ nullable: false, unique: true }) - campaignNumber: number; + campaignCode: string; @ManyToMany(() => AllUserEntity, (u) => u.campaigns, { eager: true, }) @JoinTable() receivers: AllUserEntity[]; - - @ManyToMany(() => SignatureEntity, (s) => s.campaigns, { - eager: true, - }) - @JoinTable() - contentSignatures?: SignatureEntity[]; - - @ManyToMany(() => NeedEntity, (n) => n.campaigns, { - eager: true, - }) - @JoinTable() - contentNeeds?: NeedEntity[]; } diff --git a/src/entities/flaskEntities/receipt.entity.ts b/src/entities/flaskEntities/receipt.entity.ts index 6524dd744..749993d11 100644 --- a/src/entities/flaskEntities/receipt.entity.ts +++ b/src/entities/flaskEntities/receipt.entity.ts @@ -21,6 +21,9 @@ export class Receipt extends BaseEntity { @Column() description: string; + @Column({ nullable: true }) + is_public: boolean; + @Column({ nullable: true }) deleted: Date; } diff --git a/src/entities/need.entity.ts b/src/entities/need.entity.ts index 7f5bb21b1..d955849af 100644 --- a/src/entities/need.entity.ts +++ b/src/entities/need.entity.ts @@ -179,7 +179,4 @@ export class NeedEntity extends BaseEntity { @OneToMany(() => VariableEntity, (v) => v.need, { eager: true }) variables?: VariableEntity[]; - - @ManyToMany(() => CampaignEntity, (c) => c.contentNeeds) - campaigns: CampaignEntity[]; } diff --git a/src/entities/signature.entity.ts b/src/entities/signature.entity.ts index b8c378683..5674097f8 100644 --- a/src/entities/signature.entity.ts +++ b/src/entities/signature.entity.ts @@ -39,7 +39,4 @@ export class SignatureEntity extends BaseEntity { nullable: false, }) need: NeedEntity; - - @ManyToMany(() => CampaignEntity, (c) => c.contentNeeds) - campaigns: CampaignEntity[]; } diff --git a/src/entities/user.entity.ts b/src/entities/user.entity.ts index 8a8b32615..ffe110e95 100644 --- a/src/entities/user.entity.ts +++ b/src/entities/user.entity.ts @@ -29,7 +29,7 @@ export class AllUserEntity extends BaseEntity { }) comments: CommentEntity[]; - @ManyToMany(() => CampaignEntity, (c) => c.contentNeeds) + @ManyToMany(() => CampaignEntity, (c) => c.receivers) campaigns: CampaignEntity[]; @Column({ nullable: false }) @@ -65,7 +65,7 @@ export class AllUserEntity extends BaseEntity { }) signatures: SignatureEntity[]; - @Column({ default: false }) + @Column({ default: true }) monthlyEmail: boolean; @OneToMany(() => CampaignEntity, (e) => e.receivers, { diff --git a/src/features/analytic/analytic.module.ts b/src/features/analytic/analytic.module.ts index 6a23d0d7f..7da37828d 100644 --- a/src/features/analytic/analytic.module.ts +++ b/src/features/analytic/analytic.module.ts @@ -22,11 +22,24 @@ import { AnalyticMiddleware } from './middlewares/analytic.middleware'; import { PaymentEntity } from 'src/entities/payment.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [User, SocialWorker, Need, Child, NGO, Payment, Family, UserFamily], + [ + User, + SocialWorker, + Need, + Child, + NGO, + Payment, + Family, + UserFamily, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/analytic/analytic.service.ts b/src/features/analytic/analytic.service.ts index d74a6ab2e..a11951e04 100644 --- a/src/features/analytic/analytic.service.ts +++ b/src/features/analytic/analytic.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { mean, round } from 'mathjs'; +import config from 'src/config'; import { Child } from 'src/entities/flaskEntities/child.entity'; import { Family } from 'src/entities/flaskEntities/family.entity'; import { Need } from 'src/entities/flaskEntities/need.entity'; @@ -16,7 +17,7 @@ import { import { daysDifference, getNeedsTimeLine, - removeDuplicates, + removeSpecialDuplicates, timeDifferenceWithComment, } from 'src/utils/helpers'; import { Repository } from 'typeorm'; @@ -203,6 +204,7 @@ export class AnalyticService { .getCount(); return { + noNeeds: config().dataCache.fetchChildrenNoNeeds(), allChildren: allChildren[1], dead, alivePresent, @@ -445,11 +447,13 @@ export class AnalyticService { childSayName: child.sayname_translations.en, family: { familyId: familyId, - activeUsers: removeDuplicates(activeUsersId).length, - activeUsersInOneMonths: removeDuplicates(activeUsersIdInOneMonths) - .length, - activeUsersInThreeMonths: removeDuplicates(activeUsersIdInThreeMonths) - .length, + activeUsers: removeSpecialDuplicates(activeUsersId).length, + activeUsersInOneMonths: removeSpecialDuplicates( + activeUsersIdInOneMonths, + ).length, + activeUsersInThreeMonths: removeSpecialDuplicates( + activeUsersIdInThreeMonths, + ).length, familyCount: familyCount, }, }); diff --git a/src/features/campaign/campaign.controller.ts b/src/features/campaign/campaign.controller.ts new file mode 100644 index 000000000..dc44623cc --- /dev/null +++ b/src/features/campaign/campaign.controller.ts @@ -0,0 +1,34 @@ +import { Controller, Get, Req, ForbiddenException } from '@nestjs/common'; +import { ApiHeader, ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { FlaskUserTypesEnum } from 'src/types/interfaces/interface'; +import { isAuthenticated } from 'src/utils/auth'; +import { CampaignService } from './campaign.service'; + +@ApiTags('Campaign') +@ApiSecurity('flask-access-token') +@ApiHeader({ + name: 'flaskId', + description: 'to use cache and flask authentication', + required: true, +}) +@Controller('campaign') +export class CampaignController { + constructor(private readonly campaignService: CampaignService) {} + + @Get('/all') + getAvailableContributions(@Req() req: Request) { + const panelFlaskUserId = req.headers['panelFlaskUserId']; + const panelFlaskTypeId = req.headers['panelFlaskTypeId']; + if ( + !isAuthenticated(panelFlaskUserId, panelFlaskTypeId) || + !( + panelFlaskTypeId === FlaskUserTypesEnum.SUPER_ADMIN || + panelFlaskTypeId === FlaskUserTypesEnum.ADMIN + ) + ) { + throw new ForbiddenException(403, 'You Are not authorized'); + } + + return this.campaignService.getCampaigns(); + } +} diff --git a/src/features/mail/mail.module.ts b/src/features/campaign/campaign.module.ts similarity index 84% rename from src/features/mail/mail.module.ts rename to src/features/campaign/campaign.module.ts index ebef31861..fe5e82c44 100644 --- a/src/features/mail/mail.module.ts +++ b/src/features/campaign/campaign.module.ts @@ -1,7 +1,7 @@ import { MailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { Global, Module } from '@nestjs/common'; -import { MailService } from './mail.service'; +import { CampaignService } from './campaign.service'; import { join } from 'path'; import { ConfigService } from '@nestjs/config'; import { UserService } from '../user/user.service'; @@ -26,6 +26,9 @@ import { PaymentEntity } from 'src/entities/payment.entity'; import { Family } from 'src/entities/flaskEntities/family.entity'; import { UserFamily } from 'src/entities/flaskEntities/userFamily.entity'; import { CampaignEntity } from 'src/entities/campaign.entity'; +import { CampaignController } from './campaign.controller'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Global() // 👈 global module @Module({ @@ -57,7 +60,16 @@ import { CampaignEntity } from 'src/entities/campaign.entity'; inject: [ConfigService], }), TypeOrmModule.forFeature( - [SocialWorker, User, Need, Child, Family, UserFamily], + [ + SocialWorker, + User, + Need, + Child, + Family, + UserFamily, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ @@ -75,7 +87,7 @@ import { CampaignEntity } from 'src/entities/campaign.entity'; ]), ], providers: [ - MailService, + CampaignService, UserService, NeedService, ChildrenService, @@ -83,6 +95,7 @@ import { CampaignEntity } from 'src/entities/campaign.entity'; FamilyService, MineService, ], - exports: [MailService], + controllers: [CampaignController], + exports: [CampaignService], }) -export class MailModule {} +export class CampaignModule {} diff --git a/src/features/campaign/campaign.service.ts b/src/features/campaign/campaign.service.ts new file mode 100644 index 000000000..0f71f2efa --- /dev/null +++ b/src/features/campaign/campaign.service.ts @@ -0,0 +1,301 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { UserService } from '../user/user.service'; +import { ServerError } from 'src/filters/server-exception.filter'; +import { ChildrenService } from '../children/children.service'; +import { NeedService } from '../need/need.service'; +import { + persianMonthStringFarsi, + prepareUrl, + removeDuplicates, + shuffleArray, +} from 'src/utils/helpers'; +import { + CampaignNameEnum, + CampaignTypeEnum, + ChildExistence, + NeedTypeEnum, +} from 'src/types/interfaces/interface'; +import { FamilyService } from '../family/family.service'; +import { CampaignEntity } from 'src/entities/campaign.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { AllUserEntity } from 'src/entities/user.entity'; +import { MailerService } from '@nestjs-modules/mailer'; +import config from 'src/config'; + +@Injectable() +export class CampaignService { + constructor( + @InjectRepository(CampaignEntity) + private campaignRepository: Repository, + private needService: NeedService, + private userService: UserService, + private familyService: FamilyService, + private mailerService: MailerService, + private childrenService: ChildrenService, + ) {} + private readonly logger = new Logger(CampaignService.name); + + getCampaigns(): Promise { + return this.campaignRepository.find({ + relations: { + receivers: true, + }, + }); + } + + getCampaignById(id: string): Promise { + const need = this.campaignRepository.findOne({ + where: { + id, + }, + }); + return need; + } + + getCampaignByCampaignNumber(campaignCode: string): Promise { + const need = this.campaignRepository.findOne({ + where: { + campaignCode, + }, + }); + return need; + } + + createCampaign( + campaignCode: string, + campaignName: CampaignNameEnum, + type: CampaignTypeEnum, + title: string, + users: AllUserEntity[], + ) { + const newCampaign = this.campaignRepository.create({ + campaignCode, + campaignName: campaignName, + title, + type, + }); + + newCampaign.receivers = users; + return this.campaignRepository.save(newCampaign); + } + + async updateCampaignUsers( + campaign: CampaignEntity, + currentReceivers: AllUserEntity[], + users: AllUserEntity[], + ): Promise { + const newReceivers = currentReceivers + ? [...currentReceivers, ...users] + : [...users]; + campaign.receivers = newReceivers; + return this.campaignRepository.save(campaign); + } + + async sendSocialWorkersMonthlyReminder() { + const children = await this.childrenService.getFlaskActiveChildren(); + const list = []; + for await (const child of children) { + const childUnpaidNeeds = await this.needService.getFlaskChildUnpaidNeeds( + child.id, + ); + const childUnconfirmedNeeds = + await this.needService.getFlaskChildUnconfirmedNeeds(child.id); + const allNeeds = childUnpaidNeeds.concat(childUnconfirmedNeeds); + + if (!allNeeds || !allNeeds[0]) { + list.push({ + swId: child.id_social_worker, + child, + }); + } + } + config().dataCache.updateChildrenNoNeeds(list.length); + + const swIds = removeDuplicates(list.map((e) => e.swId)); + for (let i = 0; i < swIds.length; i++) { + const selected = list.filter((e) => e.swId === swIds[i]); + const socialWorker = await this.userService.getFlaskSw(Number(swIds[i])); + const swChildren = selected.map((s) => s.child); + this.logger.warn( + `Emailing: Social worker ${socialWorker.id} of children with no need!`, + ); + + await this.mailerService.sendMail({ + from: '"NGOs" ', // override default from + to: socialWorker.email, + cc: process.env.SAY_ADMIN_EMAIL, + subject: `${swChildren.length} کودک بدون نیاز ثبت شده`, + template: './monthlyReminder', // `.hbs` extension is appended automatically + context: { + children: swChildren, + userName: socialWorker.firstName + ? socialWorker.firstName + : socialWorker.userName, + }, + }); + } + } + + async sendUserMonthlySummaries() { + try { + const today = new Date(); + const englishMonth = today.getMonth(); + const englishYear = today.getFullYear(); + const campaignCode = `${CampaignNameEnum.MONTHLY_SUMMARIES}-${CampaignTypeEnum.EMAIL}-${englishMonth}/${englishYear}`; + const persianMonth = persianMonthStringFarsi(today); + if (!persianMonth) { + throw new ServerError('We need the month string'); + } + const tittle = `نیازهای ${persianMonth} ماه کودکان شما`; + + const receivers = []; + const users = await this.userService.getFlaskUsers(); + const shuffledUsers = shuffleArray( + users.filter((u) => u.userName === 'ehsan'), + ); + const campaign = await this.getCampaignByCampaignNumber(campaignCode); + // 1- loop shuffled users + for await (const flaskUser of shuffledUsers) { + if (flaskUser.emailAddress) { + let nestUser = await this.userService.getFamilyByFlaskId( + flaskUser.id, + ); + // 2- eligible to receive? + if (!nestUser) { + nestUser = await this.userService.createFamily(flaskUser.id); + } + if (campaign) { + const alreadyReceived = campaign.receivers.find( + (r) => r.flaskUserId === flaskUser.id, + ); + if (!alreadyReceived) { + this.logger.log( + `Skipping: User ${nestUser.flaskUserId} has already received this email - ${campaignCode}`, + ); + continue; + } + } + + if (!nestUser.monthlyEmail) { + this.logger.debug( + `Skipping: User ${nestUser.flaskUserId} has turned off monthly summaries - ${campaignCode}`, + ); + continue; + } + // 3- get user children & shuffle + const children = ( + await this.childrenService.getMyChildren(flaskUser.id) + ).filter((c) => c.existence_status === ChildExistence.AlivePresent); + + // 4- send mail users with no children + if (children || !children[0]) { + this.logger.warn( + `Emailing: User ${nestUser.flaskUserId} because has no children! - ${campaignCode}`, + ); + await this.mailerService.sendMail({ + to: flaskUser.emailAddress, + subject: `گسترش خانواده مجازی`, + template: './expandFamilyNoChild', // `.hbs` extension is appended automatically + context: { + userName: flaskUser.firstName + ? flaskUser.firstName + : flaskUser.userName, + }, + }); + receivers.push(nestUser); + continue; + } + + const userChildren = []; + let counter = 1; + // 5- loop shuffled children + for await (const child of shuffleArray(children)) { + if (counter <= 3) { + const childUnpaidNeeds = + await this.needService.getFlaskChildUnpaidNeeds(child.id); + if (!childUnpaidNeeds || !childUnpaidNeeds[0]) { + // we separately email social workers + this.logger.debug( + `Skipping: Child ${child.id} has no unpaid needs - ${campaignCode}`, + ); + continue; + } + counter++; + + // 6- shuffle children needs and create an object - prioritize partial paid needs + const TAKE = 2; + const shuffledNeeds = shuffleArray(childUnpaidNeeds); + const organizedNeeds = shuffledNeeds + .sort((a, b) => b.status - a.status) + .slice(0, TAKE); + + const theChild = { + id: child.id, + sayName: child.sayname_translations.fa, + avatar: prepareUrl(child.awakeAvatarUrl), + unPaidNeeds: organizedNeeds.map((n) => { + return { + id: n.id, + name: n.name_translations.fa, + price: n._cost.toLocaleString(), + image: + n.type === NeedTypeEnum.PRODUCT + ? n.img + : prepareUrl(n.imageUrl), + }; + }), + }; + userChildren.push(theChild); + } + } + if (!userChildren || !userChildren[0]) { + this.logger.warn( + `Skipping: User ${nestUser.flaskUserId}'s children have no unpaidNeeds! - ${campaignCode}`, + ); + continue; + } + + const readyToSignNeeds = ( + await this.familyService.getFamilyReadyToSignNeeds(flaskUser.id) + ).filter((n) => n.midjourneyImage); + + this.logger.warn( + `Emailing: User ${nestUser.flaskUserId} the Campaign! - ${campaignCode}`, + ); + await this.mailerService.sendMail({ + to: flaskUser.emailAddress, + subject: `نیازهای ${persianMonth} ماه کودکان شما`, + template: './monthlyCampaign', // `.hbs` extension is appended automatically + context: { + myChildren: userChildren, + readyToSignNeeds, + }, + }); + receivers.push(nestUser); + this.logger.debug(`Email Sent to User: ${nestUser.flaskUserId}`); + } + } + + if (!campaign) { + await this.createCampaign( + campaignCode, + CampaignNameEnum.MONTHLY_SUMMARIES, + CampaignTypeEnum.EMAIL, + tittle, + receivers, + ); + this.logger.log(`Campaign Created - ${campaignCode}`); + } else if (campaign && receivers[0]) { + this.logger.log(`Campaign Updating - ${campaignCode}`); + await this.updateCampaignUsers(campaign, campaign.receivers, receivers); + this.logger.log(`Campaign Updated - ${campaignCode}`); + } else { + this.logger.debug(`No email was sent - ${campaignCode}`); + } + } catch (e) { + console.log(e); + throw new ServerError('Cold not send email!'); + } + } +} diff --git a/src/features/campaign/templates/expandFamilyNoChild.hbs b/src/features/campaign/templates/expandFamilyNoChild.hbs new file mode 100644 index 000000000..14af068d2 --- /dev/null +++ b/src/features/campaign/templates/expandFamilyNoChild.hbs @@ -0,0 +1,478 @@ + + + + + + + + + SAY DAO No Children Template + + + + + + + + + +
+ +
+ + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + +
+ Logo +
+ + + + + + + +
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + + + + +
+ +
+
+ شما کودک فعالی ندارید. +
+
+

+ سلام undefined، +

+

+ در حال حاضر شما کودک فعالی ندارید. شما + می‌توانید با رفتن به صفحه جستجو + اپلیکیشن/دپلیکیشن، کودک دیگری را به + سرپرستی قبول کنید... +

+
+ اپلیکیشن +
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+

+ در صورتی که مایل نیستید این یادآوری ماهانه + را دریافت کنید، لطفا از طریق این + + لینک + به صفحه کاربری بروید و آن را خاموش کنید. +

+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/features/campaign/templates/monthlyCampaign.hbs b/src/features/campaign/templates/monthlyCampaign.hbs new file mode 100644 index 000000000..bda9a0693 --- /dev/null +++ b/src/features/campaign/templates/monthlyCampaign.hbs @@ -0,0 +1,1144 @@ + + + + + + + + + Campaign Template + + + + + + + + + +
+ +
+ + +
+ + + + +
+ + + + +
+ + + + + + + +
+ + + + +
+ + + + +
+ SAY logo +
+
+
+ + + + + +
+ + + + +
+ وبسایت +
+
+ + + + + +
+ + + + +
+ مستندات +
+
+ + + + +
+ + + + +
+ دپلیکیشن +
+
+
+
+ + + + +
+ + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+
+
+
+ + + + +
+ + + + + + + +
+

+   + آخرین + نیاز + کودکان +   +

+
+ +
+
+
+
+ {{#each myChildren as | child |}} + + + + +
+ + + + + + + +
+ + + + + +
+ + + + + + + +
+

+   {{child.sayName}} +

+
+ + + + +
+
+
+ + + + + +
+ + + + +
+ +
+
+ +
+ + + + + +
+ + + + + + + + + + +
+

+ آخرین نیازها +

+
+ + + + +
+
+

+   + + دیگر نیاز‌های کودک ... +   +

+
+
+ {{#each unPaidNeeds as |need|}} + + + + +
+ + + + + + + + + + + + + +
+ {{need.name}} +
+

+ {{need.price}} تومان +

+
+

+ {{need.name}} +

+
+ پرداخت +
+
+ {{/each}} +
+
+ {{/each}} + + + + +
+ + + + + + + +
+ + + + +
+ + + + + + + +
+ + تمام کودکان ... +
+ + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + +
+

+   + امضای + دیجیتال +   +

+
+

+ نیازی که توسط خانواده مجازی پرداخت شده + باشد و به دست کودک رسیده باشد وارد مرحله + تازه ای می‌شود. در این مرحله امکان امضای + دیجیتال نیاز توسط مددکار، شاهد، میانجی و + خانواده مجازی فراهم می‌شود که خود پیش نیاز + توسعه SAY بر روی بستر بلاک‌چین و تبدیل شدن + به سازمانی غیرمتمرکز و خودمختار است. +

+
+ مطالعه بیشتر ... +
+ +
+
+
+
+ + + + +
+ + + + +
+ {{#each readyToSignNeeds as |readyNeed|}} + + + + + + + +
+ + + + +
+ {{readyNeed.name}} +
+
+ + + + +
+

+ {{readyNeed.name}} +

+
+
+ {{/each}} +
+
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ دیگر امضا‌ها ... +
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + +
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + +
+

+ در صورتی که با این نشانی ایمیل اقدام به + همراهی ما نکرده‌اید لطفا این ایمیل را + ندیده بگیرید و روی لینک زیر کلیک + کنید +

+
+

+ لغو همراهی +

+
+ +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/features/campaign/templates/swRemindNoNeeds.hbs b/src/features/campaign/templates/swRemindNoNeeds.hbs new file mode 100644 index 000000000..af29beca2 --- /dev/null +++ b/src/features/campaign/templates/swRemindNoNeeds.hbs @@ -0,0 +1,500 @@ + + + + + + + + + Social Workers Remidner Template + + + + + + + + + +
+ +
+ + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+

+ View online version +

+
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + +
+ Logo +
+ + + + + + + +
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + +
+ +
+
+ کودکان بدون نیاز ثبت شده! +
+
+

+ سلام {{userName}}، +

+

+ ​ +

+

+ در حال حاضر تعدادی از کودکان شما نیاز ثبت + شده‌ای ندارند.  در صورتی که کودک از + تحت پوشش شما خارج شده است یا نیازهای آنها + از  محل دیگری رفع می‌گردد، لطفا از + طریق ایمیل ngo@saydao.org به ما اطلاع + دهید، در غیر این صورت این ایمیل را نادیده + بگیرید. +

+

+ ​
با احترام، +

+

+ SAY +

+

+ ​ +

+

+ کودکان بدون نیاز ثبت شده: +

+

+

+
    + {{#each children as | child|}} +
  • + {{child.firstName_translations.fa}} + {{child.lastName_translations.fa}} + ({{child.sayname_translations.fa}}) +
  • + {{/each}} +
+

+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/features/children/children.module.ts b/src/features/children/children.module.ts index 6b24d806f..debd0c950 100644 --- a/src/features/children/children.module.ts +++ b/src/features/children/children.module.ts @@ -43,6 +43,8 @@ import { PaymentEntity } from 'src/entities/payment.entity'; import { StatusEntity } from 'src/entities/status.entity'; import { ProviderJoinNeedEntity } from 'src/entities/provider.Join.need..entity'; import { ProviderEntity } from 'src/entities/provider.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -59,6 +61,8 @@ import { ProviderEntity } from 'src/entities/provider.entity'; NGO, Countries, Payment, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), @@ -104,7 +108,10 @@ export class ChildrenModule implements NestModule { consumer .apply(ChildrenMiddleware) .exclude( - { path: 'children/avatars/images/:fileName', method: RequestMethod.GET }, + { + path: 'children/avatars/images/:fileName', + method: RequestMethod.GET, + }, { path: 'children/voices/:fileName', method: RequestMethod.GET }, { path: `children/preregister/old`, method: RequestMethod.GET }, ) diff --git a/src/features/children/children.service.ts b/src/features/children/children.service.ts index 06b900a7f..a4752c78d 100644 --- a/src/features/children/children.service.ts +++ b/src/features/children/children.service.ts @@ -314,7 +314,6 @@ export class ChildrenService { }, socialWorkerIds: number[], ): Promise> { - const queryBuilder = this.flaskChildRepository .createQueryBuilder('child') .leftJoinAndMapOne('child.ngo', NGO, 'ngo', 'ngo.id = child.id_ngo') @@ -360,8 +359,9 @@ export class ChildrenService { async getFlaskActiveChildren(): Promise { return await this.flaskChildRepository .createQueryBuilder('child') + .where('child.isConfirmed = :childConfirmed', { childConfirmed: true }) .andWhere('child.isDeleted = :isDeleted', { isDeleted: false }) - .where('child.existence_status = :existence_status', { + .andWhere('child.existence_status = :existence_status', { existence_status: ChildExistence.AlivePresent, }) .andWhere('child.isMigrated = :childIsMigrated', { diff --git a/src/features/comment/comment.module.ts b/src/features/comment/comment.module.ts index 8643a275e..0c50ab257 100644 --- a/src/features/comment/comment.module.ts +++ b/src/features/comment/comment.module.ts @@ -14,11 +14,13 @@ import { ContributorEntity } from 'src/entities/contributor.entity'; import { AllUserEntity } from 'src/entities/user.entity'; import { EthereumAccountEntity } from 'src/entities/ethereum.account.entity'; import { VariableEntity } from 'src/entities/variable.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [User, Need, SocialWorker, Child], + [User, Need, SocialWorker, Child, Receipt, NeedReceipt], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/family/family.controller.ts b/src/features/family/family.controller.ts index dcc74be55..ed91ccd65 100644 --- a/src/features/family/family.controller.ts +++ b/src/features/family/family.controller.ts @@ -500,6 +500,28 @@ export class FamilyController { }; } + @Get(`credit/:flaskUserId`) + @ApiOperation({ description: 'Get all contributors' }) + async getFamilyCredit( + @Param('flaskUserId') flaskUserId: number, + @Req() req: Request, + ) { + const panelFlaskUserId = req.headers['panelFlaskUserId']; + const panelFlaskTypeId = req.headers['panelFlaskTypeId']; + if ( + !isAuthenticated(panelFlaskUserId, panelFlaskTypeId) || + !( + panelFlaskTypeId === FlaskUserTypesEnum.SUPER_ADMIN || + panelFlaskTypeId === FlaskUserTypesEnum.ADMIN + ) + ) { + throw new ForbiddenException(403, 'You Are not authorized'); + } + + const credit = await this.paymentService.getFamilyCredit(flaskUserId); + return credit; + } + @Get(`email/status`) @ApiOperation({ description: 'Get all contributors' }) async getEmailStatus(@Req() req: Request) { diff --git a/src/features/family/family.module.ts b/src/features/family/family.module.ts index 451fbaf91..5262df658 100644 --- a/src/features/family/family.module.ts +++ b/src/features/family/family.module.ts @@ -23,11 +23,23 @@ import { PaymentService } from '../payment/payment.service'; import { Payment } from 'src/entities/flaskEntities/payment.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, Family, User, Child, SocialWorker, UserFamily, Payment], + [ + Need, + Family, + User, + Child, + SocialWorker, + UserFamily, + Payment, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/ipfs/ipfs.module.ts b/src/features/ipfs/ipfs.module.ts index e2b57187f..b2b93c177 100644 --- a/src/features/ipfs/ipfs.module.ts +++ b/src/features/ipfs/ipfs.module.ts @@ -20,11 +20,23 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { IpfsMiddleware } from './middlewares/ipfs.middleware'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, Child, Payment, SocialWorker, UserFamily, Family, User], + [ + Need, + Child, + Payment, + SocialWorker, + UserFamily, + Family, + User, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/mail/mail.service.ts b/src/features/mail/mail.service.ts deleted file mode 100644 index ca2a0a37e..000000000 --- a/src/features/mail/mail.service.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { MailerService } from '@nestjs-modules/mailer'; -import { Injectable } from '@nestjs/common'; -import { UserService } from '../user/user.service'; -import { ServerError } from 'src/filters/server-exception.filter'; -import { ChildrenService } from '../children/children.service'; -import { NeedService } from '../need/need.service'; -import { - persianMonth, - persianMonthString, - prepareUrl, - shuffleArray, - sortNeeds, -} from 'src/utils/helpers'; -import { - CampaignEnum, - CampaignTypeEnum, - ChildExistence, - NeedTypeEnum, - PaymentStatusEnum, -} from 'src/types/interfaces/interface'; -import { FamilyService } from '../family/family.service'; -import { CampaignEntity } from 'src/entities/campaign.entity'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { NeedEntity } from 'src/entities/need.entity'; -import { SignatureEntity } from 'src/entities/signature.entity'; -import { AllUserEntity } from 'src/entities/user.entity'; - -@Injectable() -export class MailService { - constructor( - @InjectRepository(CampaignEntity) - private campaignRepository: Repository, - private needService: NeedService, - private userService: UserService, - private familyService: FamilyService, - private childrenService: ChildrenService, - ) {} - - getCampaigns(): Promise { - return this.campaignRepository.find(); - } - getCampaignsByFlaskNeedId(flaskId: number): Promise { - const need = this.campaignRepository.find({ - where: { - contentNeeds: { - flaskId, - }, - }, - }); - return need; - } - - getCampaignById(id: string): Promise { - const need = this.campaignRepository.findOne({ - where: { - id, - }, - }); - return need; - } - - createCampaign( - campaignNumber: number, - campaign: CampaignEnum, - type: CampaignTypeEnum, - title: string, - needs: NeedEntity[], - signatures: SignatureEntity[], - users: AllUserEntity[], - ) { - const newCampaign = this.campaignRepository.create({ - campaignNumber, - campaign, - title, - type, - }); - newCampaign.contentNeeds = needs; - newCampaign.contentSignatures = signatures; - newCampaign.receivers = users; - return this.campaignRepository.save(newCampaign); - } - - async sendUserSummaries() { - const users = await this.userService.getFlaskUsers(); - try { - const d = new Date(); - const pm = persianMonthString(d); - const month = - pm === 'Farvardin' - ? 'فروردین' - : pm === 'Ordibehesht' - ? 'اردیبهست' - : pm === 'Khordad' - ? 'خرداد' - : pm === 'Tir' - ? 'تیر' - : pm === 'Mordad' - ? 'مرداد' - : pm === 'Shahrivar' - ? 'شهریور' - : pm === 'Mehr' - ? 'مهر' - : pm === 'Aban' - ? 'آبان' - : pm === 'Azar' - ? 'آذر' - : pm === 'Dey' - ? 'دی' - : pm === 'Bahman' - ? 'بهمن' - : pm === 'Esfand' - ? 'اسفند' - : null; - - if (!month) { - throw new ServerError('We need the month string'); - } - - const tittle = `نیازهای ${month} ماه کودکان شما`; - - const today = new Date(); - const englishMonth = today.getMonth(); - const englishYear = today.getFullYear(); - const campaignNumber = Number(`${englishMonth}${englishYear}`); - const receivers = []; - const contentNeeds = []; - const contentSignatures = []; - for await (const user of shuffleArray( - users.filter((u) => u.userName === 'ehsan'), - )) { - let nestUser = await this.userService.getFamilyByFlaskId(user.id); - if (!nestUser) { - nestUser = await this.userService.createFamily(user.id); - } - const myChildren = []; - if ( - nestUser.monthlyEmail || - nestUser.campaigns.find((c) => c.campaignNumber === campaignNumber) - ) { - // 1 - get children who are presents - const children = ( - await this.childrenService.getMyChildren(user.id) - ).filter((c) => c.existence_status === ChildExistence.AlivePresent); - - // shuffle children and get needs per child - for await (const child of shuffleArray(children)) { - if (myChildren.length < 3) { - const childUnpaidNeeds = - await this.needService.getFlaskChildUnpaidNeeds(child.id); - - const shuffledNeeds = shuffleArray(childUnpaidNeeds); - - // prioritize partial paid needs and take the first two need - const organizedNeeds = shuffledNeeds - .sort((a, b) => b.status - a.status) - .slice(0, 2); - console.log(child.id + '--------- Child--------'); - shuffledNeeds.forEach((n) => console.log(n.status)); - console.log('-----------------'); - organizedNeeds.forEach((n) => console.log(n.status)); - - // const noPayments = shuffledNeeds.filter( - // (n) => n.status === PaymentStatusEnum.NOT_PAID, - // ); - const theChild = { - id: child.id, - sayName: child.sayname_translations.fa, - avatar: prepareUrl(child.awakeAvatarUrl), - unPaidNeeds: organizedNeeds.map((n) => { - return { - id: n.id, - name: n.name_translations.fa, - price: n._cost.toLocaleString(), - image: - n.type === NeedTypeEnum.PRODUCT - ? n.img - : prepareUrl(n.imageUrl), - }; - }), - }; - contentNeeds.push([...organizedNeeds]); - myChildren.push(theChild); - } - } - const readyToSignNeeds = ( - await this.familyService.getFamilyReadyToSignNeeds(user.id) - ).filter((n) => n.midjourneyImage); - - // await this.mailerService.sendMail({ - // to: user.emailAddress, - // from: '"Support Team" ', // override default from - // subject: `نیازهای ${month} ماه کودکان شما`, - // template: './monthlySummary', // `.hbs` extension is appended automatically - // context: { - // myChildren, - // readyToSignNeeds, - // }, - // }); - contentSignatures.push(readyToSignNeeds); - receivers.push(nestUser); - } else { - continue; - } - } - await this.createCampaign( - campaignNumber, - CampaignEnum.MONTHLY_SUMMARIES, - CampaignTypeEnum.EMAIL, - tittle, - contentNeeds, - contentSignatures, - receivers, - ); - } catch (e) { - console.log(e); - throw new ServerError('Cold not send email!'); - } - } -} diff --git a/src/features/mail/templates/monthlySummary copy.hbs b/src/features/mail/templates/monthlySummary copy.hbs deleted file mode 100644 index b162ca949..000000000 --- a/src/features/mail/templates/monthlySummary copy.hbs +++ /dev/null @@ -1,2885 +0,0 @@ - - - - - - - - - - New Template - - - - - - - - - - - - - - -
- -
- - - - -
- - - - - -
- - - - -
- - - - - - - -
- - - - -
- - - - -
- SAY logo -
-
-
- - - - - - -
- - - - -
- دپلیکیشن -
-
- - - - - -
- - - - -
- - - - - -
-
- - - - - -
- - - - -
- مستندات -
-
- -
-
- - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
-
-
-
- - - - - -
- - - - - - - -
-

-   - آخرین - نیاز - کودکان -   -

-
- -
-
-
-
- - - - -
- - - - - - - - - - -
- - - - - - - -
- - - - -
- - - - -
-
-
- - - - -
- - - - -
-
-
-
- - - - - -
- - - - - - - -
-

-   {{sayName1}} -

-
- - - - -
-
-
- - - - - -
- - - - -
- -
-
- -
- - - - - - -
- - - - - - - - - - -
-

- آخرین نیازها -

-
- - - - -
-
-

-   - - دیگر نیاز‌های کودک ... -   -

-
-
- - - - - -
- - - - - - - - - - - - - -
- {{needName11}} -
-

- {{needPrice11}} -

-
-

- {{needName11}} -

-
- پرداخت -
-
- - - - - -
- - - - - - - - - - - - - -
- {{needName12}} -
-

- {{needPrice12}} -

-
-

- {{needName12}} -

-
- پرداخت -
-
- -
-
- - - - -
- - - - - - - - - - -
- - - - -
- - - - -
- - - - - -
-
-
- - - - - -
- - - - - - - -
-

-   {{sayName2}} -   -

-
- - - - -
-
-
- - - - - -
- - - - -
- -
-
- -
- - - - - - -
- - - - - - - - - - -
-

- آخرین نیازها -

-
- - - - -
-
-

-   - - دیگر نیاز‌های کودک ...
-   -

-
-
- - - - - -
- - - - - - - - - - - - - -
- {{needName21}} -
-

- {{needPrice21}} -

-
-

- {{needName21}} -

-
- پرداخت -
-
- - - - - -
- - - - - - - - - - - - - -
- {{needName22}} -
-

- {{needPrice22}} -

-
-

- {{needName22}} -

-
- پرداخت -
-
- -
-
- - - - -
- - - - - - - -
- - - - -
- - - - - - - -
- - ... تمام کودکان -
- - - - -
-
-
-
- - - - - -
- - - - - - - - - - - - - -
-

-   - امضای - دیجیتال -   -

-
-

- نیازی که توسط خانواده مجازی پرداخت شده - باشد و به دست کودک رسیده باشد وارد مرحله - تازه ای می‌شود. در این مرحله امکان امضای - دیجیتال نیاز توسط مددکار، شاهد، میانجی و - خانواده مجازی فراهم می‌شود که خود پیش نیاز - توسعه SAY بر روی بستر بلاک‌چین و تبدیل شدن - به سازمانی غیرمتمرکز و خودمختار است. -

-
- ...مطالعه بیشتر -
- -
-
-
-
- - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
-
-
-
- - {{#if signatureImage1}} - - - - - - - - -
- - - - -
- {{signatureName1}} -
-
- - - - -
-

- {{signatureName1}} -

-
-
- {{/if}} - - - {{#if signatureImage2}} - - - - - - - - -
- - - - -
- {{signatureName2}} -
-
- - - - -
-

- {{signatureName2}} -

-
-
- {{/if}} - -
-
- - -
- - - - -
- - - - -
- - - - -
- - - - -
- ...دیگر امضا‌ها -
-
-
-
- - - - -
- - - - -
- - - - -
- - - -
-
-
-
- - - - -
- - - - -
- - - - -
- - - - - - - - - - -
-

- در صورتی که با این نشانی ایمیل اقدام به - همراهی ما نکرده‌اید لطفا این ایمیل را - ندیده بگیرید و روی لینک زیر کلیک - کنید -

-
-

- لغو همراهی -

-
- -
-
-
-
-
-
- - - \ No newline at end of file diff --git a/src/features/mail/templates/monthlySummary.hbs b/src/features/mail/templates/monthlySummary.hbs deleted file mode 100644 index d1fd20344..000000000 --- a/src/features/mail/templates/monthlySummary.hbs +++ /dev/null @@ -1,2722 +0,0 @@ - - - - - - - - - New Template - - - - - - - - - - - - - - -
- -
- - - - -
- - - - - -
- - - - -
- - - - - - - -
- - - - -
- - - - -
- SAY logo -
-
-
- - - - - -
- - - - -
- وبسایت -
-
- - - - - -
- - - - -
- مستندات -
-
- - - - -
- - - - -
- دپلیکیشن -
-
- -
-
- {{!-- Curvey line --}} - - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
-
-
-
- - - - -
- - - - - {{!-- Curvey line --}} - - - -
-

-   - آخرین - نیاز - کودکان -   -

-
- -
-
-
-
- {{!-- Child table --}} {{#each myChildren as | child |}} - - - - -
- - {{!-- Sayname --}} {{!-- Avatar --}} - - - - - - -
- - - - - -
- - - - - - - -
-

-   {{child.sayName}} -

-
- - - - -
-
-
- - - - - -
- - - - -
- -
-
- -
- {{!-- آخرین نیاز Box --}} - - - - - -
- - - - - - - - - - -
-

- آخرین نیازها -

-
- - - - -
-
-

-   - - دیگر نیاز‌های کودک ... -   -

-
-
- {{#each unPaidNeeds as |need|}} - - - - -
- - - - - - - - - - - - - -
- {{need.name}} -
-

- {{need.price}} تومان -

-
-

- {{need.name}} -

-
- پرداخت -
-
- {{!-- Need --}} {{/each}} -
-
- {{/each}} {{!-- Child table END --}} - - - - - -
- - - - - - - -
- - - - -
- - - - - - - -
- - تمام کودکان ... -
- - - - -
-
-
-
- - - - - -
- - - - - - - - - - - - - -
-

-   - امضای - دیجیتال -   -

-
-

- نیازی که توسط خانواده مجازی پرداخت شده - باشد و به دست کودک رسیده باشد وارد مرحله - تازه ای می‌شود. در این مرحله امکان امضای - دیجیتال نیاز توسط مددکار، شاهد، میانجی و - خانواده مجازی فراهم می‌شود که خود پیش نیاز - توسعه SAY بر روی بستر بلاک‌چین و تبدیل شدن - به سازمانی غیرمتمرکز و خودمختار است. -

-
- مطالعه بیشتر ... -
- -
-
-
-
- - - - -
- - - - -
- {{#each readyToSignNeeds as |readyNeed|}} - - - - - - - - -
- - - - -
- {{readyNeed.name}} -
-
- - - - -
-

- {{readyNeed.name}} -

-
-
- {{/each}} -
-
- - - - - -
- - - - -
- - - - -
- - - - -
- دیگر امضا‌ها ... -
-
-
-
- - - - - - -
- - - - -
- - - - -
- - - -
-
-
-
- - - - -
- - - - -
- - - - -
- - - - - - - - - - -
-

- در صورتی که با این نشانی ایمیل اقدام به - همراهی ما نکرده‌اید لطفا این ایمیل را - ندیده بگیرید و روی لینک زیر کلیک - کنید -

-
-

- لغو همراهی -

-
- -
-
-
-
-
-
- - diff --git a/src/features/midjourney/midjourney.module.ts b/src/features/midjourney/midjourney.module.ts index f03147338..8ca94815b 100644 --- a/src/features/midjourney/midjourney.module.ts +++ b/src/features/midjourney/midjourney.module.ts @@ -22,12 +22,23 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { UserFamily } from 'src/entities/flaskEntities/userFamily.entity'; import { PaymentEntity } from 'src/entities/payment.entity'; import { VariableEntity } from 'src/entities/variable.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ HttpModule, TypeOrmModule.forFeature( - [Child, Need, SocialWorker, User, Family, UserFamily], + [ + Child, + Need, + SocialWorker, + User, + Family, + UserFamily, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/milestone/milestone.module.ts b/src/features/milestone/milestone.module.ts index 1f281c0c5..dbe634478 100644 --- a/src/features/milestone/milestone.module.ts +++ b/src/features/milestone/milestone.module.ts @@ -26,11 +26,23 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { MileStoneMiddleware } from './middlewares/milestone.middleware'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, SocialWorker, Child, Payment, UserFamily, Family, User], + [ + Need, + SocialWorker, + Child, + Payment, + UserFamily, + Family, + User, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ @@ -43,7 +55,7 @@ import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.enti AllUserEntity, PaymentEntity, EthereumAccountEntity, - ChildrenPreRegisterEntity + ChildrenPreRegisterEntity, ]), ScheduleModule.forRoot(), HttpModule, diff --git a/src/features/mine/mine.module.ts b/src/features/mine/mine.module.ts index 5afcf87c7..a86890baf 100644 --- a/src/features/mine/mine.module.ts +++ b/src/features/mine/mine.module.ts @@ -15,10 +15,15 @@ import { UserFamily } from 'src/entities/flaskEntities/userFamily.entity'; import { NeedService } from '../need/need.service'; import { VariableEntity } from 'src/entities/variable.entity'; import { AllUserEntity } from 'src/entities/user.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Need, User, Family, UserFamily], 'flaskPostgres'), + TypeOrmModule.forFeature( + [Need, User, Family, UserFamily, Receipt, NeedReceipt], + 'flaskPostgres', + ), TypeOrmModule.forFeature([ NeedEntity, PaymentEntity, diff --git a/src/features/need/need.module.ts b/src/features/need/need.module.ts index ba400b7ce..d514f8767 100644 --- a/src/features/need/need.module.ts +++ b/src/features/need/need.module.ts @@ -41,6 +41,8 @@ import { ProviderJoinNeedEntity } from 'src/entities/provider.Join.need..entity' import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; import { Countries } from 'src/entities/flaskEntities/countries.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -59,6 +61,8 @@ import { Countries } from 'src/entities/flaskEntities/countries.entity'; User, Cities, Countries, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), diff --git a/src/features/need/need.service.ts b/src/features/need/need.service.ts index 415f70d6c..2168161e0 100644 --- a/src/features/need/need.service.ts +++ b/src/features/need/need.service.ts @@ -130,6 +130,17 @@ export class NeedService { }); } + getFlaskChildUnconfirmedNeeds(flaskChildId: number): Promise { + return this.flaskNeedRepository.find({ + where: { + child_id: flaskChildId, + isConfirmed: false, + isDeleted: false, + }, + }); + } + + getFlaskPreNeed(accessToken: any): Promise { const preneedApi = new PreneedAPIApi(); const preneeds = preneedApi.apiV2PreneedsGet(accessToken); @@ -854,9 +865,9 @@ export class NeedService { startDate: new Date(2019, 1, 1), }) .andWhere('need.confirmDate < :endDate', { endDate: date }) - // .andWhere('need.status = :statusNotPaid', { - // statusNotPaid: PaymentStatusEnum.NOT_PAID, - // }) + .andWhere('need.status <= :statusNotPaid', { + statusNotPaid: PaymentStatusEnum.PARTIAL_PAY, + }) .select([ 'need.id', 'need.status', diff --git a/src/features/ngo/ngo.module.ts b/src/features/ngo/ngo.module.ts index 96b1cde05..4e515e41e 100644 --- a/src/features/ngo/ngo.module.ts +++ b/src/features/ngo/ngo.module.ts @@ -35,6 +35,8 @@ import { NgoMiddleware } from './middlewares/ngo.middleware'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; import { Countries } from 'src/entities/flaskEntities/countries.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -50,6 +52,8 @@ import { Countries } from 'src/entities/flaskEntities/countries.entity'; Family, User, Countries, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), diff --git a/src/features/payment/payment.module.ts b/src/features/payment/payment.module.ts index f9629480b..1c6d71eca 100644 --- a/src/features/payment/payment.module.ts +++ b/src/features/payment/payment.module.ts @@ -20,11 +20,23 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { PaymentMiddleware } from './middlewares/payment.middleware'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, SocialWorker, Child, Payment, UserFamily, Family, User], + [ + Need, + SocialWorker, + Child, + Payment, + UserFamily, + Family, + User, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/payment/payment.service.ts b/src/features/payment/payment.service.ts index ff837c42a..80fd0bfb9 100644 --- a/src/features/payment/payment.service.ts +++ b/src/features/payment/payment.service.ts @@ -130,12 +130,23 @@ export class PaymentService { endDate: new Date(d2), }) .andWhere('payment.need_amount > :amount', { amount: 0 }) // only positive amounts - .andWhere('payment.id_user != :sayId', { sayId: SAY_DAPP_ID }) // only positive amounts - + .andWhere('payment.id_user != :sayId', { sayId: SAY_DAPP_ID }) .andWhere('payment.id_need IS NOT NULL') .andWhere('payment.id IS NOT NULL') .andWhere('payment.verified IS NOT NULL') .select(['payment', 'need']) .getManyAndCount(); } + + getFamilyCredit(flaskUserId: number): Promise { + return ( + this.flaskPaymentRepository + .createQueryBuilder('payment') + .andWhere('payment.id_user = :flaskUserId', { flaskUserId }) + .andWhere('payment.verified IS NOT NULL') + // .andWhere('payment.id_need IS NOT NULL') // payments with id_need null are just credit payments - positive credits (used credits) , negative (can use) + .select('SUM(Payment.credit_amount)', 'credit') + .getRawOne() + ); + } } diff --git a/src/features/receipt/receipt.module.ts b/src/features/receipt/receipt.module.ts index 306eaf234..10ee1a1bb 100644 --- a/src/features/receipt/receipt.module.ts +++ b/src/features/receipt/receipt.module.ts @@ -12,11 +12,13 @@ import { Payment } from 'src/entities/flaskEntities/payment.entity'; import { SocialWorker } from 'src/entities/flaskEntities/user.entity'; import { ReceiptMiddleware } from './middlewares/receipt.middleware'; import { VariableEntity } from 'src/entities/variable.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, Child, Payment, SocialWorker], + [Need, Child, Payment, SocialWorker, Receipt, NeedReceipt], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/schedule/schedule.module.ts b/src/features/schedule/schedule.module.ts index 34bc08d09..700081ee1 100644 --- a/src/features/schedule/schedule.module.ts +++ b/src/features/schedule/schedule.module.ts @@ -15,11 +15,24 @@ import { UserFamily } from 'src/entities/flaskEntities/userFamily.entity'; import { PaymentEntity } from 'src/entities/payment.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { AllUserEntity } from 'src/entities/user.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, SocialWorker, Child, User, Family, NGO, Payment, UserFamily], + [ + Need, + SocialWorker, + Child, + User, + Family, + NGO, + Payment, + UserFamily, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/schedule/schedule.service.ts b/src/features/schedule/schedule.service.ts index b982ebc32..014f496da 100644 --- a/src/features/schedule/schedule.service.ts +++ b/src/features/schedule/schedule.service.ts @@ -4,12 +4,12 @@ import { VirtualFamilyRole } from 'src/types/interfaces/interface'; import config from 'src/config'; import { FamilyService } from '../family/family.service'; import { AnalyticService } from '../analytic/analytic.service'; -import { MailService } from '../mail/mail.service'; +import { CampaignService } from '../campaign/campaign.service'; @Injectable() export class ScheduleService { constructor( - private mailService: MailService, + private campaignService: CampaignService, private familyService: FamilyService, private analyticService: AnalyticService, ) {} @@ -92,20 +92,12 @@ export class ScheduleService { this.rolesCount(); } - @Cron(CronExpression.EVERY_WEEK) - async handleWeeklyCron() { - this.logger.debug('Called every Week'); - const data = config().dataCache.fetchFamilyAll(); - if (!data) { - this.completePays(); - } else { - this.logger.debug('Reading from cache'); - } - } - - @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT, { + name: 'ActiveFamilies', + timeZone: 'Asia/Tehran', + }) async handleMonthlyCron() { - this.logger.debug('Called every Month'); + this.logger.debug('Active Families Called every Month'); // active families let actives = config().dataCache.fetchActiveFamilies(); @@ -117,10 +109,36 @@ export class ScheduleService { } } + @Cron(CronExpression.EVERY_WEEK, { + name: 'CompletePayments', + timeZone: 'Asia/Tehran', + }) + async handleWeeklyCron() { + this.logger.debug(' Complete payments of families Called every Week'); + const data = config().dataCache.fetchFamilyAll(); + if (!data) { + this.completePays(); + } else { + this.logger.debug('Reading from cache'); + } + } + + // @Cron('32 13 * * 0', { + // name: 'MonthlyCampaigns', + // timeZone: 'Asia/Tehran', + // }) @Timeout(5000) - // @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_NOON) async handleSummaryMailCron() { - this.logger.debug('Mailing User summaries'); - // await this.mailService.sendUserSummaries(); + this.logger.debug('Sending user Campaigns at 02:00 PM, only on Sunday'); + // await this.campaignService.sendUserMonthlySummaries(); + } + + @Cron('23 8 * * Tue', { + name: 'Reminders', + timeZone: 'Asia/Tehran', + }) + async handleReminderMailCron() { + this.logger.debug('Sending Reminder to Social workers'); + // await this.campaignService.sendSocialWorkersMonthlyReminder(); } } diff --git a/src/features/sync/sync.module.ts b/src/features/sync/sync.module.ts index 26024f1d6..b15e6cd94 100644 --- a/src/features/sync/sync.module.ts +++ b/src/features/sync/sync.module.ts @@ -35,6 +35,8 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; import { Countries } from 'src/entities/flaskEntities/countries.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -50,6 +52,8 @@ import { Countries } from 'src/entities/flaskEntities/countries.entity'; UserFamily, Family, User, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), diff --git a/src/features/ticket/ticket.module.ts b/src/features/ticket/ticket.module.ts index 38e6a2b96..8f475f8e7 100644 --- a/src/features/ticket/ticket.module.ts +++ b/src/features/ticket/ticket.module.ts @@ -39,6 +39,8 @@ import { Family } from 'src/entities/flaskEntities/family.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; import { Countries } from 'src/entities/flaskEntities/countries.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -54,6 +56,8 @@ import { Countries } from 'src/entities/flaskEntities/countries.entity'; Family, User, Countries, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), diff --git a/src/features/user/user.module.ts b/src/features/user/user.module.ts index 1759d1262..3880ec170 100644 --- a/src/features/user/user.module.ts +++ b/src/features/user/user.module.ts @@ -34,11 +34,24 @@ import { UserFamily } from 'src/entities/flaskEntities/userFamily.entity'; import { Family } from 'src/entities/flaskEntities/family.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ TypeOrmModule.forFeature( - [Need, SocialWorker, Child, Payment, NGO, UserFamily, Family, User], + [ + Need, + SocialWorker, + Child, + Payment, + NGO, + UserFamily, + Family, + User, + Receipt, + NeedReceipt, + ], 'flaskPostgres', ), TypeOrmModule.forFeature([ diff --git a/src/features/user/user.service.ts b/src/features/user/user.service.ts index d46986541..1d0b6d027 100644 --- a/src/features/user/user.service.ts +++ b/src/features/user/user.service.ts @@ -203,7 +203,6 @@ export class UserService { return user; } - async createFamily(flaskUserId: number): Promise { const flaskUser = await this.flaskUserRepository .createQueryBuilder('user') @@ -273,6 +272,15 @@ export class UserService { return user; } + getFamilyById(id: string): Promise { + const user = this.allUserRepository.findOne({ + where: { + id, + }, + }); + return user; + } + async deleteOneContributor(userId: string, contIds: string[]) { for await (const id of contIds) { from(this.contributorRepository.delete(id)); diff --git a/src/features/wallet/wallet.module.ts b/src/features/wallet/wallet.module.ts index dda54fa36..8b7b7977a 100644 --- a/src/features/wallet/wallet.module.ts +++ b/src/features/wallet/wallet.module.ts @@ -46,6 +46,8 @@ import { TicketViewEntity } from 'src/entities/ticketView.entity'; import { VariableEntity } from 'src/entities/variable.entity'; import { ChildrenPreRegisterEntity } from 'src/entities/childrenPreRegister.entity'; import { Countries } from 'src/entities/flaskEntities/countries.entity'; +import { Receipt } from 'src/entities/flaskEntities/receipt.entity'; +import { NeedReceipt } from 'src/entities/flaskEntities/needReceipt.entity'; @Module({ imports: [ @@ -73,6 +75,8 @@ import { Countries } from 'src/entities/flaskEntities/countries.entity'; Family, User, Countries, + Receipt, + NeedReceipt, ], 'flaskPostgres', ), diff --git a/src/types/interfaces/interface.ts b/src/types/interfaces/interface.ts index 2830eb936..aabf7bdd7 100644 --- a/src/types/interfaces/interface.ts +++ b/src/types/interfaces/interface.ts @@ -307,8 +307,8 @@ export enum NeedTypeDefinitionEnum { PRODUCT = 'Product', } -export enum CampaignEnum { - MONTHLY_SUMMARIES = 1, +export enum CampaignNameEnum { + MONTHLY_SUMMARIES = 'MONTHLY_SUMMARIES', } export enum CampaignTypeEnum { diff --git a/src/utils/dataCache.ts b/src/utils/dataCache.ts index 270ad46e3..acc746114 100644 --- a/src/utils/dataCache.ts +++ b/src/utils/dataCache.ts @@ -5,7 +5,7 @@ import { VirtualFamilyRole, } from 'src/types/interfaces/interface'; import { quantileSeq, median } from 'mathjs'; -import { getScattered, removeDuplicates } from './helpers'; +import { getScattered } from './helpers'; export default class DataCache { childrenEcosystem = null; @@ -18,6 +18,11 @@ export default class DataCache { childActiveFamilies = null; medianList = []; midjourneyList = []; + countChildrenNoNeeds = null; + + updateChildrenNoNeeds(count: number) { + this.countChildrenNoNeeds = count; + } storeActiveFamilies = (activesList) => { this.childActiveFamilies = { actives: activesList, created: new Date() }; @@ -118,9 +123,10 @@ export default class DataCache { }; }; - fetchMidjourney = () => - (this.midjourneyList = removeDuplicates(this.midjourneyList)); - + fetchMidjourney = () => (this.midjourneyList = this.midjourneyList); + fetchChildrenNoNeeds() { + return this.countChildrenNoNeeds; + } fetchChildrenEcosystem = () => this.childrenEcosystem; fetchFamilyAll = () => this.familyData; fetchFamilyCount = () => this.familyRolesCount; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index cd99d3a4d..111390ee0 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -40,12 +40,17 @@ export function getAllFilesFromFolder(dir: string) { return results; } -export function removeDuplicates(array: any[]) { +// {id: 44 } +export function removeSpecialDuplicates(array: any[]) { return array.filter((obj, index) => { return index === array.findIndex((o) => obj.id === o.id); }); } +export function removeDuplicates(array: any[]) { + return array.filter((item, index) => array.indexOf(item) === index); +} + export function getSAYRoleInteger(sayRole: string) { let roleInteger: SAYPlatformRoles; if (sayRole === 'AUDITOR') { @@ -196,6 +201,35 @@ export function persianMonthString(value: Date) { }).format(value); } +export function persianMonthStringFarsi(value: Date) { + const pm = persianMonthString(value); + return pm === 'Farvardin' + ? 'فروردین' + : pm === 'Ordibehesht' + ? 'اردیبهست' + : pm === 'Khordad' + ? 'خرداد' + : pm === 'Tir' + ? 'تیر' + : pm === 'Mordad' + ? 'مرداد' + : pm === 'Shahrivar' + ? 'شهریور' + : pm === 'Mehr' + ? 'مهر' + : pm === 'Aban' + ? 'آبان' + : pm === 'Azar' + ? 'آذر' + : pm === 'Dey' + ? 'دی' + : pm === 'Bahman' + ? 'بهمن' + : pm === 'Esfand' + ? 'اسفند' + : null; +} + export function persianMonth(value: Date) { if (!value) { return null;