diff --git a/src/db/index.ts b/src/db/index.ts index 18b2d2d7..1cb4a29c 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -93,6 +93,8 @@ import reportDetail from './models/reportDetail'; import reportFile from './models/reportFile'; import reportingWindow from './models/reportingWindow'; import reportingWindowAssignment from './models/reportingWindowAssignment'; +import serviceModality from './models/serviceModality'; +import serviceModalityAssociation from './models/serviceModalityAssociation'; import tag from './models/tag'; import task from './models/task'; import unit from './models/unit'; @@ -218,6 +220,11 @@ const initializeTables = (masterConn: Knex, replicaConn?: Knex) => ({ reportFile: reportFile(masterConn, replicaConn), reportingWindow: reportingWindow(masterConn, replicaConn), reportingWindowAssignment: reportingWindowAssignment(masterConn, replicaConn), + serviceModality: serviceModality(masterConn, replicaConn), + serviceModalityAssociation: serviceModalityAssociation( + masterConn, + replicaConn + ), tag: tag(masterConn, replicaConn), task: task(masterConn, replicaConn), unit: unit(masterConn, replicaConn), diff --git a/src/db/models/json/attachment.ts b/src/db/models/json/attachment.ts index bb5698b3..b31ec247 100644 --- a/src/db/models/json/attachment.ts +++ b/src/db/models/json/attachment.ts @@ -4,6 +4,15 @@ import { INDICATOR_VALUE as INDICATOR_ATTACHMENT_VALUE, } from './indicatorsAndCaseloads'; +const COST_BREAKDOWN = t.array( + t.exact( + t.type({ + objectId: t.number, + cost: t.number, + }) + ) +); + const COST_ATTACHMENT_VALUE = t.intersection([ t.exact( t.type({ @@ -13,23 +22,16 @@ const COST_ATTACHMENT_VALUE = t.intersection([ t.exact( t.partial({ /** - * When necessary, a cost breakdown can be provided with respect to a - * particular collection of objects. - * - * For example, when the object of this attachment is a governing entity, - * a breakdown needs to be provided for each of the global clusters, - * (even if the governing entity has 0 global clusters) - * and the total sum of the breakdown must match the overall cost when - * non-empty. + * When necessary, a cost breakdown can be provided for each of the global + * clusters (even if the governing entity has 0 global clusters), and the + * total sum of the breakdown must match the overall cost when non-empty. */ - breakdown: t.array( - t.exact( - t.type({ - objectId: t.number, - cost: t.number, - }) - ) - ), + breakdownByGlobalCluster: COST_BREAKDOWN, + /** + * Unlike global cluster breakdown, service modality breakdown + * doesn't need to add up to the total cost + */ + breakdownByServiceModality: COST_BREAKDOWN, // TODO: delete these properties once we've confirmed that they're not // needed for any code that reads cost attachments // (they seem to be produced by RPM frontend code related to other types) diff --git a/src/db/models/serviceModality.ts b/src/db/models/serviceModality.ts new file mode 100644 index 00000000..02d64d4d --- /dev/null +++ b/src/db/models/serviceModality.ts @@ -0,0 +1,29 @@ +import * as t from 'io-ts'; + +import { brandedType } from '../../util/io-ts'; +import type { Brand } from '../../util/types'; +import { defineIDModel } from '../util/id-model'; + +export type ServiceModalityId = Brand< + number, + { readonly s: unique symbol }, + 'serviceModality.id' +>; + +export const SERVICE_MODALITY_ID = brandedType( + t.number +); + +export default defineIDModel({ + tableName: 'serviceModality', + fields: { + generated: { + id: { kind: 'branded-integer', brand: SERVICE_MODALITY_ID }, + }, + required: { + name: { kind: 'checked', type: t.string }, + }, + }, + idField: 'id', + softDeletionEnabled: false, +}); diff --git a/src/db/models/serviceModalityAssociation.ts b/src/db/models/serviceModalityAssociation.ts new file mode 100644 index 00000000..0575d65b --- /dev/null +++ b/src/db/models/serviceModalityAssociation.ts @@ -0,0 +1,20 @@ +import { defineSequelizeModel } from '../util/sequelize-model'; +import { GOVERNING_ENTITY_ID } from './governingEntity'; +import { SERVICE_MODALITY_ID } from './serviceModality'; + +export default defineSequelizeModel({ + tableName: 'serviceModalityAssociation', + fields: { + required: { + serviceModalityId: { + kind: 'branded-integer', + brand: SERVICE_MODALITY_ID, + }, + governingEntityId: { + kind: 'branded-integer', + brand: GOVERNING_ENTITY_ID, + }, + }, + }, + softDeletionEnabled: false, +});