ability to add multiple fees as a group

alter table FeeCategories
add isGroupedFee bit not null default 0
deepsource-autofix-76c6eb20
Dan Gowans 2024-06-28 11:17:19 -04:00
parent 86b82d80de
commit f384ce75df
42 changed files with 687 additions and 231 deletions

6
database/addFeeCategory.d.ts vendored 100644
View File

@ -0,0 +1,6 @@
export interface AddFeeCategoryForm {
feeCategory: string;
isGroupedFee?: '1';
orderNumber?: number;
}
export default function addFeeCategory(feeCategoryForm: AddFeeCategoryForm, user: User): Promise<number>;

View File

@ -0,0 +1,15 @@
import { acquireConnection } from './pool.js';
export default async function addFeeCategory(feeCategoryForm, user) {
const database = await acquireConnection();
const rightNowMillis = Date.now();
const result = database
.prepare(`insert into FeeCategories (
feeCategory,
isGroupedFee, orderNumber,
recordCreate_userName, recordCreate_timeMillis,
recordUpdate_userName, recordUpdate_timeMillis)
values (?, ?, ?, ?, ?, ?, ?)`)
.run(feeCategoryForm.feeCategory, (feeCategoryForm.isGroupedFee ?? '') === '1' ? 1 : 0, feeCategoryForm.orderNumber ?? -1, user.userName, rightNowMillis, user.userName, rightNowMillis);
database.release();
return result.lastInsertRowid;
}

View File

@ -0,0 +1,39 @@
import { acquireConnection } from './pool.js'
export interface AddFeeCategoryForm {
feeCategory: string
isGroupedFee?: '1'
orderNumber?: number
}
export default async function addFeeCategory(
feeCategoryForm: AddFeeCategoryForm,
user: User
): Promise<number> {
const database = await acquireConnection()
const rightNowMillis = Date.now()
const result = database
.prepare(
`insert into FeeCategories (
feeCategory,
isGroupedFee, orderNumber,
recordCreate_userName, recordCreate_timeMillis,
recordUpdate_userName, recordUpdate_timeMillis)
values (?, ?, ?, ?, ?, ?, ?)`
)
.run(
feeCategoryForm.feeCategory,
(feeCategoryForm.isGroupedFee ?? '') === '1' ? 1 : 0,
feeCategoryForm.orderNumber ?? -1,
user.userName,
rightNowMillis,
user.userName,
rightNowMillis
)
database.release()
return result.lastInsertRowid as number
}

View File

@ -1,3 +1,4 @@
import { type PoolConnection } from 'better-sqlite-pool';
export interface AddLotOccupancyFeeForm { export interface AddLotOccupancyFeeForm {
lotOccupancyId: number | string; lotOccupancyId: number | string;
feeId: number | string; feeId: number | string;
@ -5,4 +6,4 @@ export interface AddLotOccupancyFeeForm {
feeAmount?: number | string; feeAmount?: number | string;
taxAmount?: number | string; taxAmount?: number | string;
} }
export default function addLotOccupancyFee(lotOccupancyFeeForm: AddLotOccupancyFeeForm, user: User): Promise<boolean>; export default function addLotOccupancyFee(lotOccupancyFeeForm: AddLotOccupancyFeeForm, user: User, connectedDatabase?: PoolConnection): Promise<boolean>;

View File

@ -2,8 +2,8 @@ import { calculateFeeAmount, calculateTaxAmount } from '../helpers/functions.fee
import getFee from './getFee.js'; import getFee from './getFee.js';
import getLotOccupancy from './getLotOccupancy.js'; import getLotOccupancy from './getLotOccupancy.js';
import { acquireConnection } from './pool.js'; import { acquireConnection } from './pool.js';
export default async function addLotOccupancyFee(lotOccupancyFeeForm, user) { export default async function addLotOccupancyFee(lotOccupancyFeeForm, user, connectedDatabase) {
const database = await acquireConnection(); const database = connectedDatabase ?? (await acquireConnection());
const rightNowMillis = Date.now(); const rightNowMillis = Date.now();
// Calculate fee and tax (if not set) // Calculate fee and tax (if not set)
let feeAmount; let feeAmount;
@ -24,62 +24,66 @@ export default async function addLotOccupancyFee(lotOccupancyFeeForm, user) {
? Number.parseFloat(lotOccupancyFeeForm.taxAmount) ? Number.parseFloat(lotOccupancyFeeForm.taxAmount)
: 0; : 0;
} }
// Check if record already exists try {
const record = database // Check if record already exists
.prepare(`select feeAmount, taxAmount, recordDelete_timeMillis const record = database
from LotOccupancyFees .prepare(`select feeAmount, taxAmount, recordDelete_timeMillis
where lotOccupancyId = ? from LotOccupancyFees
and feeId = ?`) where lotOccupancyId = ?
.get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId); and feeId = ?`)
if (record) { .get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
if (record.recordDelete_timeMillis) { if (record) {
database if (record.recordDelete_timeMillis) {
.prepare(`delete from LotOccupancyFees database
where recordDelete_timeMillis is not null .prepare(`delete from LotOccupancyFees
and lotOccupancyId = ? where recordDelete_timeMillis is not null
and feeId = ?`) and lotOccupancyId = ?
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId); and feeId = ?`)
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
}
else if (record.feeAmount === feeAmount &&
record.taxAmount === taxAmount) {
database
.prepare(`update LotOccupancyFees
set quantity = quantity + ?,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where lotOccupancyId = ?
and feeId = ?`)
.run(lotOccupancyFeeForm.quantity, user.userName, rightNowMillis, lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
return true;
}
else {
const quantity = typeof lotOccupancyFeeForm.quantity === 'string'
? Number.parseFloat(lotOccupancyFeeForm.quantity)
: lotOccupancyFeeForm.quantity;
database
.prepare(`update LotOccupancyFees
set feeAmount = (feeAmount * quantity) + ?,
taxAmount = (taxAmount * quantity) + ?,
quantity = 1,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where lotOccupancyId = ?
and feeId = ?`)
.run(feeAmount * quantity, taxAmount * quantity, user.userName, rightNowMillis, lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
return true;
}
} }
else if (record.feeAmount === feeAmount && // Create new record
record.taxAmount === taxAmount) { const result = database
database .prepare(`insert into LotOccupancyFees (
.prepare(`update LotOccupancyFees lotOccupancyId, feeId,
set quantity = quantity + ?, quantity, feeAmount, taxAmount,
recordUpdate_userName = ?, recordCreate_userName, recordCreate_timeMillis,
recordUpdate_timeMillis = ? recordUpdate_userName, recordUpdate_timeMillis)
where lotOccupancyId = ? values (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
and feeId = ?`) .run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId, lotOccupancyFeeForm.quantity, feeAmount, taxAmount, user.userName, rightNowMillis, user.userName, rightNowMillis);
.run(lotOccupancyFeeForm.quantity, user.userName, rightNowMillis, lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId); return result.changes > 0;
}
finally {
if (connectedDatabase === undefined) {
database.release(); database.release();
return true;
}
else {
const quantity = typeof lotOccupancyFeeForm.quantity === 'string'
? Number.parseFloat(lotOccupancyFeeForm.quantity)
: lotOccupancyFeeForm.quantity;
database
.prepare(`update LotOccupancyFees
set feeAmount = (feeAmount * quantity) + ?,
taxAmount = (taxAmount * quantity) + ?,
quantity = 1,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where lotOccupancyId = ?
and feeId = ?`)
.run(feeAmount * quantity, taxAmount * quantity, user.userName, rightNowMillis, lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
database.release();
return true;
} }
} }
// Create new record
const result = database
.prepare(`insert into LotOccupancyFees (
lotOccupancyId, feeId,
quantity, feeAmount, taxAmount,
recordCreate_userName, recordCreate_timeMillis,
recordUpdate_userName, recordUpdate_timeMillis)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId, lotOccupancyFeeForm.quantity, feeAmount, taxAmount, user.userName, rightNowMillis, user.userName, rightNowMillis);
database.release();
return result.changes > 0;
} }

View File

@ -1,3 +1,5 @@
import { type PoolConnection } from 'better-sqlite-pool'
import { import {
calculateFeeAmount, calculateFeeAmount,
calculateTaxAmount calculateTaxAmount
@ -18,9 +20,10 @@ export interface AddLotOccupancyFeeForm {
export default async function addLotOccupancyFee( export default async function addLotOccupancyFee(
lotOccupancyFeeForm: AddLotOccupancyFeeForm, lotOccupancyFeeForm: AddLotOccupancyFeeForm,
user: User user: User,
connectedDatabase?: PoolConnection
): Promise<boolean> { ): Promise<boolean> {
const database = await acquireConnection() const database = connectedDatabase ?? (await acquireConnection())
const rightNowMillis = Date.now() const rightNowMillis = Date.now()
@ -48,109 +51,109 @@ export default async function addLotOccupancyFee(
: 0 : 0
} }
// Check if record already exists try {
const record = database // Check if record already exists
.prepare( const record = database
`select feeAmount, taxAmount, recordDelete_timeMillis .prepare(
from LotOccupancyFees `select feeAmount, taxAmount, recordDelete_timeMillis
where lotOccupancyId = ? from LotOccupancyFees
and feeId = ?` where lotOccupancyId = ?
) and feeId = ?`
.get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId) as { )
feeAmount?: number .get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId) as {
taxAmount?: number feeAmount?: number
recordDelete_timeMillis?: number taxAmount?: number
} recordDelete_timeMillis?: number
}
if (record) { if (record) {
if (record.recordDelete_timeMillis) { if (record.recordDelete_timeMillis) {
database database
.prepare( .prepare(
`delete from LotOccupancyFees `delete from LotOccupancyFees
where recordDelete_timeMillis is not null where recordDelete_timeMillis is not null
and lotOccupancyId = ? and lotOccupancyId = ?
and feeId = ?` and feeId = ?`
) )
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId) .run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId)
} else if ( } else if (
record.feeAmount === feeAmount && record.feeAmount === feeAmount &&
record.taxAmount === taxAmount record.taxAmount === taxAmount
) { ) {
database database
.prepare( .prepare(
`update LotOccupancyFees `update LotOccupancyFees
set quantity = quantity + ?, set quantity = quantity + ?,
recordUpdate_userName = ?, recordUpdate_userName = ?,
recordUpdate_timeMillis = ? recordUpdate_timeMillis = ?
where lotOccupancyId = ? where lotOccupancyId = ?
and feeId = ?` and feeId = ?`
) )
.run( .run(
lotOccupancyFeeForm.quantity, lotOccupancyFeeForm.quantity,
user.userName, user.userName,
rightNowMillis, rightNowMillis,
lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId lotOccupancyFeeForm.feeId
) )
return true
} else {
const quantity =
typeof lotOccupancyFeeForm.quantity === 'string'
? Number.parseFloat(lotOccupancyFeeForm.quantity)
: lotOccupancyFeeForm.quantity
database
.prepare(
`update LotOccupancyFees
set feeAmount = (feeAmount * quantity) + ?,
taxAmount = (taxAmount * quantity) + ?,
quantity = 1,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where lotOccupancyId = ?
and feeId = ?`
)
.run(
feeAmount * quantity,
taxAmount * quantity,
user.userName,
rightNowMillis,
lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId
)
return true
}
}
// Create new record
const result = database
.prepare(
`insert into LotOccupancyFees (
lotOccupancyId, feeId,
quantity, feeAmount, taxAmount,
recordCreate_userName, recordCreate_timeMillis,
recordUpdate_userName, recordUpdate_timeMillis)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
.run(
lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId,
lotOccupancyFeeForm.quantity,
feeAmount,
taxAmount,
user.userName,
rightNowMillis,
user.userName,
rightNowMillis
)
return result.changes > 0
} finally {
if (connectedDatabase === undefined) {
database.release() database.release()
return true
} else {
const quantity =
typeof lotOccupancyFeeForm.quantity === 'string'
? Number.parseFloat(lotOccupancyFeeForm.quantity)
: lotOccupancyFeeForm.quantity
database
.prepare(
`update LotOccupancyFees
set feeAmount = (feeAmount * quantity) + ?,
taxAmount = (taxAmount * quantity) + ?,
quantity = 1,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where lotOccupancyId = ?
and feeId = ?`
)
.run(
feeAmount * quantity,
taxAmount * quantity,
user.userName,
rightNowMillis,
lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId
)
database.release()
return true
} }
} }
// Create new record
const result = database
.prepare(
`insert into LotOccupancyFees (
lotOccupancyId, feeId,
quantity, feeAmount, taxAmount,
recordCreate_userName, recordCreate_timeMillis,
recordUpdate_userName, recordUpdate_timeMillis)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
.run(
lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId,
lotOccupancyFeeForm.quantity,
feeAmount,
taxAmount,
user.userName,
rightNowMillis,
user.userName,
rightNowMillis
)
database.release()
return result.changes > 0
} }

View File

@ -0,0 +1,5 @@
export interface AddLotOccupancyFeeCategoryForm {
lotOccupancyId: number | string;
feeCategoryId: number | string;
}
export default function addLotOccupancyFeeCategory(lotOccupancyFeeCategoryForm: AddLotOccupancyFeeCategoryForm, user: User): Promise<number>;

View File

@ -0,0 +1,20 @@
import addLotOccupancyFee from './addLotOccupancyFee.js';
import { getFeeCategory } from './getFeeCategories.js';
import { acquireConnection } from './pool.js';
export default async function addLotOccupancyFeeCategory(lotOccupancyFeeCategoryForm, user) {
const database = await acquireConnection();
const feeCategory = await getFeeCategory(lotOccupancyFeeCategoryForm.feeCategoryId, database);
let addedFeeCount = 0;
for (const fee of feeCategory?.fees ?? []) {
const success = await addLotOccupancyFee({
lotOccupancyId: lotOccupancyFeeCategoryForm.lotOccupancyId,
feeId: fee.feeId,
quantity: 1
}, user, database);
if (success) {
addedFeeCount += 1;
}
}
database.release();
return addedFeeCount;
}

View File

@ -0,0 +1,42 @@
import addLotOccupancyFee from './addLotOccupancyFee.js'
import { getFeeCategory } from './getFeeCategories.js'
import { acquireConnection } from './pool.js'
export interface AddLotOccupancyFeeCategoryForm {
lotOccupancyId: number | string
feeCategoryId: number | string
}
export default async function addLotOccupancyFeeCategory(
lotOccupancyFeeCategoryForm: AddLotOccupancyFeeCategoryForm,
user: User
): Promise<number> {
const database = await acquireConnection()
const feeCategory = await getFeeCategory(
lotOccupancyFeeCategoryForm.feeCategoryId,
database
)
let addedFeeCount = 0
for (const fee of feeCategory?.fees ?? []) {
const success = await addLotOccupancyFee(
{
lotOccupancyId: lotOccupancyFeeCategoryForm.lotOccupancyId,
feeId: fee.feeId,
quantity: 1
},
user,
database
)
if (success) {
addedFeeCount += 1
}
}
database.release()
return addedFeeCount
}

View File

@ -1,3 +1,3 @@
type RecordTable = 'FeeCategories' | 'LotStatuses' | 'LotTypes' | 'OccupancyTypes' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes'; type RecordTable = 'LotStatuses' | 'LotTypes' | 'OccupancyTypes' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes';
export declare function addRecord(recordTable: RecordTable, recordName: string, orderNumber: number | string, user: User): Promise<number>; export declare function addRecord(recordTable: RecordTable, recordName: string, orderNumber: number | string, user: User): Promise<number>;
export {}; export {};

View File

@ -1,7 +1,6 @@
import { clearCacheByTableName } from '../helpers/functions.cache.js'; import { clearCacheByTableName } from '../helpers/functions.cache.js';
import { acquireConnection } from './pool.js'; import { acquireConnection } from './pool.js';
const recordNameColumns = new Map(); const recordNameColumns = new Map();
recordNameColumns.set('FeeCategories', 'feeCategory');
recordNameColumns.set('LotStatuses', 'lotStatus'); recordNameColumns.set('LotStatuses', 'lotStatus');
recordNameColumns.set('LotTypes', 'lotType'); recordNameColumns.set('LotTypes', 'lotType');
recordNameColumns.set('OccupancyTypes', 'occupancyType'); recordNameColumns.set('OccupancyTypes', 'occupancyType');

View File

@ -3,7 +3,6 @@ import { clearCacheByTableName } from '../helpers/functions.cache.js'
import { acquireConnection } from './pool.js' import { acquireConnection } from './pool.js'
type RecordTable = type RecordTable =
| 'FeeCategories'
| 'LotStatuses' | 'LotStatuses'
| 'LotTypes' | 'LotTypes'
| 'OccupancyTypes' | 'OccupancyTypes'
@ -11,7 +10,6 @@ type RecordTable =
| 'WorkOrderTypes' | 'WorkOrderTypes'
const recordNameColumns = new Map<RecordTable, string>() const recordNameColumns = new Map<RecordTable, string>()
recordNameColumns.set('FeeCategories', 'feeCategory')
recordNameColumns.set('LotStatuses', 'lotStatus') recordNameColumns.set('LotStatuses', 'lotStatus')
recordNameColumns.set('LotTypes', 'lotType') recordNameColumns.set('LotTypes', 'lotType')
recordNameColumns.set('OccupancyTypes', 'occupancyType') recordNameColumns.set('OccupancyTypes', 'occupancyType')

View File

@ -1,10 +1,13 @@
import { type PoolConnection } from 'better-sqlite-pool';
import type { FeeCategory } from '../types/recordTypes.js'; import type { FeeCategory } from '../types/recordTypes.js';
interface GetFeeCategoriesFilters { interface GetFeeCategoriesFilters {
occupancyTypeId?: number | string; occupancyTypeId?: number | string;
lotTypeId?: number | string; lotTypeId?: number | string;
feeCategoryId?: number | string;
} }
interface GetFeeCategoriesOptions { interface GetFeeCategoriesOptions {
includeFees?: boolean; includeFees?: boolean;
} }
export default function getFeeCategories(filters: GetFeeCategoriesFilters, options: GetFeeCategoriesOptions): Promise<FeeCategory[]>; export default function getFeeCategories(filters: GetFeeCategoriesFilters, options: GetFeeCategoriesOptions, connectedDatabase?: PoolConnection): Promise<FeeCategory[]>;
export declare function getFeeCategory(feeCategoryId: number | string, connectedDatabase?: PoolConnection): Promise<FeeCategory | undefined>;
export {}; export {};

View File

@ -1,7 +1,7 @@
import getFees from './getFees.js'; import getFees from './getFees.js';
import { acquireConnection } from './pool.js'; import { acquireConnection } from './pool.js';
import { updateRecordOrderNumber } from './updateRecordOrderNumber.js'; import { updateRecordOrderNumber } from './updateRecordOrderNumber.js';
export default async function getFeeCategories(filters, options) { export default async function getFeeCategories(filters, options, connectedDatabase) {
const updateOrderNumbers = !(filters.lotTypeId || filters.occupancyTypeId) && options.includeFees; const updateOrderNumbers = !(filters.lotTypeId || filters.occupancyTypeId) && options.includeFees;
const database = await acquireConnection(); const database = await acquireConnection();
let sqlWhereClause = ' where recordDelete_timeMillis is null'; let sqlWhereClause = ' where recordDelete_timeMillis is null';
@ -17,7 +17,7 @@ export default async function getFeeCategories(filters, options) {
sqlParameters.push(filters.lotTypeId); sqlParameters.push(filters.lotTypeId);
} }
const feeCategories = database const feeCategories = database
.prepare(`select feeCategoryId, feeCategory, orderNumber .prepare(`select feeCategoryId, feeCategory, isGroupedFee, orderNumber
from FeeCategories from FeeCategories
${sqlWhereClause} ${sqlWhereClause}
order by orderNumber, feeCategory`) order by orderNumber, feeCategory`)
@ -34,6 +34,19 @@ export default async function getFeeCategories(filters, options) {
feeCategory.fees = await getFees(feeCategory.feeCategoryId, filters, database); feeCategory.fees = await getFees(feeCategory.feeCategoryId, filters, database);
} }
} }
database.release(); if (connectedDatabase === undefined) {
database.release();
}
return feeCategories; return feeCategories;
} }
export async function getFeeCategory(feeCategoryId, connectedDatabase) {
const feeCategories = await getFeeCategories({
feeCategoryId
}, {
includeFees: true
}, connectedDatabase);
if (feeCategories.length > 0) {
return feeCategories[0];
}
return undefined;
}

View File

@ -1,3 +1,5 @@
import { type PoolConnection } from 'better-sqlite-pool'
import type { FeeCategory } from '../types/recordTypes.js' import type { FeeCategory } from '../types/recordTypes.js'
import getFees from './getFees.js' import getFees from './getFees.js'
@ -7,6 +9,7 @@ import { updateRecordOrderNumber } from './updateRecordOrderNumber.js'
interface GetFeeCategoriesFilters { interface GetFeeCategoriesFilters {
occupancyTypeId?: number | string occupancyTypeId?: number | string
lotTypeId?: number | string lotTypeId?: number | string
feeCategoryId?: number | string
} }
interface GetFeeCategoriesOptions { interface GetFeeCategoriesOptions {
@ -15,7 +18,8 @@ interface GetFeeCategoriesOptions {
export default async function getFeeCategories( export default async function getFeeCategories(
filters: GetFeeCategoriesFilters, filters: GetFeeCategoriesFilters,
options: GetFeeCategoriesOptions options: GetFeeCategoriesOptions,
connectedDatabase?: PoolConnection
): Promise<FeeCategory[]> { ): Promise<FeeCategory[]> {
const updateOrderNumbers = const updateOrderNumbers =
!(filters.lotTypeId || filters.occupancyTypeId) && options.includeFees !(filters.lotTypeId || filters.occupancyTypeId) && options.includeFees
@ -42,7 +46,7 @@ export default async function getFeeCategories(
const feeCategories = database const feeCategories = database
.prepare( .prepare(
`select feeCategoryId, feeCategory, orderNumber `select feeCategoryId, feeCategory, isGroupedFee, orderNumber
from FeeCategories from FeeCategories
${sqlWhereClause} ${sqlWhereClause}
order by orderNumber, feeCategory` order by orderNumber, feeCategory`
@ -77,7 +81,30 @@ export default async function getFeeCategories(
} }
} }
database.release() if (connectedDatabase === undefined) {
database.release()
}
return feeCategories return feeCategories
} }
export async function getFeeCategory(
feeCategoryId: number | string,
connectedDatabase?: PoolConnection
): Promise<FeeCategory | undefined> {
const feeCategories = await getFeeCategories(
{
feeCategoryId
},
{
includeFees: true
},
connectedDatabase
)
if (feeCategories.length > 0) {
return feeCategories[0]
}
return undefined
}

View File

@ -1,5 +1,6 @@
export interface UpdateFeeCategoryForm { export interface UpdateFeeCategoryForm {
feeCategoryId: number | string; feeCategoryId: number | string;
feeCategory: string; feeCategory: string;
isGroupedFee?: '1';
} }
export default function updateFeeCategory(feeCategoryForm: UpdateFeeCategoryForm, user: User): Promise<boolean>; export default function updateFeeCategory(feeCategoryForm: UpdateFeeCategoryForm, user: User): Promise<boolean>;

View File

@ -1,4 +1,15 @@
import { updateRecord } from './updateRecord.js'; import { acquireConnection } from './pool.js';
export default async function updateFeeCategory(feeCategoryForm, user) { export default async function updateFeeCategory(feeCategoryForm, user) {
return await updateRecord('FeeCategories', feeCategoryForm.feeCategoryId, feeCategoryForm.feeCategory, user); const database = await acquireConnection();
const result = database
.prepare(`update FeeCategories
set feeCategory = ?,
isGroupedFee = ?,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where recordDelete_timeMillis is null
and feeCategoryId = ?`)
.run(feeCategoryForm.feeCategory, (feeCategoryForm.isGroupedFee ?? '') === '1' ? 1 : 0, user.userName, Date.now(), feeCategoryForm.feeCategoryId);
database.release();
return result.changes > 0;
} }

View File

@ -1,18 +1,36 @@
import { updateRecord } from './updateRecord.js' import { acquireConnection } from './pool.js'
export interface UpdateFeeCategoryForm { export interface UpdateFeeCategoryForm {
feeCategoryId: number | string feeCategoryId: number | string
feeCategory: string feeCategory: string
isGroupedFee?: '1'
} }
export default async function updateFeeCategory( export default async function updateFeeCategory(
feeCategoryForm: UpdateFeeCategoryForm, feeCategoryForm: UpdateFeeCategoryForm,
user: User user: User
): Promise<boolean> { ): Promise<boolean> {
return await updateRecord( const database = await acquireConnection()
'FeeCategories',
feeCategoryForm.feeCategoryId, const result = database
feeCategoryForm.feeCategory, .prepare(
user `update FeeCategories
) set feeCategory = ?,
isGroupedFee = ?,
recordUpdate_userName = ?,
recordUpdate_timeMillis = ?
where recordDelete_timeMillis is null
and feeCategoryId = ?`
)
.run(
feeCategoryForm.feeCategory,
(feeCategoryForm.isGroupedFee ?? '') === '1' ? 1 : 0,
user.userName,
Date.now(),
feeCategoryForm.feeCategoryId
)
database.release()
return result.changes > 0
} }

View File

@ -1,3 +1,3 @@
type RecordTable = 'FeeCategories' | 'LotStatuses' | 'LotTypes' | 'OccupancyTypes' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes'; type RecordTable = 'LotStatuses' | 'LotTypes' | 'OccupancyTypes' | 'WorkOrderMilestoneTypes' | 'WorkOrderTypes';
export declare function updateRecord(recordTable: RecordTable, recordId: number | string, recordName: string, user: User): Promise<boolean>; export declare function updateRecord(recordTable: RecordTable, recordId: number | string, recordName: string, user: User): Promise<boolean>;
export {}; export {};

View File

@ -1,7 +1,6 @@
import { clearCacheByTableName } from '../helpers/functions.cache.js'; import { clearCacheByTableName } from '../helpers/functions.cache.js';
import { acquireConnection } from './pool.js'; import { acquireConnection } from './pool.js';
const recordNameIdColumns = new Map(); const recordNameIdColumns = new Map();
recordNameIdColumns.set('FeeCategories', ['feeCategory', 'feeCategoryId']);
recordNameIdColumns.set('LotStatuses', ['lotStatus', 'lotStatusId']); recordNameIdColumns.set('LotStatuses', ['lotStatus', 'lotStatusId']);
recordNameIdColumns.set('LotTypes', ['lotType', 'lotTypeId']); recordNameIdColumns.set('LotTypes', ['lotType', 'lotTypeId']);
recordNameIdColumns.set('OccupancyTypes', ['occupancyType', 'occupancyTypeId']); recordNameIdColumns.set('OccupancyTypes', ['occupancyType', 'occupancyTypeId']);

View File

@ -3,7 +3,6 @@ import { clearCacheByTableName } from '../helpers/functions.cache.js'
import { acquireConnection } from './pool.js' import { acquireConnection } from './pool.js'
type RecordTable = type RecordTable =
| 'FeeCategories'
| 'LotStatuses' | 'LotStatuses'
| 'LotTypes' | 'LotTypes'
| 'OccupancyTypes' | 'OccupancyTypes'
@ -11,7 +10,6 @@ type RecordTable =
| 'WorkOrderTypes' | 'WorkOrderTypes'
const recordNameIdColumns = new Map<RecordTable, string[]>() const recordNameIdColumns = new Map<RecordTable, string[]>()
recordNameIdColumns.set('FeeCategories', ['feeCategory', 'feeCategoryId'])
recordNameIdColumns.set('LotStatuses', ['lotStatus', 'lotStatusId']) recordNameIdColumns.set('LotStatuses', ['lotStatus', 'lotStatusId'])
recordNameIdColumns.set('LotTypes', ['lotType', 'lotTypeId']) recordNameIdColumns.set('LotTypes', ['lotType', 'lotTypeId'])
recordNameIdColumns.set('OccupancyTypes', ['occupancyType', 'occupancyTypeId']) recordNameIdColumns.set('OccupancyTypes', ['occupancyType', 'occupancyTypeId'])

View File

@ -1,7 +1,7 @@
import { addRecord } from '../../database/addRecord.js'; import addFeeCategory from '../../database/addFeeCategory.js';
import getFeeCategories from '../../database/getFeeCategories.js'; import getFeeCategories from '../../database/getFeeCategories.js';
export default async function handler(request, response) { export default async function handler(request, response) {
const feeCategoryId = await addRecord('FeeCategories', request.body.feeCategory, request.body.orderNumber ?? -1, request.session.user); const feeCategoryId = await addFeeCategory(request.body, request.session.user);
const feeCategories = await getFeeCategories({}, { const feeCategories = await getFeeCategories({}, {
includeFees: true includeFees: true
}); });

View File

@ -1,16 +1,16 @@
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { addRecord } from '../../database/addRecord.js' import addFeeCategory, {
type AddFeeCategoryForm
} from '../../database/addFeeCategory.js'
import getFeeCategories from '../../database/getFeeCategories.js' import getFeeCategories from '../../database/getFeeCategories.js'
export default async function handler( export default async function handler(
request: Request, request: Request,
response: Response response: Response
): Promise<void> { ): Promise<void> {
const feeCategoryId = await addRecord( const feeCategoryId = await addFeeCategory(
'FeeCategories', request.body as AddFeeCategoryForm,
request.body.feeCategory,
request.body.orderNumber ?? -1,
request.session.user as User request.session.user as User
) )

View File

@ -15,7 +15,7 @@ export default async function handler(
) )
const lotOccupancyFees = await getLotOccupancyFees( const lotOccupancyFees = await getLotOccupancyFees(
request.body.lotOccupancyId request.body.lotOccupancyId as string
) )
response.json({ response.json({

View File

@ -0,0 +1,3 @@
/// <reference types="cookie-parser" />
import type { Request, Response } from 'express';
export default function handler(request: Request, response: Response): Promise<void>;

View File

@ -0,0 +1,10 @@
import addLotOccupancyFeeCategory from '../../database/addLotOccupancyFeeCategory.js';
import getLotOccupancyFees from '../../database/getLotOccupancyFees.js';
export default async function handler(request, response) {
await addLotOccupancyFeeCategory(request.body, request.session.user);
const lotOccupancyFees = await getLotOccupancyFees(request.body.lotOccupancyId);
response.json({
success: true,
lotOccupancyFees
});
}

View File

@ -0,0 +1,25 @@
import type { Request, Response } from 'express'
import addLotOccupancyFeeCategory, {
type AddLotOccupancyFeeCategoryForm
} from '../../database/addLotOccupancyFeeCategory.js'
import getLotOccupancyFees from '../../database/getLotOccupancyFees.js'
export default async function handler(
request: Request,
response: Response
): Promise<void> {
await addLotOccupancyFeeCategory(
request.body as AddLotOccupancyFeeCategoryForm,
request.session.user as User
)
const lotOccupancyFees = await getLotOccupancyFees(
request.body.lotOccupancyId as string
)
response.json({
success: true,
lotOccupancyFees
})
}

View File

@ -56,7 +56,12 @@ const createStatements = [
/* /*
* Fees and Transactions * Fees and Transactions
*/ */
`create table if not exists FeeCategories (feeCategoryId integer not null primary key autoincrement, feeCategory varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`, `create table if not exists FeeCategories (
feeCategoryId integer not null primary key autoincrement,
feeCategory varchar(100) not null,
isGroupedFee bit not null default 0,
orderNumber smallint not null default 0,
${recordColumns})`,
`create table if not exists Fees ( `create table if not exists Fees (
feeId integer not null primary key autoincrement, feeId integer not null primary key autoincrement,
feeCategoryId integer not null, feeCategoryId integer not null,

View File

@ -69,7 +69,13 @@ const createStatements = [
* Fees and Transactions * Fees and Transactions
*/ */
`create table if not exists FeeCategories (feeCategoryId integer not null primary key autoincrement, feeCategory varchar(100) not null, orderNumber smallint not null default 0, ${recordColumns})`, `create table if not exists FeeCategories (
feeCategoryId integer not null primary key autoincrement,
feeCategory varchar(100) not null,
isGroupedFee bit not null default 0,
orderNumber smallint not null default 0,
${recordColumns})`,
`create table if not exists Fees ( `create table if not exists Fees (
feeId integer not null primary key autoincrement, feeId integer not null primary key autoincrement,
feeCategoryId integer not null, feeCategoryId integer not null,

View File

@ -26,7 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
feeCategoryContainerElement.innerHTML = `<div class="panel-heading"> feeCategoryContainerElement.innerHTML = `<div class="panel-heading">
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<h2 class="title is-4">${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')}</h2> <h2 class="title is-4 mb-2">${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')}</h2>
${feeCategory.isGroupedFee
? '<span class="tag">Grouped Fee</span>'
: ''}
</div> </div>
<div class="column is-narrow"> <div class="column is-narrow">
<div class="field is-grouped is-justify-content-end"> <div class="field is-grouped is-justify-content-end">
@ -82,7 +85,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
<p> <p>
<a class="has-text-weight-bold" href="#">${cityssm.escapeHTML((_e = fee.feeName) !== null && _e !== void 0 ? _e : '')}</a><br /> <a class="has-text-weight-bold" href="#">${cityssm.escapeHTML((_e = fee.feeName) !== null && _e !== void 0 ? _e : '')}</a><br />
<small> <small>
${cityssm ${
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
cityssm
.escapeHTML((_f = fee.feeDescription) !== null && _f !== void 0 ? _f : '') .escapeHTML((_f = fee.feeDescription) !== null && _f !== void 0 ? _f : '')
.replaceAll('\n', '<br />')} .replaceAll('\n', '<br />')}
</small> </small>
@ -220,6 +225,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
; ;
modalElement.querySelector('#feeCategoryEdit--feeCategoryId').value = feeCategory.feeCategoryId.toString(); modalElement.querySelector('#feeCategoryEdit--feeCategoryId').value = feeCategory.feeCategoryId.toString();
modalElement.querySelector('#feeCategoryEdit--feeCategory').value = feeCategory.feeCategory; modalElement.querySelector('#feeCategoryEdit--feeCategory').value = feeCategory.feeCategory;
if (feeCategory.isGroupedFee) {
;
modalElement.querySelector('#feeCategoryEdit--isGroupedFee').checked = true;
}
}, },
onshown(modalElement, closeModalFunction) { onshown(modalElement, closeModalFunction) {
var _a; var _a;

View File

@ -59,7 +59,12 @@ declare const exports: Record<string, unknown>
feeCategoryContainerElement.innerHTML = `<div class="panel-heading"> feeCategoryContainerElement.innerHTML = `<div class="panel-heading">
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<h2 class="title is-4">${cityssm.escapeHTML(feeCategory.feeCategory ?? '')}</h2> <h2 class="title is-4 mb-2">${cityssm.escapeHTML(feeCategory.feeCategory ?? '')}</h2>
${
feeCategory.isGroupedFee
? '<span class="tag">Grouped Fee</span>'
: ''
}
</div> </div>
<div class="column is-narrow"> <div class="column is-narrow">
<div class="field is-grouped is-justify-content-end"> <div class="field is-grouped is-justify-content-end">
@ -131,9 +136,12 @@ declare const exports: Record<string, unknown>
<p> <p>
<a class="has-text-weight-bold" href="#">${cityssm.escapeHTML(fee.feeName ?? '')}</a><br /> <a class="has-text-weight-bold" href="#">${cityssm.escapeHTML(fee.feeName ?? '')}</a><br />
<small> <small>
${cityssm ${
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
cityssm
.escapeHTML(fee.feeDescription ?? '') .escapeHTML(fee.feeDescription ?? '')
.replaceAll('\n', '<br />')} .replaceAll('\n', '<br />')
}
</small> </small>
</p> </p>
${ ${
@ -350,6 +358,14 @@ declare const exports: Record<string, unknown>
'#feeCategoryEdit--feeCategory' '#feeCategoryEdit--feeCategory'
) as HTMLInputElement ) as HTMLInputElement
).value = feeCategory.feeCategory ).value = feeCategory.feeCategory
if (feeCategory.isGroupedFee) {
;(
modalElement.querySelector(
'#feeCategoryEdit--isGroupedFee'
) as HTMLInputElement
).checked = true
}
}, },
onshown(modalElement, closeModalFunction) { onshown(modalElement, closeModalFunction) {
bulmaJS.toggleHtmlClipped() bulmaJS.toggleHtmlClipped()

View File

@ -1265,6 +1265,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
let feeCategories; let feeCategories;
let feeFilterElement; let feeFilterElement;
let feeFilterResultsElement; let feeFilterResultsElement;
function doAddFeeCategory(clickEvent) {
var _a;
clickEvent.preventDefault();
const feeCategoryId = Number.parseInt((_a = clickEvent.currentTarget.dataset.feeCategoryId) !== null && _a !== void 0 ? _a : '', 10);
cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFeeCategory`, {
lotOccupancyId,
feeCategoryId
}, (rawResponseJSON) => {
var _a;
const responseJSON = rawResponseJSON;
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
bulmaJS.alert({
message: 'Fee Group Added Successfully',
contextualColorName: 'success'
});
}
else {
bulmaJS.alert({
title: 'Error Adding Fee',
message: (_a = responseJSON.errorMessage) !== null && _a !== void 0 ? _a : '',
contextualColorName: 'danger'
});
}
});
}
function doAddFee(feeId, quantity = 1) { function doAddFee(feeId, quantity = 1) {
cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`, { cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`, {
lotOccupancyId, lotOccupancyId,
@ -1329,7 +1356,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
} }
} }
function filterFees() { function filterFees() {
var _a, _b, _c, _d, _e, _f; var _a, _b, _c, _d, _e, _f, _g, _h;
const filterStringPieces = feeFilterElement.value const filterStringPieces = feeFilterElement.value
.trim() .trim()
.toLowerCase() .toLowerCase()
@ -1340,10 +1367,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
categoryContainerElement.className = 'container--feeCategory'; categoryContainerElement.className = 'container--feeCategory';
categoryContainerElement.dataset.feeCategoryId = categoryContainerElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString(); feeCategory.feeCategoryId.toString();
categoryContainerElement.innerHTML = `<h4 class="title is-5 mt-2"> categoryContainerElement.innerHTML = `<div class="columns is-vcentered">
${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')} <div class="column">
</h4> <h4 class="title is-5">
${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')}
</h4>
</div>
</div>
<div class="panel mb-5"></div>`; <div class="panel mb-5"></div>`;
if (feeCategory.isGroupedFee) {
// eslint-disable-next-line no-unsanitized/method
(_b = categoryContainerElement.querySelector('.columns')) === null || _b === void 0 ? void 0 : _b.insertAdjacentHTML('beforeend', `<div class="column is-narrow has-text-right">
<button class="button is-small is-success" type="button" data-fee-category-id="${feeCategory.feeCategoryId}">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee Group</span>
</button>
</div>`);
(_c = categoryContainerElement.querySelector('button')) === null || _c === void 0 ? void 0 : _c.addEventListener('click', doAddFeeCategory);
}
let hasFees = false; let hasFees = false;
for (const fee of feeCategory.fees) { for (const fee of feeCategory.fees) {
// Don't include already applied fees that limit quantity // Don't include already applied fees that limit quantity
@ -1351,7 +1392,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
continue; continue;
} }
let includeFee = true; let includeFee = true;
const feeSearchString = `${(_b = feeCategory.feeCategory) !== null && _b !== void 0 ? _b : ''} ${(_c = fee.feeName) !== null && _c !== void 0 ? _c : ''} ${(_d = fee.feeDescription) !== null && _d !== void 0 ? _d : ''}`.toLowerCase(); const feeSearchString = `${(_d = feeCategory.feeCategory) !== null && _d !== void 0 ? _d : ''} ${(_e = fee.feeName) !== null && _e !== void 0 ? _e : ''} ${(_f = fee.feeDescription) !== null && _f !== void 0 ? _f : ''}`.toLowerCase();
for (const filterStringPiece of filterStringPieces) { for (const filterStringPiece of filterStringPieces) {
if (!feeSearchString.includes(filterStringPiece)) { if (!feeSearchString.includes(filterStringPiece)) {
includeFee = false; includeFee = false;
@ -1362,20 +1403,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
continue; continue;
} }
hasFees = true; hasFees = true;
const panelBlockElement = document.createElement('a'); const panelBlockElement = document.createElement(feeCategory.isGroupedFee ? 'div' : 'a');
panelBlockElement.className = 'panel-block is-block container--fee'; panelBlockElement.className = 'panel-block is-block container--fee';
panelBlockElement.dataset.feeId = fee.feeId.toString(); panelBlockElement.dataset.feeId = fee.feeId.toString();
panelBlockElement.dataset.feeCategoryId = panelBlockElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString(); feeCategory.feeCategoryId.toString();
panelBlockElement.href = '#';
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML((_e = fee.feeName) !== null && _e !== void 0 ? _e : '')}</strong><br /> panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML((_g = fee.feeName) !== null && _g !== void 0 ? _g : '')}</strong><br />
<small> <small>
${cityssm ${
.escapeHTML((_f = fee.feeDescription) !== null && _f !== void 0 ? _f : '') // eslint-disable-next-line @typescript-eslint/no-unsafe-call
cityssm
.escapeHTML((_h = fee.feeDescription) !== null && _h !== void 0 ? _h : '')
.replaceAll('\n', '<br />')} .replaceAll('\n', '<br />')}
</small>`; </small>`;
panelBlockElement.addEventListener('click', tryAddFee); if (!feeCategory.isGroupedFee) {
;
panelBlockElement.href = '#';
panelBlockElement.addEventListener('click', tryAddFee);
}
;
categoryContainerElement.querySelector('.panel').append(panelBlockElement); categoryContainerElement.querySelector('.panel').append(panelBlockElement);
} }
if (hasFees) { if (hasFees) {

View File

@ -194,6 +194,33 @@ addFeeButtonElement.addEventListener('click', () => {
let feeCategories; let feeCategories;
let feeFilterElement; let feeFilterElement;
let feeFilterResultsElement; let feeFilterResultsElement;
function doAddFeeCategory(clickEvent) {
var _a;
clickEvent.preventDefault();
const feeCategoryId = Number.parseInt((_a = clickEvent.currentTarget.dataset.feeCategoryId) !== null && _a !== void 0 ? _a : '', 10);
cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFeeCategory`, {
lotOccupancyId,
feeCategoryId
}, (rawResponseJSON) => {
var _a;
const responseJSON = rawResponseJSON;
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
bulmaJS.alert({
message: 'Fee Group Added Successfully',
contextualColorName: 'success'
});
}
else {
bulmaJS.alert({
title: 'Error Adding Fee',
message: (_a = responseJSON.errorMessage) !== null && _a !== void 0 ? _a : '',
contextualColorName: 'danger'
});
}
});
}
function doAddFee(feeId, quantity = 1) { function doAddFee(feeId, quantity = 1) {
cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`, { cityssm.postJSON(`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`, {
lotOccupancyId, lotOccupancyId,
@ -258,7 +285,7 @@ addFeeButtonElement.addEventListener('click', () => {
} }
} }
function filterFees() { function filterFees() {
var _a, _b, _c, _d, _e, _f; var _a, _b, _c, _d, _e, _f, _g, _h;
const filterStringPieces = feeFilterElement.value const filterStringPieces = feeFilterElement.value
.trim() .trim()
.toLowerCase() .toLowerCase()
@ -269,10 +296,24 @@ addFeeButtonElement.addEventListener('click', () => {
categoryContainerElement.className = 'container--feeCategory'; categoryContainerElement.className = 'container--feeCategory';
categoryContainerElement.dataset.feeCategoryId = categoryContainerElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString(); feeCategory.feeCategoryId.toString();
categoryContainerElement.innerHTML = `<h4 class="title is-5 mt-2"> categoryContainerElement.innerHTML = `<div class="columns is-vcentered">
${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')} <div class="column">
</h4> <h4 class="title is-5">
${cityssm.escapeHTML((_a = feeCategory.feeCategory) !== null && _a !== void 0 ? _a : '')}
</h4>
</div>
</div>
<div class="panel mb-5"></div>`; <div class="panel mb-5"></div>`;
if (feeCategory.isGroupedFee) {
// eslint-disable-next-line no-unsanitized/method
(_b = categoryContainerElement.querySelector('.columns')) === null || _b === void 0 ? void 0 : _b.insertAdjacentHTML('beforeend', `<div class="column is-narrow has-text-right">
<button class="button is-small is-success" type="button" data-fee-category-id="${feeCategory.feeCategoryId}">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee Group</span>
</button>
</div>`);
(_c = categoryContainerElement.querySelector('button')) === null || _c === void 0 ? void 0 : _c.addEventListener('click', doAddFeeCategory);
}
let hasFees = false; let hasFees = false;
for (const fee of feeCategory.fees) { for (const fee of feeCategory.fees) {
// Don't include already applied fees that limit quantity // Don't include already applied fees that limit quantity
@ -280,7 +321,7 @@ addFeeButtonElement.addEventListener('click', () => {
continue; continue;
} }
let includeFee = true; let includeFee = true;
const feeSearchString = `${(_b = feeCategory.feeCategory) !== null && _b !== void 0 ? _b : ''} ${(_c = fee.feeName) !== null && _c !== void 0 ? _c : ''} ${(_d = fee.feeDescription) !== null && _d !== void 0 ? _d : ''}`.toLowerCase(); const feeSearchString = `${(_d = feeCategory.feeCategory) !== null && _d !== void 0 ? _d : ''} ${(_e = fee.feeName) !== null && _e !== void 0 ? _e : ''} ${(_f = fee.feeDescription) !== null && _f !== void 0 ? _f : ''}`.toLowerCase();
for (const filterStringPiece of filterStringPieces) { for (const filterStringPiece of filterStringPieces) {
if (!feeSearchString.includes(filterStringPiece)) { if (!feeSearchString.includes(filterStringPiece)) {
includeFee = false; includeFee = false;
@ -291,20 +332,26 @@ addFeeButtonElement.addEventListener('click', () => {
continue; continue;
} }
hasFees = true; hasFees = true;
const panelBlockElement = document.createElement('a'); const panelBlockElement = document.createElement(feeCategory.isGroupedFee ? 'div' : 'a');
panelBlockElement.className = 'panel-block is-block container--fee'; panelBlockElement.className = 'panel-block is-block container--fee';
panelBlockElement.dataset.feeId = fee.feeId.toString(); panelBlockElement.dataset.feeId = fee.feeId.toString();
panelBlockElement.dataset.feeCategoryId = panelBlockElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString(); feeCategory.feeCategoryId.toString();
panelBlockElement.href = '#';
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML((_e = fee.feeName) !== null && _e !== void 0 ? _e : '')}</strong><br /> panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML((_g = fee.feeName) !== null && _g !== void 0 ? _g : '')}</strong><br />
<small> <small>
${cityssm ${
.escapeHTML((_f = fee.feeDescription) !== null && _f !== void 0 ? _f : '') // eslint-disable-next-line @typescript-eslint/no-unsafe-call
cityssm
.escapeHTML((_h = fee.feeDescription) !== null && _h !== void 0 ? _h : '')
.replaceAll('\n', '<br />')} .replaceAll('\n', '<br />')}
</small>`; </small>`;
panelBlockElement.addEventListener('click', tryAddFee); if (!feeCategory.isGroupedFee) {
;
panelBlockElement.href = '#';
panelBlockElement.addEventListener('click', tryAddFee);
}
;
categoryContainerElement.querySelector('.panel').append(panelBlockElement); categoryContainerElement.querySelector('.panel').append(panelBlockElement);
} }
if (hasFees) { if (hasFees) {

View File

@ -307,6 +307,47 @@ addFeeButtonElement.addEventListener('click', () => {
let feeFilterElement: HTMLInputElement let feeFilterElement: HTMLInputElement
let feeFilterResultsElement: HTMLElement let feeFilterResultsElement: HTMLElement
function doAddFeeCategory(clickEvent: Event): void {
clickEvent.preventDefault()
const feeCategoryId = Number.parseInt(
(clickEvent.currentTarget as HTMLElement).dataset.feeCategoryId ?? '',
10
)
cityssm.postJSON(
`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFeeCategory`,
{
lotOccupancyId,
feeCategoryId
},
(rawResponseJSON) => {
const responseJSON = rawResponseJSON as {
success: boolean
errorMessage?: string
lotOccupancyFees: LotOccupancyFee[]
}
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees
renderLotOccupancyFees()
bulmaJS.alert({
message: 'Fee Group Added Successfully',
contextualColorName: 'success'
})
} else {
bulmaJS.alert({
title: 'Error Adding Fee',
message: responseJSON.errorMessage ?? '',
contextualColorName: 'danger'
})
}
}
)
}
function doAddFee(feeId: number, quantity: number | string = 1): void { function doAddFee(feeId: number, quantity: number | string = 1): void {
cityssm.postJSON( cityssm.postJSON(
`${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`, `${los.urlPrefix}/lotOccupancies/doAddLotOccupancyFee`,
@ -412,11 +453,30 @@ addFeeButtonElement.addEventListener('click', () => {
categoryContainerElement.dataset.feeCategoryId = categoryContainerElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString() feeCategory.feeCategoryId.toString()
categoryContainerElement.innerHTML = `<h4 class="title is-5 mt-2"> categoryContainerElement.innerHTML = `<div class="columns is-vcentered">
${cityssm.escapeHTML(feeCategory.feeCategory ?? '')} <div class="column">
</h4> <h4 class="title is-5">
${cityssm.escapeHTML(feeCategory.feeCategory ?? '')}
</h4>
</div>
</div>
<div class="panel mb-5"></div>` <div class="panel mb-5"></div>`
if (feeCategory.isGroupedFee) {
// eslint-disable-next-line no-unsanitized/method
categoryContainerElement.querySelector('.columns')?.insertAdjacentHTML(
'beforeend',
`<div class="column is-narrow has-text-right">
<button class="button is-small is-success" type="button" data-fee-category-id="${feeCategory.feeCategoryId}">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee Group</span>
</button>
</div>`
)
categoryContainerElement.querySelector('button')?.addEventListener('click', doAddFeeCategory)
}
let hasFees = false let hasFees = false
for (const fee of feeCategory.fees) { for (const fee of feeCategory.fees) {
@ -447,22 +507,29 @@ addFeeButtonElement.addEventListener('click', () => {
hasFees = true hasFees = true
const panelBlockElement = document.createElement('a') const panelBlockElement = document.createElement(
feeCategory.isGroupedFee ? 'div' : 'a'
)
panelBlockElement.className = 'panel-block is-block container--fee' panelBlockElement.className = 'panel-block is-block container--fee'
panelBlockElement.dataset.feeId = fee.feeId.toString() panelBlockElement.dataset.feeId = fee.feeId.toString()
panelBlockElement.dataset.feeCategoryId = panelBlockElement.dataset.feeCategoryId =
feeCategory.feeCategoryId.toString() feeCategory.feeCategoryId.toString()
panelBlockElement.href = '#'
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML(fee.feeName ?? '')}</strong><br /> panelBlockElement.innerHTML = `<strong>${cityssm.escapeHTML(fee.feeName ?? '')}</strong><br />
<small> <small>
${cityssm ${
.escapeHTML(fee.feeDescription ?? '') // eslint-disable-next-line @typescript-eslint/no-unsafe-call
.replaceAll('\n', '<br />')} cityssm
.escapeHTML(fee.feeDescription ?? '')
.replaceAll('\n', '<br />')
}
</small>` </small>`
panelBlockElement.addEventListener('click', tryAddFee) if (!feeCategory.isGroupedFee) {
;(panelBlockElement as HTMLAnchorElement).href = '#'
panelBlockElement.addEventListener('click', tryAddFee)
}
;( ;(
categoryContainerElement.querySelector('.panel') as HTMLElement categoryContainerElement.querySelector('.panel') as HTMLElement
).append(panelBlockElement) ).append(panelBlockElement)

View File

@ -26,6 +26,10 @@
/> />
</div> </div>
</div> </div>
<label class="checkbox is-block">
<input id="feeCategoryAdd--isGroupedFee" name="isGroupedFee" type="checkbox" value="1" />
Treat Category as a Group of Fees
</label>
</form> </form>
</section> </section>
<div class="modal-card-foot justify-right"> <div class="modal-card-foot justify-right">

View File

@ -31,6 +31,10 @@
/> />
</div> </div>
</div> </div>
<label class="checkbox is-block">
<input id="feeCategoryEdit--isGroupedFee" name="isGroupedFee" type="checkbox" value="1" />
Treat Category as a Group of Fees
</label>
</form> </form>
</section> </section>
<footer class="modal-card-foot justify-right"> <footer class="modal-card-foot justify-right">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@ import handler_search from '../handlers/lotOccupancies-get/search.js';
import handler_view from '../handlers/lotOccupancies-get/view.js'; import handler_view from '../handlers/lotOccupancies-get/view.js';
import handler_doAddLotOccupancyComment from '../handlers/lotOccupancies-post/doAddLotOccupancyComment.js'; import handler_doAddLotOccupancyComment from '../handlers/lotOccupancies-post/doAddLotOccupancyComment.js';
import handler_doAddLotOccupancyFee from '../handlers/lotOccupancies-post/doAddLotOccupancyFee.js'; import handler_doAddLotOccupancyFee from '../handlers/lotOccupancies-post/doAddLotOccupancyFee.js';
import handler_doAddLotOccupancyFeeCategory from '../handlers/lotOccupancies-post/doAddLotOccupancyFeeCategory.js';
import handler_doAddLotOccupancyOccupant from '../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js'; import handler_doAddLotOccupancyOccupant from '../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js';
import handler_doAddLotOccupancyTransaction from '../handlers/lotOccupancies-post/doAddLotOccupancyTransaction.js'; import handler_doAddLotOccupancyTransaction from '../handlers/lotOccupancies-post/doAddLotOccupancyTransaction.js';
import handler_doCopyLotOccupancy from '../handlers/lotOccupancies-post/doCopyLotOccupancy.js'; import handler_doCopyLotOccupancy from '../handlers/lotOccupancies-post/doCopyLotOccupancy.js';
@ -53,6 +54,7 @@ router.post('/doDeleteLotOccupancyComment', updatePostHandler, handler_doDeleteL
// Fees // Fees
router.post('/doGetFees', updatePostHandler, handler_doGetFees); router.post('/doGetFees', updatePostHandler, handler_doGetFees);
router.post('/doAddLotOccupancyFee', updatePostHandler, handler_doAddLotOccupancyFee); router.post('/doAddLotOccupancyFee', updatePostHandler, handler_doAddLotOccupancyFee);
router.post('/doAddLotOccupancyFeeCategory', updatePostHandler, handler_doAddLotOccupancyFeeCategory);
router.post('/doUpdateLotOccupancyFeeQuantity', updatePostHandler, handler_doUpdateLotOccupancyFeeQuantity); router.post('/doUpdateLotOccupancyFeeQuantity', updatePostHandler, handler_doUpdateLotOccupancyFeeQuantity);
router.post('/doDeleteLotOccupancyFee', updatePostHandler, handler_doDeleteLotOccupancyFee); router.post('/doDeleteLotOccupancyFee', updatePostHandler, handler_doDeleteLotOccupancyFee);
// Transactions // Transactions

View File

@ -6,6 +6,7 @@ import handler_search from '../handlers/lotOccupancies-get/search.js'
import handler_view from '../handlers/lotOccupancies-get/view.js' import handler_view from '../handlers/lotOccupancies-get/view.js'
import handler_doAddLotOccupancyComment from '../handlers/lotOccupancies-post/doAddLotOccupancyComment.js' import handler_doAddLotOccupancyComment from '../handlers/lotOccupancies-post/doAddLotOccupancyComment.js'
import handler_doAddLotOccupancyFee from '../handlers/lotOccupancies-post/doAddLotOccupancyFee.js' import handler_doAddLotOccupancyFee from '../handlers/lotOccupancies-post/doAddLotOccupancyFee.js'
import handler_doAddLotOccupancyFeeCategory from '../handlers/lotOccupancies-post/doAddLotOccupancyFeeCategory.js'
import handler_doAddLotOccupancyOccupant from '../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js' import handler_doAddLotOccupancyOccupant from '../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js'
import handler_doAddLotOccupancyTransaction from '../handlers/lotOccupancies-post/doAddLotOccupancyTransaction.js' import handler_doAddLotOccupancyTransaction from '../handlers/lotOccupancies-post/doAddLotOccupancyTransaction.js'
import handler_doCopyLotOccupancy from '../handlers/lotOccupancies-post/doCopyLotOccupancy.js' import handler_doCopyLotOccupancy from '../handlers/lotOccupancies-post/doCopyLotOccupancy.js'
@ -145,6 +146,12 @@ router.post(
handler_doAddLotOccupancyFee as RequestHandler handler_doAddLotOccupancyFee as RequestHandler
) )
router.post(
'/doAddLotOccupancyFeeCategory',
updatePostHandler,
handler_doAddLotOccupancyFeeCategory as RequestHandler
)
router.post( router.post(
'/doUpdateLotOccupancyFeeQuantity', '/doUpdateLotOccupancyFeeQuantity',
updatePostHandler, updatePostHandler,

View File

@ -110,6 +110,7 @@ export interface FeeCategory extends Record {
feeCategoryId: number; feeCategoryId: number;
feeCategory: string; feeCategory: string;
fees: Fee[]; fees: Fee[];
isGroupedFee: boolean;
orderNumber?: number; orderNumber?: number;
} }
export interface Fee extends Record { export interface Fee extends Record {

View File

@ -143,6 +143,7 @@ export interface FeeCategory extends Record {
feeCategoryId: number feeCategoryId: number
feeCategory: string feeCategory: string
fees: Fee[] fees: Fee[]
isGroupedFee: boolean
orderNumber?: number orderNumber?: number
} }