Normalize time unit duplicate handling

This commit is contained in:
Kari Lintulaakso
2025-11-26 16:40:58 +02:00
parent 7aca39bc5f
commit b98a34ecf5
2 changed files with 38 additions and 14 deletions
+13 -1
View File
@@ -36,7 +36,7 @@ describe('Creating new time unit', () => {
it('Contains correct data', () => {
const { tu_name, tu_display_name, tu_comment } = createdTimeUnit!
expect(tu_name).toEqual(newTimeUnitBasis.tu_display_name?.toLowerCase().replace(' ', '')) //'Name is different than expected'
expect(tu_name).toEqual(newTimeUnitBasis.tu_display_name?.toLowerCase().replace(/[\s_-]+/g, '')) //'Name is different than expected'
expect(tu_display_name).toEqual(newTimeUnitBasis.tu_display_name) // 'Display name differs'
expect(tu_comment).toEqual(newTimeUnitBasis.tu_comment) // 'Comment differs'
})
@@ -70,6 +70,18 @@ describe('Creating new time unit', () => {
})
})
it('Creation fails when normalized name collides despite punctuation changes', async () => {
const duplicateResult = await send('time-unit', 'PUT', {
timeUnit: { ...newTimeUnitBasis, tu_display_name: 'Bahean-Test' },
})
expect(duplicateResult.status).toEqual(409)
expect(duplicateResult.body).toEqual({
message: 'Time unit with the provided name already exists',
code: 'duplicate_name',
})
})
it('Creation fails without permissions', async () => {
logout()
const resultNoPerm = await send('time-unit', 'PUT', {
+25 -13
View File
@@ -39,6 +39,12 @@ const isForeignKeyConstraintError = (error: unknown) => {
const DUPLICATE_ENTRY_ERROR_CODE = 'ER_DUP_ENTRY'
const DUPLICATE_TIME_UNIT_MESSAGE = 'Time unit with the provided name already exists'
const normalizeTimeUnitName = (name: string) =>
name
.toLowerCase()
.replace(/[\s_-]+/g, '')
.trim()
export class DuplicateTimeUnitError extends Error {
declare status: number
code: 'duplicate_name'
@@ -73,15 +79,24 @@ const assertTimeUnitIsNotInUse = async (id: string) => {
}
const assertTimeUnitNameIsUnique = async (displayName: string, existingId?: string) => {
const normalizedId = createTimeUnitId(displayName)
const existingTimeUnit = await nowDb.now_time_unit.findFirst({
where: {
OR: [{ tu_name: normalizedId }, { tu_display_name: displayName }],
},
select: { tu_name: true },
const normalizedName = normalizeTimeUnitName(displayName)
const timeUnits = await nowDb.now_time_unit.findMany({
select: { tu_name: true, tu_display_name: true },
})
if (existingTimeUnit && (!existingId || existingTimeUnit.tu_name !== existingId)) {
const duplicate = timeUnits.find(timeUnit => {
const normalizedExistingId = normalizeTimeUnitName(timeUnit.tu_name)
const normalizedExistingDisplayName = normalizeTimeUnitName(timeUnit.tu_display_name)
if (existingId && timeUnit.tu_name === existingId) {
return false
}
return normalizedExistingId === normalizedName || normalizedExistingDisplayName === normalizedName
})
if (duplicate) {
throw new DuplicateTimeUnitError(DUPLICATE_TIME_UNIT_MESSAGE)
}
}
@@ -96,7 +111,7 @@ const getTimeUnitWriteHandler = (type: ActionType) => {
})
}
const createTimeUnitId = (displayName: string) => displayName.toLowerCase().replace(' ', '')
const createTimeUnitId = (displayName: string) => normalizeTimeUnitName(displayName)
export const writeTimeUnit = async (
timeUnit: EditDataType<TimeUnitDetailsType>,
@@ -105,12 +120,9 @@ export const writeTimeUnit = async (
authorizer: string
) => {
const writeHandler = getTimeUnitWriteHandler(timeUnit.tu_name ? 'update' : 'add')
const createdId = timeUnit.tu_name ?? createTimeUnitId(timeUnit.tu_display_name!)
const createdId = createTimeUnitId(timeUnit.tu_display_name!)
if (!timeUnit.tu_name) {
await assertTimeUnitNameIsUnique(timeUnit.tu_display_name!)
}
await assertTimeUnitNameIsUnique(timeUnit.tu_display_name!, timeUnit.tu_name)
try {
await writeHandler.start()