Skip to content

Commit

Permalink
Merge pull request #59 from icefoganalytics/elcc-18/support-customiza…
Browse files Browse the repository at this point in the history
…tion-of-employee-benefits-section

ELCC 18: Support Customization of Employee Benefits Section
  • Loading branch information
klondikemarlen authored Dec 14, 2023
2 parents d4e5ed7 + 54e7b77 commit be2a20f
Show file tree
Hide file tree
Showing 25 changed files with 1,126 additions and 135 deletions.
93 changes: 93 additions & 0 deletions api/src/controllers/employee-benefits-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { isNil } from "lodash"
import { WhereOptions } from "sequelize"

import BaseController from "./base-controller"

import { EmployeeBenefit } from "@/models"
import { EmployeeBenefitSerializer } from "@/serializers"

export class EmployeeBenefitsController extends BaseController {
index() {
const where = this.query.where as WhereOptions<EmployeeBenefit>
return EmployeeBenefit.findAll({
where,
})
.then((employeeBenefits) => {
const serializedEmployeeBenefits = EmployeeBenefitSerializer.asTable(employeeBenefits)
return this.response.json({
employeeBenefits: serializedEmployeeBenefits,
})
})
.catch((error) => {
return this.response
.status(400)
.json({ message: `Invalid query for employee benefits: ${error}` })
})
}

async show() {
const employeeBenefit = await this.loadEmployeeBenefit()
if (isNil(employeeBenefit))
return this.response.status(404).json({ message: "employee benefit not found." })

const serializedemployeeBenefit = EmployeeBenefitSerializer.asDetailed(employeeBenefit)
return this.response.json({
employeeBenefit: serializedemployeeBenefit,
})
}

async create() {
return EmployeeBenefit.create(this.request.body)
.then((employeeBenefit) => {
return this.response.status(201).json({ employeeBenefit })
})
.catch((error) => {
return this.response
.status(422)
.json({ message: `employee benefit creation failed: ${error}` })
})
}

async update() {
const employeeBenefit = await this.loadEmployeeBenefit()
if (isNil(employeeBenefit))
return this.response.status(404).json({ message: "employee benefit not found." })

return employeeBenefit
.update(this.request.body)
.then((employeeBenefit) => {
const serializedemployeeBenefit = EmployeeBenefitSerializer.asDetailed(employeeBenefit)
return this.response.json({
employeeBenefit: serializedemployeeBenefit,
})
})
.catch((error) => {
return this.response
.status(422)
.json({ message: `employee benefit update failed: ${error}` })
})
}

async destroy() {
const employeeBenefit = await this.loadEmployeeBenefit()
if (isNil(employeeBenefit))
return this.response.status(404).json({ message: "employee benefit not found." })

return employeeBenefit
.destroy()
.then(() => {
return this.response.status(204).end()
})
.catch((error) => {
return this.response
.status(422)
.json({ message: `employee benefit deletion failed: ${error}` })
})
}

private loadEmployeeBenefit(): Promise<EmployeeBenefit | null> {
return EmployeeBenefit.findByPk(this.params.employeeBenefitId)
}
}

export default EmployeeBenefitsController
1 change: 1 addition & 0 deletions api/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { EmployeeBenefitsController } from "./employee-benefits-controller"
export { FiscalPeriodsController } from "./fiscal-periods-controller"
export { FundingSubmissionLineJsonsController } from "./funding-submission-line-jsons-controller"
export { PaymentsController } from "./payments-controller"
60 changes: 0 additions & 60 deletions api/src/db/data/funding-submission-lines.json
Original file line number Diff line number Diff line change
Expand Up @@ -389,36 +389,6 @@
"createdAt": "2023-09-29T17:43:40.860Z",
"updatedAt": "2023-09-29T17:43:40.860Z"
},
{
"fiscalYear": "2022/23",
"sectionName": "Employee Benefits",
"lineName": "Gross Payroll",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:40.870Z",
"updatedAt": "2023-09-29T17:43:40.870Z"
},
{
"fiscalYear": "2022/23",
"sectionName": "Employee Benefits",
"lineName": "Employee Cost",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:40.876Z",
"updatedAt": "2023-09-29T17:43:40.876Z"
},
{
"fiscalYear": "2022/23",
"sectionName": "Employee Benefits",
"lineName": "Employer Cost",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:40.883Z",
"updatedAt": "2023-09-29T17:43:40.883Z"
},
{
"fiscalYear": "2022/23",
"sectionName": "Wage Enhancement",
Expand Down Expand Up @@ -879,36 +849,6 @@
"createdAt": "2023-09-29T17:43:41.160Z",
"updatedAt": "2023-09-29T17:43:41.160Z"
},
{
"fiscalYear": "2023/24",
"sectionName": "Employee Benefits",
"lineName": "Gross Payroll",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:41.166Z",
"updatedAt": "2023-09-29T17:43:41.166Z"
},
{
"fiscalYear": "2023/24",
"sectionName": "Employee Benefits",
"lineName": "Employee Cost",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:41.170Z",
"updatedAt": "2023-09-29T17:43:41.170Z"
},
{
"fiscalYear": "2023/24",
"sectionName": "Employee Benefits",
"lineName": "Employer Cost",
"fromAge": 0,
"toAge": 0,
"monthlyAmount": 0.0,
"createdAt": "2023-09-29T17:43:41.176Z",
"updatedAt": "2023-09-29T17:43:41.176Z"
},
{
"fiscalYear": "2023/24",
"sectionName": "Wage Enhancement",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Migration } from "@/db/umzug"

export const up: Migration = async ({ context: queryInterface }) => {
await queryInterface.addIndex("fiscal_periods", {
fields: ["fiscal_year", "month"],
unique: true,
})
}

export const down: Migration = async ({ context: queryInterface }) => {
await queryInterface.removeIndex("fiscal_periods", ["fiscal_year", "month"])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Migration } from "@/db/umzug"

type ForeignKeyReference = {
constraint_name: string // "FK__employee___fislc__22401542"
constraintName: string // "FK__employee___fislc__22401542"
constraintCatalog: string // "elcc_development"
constraintSchema: string // "dbo"
tableName: string // "employee_benefits"
tableSchema: string // "dbo"
tableCatalog: string // "elcc_development"
columnName: string // "fislcal_period_id"
referencedTableSchema: string // "dbo"
referencedCatalog: string // "elcc_development"
referencedTableName: string // "fiscal_periods"
referencedColumnName: string // "id"
}

export const up: Migration = async ({ context: queryInterface }) => {
const references = (await queryInterface.getForeignKeyReferencesForTable(
"employee_benefits"
)) as ForeignKeyReference[]

const foreignKey = references.find((reference) => reference.columnName === "fislcal_period_id")
if (foreignKey !== undefined) {
await queryInterface.removeConstraint("employee_benefits", foreignKey.constraintName)
}

await queryInterface.renameColumn("employee_benefits", "fislcal_period_id", "fiscal_period_id")

await queryInterface.addConstraint("employee_benefits", {
fields: ["fiscal_period_id"],
type: "foreign key",
references: {
table: "fiscal_periods",
field: "id",
},
onDelete: "", // RESTRICT is default for MSSQL, so you can't set it; and must use and empty string
onUpdate: "",
})
}

export const down: Migration = async ({ context: queryInterface }) => {
const references = (await queryInterface.getForeignKeyReferencesForTable(
"employee_benefits"
)) as ForeignKeyReference[]

const foreignKey = references.find((reference) => reference.columnName === "fiscal_period_id")
if (foreignKey !== undefined) {
await queryInterface.removeConstraint("employee_benefits", foreignKey.constraintName)
}

await queryInterface.renameColumn("employee_benefits", "fiscal_period_id", "fislcal_period_id")

await queryInterface.addConstraint("employee_benefits", {
fields: ["fislcal_period_id"],
type: "foreign key",
references: {
table: "fiscal_periods",
field: "id",
},
onDelete: "", // RESTRICT is default for MSSQL, so you can't set it; and must use and empty string
onUpdate: "",
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Migration } from "@/db/umzug"

export const up: Migration = async ({ context: queryInterface }) => {
await queryInterface.addIndex("employee_benefits", {
fields: ["centre_id", "fiscal_period_id"],
unique: true,
})
}

export const down: Migration = async ({ context: queryInterface }) => {
await queryInterface.removeIndex("employee_benefits", ["centre_id", "fiscal_period_id"])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Migration } from "@/db/umzug"

import { FundingSubmissionLine, FundingSubmissionLineJson } from "@/models"

export const up: Migration = async () => {
const EMPLOYEE_BENEFITS_SECTION_NAME = "Employee Benefits"

const fundingSubmissionLines = await FundingSubmissionLine.findAll({
where: {
sectionName: EMPLOYEE_BENEFITS_SECTION_NAME,
},
})
const fundingSubmissionLinesIds = fundingSubmissionLines.map(
(fundingSubmissionLine) => fundingSubmissionLine.id
)

await FundingSubmissionLineJson.findEach(async (fundingSubmissionLineJson) => {
const lines = fundingSubmissionLineJson.lines
const cleanLines = lines.filter(
(line) => !fundingSubmissionLinesIds.includes(line.submissionLineId)
)

await fundingSubmissionLineJson.update({ lines: cleanLines })
return
})

await FundingSubmissionLine.destroy({
where: {
sectionName: EMPLOYEE_BENEFITS_SECTION_NAME,
},
})
}

export const down: Migration = async () => {
// no-op - this migration is not reversible, it is however idempotent
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const up: SeedMigration = async ({ context: { FiscalPeriod } }) => {
for (let i = 0; i < 12; i++) {
const dateStart = moment(date).startOf("month")
const dateEnd = moment(dateStart).endOf("month").milliseconds(0)
const dateName = dateStart.format("MMMM")
const dateName = dateStart.format("MMMM").toLowerCase()

const fiscalYear = `${year}-${(year + 1).toString().slice(-2)}`

Expand Down
70 changes: 70 additions & 0 deletions api/src/models/base-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Attributes, CreationOptional, FindOptions, Model, ModelStatic, Op } from "sequelize"

// See /home/marlen/code/icefoganalytics/elcc-data-management/api/node_modules/sequelize/types/model.d.ts -> Model
export abstract class BaseModel<
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
TModelAttributes extends {} = any,
// eslint-disable-next-line @typescript-eslint/ban-types
TCreationAttributes extends {} = TModelAttributes,
> extends Model<TModelAttributes, TCreationAttributes> {
declare id: CreationOptional<number>

// See /home/marlen/code/icefoganalytics/elcc-data-management/api/node_modules/sequelize/types/model.d.ts -> findAll
// Taken from https://api.rubyonrails.org/v7.1.0/classes/ActiveRecord/Batches.html#method-i-find_each
// Enforces sort by id, overwriting any supplied order
public static async findEach<M extends BaseModel>(
this: ModelStatic<M>,
processFunction: (record: M) => Promise<void>
): Promise<void>
public static async findEach<M extends BaseModel>(
this: ModelStatic<M>,
options: FindOptions<Attributes<M>> & { batchSize?: number },
processFunction: (record: M) => Promise<void>
): Promise<void>
public static async findEach<M extends BaseModel>(
this: ModelStatic<M>,
optionsOrFunction:
| ((record: M) => Promise<void>)
| (FindOptions<Attributes<M>> & { batchSize?: number }),
maybeFunction?: (record: M) => Promise<void>
): Promise<void> {
let options: FindOptions<Attributes<M>> & { batchSize?: number }
let processFunction: (record: M) => Promise<void>

if (typeof optionsOrFunction === "function") {
options = {}
processFunction = optionsOrFunction
} else {
options = optionsOrFunction
processFunction = maybeFunction!
}

const batchSize = options.batchSize ?? 1000
let lastId = 0
let continueProcessing = true

while (continueProcessing) {
const whereClause = {
...options.where,
id: { [Op.gt]: lastId },
}
const records = await this.findAll({
...options,
where: whereClause,
limit: batchSize,
order: [["id", "ASC"]],
})

for (const record of records) {
await processFunction(record)
lastId = record.id
}

if (records.length < batchSize) {
continueProcessing = false
}
}
}
}

export default BaseModel
Loading

0 comments on commit be2a20f

Please sign in to comment.