Skip to content

Commit 20313bb

Browse files
author
Brijesh
committed
#492 - Fix snakeCase producing wrong column name for pldAUMs/crownAUMs
The custom snakeCase function used regex /([A-Z])/g to insert underscores before each capital letter, producing 'pld_a_u_ms' from 'pldAUMs'. The sort-special-case check in fetchGrazingSchedulesEntries expected 'pld_au_ms', so the mismatch caused the code to use 'pld_a_u_ms' as a database ORDER BY column name, crashing with: column "pld_a_u_ms" does not exist Fix: use a two-regex approach that groups consecutive uppercase letters (/[A-Z]+(?=[A-Z][a-z])/) before splitting on camelCase boundaries (/([a-z0-9])([A-Z])/). This produces 'pld_au_ms' and 'crown_au_ms', matching the existing checks in grazingschedule.ts.
1 parent 6496a8f commit 20313bb

5 files changed

Lines changed: 40 additions & 33 deletions

File tree

__tests__/utils.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ describe('utility helpers', () => {
5151
expect(isNumeric(isAnumber)).toBe(true);
5252
});
5353

54-
test('snakeCase converts pldAUMs to pld_au_ms', async () => {
55-
expect(snakeCase('pldAUMs')).toBe('pld_au_ms');
54+
test('snakeCase converts pldAUMs to pld_aums', async () => {
55+
expect(snakeCase('pldAUMs')).toBe('pld_aums');
5656
});
5757

58-
test('snakeCase converts crownAUMs to crown_au_ms', async () => {
59-
expect(snakeCase('crownAUMs')).toBe('crown_au_ms');
58+
test('snakeCase converts crownAUMs to crown_aums', async () => {
59+
expect(snakeCase('crownAUMs')).toBe('crown_aums');
6060
});
6161

6262
test('snakeCase converts standard camelCase correctly', async () => {
@@ -78,8 +78,8 @@ describe('utility helpers', () => {
7878

7979
test('objPathToSnakeCase converts dotted paths', async () => {
8080
expect(objPathToSnakeCase('ref_livestock.name')).toBe('ref_livestock.name');
81-
expect(objPathToSnakeCase('pldAUMs')).toBe('pld_au_ms');
82-
expect(objPathToSnakeCase('crownAUMs')).toBe('crown_au_ms');
81+
expect(objPathToSnakeCase('pldAUMs')).toBe('pld_aums');
82+
expect(objPathToSnakeCase('crownAUMs')).toBe('crown_aums');
8383
});
8484

8585
test('objPathToCamelCase converts snake paths to camelCase', async () => {

src/libs/db2/model/grazingschedule.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,19 @@ export default class Schedule extends KyselyModel {
4545
}
4646
const where = { haycutting_schedule_id: this.id };
4747
this.scheduleEntries = await HayCuttingScheduleEntry.findWithOrder(db, where, order, orderRaw);
48-
this.scheduleEntries = this.scheduleEntries.map(
49-
(entry: any) =>
50-
new HayCuttingScheduleEntry(
51-
{
52-
...entry,
53-
dateIn: entry.dateIn ? new Date(entry.dateIn).toISOString().split('T')[0] : null,
54-
dateOut: entry.dateOut ? new Date(entry.dateOut).toISOString().split('T')[0] : null,
55-
},
56-
db,
57-
),
58-
);
48+
this.scheduleEntries = this.scheduleEntries.map((entry: any) => {
49+
const formatted = { ...entry };
50+
formatted.date_in = formatted.date_in ? new Date(formatted.date_in).toISOString().split('T')[0] : null;
51+
formatted.date_out = formatted.date_out ? new Date(formatted.date_out).toISOString().split('T')[0] : null;
52+
return new HayCuttingScheduleEntry(formatted, db);
53+
});
5954
}
6055

6156
async fetchGrazingSchedulesEntries(_db?: any) {
6257
const db = _db || kyselyDb;
6358
let order: any;
6459
let orderRaw = false;
65-
if (this.sortBy !== 'pld_au_ms' && this.sortBy !== 'crown_au_ms') {
60+
if (this.sortBy !== 'pld_aums' && this.sortBy !== 'crown_aums') {
6661
if (this.sortBy === 'days') {
6762
order = `date_out - date_in ${this.sortOrder ? this.sortOrder : 'asc'}`;
6863
orderRaw = true;
@@ -75,7 +70,7 @@ export default class Schedule extends KyselyModel {
7570
}
7671
const where = { grazing_schedule_id: this.id };
7772
let entries = await GrazingScheduleEntry.findWithLivestockType(db, where, order, orderRaw);
78-
if (this.sortBy === 'pld_au_ms' || this.sortBy === 'crown_au_ms') {
73+
if (this.sortBy === 'pld_aums' || this.sortBy === 'crown_aums') {
7974
entries = entries.map((row: any) => {
8075
const days = calcDateDiff(row.date_out, row.date_in, false);
8176
const pldPercent = row.pasture_pld_percent;
@@ -85,18 +80,21 @@ export default class Schedule extends KyselyModel {
8580
row.pldAUMs = round(calcPldAUMs(totalAUMs, pldPercent), 0);
8681
const crownAUMWithDecimal = calcCrownAUMs(totalAUMs, row.pldAUMs);
8782
row.crownAUMs = crownAUMWithDecimal > 0 && crownAUMWithDecimal < 1 ? 1 : round(crownAUMWithDecimal, 0);
88-
row.dateIn = row.dateIn ? new Date(row.dateIn).toISOString().split('T')[0] : null;
89-
row.dateOut = row.dateOut ? new Date(row.dateOut).toISOString().split('T')[0] : null;
9083
return row;
9184
});
92-
if (this.sortBy === 'pld_au_ms') {
85+
if (this.sortBy === 'pld_aums') {
9386
if (this.sortOrder === 'asc') entries.sort((a: any, b: any) => a.pldAUMs - b.pldAUMs);
9487
else entries.sort((a: any, b: any) => b.pldAUMs - a.pldAUMs);
9588
} else {
9689
if (this.sortOrder === 'asc') entries.sort((a: any, b: any) => a.crownAUMs - b.crownAUMs);
9790
else entries.sort((a: any, b: any) => b.crownAUMs - a.crownAUMs);
9891
}
9992
}
100-
this.scheduleEntries = entries.map((entry: any) => new GrazingScheduleEntry(entry, db));
93+
this.scheduleEntries = entries.map((entry: any) => {
94+
const formatted = { ...entry };
95+
formatted.date_in = formatted.date_in ? new Date(formatted.date_in).toISOString().split('T')[0] : null;
96+
formatted.date_out = formatted.date_out ? new Date(formatted.date_out).toISOString().split('T')[0] : null;
97+
return new GrazingScheduleEntry(formatted, db);
98+
});
10199
}
102100
}

src/libs/utils.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@
2424
import { errorWithCode } from './bcgov-shim.js';
2525

2626
const camelCase = (str) => str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
27-
export const snakeCase = (str) =>
28-
str
29-
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
30-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
31-
.toLowerCase();
27+
export const snakeCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
3228

3329
/**
3430
* Check if a string consits of [0-9].

src/router/controllers_v1/PlanController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export default class PlanController {
7676
let sanitizedSortBy = schedule.sortBy && objPathToCamelCase(schedule.sortBy);
7777
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('pastureName', 'pasture.name');
7878
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('refLivestockName', 'livestockType.name');
79-
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('pldAuMs', 'pldAUMs');
80-
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('crownAuMs', 'crownAUMs');
79+
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('pldAums', 'pldAUMs');
80+
sanitizedSortBy = sanitizedSortBy && sanitizedSortBy.replace('crownAums', 'crownAUMs');
8181
return {
8282
...schedule,
8383
sortBy: sanitizedSortBy,

src/router/controllers_v1/PlanScheduleController.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ export default class PlanScheduleController {
6262
schedule = await Schedule.create(trx, {
6363
...rest,
6464
plan_id: planId,
65-
sort_by: sortBy && objPathToSnakeCase(sortBy),
65+
sort_by:
66+
sortBy &&
67+
objPathToSnakeCase(sortBy.replace('AUMs', 'Aums').replace('livestockType', 'ref_livestock')).replace(
68+
'.',
69+
'_',
70+
),
6671
});
6772

6873
const entryCreates = scheduleEntries.map((entry) => {
@@ -145,7 +150,12 @@ export default class PlanScheduleController {
145150
{
146151
...rest,
147152
plan_id: planId,
148-
sort_by: sortBy && objPathToSnakeCase(sortBy),
153+
sort_by:
154+
sortBy &&
155+
objPathToSnakeCase(sortBy.replace('AUMs', 'Aums').replace('livestockType', 'ref_livestock')).replace(
156+
'.',
157+
'_',
158+
),
149159
},
150160
);
151161

@@ -314,7 +324,10 @@ export default class PlanScheduleController {
314324
await PlanRouteHelper.canUserAccessThisAgreement(trx, Agreement, user, agreementId);
315325

316326
if (sortBy) {
317-
sortBy = objPathToSnakeCase(sortBy.replace('livestockType', 'ref_livestock')).replace('.', '_');
327+
sortBy = objPathToSnakeCase(sortBy.replace('AUMs', 'Aums').replace('livestockType', 'ref_livestock')).replace(
328+
'.',
329+
'_',
330+
);
318331
}
319332

320333
const result = await trx

0 commit comments

Comments
 (0)