Skip to content

Commit

Permalink
Merge pull request #16 from icefoganalytics/main
Browse files Browse the repository at this point in the history
Updates from IceFog
  • Loading branch information
datajohnson authored Dec 28, 2023
2 parents 1b75e8e + cf91ad5 commit eedb081
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 296 deletions.
2 changes: 1 addition & 1 deletion api/src/routes/centre-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ centreRouter.get("/:id/worksheets", async (req: Request, res: Response) => {
where: { centreId: id },
order: ["dateStart"],
})
const serializedGroups = FundingSubmissionLineJsonSerializer.serializeWorksheetsView(worksheets)
const serializedGroups = FundingSubmissionLineJsonSerializer.asWorksheet(worksheets)
return res.json({ data: serializedGroups })
})

Expand Down
2 changes: 1 addition & 1 deletion api/src/routes/submission-line-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ submissionLineRouter.get("/", async (req: Request, res: Response) => {
const fundingSubmissionLines = await FundingSubmissionLine.findAll()

const serailizedFundingSubmissionLines =
FundingSubmissionLineSerializer.serialize(fundingSubmissionLines)
FundingSubmissionLineSerializer.asTable(fundingSubmissionLines)

res.json({ data: serailizedFundingSubmissionLines })
})
Expand Down
143 changes: 5 additions & 138 deletions api/src/serializers/base-serializer.ts
Original file line number Diff line number Diff line change
@@ -1,142 +1,9 @@
import { isArray, isFunction, isNil, reduce } from "lodash"
import { Model } from "sequelize"

type Extractor<Model> = (model: Model) => any | undefined
type FieldConstructorOptions<Model> = { name?: string; extractor?: Extractor<Model> }
export default class BaseSerializer<T extends Model> {
protected record: T

class Field<Model> {
method: string
name: string | undefined
extractor: Extractor<Model> | undefined

constructor(method: string)
constructor(method: string, extractor: Extractor<Model>)
constructor(method: string, options: FieldConstructorOptions<Model>)
constructor(method: string, extractor: Extractor<Model>, options: FieldConstructorOptions<Model>)
constructor(
method: string,
optionsOrExtractor?: Extractor<Model> | FieldConstructorOptions<Model>,
options?: FieldConstructorOptions<Model>
)
constructor(
method: string,
optionsOrExtractor?: Extractor<Model> | FieldConstructorOptions<Model>,
options?: FieldConstructorOptions<Model>
) {
if (isFunction(optionsOrExtractor)) {
this.method = method
this.extractor = optionsOrExtractor
this.name = options?.name
} else {
this.method = method
this.extractor = options?.extractor
this.name = options?.name
}
}
}

class View<Model extends Record<string, any>> {
name: string
fields: Record<string, Field<Model>>

constructor(name: string) {
this.name = name
this.fields = {}
}

serialize(model: Model) {
return reduce(
this.fields,
(result: Record<string, any>, field: Field<Model>) => {
const { method, name, extractor } = field
if (name !== undefined && extractor !== undefined) {
result[name] = extractor(model)
} else if (!isNil(extractor)) {
result[method] = extractor(model)
} else {
result[method] = model[method]
}
return result
},
{}
)
}

addFields(...methods: string[]): Field<Model>[] {
return methods.map((method) => {
return this.addField(method)
})
}

addField(
...args: ConstructorParameters<typeof Field<Model>>
): Field<Model> {
const method = args[0]
const field = new Field<Model>(...args)
this.fields[method] = field
return field
}
}

type SerializationOptions = { view?: string }

export default class BaseSerializer<Model extends Record<string, any>> {
protected views: Record<string, View<Model>>
protected models: Model[]
protected model: Model
protected singular: boolean

constructor(model: Model)
constructor(models: Model[])
constructor(modelOrModels: Model | Model[])
constructor(modelOrModels: Model | Model[]) {
if (isArray(modelOrModels)) {
this.models = modelOrModels
this.model = modelOrModels[0] // or maybe should be undefined?
this.singular = false
} else {
this.models = [modelOrModels] // or maybe should be undefined?
this.model = modelOrModels
this.singular = true
}

this.views = {}
this.registerDefaultView()
}

public static serialize<Model extends Record<string, any>>(
modelOrModels: Model | Model[],
options: SerializationOptions = {}
): Record<string, any>[] | Record<string, any> {
const instance = new this(modelOrModels)
return instance.serialize(options)
}

public serialize({ view }: SerializationOptions = {}):
| Record<string, any>[]
| Record<string, any> {
return this.callView(view)
}

protected callView(viewName: string = "default") {
const view = this.views[viewName]
if (isNil("view")) throw new Error(`View ${viewName} has not been declared on serializer.`)

if (this.singular) {
return view.serialize(this.model)
} else {
return this.models.map((model) => {
return view.serialize(model)
})
}
}

protected addView(name: string): View<Model> {
const view = new View<Model>(name)
this.views[name] = view
return view
}

protected registerDefaultView() {
throw new Error("Not implemented")
constructor(record: T) {
this.record = record
}
}
36 changes: 20 additions & 16 deletions api/src/serializers/employee-benefit-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,7 @@ import BaseSerializer from "@/serializers/base-serializer"
export class EmployeeBenefitSerializer extends BaseSerializer<EmployeeBenefit> {
static asTable(employeeBenefits: EmployeeBenefit[]) {
return employeeBenefits.map((employeeBenefit) => {
return {
...pick(employeeBenefit, [
"id",
"centreId",
"fiscalPeriodId",
"grossPayrollMonthlyActual",
"grossPayrollMonthlyEstimated",
"costCapPercentage",
"employeeCostActual",
"employeeCostEstimated",
"employerCostActual",
"employerCostEstimated",
"createdAt",
"updatedAt",
]),
}
return new this(employeeBenefit).asTable()
})
}

Expand All @@ -43,6 +28,25 @@ export class EmployeeBenefitSerializer extends BaseSerializer<EmployeeBenefit> {
]),
}
}

private asTable() {
return {
...pick(this.record, [
"id",
"centreId",
"fiscalPeriodId",
"grossPayrollMonthlyActual",
"grossPayrollMonthlyEstimated",
"costCapPercentage",
"employeeCostActual",
"employeeCostEstimated",
"employerCostActual",
"employerCostEstimated",
"createdAt",
"updatedAt",
]),
}
}
}

export default EmployeeBenefitSerializer
12 changes: 1 addition & 11 deletions api/src/serializers/funding-submission-line-json-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,11 @@ export class FundingSubmissionLineJsonSerializer extends BaseSerializer<FundingS
}
}

constructor(modelOrModels: FundingSubmissionLineJson | FundingSubmissionLineJson[]) {
super(modelOrModels)
}

protected registerDefaultView() {
const view = this.addView("default")
view.addFields("id", "centreId", "fiscalYear", "dateName", "dateStart", "dateEnd")
return view
}

// I have no clue what this code is trying to accomplish.
// I hope to be able to fully rebuild this code, until it fits into a standard view -> fields paradigm.
// It looks like we might be missing a database model for a worksheet?
// Or maybe a model for a line entry?
static serializeWorksheetsView(worksheets: FundingSubmissionLineJson[]) {
static asWorksheet(worksheets: FundingSubmissionLineJson[]) {
const groups = []
const years = uniq(worksheets.map((m) => m.fiscalYear))

Expand Down
48 changes: 23 additions & 25 deletions api/src/serializers/funding-submission-line-serializer.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import { pick } from "lodash"

import { formatDollar } from "@/utils/formatter"

import { FundingSubmissionLine } from "@/models"

import BaseSerializer from "@/serializers/base-serializer"

import { formatDollar } from "@/utils/formatter"

export class FundingSubmissionLineSerializer extends BaseSerializer<FundingSubmissionLine> {
constructor(modelOrModels: FundingSubmissionLine | FundingSubmissionLine[]) {
super(modelOrModels)
static asTable(fundingSubmissionLines: FundingSubmissionLine[]) {
return fundingSubmissionLines.map((fundingSubmissionLine) => {
return new this(fundingSubmissionLine).asTable()
})
}

protected registerDefaultView() {
const view = this.addView("default")
view.addFields(
"id",
"fiscalYear",
"sectionName",
"lineName",
"fromAge",
"toAge",
"monthlyAmount"
)

view.addField(
"ageRange",
(model: FundingSubmissionLine): string => `${model.fromAge} ${model.toAge}`
)
// TODO: switch money to being stored as cents and move all formating of it to the front-end
view.addField("monthlyAmountDisplay", (model: FundingSubmissionLine): string =>
formatDollar(model.monthlyAmount)
)
return view
private asTable() {
return {
...pick(this.record, [
"id",
"fiscalYear",
"sectionName",
"lineName",
"fromAge",
"toAge",
"monthlyAmount",
]),
ageRange: `${this.record.fromAge} ${this.record.toAge}`,
// TODO: move formating of it to the front-end
monthlyAmountDisplay: formatDollar(this.record.monthlyAmount),
}
}
}

Expand Down
11 changes: 8 additions & 3 deletions api/src/utils/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export function formatDollar(
input: number | undefined,
locales: string | string[] | undefined = "en-US",
options: Intl.NumberFormatOptions = {}
options: Intl.NumberFormatOptions & {
locales?: string | string[] | undefined
} = {}
) {
const locales = options.locales || "en-CA"
delete options.locales

const formatter = new Intl.NumberFormat(locales, {
style: "currency",
currency: "USD",
currency: "CAD",
currencyDisplay: "symbol",
...options,
// These options are needed to round to whole numbers if that's what you want.
//minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
Expand Down
2 changes: 2 additions & 0 deletions bin/dev
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class DevHelper
run(*%w[test_api npm run test], *args, **kwargs)
end

alias test test_api

def migrate(*args, **kwargs)
if RUBY_PLATFORM =~ /linux/
run(*%w[api npm run migrate], *args, execution_mode: WAIT_FOR_PROCESS, **kwargs)
Expand Down
10 changes: 9 additions & 1 deletion web/src/api/employee-benefits-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@ export type EmployeeBenefit = {
updatedAt: Date
}

export type NonPersistedEmployeeBenefit = Omit<EmployeeBenefit, "id" | "createdAt" | "updatedAt">

export type Params = {
where?: {
centreId?: EmployeeBenefit["centreId"]
fiscalPeriodId?: EmployeeBenefit["fiscalPeriodId"]
fiscalPeriodId?: EmployeeBenefit["fiscalPeriodId"] | EmployeeBenefit["fiscalPeriodId"][]
}
page?: number
perPage?: number
}

export function isPersistedEmployeeBenefit(
employeeBenefit: EmployeeBenefit | NonPersistedEmployeeBenefit
): employeeBenefit is EmployeeBenefit {
return "id" in employeeBenefit && employeeBenefit.id !== undefined
}

export const employeeBenefitsApi = {
list(params: Params = {}): Promise<{
employeeBenefits: EmployeeBenefit[]
Expand Down
3 changes: 3 additions & 0 deletions web/src/api/wage-enhancements-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export type Params = {
}
}

// TODO: store this in the back-end, probably in the fiscal_periods table
export const EI_CPP_WCB_RATE = 0.14

export const wageEnhancementsApi = {
list(params: Params = {}): Promise<{
wageEnhancements: WageEnhancement[]
Expand Down
Loading

0 comments on commit eedb081

Please sign in to comment.