Skip to content

Commit

Permalink
feat(financial-statement-political-party): political party as a new a…
Browse files Browse the repository at this point in the history
…pplication (#16076)

* feat: political party as a new application

* fix: add tags

* fix: failed test and coderabbit

* fix: more coderabbit suggestions

* fix: remove old dev condition

* fix: just change my dataschema

* fix: add missing line to zod

* fix: more codeRabbit comments

* fix: pr comments

* chore: module instead of dynamic module

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
jonnigs and kodiakhq[bot] authored Oct 14, 2024
1 parent 3e13aff commit 83b076f
Show file tree
Hide file tree
Showing 70 changed files with 3,706 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,45 @@ import {
CemeteryFinancialStatementValues,
FinancialStatementsInaoClientService,
ClientRoles,
Contact,
ContactType,
DigitalSignee,
} from '@island.is/clients/financial-statements-inao'
import {
ApplicationTypes,
ApplicationWithAttachments as Application,
PerformActionResult,
} from '@island.is/application/types'
import { getValueViaPath } from '@island.is/application/core'
import AmazonS3URI from 'amazon-s3-uri'
import { TemplateApiModuleActionProps } from '../../../types'
import * as kennitala from 'kennitala'
import {
DataResponse,
getCurrentUserType,
} from '../financial-statements-inao/financial-statements-inao.service'
import {
BoardMember,
FSIUSERTYPE,
} from '@island.is/application/templates/financial-statements-inao/types'
import {
mapValuesToCemeterytype,
getNeededCemeteryValues,
mapContactsAnswersToContacts,
mapDigitalSignee,
} from '../financial-statement-cemetery/mappers/mapValuesToUserType'
import { TemplateApiError } from '@island.is/nest/problem'
import { ApplicationApiAction } from '../../template-api.service'

export type AttachmentData = {
key: string
name: string
}

export interface DataResponse {
success: boolean
message?: string
}

export const getCurrentUserType = (
answers: Application['answers'],
externalData: Application['externalData'],
) => {
const fakeUserType: any = getValueViaPath(answers, 'fakeData.options')

const currentUserType: any = getValueViaPath(
externalData,
'getUserType.data.value',
)
return fakeUserType ?? currentUserType
}

export class FinancialStatementCemeteryTemplateService extends BaseTemplateApiService {
s3: S3
constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getValueViaPath } from '@island.is/application/core'
import { BoardMember } from '@island.is/application/templates/financial-statements-inao/types'
import { FormValue } from '@island.is/application/types'
import {
Contact,
Expand All @@ -8,6 +7,12 @@ import {
DigitalSignee,
} from '@island.is/clients/financial-statements-inao'

type BoardMember = {
nationalId: string
name: string
role: string
}

export const mapValuesToCemeterytype = (answers: FormValue) => {
return {
careIncome: Number(getValueViaPath(answers, 'cemetryIncome.careIncome')),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common'
import { SharedTemplateAPIModule } from '../../shared'
import { ConfigModule } from '@nestjs/config'
import {
FinancialStatementsInaoClientConfig,
FinancialStatementsInaoClientModule,
} from '@island.is/clients/financial-statements-inao'
import { FinancialStatementPoliticalPartyTemplateService } from './financial-statement-political-party.service'

@Module({
imports: [
SharedTemplateAPIModule,
ConfigModule.forRoot({
load: [FinancialStatementsInaoClientConfig],
}),
FinancialStatementsInaoClientModule,
],
providers: [FinancialStatementPoliticalPartyTemplateService],
exports: [FinancialStatementPoliticalPartyTemplateService],
})
export class FinancialStatementPoliticalPartyTemplateModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { Inject, Injectable } from '@nestjs/common'
import { BaseTemplateApiService } from '../../base-template-api.service'
import { S3 } from 'aws-sdk'
import { LOGGER_PROVIDER } from '@island.is/logging'
import type { Logger } from '@island.is/logging'
import {
ApplicationTypes,
ApplicationWithAttachments as Application,
} from '@island.is/application/types'
import {
Contact,
ContactType,
DigitalSignee,
FinancialStatementsInaoClientService,
PoliticalPartyFinancialStatementValues,
} from '@island.is/clients/financial-statements-inao'
import { getValueViaPath } from '@island.is/application/core'
import AmazonS3Uri from 'amazon-s3-uri'
import { TemplateApiModuleActionProps } from '../../../types'
import * as kennitala from 'kennitala'
import { mapValuesToPartyTypes } from './mappers/mapValuesToPartyTypes'

export interface AttachmentData {
key: string
name: string
}

export interface DataResponse {
success: boolean
message?: string
}

export const getCurrentUserType = (
answers: Application['answers'],
externalData: Application['externalData'],
) => {
const fakeUserType = getValueViaPath(answers, 'fakeData.options') as
| number
| undefined

const currentUserType = getValueViaPath(
externalData,
'getUserType.data.value',
) as number | undefined

return fakeUserType ?? currentUserType
}

const PARTY_USER_TYPE = 150000001

@Injectable()
export class FinancialStatementPoliticalPartyTemplateService extends BaseTemplateApiService {
s3: S3
constructor(
@Inject(LOGGER_PROVIDER) private logger: Logger,
private financialStatementClientService: FinancialStatementsInaoClientService,
) {
super(ApplicationTypes.FINANCIAL_STATEMENT_POLITICAL_PARTY)
this.s3 = new S3()
}

private async getAttachment(application: Application): Promise<string> {
const attachments = getValueViaPath(
application.answers,
'attachments.files',
) as Array<AttachmentData>

if (!attachments || attachments.length === 0) {
throw new Error('No attachments found in application')
}

const attachmentKey = attachments[0].key

const fileName = (
application.attachments as {
[key: string]: string
}
)[attachmentKey]

if (!fileName) {
throw new Error('Attachment file name not found')
}

const { bucket, key } = AmazonS3Uri(fileName)

const uploadBucket = bucket
try {
const file = await this.s3
.getObject({ Bucket: uploadBucket, Key: key })
.promise()
const fileContent = file.Body as Buffer
return fileContent?.toString('base64') || ''
} catch (error) {
this.logger.error('Error retrieving attachment from S3', error)
throw new Error('Failed to retrieve attachment from S3')
}
}

async getUserType({ auth }: TemplateApiModuleActionProps) {
const { nationalId } = auth
if (kennitala.isPerson(nationalId)) {
return this.financialStatementClientService.getClientType('Einstaklingur')
} else {
return this.financialStatementClientService.getUserClientType(nationalId)
}
}

async submitApplication({ application, auth }: TemplateApiModuleActionProps) {
const { nationalId, actor } = auth

if (!actor) {
throw new Error('Enginn umboðsmaður fannst')
}

this.validateUserType(application)

const values = this.prepareValues(application)
const year = this.getOperatingYear(application)
const fileName = await this.getAttachment(application)
const client = { nationalId }
const contacts = this.prepareContacts(application, actor)
const digitalSignee = this.prepareDigitalSignee(application)

try {
const result =
await this.financialStatementClientService.postFinancialStatementForPoliticalParty(
client,
contacts,
digitalSignee,
year,
'',
values,
fileName,
)

if (!result) {
throw new Error('Application submission failed')
}

return { success: true }
} catch (error) {
this.logger.error('Error submitting application', error)
return {
success: false,
message: error.message,
}
// throw new Error('Application submission failed')
}
}

private prepareValues(
application: Application,
): PoliticalPartyFinancialStatementValues {
return mapValuesToPartyTypes(application.answers)
}

private getOperatingYear(application: Application) {
const year = getValueViaPath(
application.answers,
'conditionalAbout.operatingYear',
)
if (typeof year !== 'string') {
throw new Error('Operating year not found or invalid')
}
return year
}

private validateUserType(application: Application) {
const currentUserType = getCurrentUserType(
application.answers,
application.externalData,
)
if (currentUserType !== PARTY_USER_TYPE) {
throw new Error('Invalid user type for application submission')
}
}

private prepareContacts(
application: Application,
actor: { nationalId: string },
): Array<Contact> {
const actorsName = getValueViaPath(
application.answers,
'about.powerOfAttorneyName',
) as string
return [
{
nationalId: actor.nationalId,
name: actorsName,
contactType: ContactType.Actor,
},
]
}

private prepareDigitalSignee(application: Application): DigitalSignee {
const clientPhone = getValueViaPath(
application.answers,
'about.phoneNumber',
) as string
const clientEmail = getValueViaPath(
application.answers,
'about.email',
) as string
return {
email: clientEmail,
phone: clientPhone,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getValueViaPath } from '@island.is/application/core'
import { FormValue } from '@island.is/application/types'

export const mapValuesToPartyTypes = (answers: FormValue) => {
return {
contributionsFromTheTreasury: Number(
getValueViaPath(answers, 'partyIncome.contributionsFromTheTreasury'),
),
parliamentaryPartySupport: Number(
getValueViaPath(answers, 'partyIncome.parliamentaryPartySupport'),
),
municipalContributions: Number(
getValueViaPath(answers, 'partyIncome.municipalContributions'),
),
contributionsFromLegalEntities: Number(
getValueViaPath(answers, 'partyIncome.contributionsFromLegalEntities'),
),
contributionsFromIndividuals: Number(
getValueViaPath(answers, 'partyIncome.contributionsFromIndividuals'),
),
generalMembershipFees: Number(
getValueViaPath(answers, 'partyIncome.generalMembershipFees'),
),
otherIncome: Number(getValueViaPath(answers, 'partyIncome.otherIncome')),
capitalIncome: Number(
getValueViaPath(answers, 'capitalNumbers.capitalIncome'),
),
officeOperations: Number(
getValueViaPath(answers, 'partyExpense.electionOffice'),
),
otherOperatingExpenses: Number(
getValueViaPath(answers, 'partyExpense.otherCost'),
),
financialExpenses: Number(
getValueViaPath(answers, 'capitalNumbers.capitalCost'),
),
fixedAssetsTotal: Number(
getValueViaPath(answers, 'asset.fixedAssetsTotal'),
),
currentAssets: Number(getValueViaPath(answers, 'asset.currentAssets')),
longTermLiabilitiesTotal: Number(
getValueViaPath(answers, 'liability.longTerm'),
),
shortTermLiabilitiesTotal: Number(
getValueViaPath(answers, 'liability.shortTerm'),
),
equityTotal: Number(getValueViaPath(answers, 'equity.totalEquity')),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type KeyValue = {
key: number
value: number
}
Loading

0 comments on commit 83b076f

Please sign in to comment.