diff --git a/package.json b/package.json index 4555ea968..17349eadc 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "test:qfRoundHistoryRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/qfRoundHistoryRepository.test.ts", "test:qfRoundService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/qfRoundService.test.ts", "test:project": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/entities/project.test.ts", + "test:projectsTab": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/server/adminJs/tabs/projectsTab.test.ts", "test:syncUsersModelScore": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/syncUsersModelScore.test.ts", "test:notifyDonationsWithSegment": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/notifyDonationsWithSegment.test.ts", "test:checkProjectVerificationStatus": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/checkProjectVerificationStatus.test.ts", diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index a17703158..34bb4f907 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -279,7 +279,7 @@ export const projectsWithoutUpdateAfterTimeFrame = async ( 'project.title', ]) .where('project.isImported = false') - .andWhere('project.verified = true') + .andWhere('project.isGivbackEligible = true') .andWhere( '(project.verificationStatus NOT IN (:...statuses) OR project.verificationStatus IS NULL)', { diff --git a/src/server/adminJs/tabs/projectsTab.test.ts b/src/server/adminJs/tabs/projectsTab.test.ts index 8b53726e3..b1009f4ef 100644 --- a/src/server/adminJs/tabs/projectsTab.test.ts +++ b/src/server/adminJs/tabs/projectsTab.test.ts @@ -35,6 +35,7 @@ import { addFeaturedProjectUpdate, exportProjectsWithFiltersToCsv, listDelist, + revokeGivbacksEligibility, updateStatusOfProjects, verifyProjects, } from './projectsTab'; @@ -452,8 +453,20 @@ function verifyProjectsTestCases() { recordIds: String(project.id), }, }, - true, // give priority to revoke badge - true, // revoke badge + false, + ); + await revokeGivbacksEligibility( + { + currentAdmin: adminUser as User, + h: {}, + resource: {}, + records: [], + }, + { + query: { + recordIds: String(project.id), + }, + }, ); const updatedProject = await findProjectById(project.id); @@ -527,15 +540,20 @@ function verifyProjectsTestCases() { assert.isTrue(updatedProject?.listed); assert.equal(updatedProject?.reviewStatus, ReviewStatus.Listed); assert.isTrue(project!.verificationStatus === RevokeSteps.Revoked); - assert.isTrue(updatedProject!.verificationStatus === null); + assert.isTrue( + updatedProject!.verificationStatus === project.verificationStatus, + ); assert.equal( updatedVerificationForm!.status, - PROJECT_VERIFICATION_STATUSES.VERIFIED, + PROJECT_VERIFICATION_STATUSES.DRAFT, + ); + assert.equal( + updatedVerificationForm!.isTermAndConditionsAccepted, + projectVerificationForm.isTermAndConditionsAccepted, ); - assert.equal(updatedVerificationForm!.isTermAndConditionsAccepted, true); assert.equal( updatedVerificationForm!.lastStep, - PROJECT_VERIFICATION_STEPS.SUBMIT, + projectVerificationForm.lastStep, ); }); @@ -616,12 +634,15 @@ function verifyProjectsTestCases() { assert.isTrue(updatedProject!.verificationStatus === RevokeSteps.Revoked); assert.equal( updatedVerificationForm!.status, - PROJECT_VERIFICATION_STATUSES.DRAFT, + PROJECT_VERIFICATION_STATUSES.VERIFIED, + ); + assert.equal( + updatedVerificationForm!.isTermAndConditionsAccepted, + projectVerificationForm.isTermAndConditionsAccepted, ); - assert.equal(updatedVerificationForm!.isTermAndConditionsAccepted, false); assert.equal( updatedVerificationForm!.lastStep, - PROJECT_VERIFICATION_STEPS.MANAGING_FUNDS, + projectVerificationForm.lastStep, ); }); diff --git a/src/server/adminJs/tabs/projectsTab.ts b/src/server/adminJs/tabs/projectsTab.ts index a61aedfa6..49e2e29b9 100644 --- a/src/server/adminJs/tabs/projectsTab.ts +++ b/src/server/adminJs/tabs/projectsTab.ts @@ -184,29 +184,72 @@ export const addFeaturedProjectUpdate = async ( }; }; +export const revokeGivbacksEligibility = async ( + context: AdminJsContextInterface, + request: AdminJsRequestInterface, +) => { + const { records, currentAdmin } = context; + try { + const projectIds = request?.query?.recordIds + ?.split(',') + ?.map(strId => Number(strId)) as number[]; + const updateParams = { isGivbackEligible: false }; + const projects = await Project.createQueryBuilder('project') + .update(Project, updateParams) + .where('project.id IN (:...ids)') + .setParameter('ids', projectIds) + .returning('*') + .updateEntity(true) + .execute(); + + for (const project of projects.raw) { + const projectWithAdmin = (await findProjectById(project.id)) as Project; + projectWithAdmin.verificationStatus = RevokeSteps.Revoked; + await projectWithAdmin.save(); + await getNotificationAdapter().projectBadgeRevoked({ + project: projectWithAdmin, + }); + const verificationForm = await getVerificationFormByProjectId(project.id); + if (verificationForm) { + await makeFormDraft({ + formId: verificationForm.id, + adminId: currentAdmin.id, + }); + } + } + await Promise.all([ + refreshUserProjectPowerView(), + refreshProjectPowerView(), + refreshProjectFuturePowerView(), + ]); + } catch (error) { + logger.error('revokeGivbacksEligibility() error', error); + throw error; + } + return { + redirectUrl: '/admin/resources/Project', + records: records.map(record => { + record.toJSON(context.currentAdmin); + }), + notice: { + message: 'Project(s) successfully revoked from Givbacks eligibility', + type: 'success', + }, + }; +}; + export const verifyProjects = async ( context: AdminJsContextInterface, request: AdminJsRequestInterface, - verified: boolean = true, - revokeBadge: boolean = false, + vouchedStatus: boolean = true, ) => { const { records, currentAdmin } = context; - // prioritize revokeBadge - const verificationStatus = revokeBadge ? false : verified; try { const projectIds = request?.query?.recordIds ?.split(',') ?.map(strId => Number(strId)) as number[]; const projectsBeforeUpdating = await findProjectsByIdArray(projectIds); - const updateParams = { verified: verificationStatus }; - - if (verificationStatus) { - await Project.query(` - UPDATE project - SET "verificationStatus" = NULL - WHERE id IN (${request?.query?.recordIds}) - `); - } + const updateParams = { verified: vouchedStatus }; const projects = await Project.createQueryBuilder('project') .update(Project, updateParams) @@ -219,11 +262,11 @@ export const verifyProjects = async ( for (const project of projects.raw) { if ( projectsBeforeUpdating.find(p => p.id === project.id)?.verified === - verificationStatus + vouchedStatus ) { logger.debug('verifying/unVerifying project but no changes happened', { projectId: project.id, - verificationStatus, + verificationStatus: vouchedStatus, }); // if project.verified have not changed, so we should not execute rest of the codes continue; @@ -232,42 +275,10 @@ export const verifyProjects = async ( project, status: project.status, userId: currentAdmin.id, - description: verified + description: vouchedStatus ? HISTORY_DESCRIPTIONS.CHANGED_TO_VERIFIED : HISTORY_DESCRIPTIONS.CHANGED_TO_UNVERIFIED, }); - const projectWithAdmin = (await findProjectById(project.id)) as Project; - - if (revokeBadge) { - projectWithAdmin.verificationStatus = RevokeSteps.Revoked; - await projectWithAdmin.save(); - await getNotificationAdapter().projectBadgeRevoked({ - project: projectWithAdmin, - }); - } else if (verificationStatus) { - await getNotificationAdapter().projectVerified({ - project: projectWithAdmin, - }); - } else { - await getNotificationAdapter().projectUnVerified({ - project: projectWithAdmin, - }); - } - - const verificationForm = await getVerificationFormByProjectId(project.id); - if (verificationForm) { - if (verificationStatus) { - await makeFormVerified({ - formId: verificationForm.id, - adminId: currentAdmin.id, - }); - } else { - await makeFormDraft({ - formId: verificationForm.id, - adminId: currentAdmin.id, - }); - } - } } await Promise.all([ @@ -286,7 +297,7 @@ export const verifyProjects = async ( }), notice: { message: `Project(s) successfully ${ - verificationStatus ? 'verified' : 'unverified' + vouchedStatus ? 'vouched' : 'unvouched' }`, type: 'success', }, @@ -1273,7 +1284,7 @@ export const projectsTab = { }, component: false, }, - verify: { + approveVouched: { actionType: 'bulk', isVisible: true, isAccessible: ({ currentAdmin }) => @@ -1286,7 +1297,7 @@ export const projectsTab = { }, component: false, }, - reject: { + removeVouched: { actionType: 'bulk', isVisible: true, isAccessible: ({ currentAdmin }) => @@ -1299,8 +1310,7 @@ export const projectsTab = { }, component: false, }, - // the difference is that it sends another segment event - revokeBadge: { + revokeGivbacksEligible: { actionType: 'bulk', isVisible: true, isAccessible: ({ currentAdmin }) => @@ -1308,8 +1318,8 @@ export const projectsTab = { { currentAdmin }, ResourceActions.REVOKE_BADGE, ), - handler: async (request, response, context) => { - return verifyProjects(context, request, false, true); + handler: async (request, _response, context) => { + return revokeGivbacksEligibility(context, request); }, component: false, }, diff --git a/src/services/cronJobs/checkProjectVerificationStatus.test.ts b/src/services/cronJobs/checkProjectVerificationStatus.test.ts index 3b1d2d66e..998ac3b46 100644 --- a/src/services/cronJobs/checkProjectVerificationStatus.test.ts +++ b/src/services/cronJobs/checkProjectVerificationStatus.test.ts @@ -24,7 +24,7 @@ function checkProjectVerificationStatusTestCases() { ...createProjectData(), title: String(new Date().getTime()), slug: String(new Date().getTime()), - verified: true, + isGivbackEligible: true, latestUpdateCreationDate: moment() .subtract(46, 'days') .endOf('day') @@ -36,7 +36,7 @@ function checkProjectVerificationStatusTestCases() { const warnableProjectUpdate = await findProjectById(warnableProject.id); - assert.isTrue(warnableProjectUpdate!.verified); + assert.isTrue(warnableProjectUpdate!.isGivbackEligible); assert.equal( warnableProjectUpdate!.verificationStatus, RevokeSteps.Warning, @@ -47,7 +47,7 @@ function checkProjectVerificationStatusTestCases() { ...createProjectData(), title: String(new Date().getTime()), slug: String(new Date().getTime()), - verified: true, + isGivbackEligible: true, latestUpdateCreationDate: moment().subtract(91, 'days').endOf('day'), verificationStatus: RevokeSteps.Warning, }); @@ -56,7 +56,7 @@ function checkProjectVerificationStatusTestCases() { const warnableProjectUpdate = await findProjectById(warnableProject.id); - assert.isTrue(warnableProjectUpdate!.verified); + assert.isTrue(warnableProjectUpdate!.isGivbackEligible); assert.equal( warnableProjectUpdate!.verificationStatus, RevokeSteps.LastChance, @@ -67,7 +67,7 @@ function checkProjectVerificationStatusTestCases() { ...createProjectData(), title: String(new Date().getTime()), slug: String(new Date().getTime()), - verified: true, + isGivbackEligible: true, latestUpdateCreationDate: moment().subtract(105, 'days').endOf('day'), verificationStatus: RevokeSteps.LastChance, }); @@ -78,7 +78,7 @@ function checkProjectVerificationStatusTestCases() { lastWarningProject.id, ); - assert.isTrue(lastWarningProjectUpdated!.verified); + assert.isTrue(lastWarningProjectUpdated!.isGivbackEligible); assert.equal( lastWarningProjectUpdated!.verificationStatus, RevokeSteps.UpForRevoking, @@ -89,7 +89,7 @@ function checkProjectVerificationStatusTestCases() { ...createProjectData(), title: String(new Date().getTime()), slug: String(new Date().getTime()), - verified: true, + isGivbackEligible: true, latestUpdateCreationDate: moment().subtract(105, 'days').endOf('day'), isImported: true, }); @@ -98,7 +98,7 @@ function checkProjectVerificationStatusTestCases() { const importedProjectUpdated = await findProjectById(importedProject.id); - assert.isTrue(importedProjectUpdated!.verified); + assert.isTrue(importedProjectUpdated!.isGivbackEligible); assert.equal(importedProjectUpdated!.verificationStatus, null); }); // it('should revoke project verification after last chance time frame expired', async () => {