diff --git a/handlers/admin-post/doAddFee.d.ts b/handlers/admin-post/doAddFee.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/admin-post/doAddFee.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/admin-post/doAddFee.js b/handlers/admin-post/doAddFee.js new file mode 100644 index 00000000..fd592402 --- /dev/null +++ b/handlers/admin-post/doAddFee.js @@ -0,0 +1,14 @@ +import { addFee } from "../../helpers/lotOccupancyDB/addFee.js"; +import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js"; +export const handler = async (request, response) => { + const feeId = addFee(request.body, request.session); + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + response.json({ + success: true, + feeId, + feeCategories + }); +}; +export default handler; diff --git a/handlers/admin-post/doAddFee.ts b/handlers/admin-post/doAddFee.ts new file mode 100644 index 00000000..e104edf6 --- /dev/null +++ b/handlers/admin-post/doAddFee.ts @@ -0,0 +1,31 @@ +import type { + RequestHandler +} from "express"; + +import { + addFee +} from "../../helpers/lotOccupancyDB/addFee.js"; + +import { + getFeeCategories +} from "../../helpers/lotOccupancyDB/getFeeCategories.js"; + + + +export const handler: RequestHandler = async (request, response) => { + + const feeId = addFee(request.body, request.session); + + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + + response.json({ + success: true, + feeId, + feeCategories + }); +}; + + +export default handler; \ No newline at end of file diff --git a/handlers/admin-post/doDeleteFee.d.ts b/handlers/admin-post/doDeleteFee.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/admin-post/doDeleteFee.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/admin-post/doDeleteFee.js b/handlers/admin-post/doDeleteFee.js new file mode 100644 index 00000000..1895f21b --- /dev/null +++ b/handlers/admin-post/doDeleteFee.js @@ -0,0 +1,13 @@ +import { deleteFee } from "../../helpers/lotOccupancyDB/deleteFee.js"; +import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js"; +export const handler = async (request, response) => { + const success = deleteFee(request.body.feeId, request.session); + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + response.json({ + success, + feeCategories + }); +}; +export default handler; diff --git a/handlers/admin-post/doDeleteFee.ts b/handlers/admin-post/doDeleteFee.ts new file mode 100644 index 00000000..b5ce4442 --- /dev/null +++ b/handlers/admin-post/doDeleteFee.ts @@ -0,0 +1,30 @@ +import type { + RequestHandler +} from "express"; + +import { + deleteFee +} from "../../helpers/lotOccupancyDB/deleteFee.js"; + +import { + getFeeCategories +} from "../../helpers/lotOccupancyDB/getFeeCategories.js"; + + + +export const handler: RequestHandler = async (request, response) => { + + const success = deleteFee(request.body.feeId, request.session); + + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + + response.json({ + success, + feeCategories + }); +}; + + +export default handler; \ No newline at end of file diff --git a/handlers/admin-post/doDeleteFeeCategory.d.ts b/handlers/admin-post/doDeleteFeeCategory.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/admin-post/doDeleteFeeCategory.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/admin-post/doDeleteFeeCategory.js b/handlers/admin-post/doDeleteFeeCategory.js new file mode 100644 index 00000000..65e6ed94 --- /dev/null +++ b/handlers/admin-post/doDeleteFeeCategory.js @@ -0,0 +1,13 @@ +import { deleteFeeCategory } from "../../helpers/lotOccupancyDB/deleteFeeCategory.js"; +import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js"; +export const handler = async (request, response) => { + const success = deleteFeeCategory(request.body.feeCategoryId, request.session); + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + response.json({ + success, + feeCategories + }); +}; +export default handler; diff --git a/handlers/admin-post/doDeleteFeeCategory.ts b/handlers/admin-post/doDeleteFeeCategory.ts new file mode 100644 index 00000000..e82af450 --- /dev/null +++ b/handlers/admin-post/doDeleteFeeCategory.ts @@ -0,0 +1,30 @@ +import type { + RequestHandler +} from "express"; + +import { + deleteFeeCategory +} from "../../helpers/lotOccupancyDB/deleteFeeCategory.js"; + +import { + getFeeCategories +} from "../../helpers/lotOccupancyDB/getFeeCategories.js"; + + + +export const handler: RequestHandler = async (request, response) => { + + const success = deleteFeeCategory(request.body.feeCategoryId, request.session); + + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + + response.json({ + success, + feeCategories + }); +}; + + +export default handler; \ No newline at end of file diff --git a/handlers/admin-post/doUpdateFee.d.ts b/handlers/admin-post/doUpdateFee.d.ts new file mode 100644 index 00000000..9621c611 --- /dev/null +++ b/handlers/admin-post/doUpdateFee.d.ts @@ -0,0 +1,3 @@ +import type { RequestHandler } from "express"; +export declare const handler: RequestHandler; +export default handler; diff --git a/handlers/admin-post/doUpdateFee.js b/handlers/admin-post/doUpdateFee.js new file mode 100644 index 00000000..8bc3ca6f --- /dev/null +++ b/handlers/admin-post/doUpdateFee.js @@ -0,0 +1,13 @@ +import { updateFee } from "../../helpers/lotOccupancyDB/updateFee.js"; +import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js"; +export const handler = async (request, response) => { + const success = updateFee(request.body, request.session); + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + response.json({ + success, + feeCategories + }); +}; +export default handler; diff --git a/handlers/admin-post/doUpdateFee.ts b/handlers/admin-post/doUpdateFee.ts new file mode 100644 index 00000000..a91bbb1e --- /dev/null +++ b/handlers/admin-post/doUpdateFee.ts @@ -0,0 +1,30 @@ +import type { + RequestHandler +} from "express"; + +import { + updateFee +} from "../../helpers/lotOccupancyDB/updateFee.js"; + +import { + getFeeCategories +} from "../../helpers/lotOccupancyDB/getFeeCategories.js"; + + + +export const handler: RequestHandler = async (request, response) => { + + const success = updateFee(request.body, request.session); + + const feeCategories = getFeeCategories({}, { + includeFees: true + }); + + response.json({ + success, + feeCategories + }); +}; + + +export default handler; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/addFee.d.ts b/helpers/lotOccupancyDB/addFee.d.ts new file mode 100644 index 00000000..937be70a --- /dev/null +++ b/helpers/lotOccupancyDB/addFee.d.ts @@ -0,0 +1,18 @@ +import type * as recordTypes from "../../types/recordTypes"; +interface AddFeeForm { + feeCategoryId: string; + feeName: string; + feeDescription: string; + occupancyTypeId?: string; + lotTypeId?: string; + feeAmount?: string; + feeFunction?: string; + taxAmount?: string; + taxPercentage?: string; + includeQuantity: "" | "1"; + quantityUnit?: string; + isRequired: "" | "1"; + orderNumber?: number; +} +export declare const addFee: (feeForm: AddFeeForm, requestSession: recordTypes.PartialSession) => number; +export default addFee; diff --git a/helpers/lotOccupancyDB/addFee.js b/helpers/lotOccupancyDB/addFee.js new file mode 100644 index 00000000..f0d96923 --- /dev/null +++ b/helpers/lotOccupancyDB/addFee.js @@ -0,0 +1,21 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const addFee = (feeForm, requestSession) => { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("insert into Fees (" + + "feeCategoryId, feeName, feeDescription," + + " occupancyTypeId, lotTypeId," + + " feeAmount, feeFunction," + + " taxAmount, taxPercentage," + + " includeQuantity, quantityUnit," + + " isRequired, orderNumber," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + .run(feeForm.feeCategoryId, feeForm.feeName, feeForm.feeDescription, (feeForm.occupancyTypeId || undefined), (feeForm.lotTypeId || undefined), (feeForm.feeAmount || undefined), (feeForm.feeFunction || undefined), (feeForm.taxAmount || undefined), (feeForm.taxPercentage || undefined), (feeForm.includeQuantity ? 1 : 0), feeForm.quantityUnit, (feeForm.isRequired ? 1 : 0), (feeForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis); + database.close(); + return result.lastInsertRowid; +}; +export default addFee; diff --git a/helpers/lotOccupancyDB/addFee.ts b/helpers/lotOccupancyDB/addFee.ts new file mode 100644 index 00000000..4d8d7454 --- /dev/null +++ b/helpers/lotOccupancyDB/addFee.ts @@ -0,0 +1,66 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +interface AddFeeForm { + feeCategoryId: string; + feeName: string; + feeDescription: string; + occupancyTypeId?: string; + lotTypeId?: string; + feeAmount?: string; + feeFunction?: string; + taxAmount?: string; + taxPercentage?: string; + includeQuantity: "" | "1"; + quantityUnit?: string; + isRequired: "" | "1"; + orderNumber?: number; +} + + +export const addFee = + (feeForm: AddFeeForm, requestSession: recordTypes.PartialSession): number => { + + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare("insert into Fees (" + + "feeCategoryId, feeName, feeDescription," + + " occupancyTypeId, lotTypeId," + + " feeAmount, feeFunction," + + " taxAmount, taxPercentage," + + " includeQuantity, quantityUnit," + + " isRequired, orderNumber," + + " recordCreate_userName, recordCreate_timeMillis," + + " recordUpdate_userName, recordUpdate_timeMillis)" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + .run(feeForm.feeCategoryId, + feeForm.feeName, + feeForm.feeDescription, + (feeForm.occupancyTypeId || undefined), + (feeForm.lotTypeId || undefined), + (feeForm.feeAmount || undefined), + (feeForm.feeFunction || undefined), + (feeForm.taxAmount || undefined), + (feeForm.taxPercentage || undefined), + (feeForm.includeQuantity ? 1 : 0), + feeForm.quantityUnit, + (feeForm.isRequired ? 1 : 0), + (feeForm.orderNumber || 0), + requestSession.user.userName, + rightNowMillis, + requestSession.user.userName, + rightNowMillis); + + database.close(); + + return result.lastInsertRowid as number; + }; + + +export default addFee; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/deleteFee.d.ts b/helpers/lotOccupancyDB/deleteFee.d.ts new file mode 100644 index 00000000..48bcdd3b --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFee.d.ts @@ -0,0 +1,3 @@ +import type * as recordTypes from "../../types/recordTypes"; +export declare const deleteFee: (feeId: number | string, requestSession: recordTypes.PartialSession) => boolean; +export default deleteFee; diff --git a/helpers/lotOccupancyDB/deleteFee.js b/helpers/lotOccupancyDB/deleteFee.js new file mode 100644 index 00000000..763e0591 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFee.js @@ -0,0 +1,15 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const deleteFee = (feeId, requestSession) => { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update Fees" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where feeId = ?") + .run(requestSession.user.userName, rightNowMillis, feeId); + database.close(); + return (result.changes > 0); +}; +export default deleteFee; diff --git a/helpers/lotOccupancyDB/deleteFee.ts b/helpers/lotOccupancyDB/deleteFee.ts new file mode 100644 index 00000000..ee7a9cc5 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFee.ts @@ -0,0 +1,33 @@ +import sqlite from "better-sqlite3"; + +import { + lotOccupancyDB as databasePath +} from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +export const deleteFee = + (feeId: number | string, + requestSession: recordTypes.PartialSession): boolean => { + + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare("update Fees" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where feeId = ?") + .run(requestSession.user.userName, + rightNowMillis, + feeId); + + database.close(); + + return (result.changes > 0); + }; + + +export default deleteFee; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/deleteFeeCategory.d.ts b/helpers/lotOccupancyDB/deleteFeeCategory.d.ts new file mode 100644 index 00000000..f5536fb1 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFeeCategory.d.ts @@ -0,0 +1,3 @@ +import type * as recordTypes from "../../types/recordTypes"; +export declare const deleteFeeCategory: (feeCategoryId: number | string, requestSession: recordTypes.PartialSession) => boolean; +export default deleteFeeCategory; diff --git a/helpers/lotOccupancyDB/deleteFeeCategory.js b/helpers/lotOccupancyDB/deleteFeeCategory.js new file mode 100644 index 00000000..93cae785 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFeeCategory.js @@ -0,0 +1,15 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const deleteFeeCategory = (feeCategoryId, requestSession) => { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update FeeCategories" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where feeCategoryId = ?") + .run(requestSession.user.userName, rightNowMillis, feeCategoryId); + database.close(); + return (result.changes > 0); +}; +export default deleteFeeCategory; diff --git a/helpers/lotOccupancyDB/deleteFeeCategory.ts b/helpers/lotOccupancyDB/deleteFeeCategory.ts new file mode 100644 index 00000000..19820265 --- /dev/null +++ b/helpers/lotOccupancyDB/deleteFeeCategory.ts @@ -0,0 +1,33 @@ +import sqlite from "better-sqlite3"; + +import { + lotOccupancyDB as databasePath +} from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +export const deleteFeeCategory = + (feeCategoryId: number | string, + requestSession: recordTypes.PartialSession): boolean => { + + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare("update FeeCategories" + + " set recordDelete_userName = ?," + + " recordDelete_timeMillis = ?" + + " where feeCategoryId = ?") + .run(requestSession.user.userName, + rightNowMillis, + feeCategoryId); + + database.close(); + + return (result.changes > 0); + }; + + +export default deleteFeeCategory; \ No newline at end of file diff --git a/helpers/lotOccupancyDB/getFeeCategories.js b/helpers/lotOccupancyDB/getFeeCategories.js index 263eedab..b14a965d 100644 --- a/helpers/lotOccupancyDB/getFeeCategories.js +++ b/helpers/lotOccupancyDB/getFeeCategories.js @@ -26,26 +26,30 @@ export const getFeeCategories = (filters, options) => { " order by orderNumber, feeCategory") .all(sqlParameters); if (options.includeFees) { - sql = "select feeId, feeName, feeDescription," + - " occupancyTypeId, lotTypeId," + - " feeAmount, feeFunction, taxAmount, taxPercentage," + - " isRequired" + - " from Fees" + - " where recordDelete_timeMillis is null" + - " and feeCategoryId = ?"; - sqlParameters = []; for (const feeCategory of feeCategories) { + sql = "select f.feeId, f.feeName, f.feeDescription," + + " f.occupancyTypeId, o.occupancyType," + + " f.lotTypeId, l.lotType," + + " f.feeAmount, f.feeFunction, f.taxAmount, f.taxPercentage," + + " f.includeQuantity, f.quantityUnit," + + " f.isRequired" + + " from Fees f" + + " left join OccupancyTypes o on f.occupancyTypeId = o.occupancyTypeId" + + " left join LotTypes l on f.lotTypeId = l.lotTypeId" + + " where f.recordDelete_timeMillis is null" + + " and f.feeCategoryId = ?"; + sqlParameters = []; sqlParameters.push(feeCategory.feeCategoryId); if (filters.occupancyTypeId) { - sql += " and (occupancyTypeId is null or occupancyTypeId = ?)"; + sql += " and (f.occupancyTypeId is null or f.occupancyTypeId = ?)"; sqlParameters.push(filters.occupancyTypeId); } if (filters.lotTypeId) { - sql += " and (lotTypeId is null or lotTypeId = ?)"; + sql += " and (f.lotTypeId is null or f.lotTypeId = ?)"; sqlParameters.push(filters.lotTypeId); } feeCategory.fees = database.prepare(sql + - " order by orderNumber, feeName") + " order by f.orderNumber, f.feeName") .all(sqlParameters); } } diff --git a/helpers/lotOccupancyDB/getFeeCategories.ts b/helpers/lotOccupancyDB/getFeeCategories.ts index f707501c..8bc66666 100644 --- a/helpers/lotOccupancyDB/getFeeCategories.ts +++ b/helpers/lotOccupancyDB/getFeeCategories.ts @@ -52,35 +52,39 @@ export const getFeeCategories = (filters ? : GetFeeCategoriesFilters, options ? .all(sqlParameters); if (options.includeFees) { - - sql = "select feeId, feeName, feeDescription," + - " occupancyTypeId, lotTypeId," + - " feeAmount, feeFunction, taxAmount, taxPercentage," + - " isRequired" + - " from Fees" + - " where recordDelete_timeMillis is null" + - " and feeCategoryId = ?"; - - sqlParameters = []; - + for (const feeCategory of feeCategories) { + sql = "select f.feeId, f.feeName, f.feeDescription," + + " f.occupancyTypeId, o.occupancyType," + + " f.lotTypeId, l.lotType," + + " f.feeAmount, f.feeFunction, f.taxAmount, f.taxPercentage," + + " f.includeQuantity, f.quantityUnit," + + " f.isRequired" + + " from Fees f" + + " left join OccupancyTypes o on f.occupancyTypeId = o.occupancyTypeId" + + " left join LotTypes l on f.lotTypeId = l.lotTypeId" + + " where f.recordDelete_timeMillis is null" + + " and f.feeCategoryId = ?"; + + sqlParameters = []; + sqlParameters.push(feeCategory.feeCategoryId); if (filters.occupancyTypeId) { - sql += " and (occupancyTypeId is null or occupancyTypeId = ?)"; + sql += " and (f.occupancyTypeId is null or f.occupancyTypeId = ?)"; sqlParameters.push(filters.occupancyTypeId); } if (filters.lotTypeId) { - sql += " and (lotTypeId is null or lotTypeId = ?)"; + sql += " and (f.lotTypeId is null or f.lotTypeId = ?)"; sqlParameters.push(filters.lotTypeId); } feeCategory.fees = database.prepare(sql + - " order by orderNumber, feeName") + " order by f.orderNumber, f.feeName") .all(sqlParameters); } } diff --git a/helpers/lotOccupancyDB/updateFee.d.ts b/helpers/lotOccupancyDB/updateFee.d.ts new file mode 100644 index 00000000..a9b58165 --- /dev/null +++ b/helpers/lotOccupancyDB/updateFee.d.ts @@ -0,0 +1,18 @@ +import type * as recordTypes from "../../types/recordTypes"; +interface UpdateFeeForm { + feeId: string; + feeCategoryId: string; + feeName: string; + feeDescription: string; + occupancyTypeId?: string; + lotTypeId?: string; + feeAmount?: string; + feeFunction?: string; + taxAmount?: string; + taxPercentage?: string; + includeQuantity: "" | "1"; + quantityUnit?: string; + isRequired: "" | "1"; +} +export declare const updateFee: (feeForm: UpdateFeeForm, requestSession: recordTypes.PartialSession) => boolean; +export default updateFee; diff --git a/helpers/lotOccupancyDB/updateFee.js b/helpers/lotOccupancyDB/updateFee.js new file mode 100644 index 00000000..b9ada7cb --- /dev/null +++ b/helpers/lotOccupancyDB/updateFee.js @@ -0,0 +1,28 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; +export const updateFee = (feeForm, requestSession) => { + const database = sqlite(databasePath); + const rightNowMillis = Date.now(); + const result = database + .prepare("update Fees" + + " set feeCategoryId = ?," + + " feeName = ?," + + " feeDescription = ?," + + " occupancyTypeId = ?," + + " lotTypeId = ?," + + " feeAmount = ?," + + " feeFunction = ?," + + " taxAmount = ?," + + " taxPercentage = ?," + + " includeQuantity = ?," + + " quantityUnit = ?," + + " isRequired = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where recordDelete_timeMillis is null" + + " and feeId = ?") + .run(feeForm.feeCategoryId, feeForm.feeName, feeForm.feeDescription, (feeForm.occupancyTypeId || undefined), (feeForm.lotTypeId || undefined), (feeForm.feeAmount || undefined), (feeForm.feeFunction || undefined), (feeForm.taxAmount || undefined), (feeForm.taxPercentage || undefined), (feeForm.includeQuantity ? 1 : 0), feeForm.quantityUnit, (feeForm.isRequired ? 1 : 0), requestSession.user.userName, rightNowMillis, feeForm.feeId); + database.close(); + return result.changes > 0; +}; +export default updateFee; diff --git a/helpers/lotOccupancyDB/updateFee.ts b/helpers/lotOccupancyDB/updateFee.ts new file mode 100644 index 00000000..3f40f0c0 --- /dev/null +++ b/helpers/lotOccupancyDB/updateFee.ts @@ -0,0 +1,71 @@ +import sqlite from "better-sqlite3"; +import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js"; + +import type * as recordTypes from "../../types/recordTypes"; + + +interface UpdateFeeForm { + feeId: string; + feeCategoryId: string; + feeName: string; + feeDescription: string; + occupancyTypeId?: string; + lotTypeId?: string; + feeAmount?: string; + feeFunction?: string; + taxAmount?: string; + taxPercentage?: string; + includeQuantity: "" | "1"; + quantityUnit?: string; + isRequired: "" | "1"; +} + + +export const updateFee = + (feeForm: UpdateFeeForm, requestSession: recordTypes.PartialSession): boolean => { + + const database = sqlite(databasePath); + + const rightNowMillis = Date.now(); + + const result = database + .prepare("update Fees" + + " set feeCategoryId = ?," + + " feeName = ?," + + " feeDescription = ?," + + " occupancyTypeId = ?," + + " lotTypeId = ?," + + " feeAmount = ?," + + " feeFunction = ?," + + " taxAmount = ?," + + " taxPercentage = ?," + + " includeQuantity = ?," + + " quantityUnit = ?," + + " isRequired = ?," + + " recordUpdate_userName = ?," + + " recordUpdate_timeMillis = ?" + + " where recordDelete_timeMillis is null" + + " and feeId = ?") + .run(feeForm.feeCategoryId, + feeForm.feeName, + feeForm.feeDescription, + (feeForm.occupancyTypeId || undefined), + (feeForm.lotTypeId || undefined), + (feeForm.feeAmount || undefined), + (feeForm.feeFunction || undefined), + (feeForm.taxAmount || undefined), + (feeForm.taxPercentage || undefined), + (feeForm.includeQuantity ? 1 : 0), + feeForm.quantityUnit, + (feeForm.isRequired ? 1 : 0), + requestSession.user.userName, + rightNowMillis, + feeForm.feeId); + + database.close(); + + return result.changes > 0; + }; + + +export default updateFee; \ No newline at end of file diff --git a/public-typescript/adminFees.js b/public-typescript/adminFees.js index 6a794668..1c3253d3 100644 --- a/public-typescript/adminFees.js +++ b/public-typescript/adminFees.js @@ -5,6 +5,150 @@ Object.defineProperty(exports, "__esModule", { value: true }); const urlPrefix = document.querySelector("main").dataset.urlPrefix; const feeCategoriesContainerElement = document.querySelector("#container--feeCategories"); let feeCategories = exports.feeCategories; + const renderFeeCategories = () => { + if (feeCategories.length === 0) { + feeCategoriesContainerElement.innerHTML = "
" + + "

There are no available fees.

" + + "
"; + return; + } + feeCategoriesContainerElement.innerHTML = ""; + for (const feeCategory of feeCategories) { + const feeCategoryContainerElement = document.createElement("section"); + feeCategoryContainerElement.className = "container--feeCategory mb-5"; + feeCategoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString(); + feeCategoryContainerElement.insertAdjacentHTML("beforeend", "
" + + ("
" + + "
" + + "

" + cityssm.escapeHTML(feeCategory.feeCategory) + "

" + + "
" + + "
") + + ("
" + + (feeCategory.fees.length === 0 ? + "
" + + "" + + "
" : + "") + + "
" + + "" + + "
" + + "
" + + "" + + "
" + + "
") + + "
"); + if (feeCategory.fees.length === 0) { + feeCategoryContainerElement.insertAdjacentHTML("beforeend", "
" + + "

There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.

" + + "
"); + } + else { + const panelElement = document.createElement("div"); + panelElement.className = "panel"; + for (const fee of feeCategory.fees) { + const panelBlockElement = document.createElement("a"); + panelBlockElement.className = "panel-block is-block container--fee"; + panelBlockElement.dataset.feeId = fee.feeId.toString(); + panelBlockElement.innerHTML = "
" + + ("
" + + "

" + + "" + cityssm.escapeHTML(fee.feeName) + "
" + + "" + cityssm.escapeHTML(fee.feeDescription).replace(/\n/g, "
") + "
" + + "

" + + "

" + + (fee.isRequired ? + "Required" : + "") + + (fee.occupancyTypeId ? + " " + + cityssm.escapeHTML(fee.occupancyType) + "" : + "") + + (fee.lotTypeId ? + " " + + cityssm.escapeHTML(fee.lotType) + "" : + "") + + "

" + + "
") + + ("
" + + (fee.feeFunction ? + cityssm.escapeHTML(fee.feeFunction) + "
" + + "Fee Function" : + "$" + fee.feeAmount.toFixed(2) + "
" + + "Fee") + + "
") + + ("
" + + (fee.taxPercentage ? + fee.taxPercentage + "%" : + "$" + fee.taxAmount.toFixed(2)) + + "
Tax" + + "
") + + ("
" + + (fee.includeQuantity ? + cityssm.escapeHTML(fee.quantityUnit) + "
" + + "Quantity" : + "") + + "
") + + "
"; + panelBlockElement.addEventListener("click", openEditFee); + panelElement.append(panelBlockElement); + } + feeCategoryContainerElement.append(panelElement); + } + feeCategoriesContainerElement.append(feeCategoryContainerElement); + } + const deleteCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--deleteFeeCategory"); + for (const deleteCategoryButtonElement of deleteCategoryButtonElements) { + deleteCategoryButtonElement.addEventListener("click", confirmDeleteFeeCategory); + } + const editCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--editFeeCategory"); + for (const editCategoryButtonElement of editCategoryButtonElements) { + editCategoryButtonElement.addEventListener("click", openEditFeeCategory); + } + const addFeeButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--addFee"); + for (const addFeeButtonElement of addFeeButtonElements) { + addFeeButtonElement.addEventListener("click", openAddFee); + } + }; + document.querySelector("#button--addFeeCategory").addEventListener("click", () => { + let addCloseModalFunction; + const doAddFeeCategory = (submitEvent) => { + submitEvent.preventDefault(); + cityssm.postJSON(urlPrefix + "/admin/doAddFeeCategory", submitEvent.currentTarget, (responseJSON) => { + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: "Error Creating Fee Category", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + cityssm.openHtmlModal("adminFees-addFeeCategory", { + onshown: (modalElement, closeModalFunction) => { + bulmaJS.toggleHtmlClipped(); + modalElement.querySelector("#feeCategoryAdd--feeCategory").focus(); + addCloseModalFunction = closeModalFunction; + modalElement.querySelector("form").addEventListener("submit", doAddFeeCategory); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); const openEditFeeCategory = (clickEvent) => { const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10); const feeCategory = feeCategories.find((currentFeeCategory) => { @@ -43,11 +187,54 @@ Object.defineProperty(exports, "__esModule", { value: true }); } }); }; + const confirmDeleteFeeCategory = (clickEvent) => { + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10); + const doDelete = () => { + cityssm.postJSON(urlPrefix + "/admin/doDeleteFeeCategory", { + feeCategoryId + }, (responseJSON) => { + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: "Error Updating Fee Category", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + bulmaJS.confirm({ + title: "Delete Fee Category?", + message: "Are you sure you want to delete this fee category?", + contextualColorName: "warning", + okButton: { + text: "Yes, Delete the Fee Category", + callbackFunction: doDelete + } + }); + }; const openAddFee = (clickEvent) => { const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10); let addCloseModalFunction; const doAddFee = (submitEvent) => { submitEvent.preventDefault(); + cityssm.postJSON(urlPrefix + "/admin/doAddFee", submitEvent.currentTarget, (responseJSON) => { + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: "Error Adding Fee", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); }; cityssm.openHtmlModal("adminFees-addFee", { onshow: (modalElement) => { @@ -75,102 +262,209 @@ Object.defineProperty(exports, "__esModule", { value: true }); optionElement.textContent = lotType.lotType; lotTypeElement.append(optionElement); } + modalElement.querySelector("#feeAdd--taxPercentage").value = exports.taxPercentageDefault.toString(); los.populateAliases(modalElement); }, onshown: (modalElement, closeModalFunction) => { bulmaJS.toggleHtmlClipped(); addCloseModalFunction = closeModalFunction; modalElement.querySelector("form").addEventListener("submit", doAddFee); + modalElement.querySelector("#feeAdd--feeFunction").addEventListener("change", () => { + const feeAmountElement = modalElement.querySelector("#feeAdd--feeAmount"); + const feeFunctionElement = modalElement.querySelector("#feeAdd--feeFunction"); + if (feeFunctionElement.value === "") { + feeFunctionElement.closest(".select").classList.remove("is-success"); + feeAmountElement.classList.add("is-success"); + feeAmountElement.disabled = false; + } + else { + feeFunctionElement.closest(".select").classList.add("is-success"); + feeAmountElement.classList.remove("is-success"); + feeAmountElement.disabled = true; + } + }); + modalElement.querySelector("#feeAdd--taxPercentage").addEventListener("keyup", () => { + const taxAmountElement = modalElement.querySelector("#feeAdd--taxAmount"); + const taxPercentageElement = modalElement.querySelector("#feeAdd--taxPercentage"); + if (taxPercentageElement.value === "") { + taxPercentageElement.classList.remove("is-success"); + taxAmountElement.classList.add("is-success"); + taxAmountElement.disabled = false; + } + else { + taxPercentageElement.classList.add("is-success"); + taxAmountElement.classList.remove("is-success"); + taxAmountElement.disabled = true; + } + }); + modalElement.querySelector("#feeAdd--includeQuantity").addEventListener("change", () => { + modalElement.querySelector("#feeAdd--quantityUnit").disabled = + modalElement.querySelector("#feeAdd--includeQuantity").value === ""; + }); }, onremoved: () => { bulmaJS.toggleHtmlClipped(); } }); }; - const renderFeeCategories = () => { - if (feeCategories.length === 0) { - feeCategoriesContainerElement.innerHTML = "
" + - "

There are no available fees.

" + - "
"; - return; - } - feeCategoriesContainerElement.innerHTML = ""; - for (const feeCategory of feeCategories) { - const feeCategoryContainerElement = document.createElement("section"); - feeCategoryContainerElement.className = "container--feeCategory"; - feeCategoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString(); - feeCategoryContainerElement.insertAdjacentHTML("beforeend", "
" + - ("
" + - "
" + - "

" + cityssm.escapeHTML(feeCategory.feeCategory) + "

" + - "
" + - "
") + - ("
" + - "
" + - "" + - "
" + - "
" + - "" + - "
" + - "
") + - "
"); - if (feeCategory.fees.length === 0) { - feeCategoryContainerElement.insertAdjacentHTML("beforeend", "
" + - "

There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.

" + - "
"); - } - else { - const panelElement = document.createElement("div"); - panelElement.className = "panel"; - feeCategoryContainerElement.append(panelElement); - } - feeCategoriesContainerElement.append(feeCategoryContainerElement); - } - const editCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--editFeeCategory"); - for (const editCategoryButtonElement of editCategoryButtonElements) { - editCategoryButtonElement.addEventListener("click", openEditFeeCategory); - } - const addFeeButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--addFee"); - for (const addFeeButtonElement of addFeeButtonElements) { - addFeeButtonElement.addEventListener("click", openAddFee); - } - }; - renderFeeCategories(); - document.querySelector("#button--addFeeCategory").addEventListener("click", () => { - let addCloseModalFunction; - const doAddFeeCategory = (submitEvent) => { + const openEditFee = (clickEvent) => { + clickEvent.preventDefault(); + const feeId = Number.parseInt(clickEvent.currentTarget.dataset.feeId, 10); + const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId); + const feeCategory = feeCategories.find((currentFeeCategory) => { + return currentFeeCategory.feeCategoryId === feeCategoryId; + }); + const fee = feeCategory.fees.find((currentFee) => { + return currentFee.feeId === feeId; + }); + let editCloseModalFunction; + let editModalElement; + const doUpdateFee = (submitEvent) => { submitEvent.preventDefault(); - cityssm.postJSON(urlPrefix + "/admin/doAddFeeCategory", submitEvent.currentTarget, (responseJSON) => { + cityssm.postJSON(urlPrefix + "/admin/doUpdateFee", submitEvent.currentTarget, (responseJSON) => { if (responseJSON.success) { feeCategories = responseJSON.feeCategories; - addCloseModalFunction(); + editCloseModalFunction(); renderFeeCategories(); } else { bulmaJS.alert({ - title: "Error Creating Fee Category", + title: "Error Updating Fee", message: responseJSON.errorMessage, contextualColorName: "danger" }); } }); }; - cityssm.openHtmlModal("adminFees-addFeeCategory", { + const confirmDeleteFee = (clickEvent) => { + clickEvent.preventDefault(); + const doDelete = () => { + cityssm.postJSON(urlPrefix + "/admin/doDeleteFee", { + feeId + }, (responseJSON) => { + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } + else { + bulmaJS.alert({ + title: "Error Deleting Fee", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + bulmaJS.confirm({ + title: "Delete Fee?", + message: "Are you sure you want to delete this fee?", + contextualColorName: "warning", + okButton: { + text: "Yes, Delete the Fee", + callbackFunction: doDelete + } + }); + }; + const toggleFeeFields = () => { + const feeAmountElement = editModalElement.querySelector("#feeEdit--feeAmount"); + const feeFunctionElement = editModalElement.querySelector("#feeEdit--feeFunction"); + if (feeFunctionElement.value === "") { + feeFunctionElement.closest(".select").classList.remove("is-success"); + feeAmountElement.classList.add("is-success"); + feeAmountElement.disabled = false; + } + else { + feeFunctionElement.closest(".select").classList.add("is-success"); + feeAmountElement.classList.remove("is-success"); + feeAmountElement.disabled = true; + } + }; + const toggleTaxFields = () => { + const taxAmountElement = editModalElement.querySelector("#feeEdit--taxAmount"); + const taxPercentageElement = editModalElement.querySelector("#feeEdit--taxPercentage"); + if (taxPercentageElement.value === "") { + taxPercentageElement.classList.remove("is-success"); + taxAmountElement.classList.add("is-success"); + taxAmountElement.disabled = false; + } + else { + taxPercentageElement.classList.add("is-success"); + taxAmountElement.classList.remove("is-success"); + taxAmountElement.disabled = true; + } + }; + const toggleQuantityFields = () => { + editModalElement.querySelector("#feeEdit--quantityUnit").disabled = + editModalElement.querySelector("#feeEdit--includeQuantity").value === ""; + }; + cityssm.openHtmlModal("adminFees-editFee", { + onshow: (modalElement) => { + editModalElement = modalElement; + modalElement.querySelector("#feeEdit--feeId").value = fee.feeId.toString(); + const feeCategoryElement = modalElement.querySelector("#feeEdit--feeCategoryId"); + for (const feeCategory of feeCategories) { + const optionElement = document.createElement("option"); + optionElement.value = feeCategory.feeCategoryId.toString(); + optionElement.textContent = feeCategory.feeCategory; + if (feeCategory.feeCategoryId === feeCategoryId) { + optionElement.selected = true; + } + feeCategoryElement.append(optionElement); + } + modalElement.querySelector("#feeEdit--feeName").value = fee.feeName; + modalElement.querySelector("#feeEdit--feeDescription").value = fee.feeDescription; + const occupancyTypeElement = modalElement.querySelector("#feeEdit--occupancyTypeId"); + for (const occupancyType of exports.occupancyTypes) { + const optionElement = document.createElement("option"); + optionElement.value = occupancyType.occupancyTypeId.toString(); + optionElement.textContent = occupancyType.occupancyType; + if (occupancyType.occupancyTypeId === fee.occupancyTypeId) { + optionElement.selected = true; + } + occupancyTypeElement.append(optionElement); + } + const lotTypeElement = modalElement.querySelector("#feeEdit--lotTypeId"); + for (const lotType of exports.lotTypes) { + const optionElement = document.createElement("option"); + optionElement.value = lotType.lotTypeId.toString(); + optionElement.textContent = lotType.lotType; + if (lotType.lotTypeId === fee.lotTypeId) { + optionElement.selected = true; + } + lotTypeElement.append(optionElement); + } + modalElement.querySelector("#feeEdit--feeAmount").value = fee.feeAmount ? fee.feeAmount.toFixed(2) : ""; + modalElement.querySelector("#feeEdit--feeFunction").addEventListener("change", toggleFeeFields); + toggleFeeFields(); + modalElement.querySelector("#feeEdit--taxAmount").value = fee.taxAmount ? fee.taxAmount.toFixed(2) : ""; + const taxPercentageElement = modalElement.querySelector("#feeEdit--taxPercentage"); + taxPercentageElement.value = fee.taxPercentage ? fee.taxPercentage.toString() : ""; + taxPercentageElement.addEventListener("keyup", toggleTaxFields); + toggleTaxFields(); + const includeQuantityElement = modalElement.querySelector("#feeEdit--includeQuantity"); + if (fee.includeQuantity) { + includeQuantityElement.value = "1"; + } + includeQuantityElement.addEventListener("change", toggleQuantityFields); + modalElement.querySelector("#feeEdit--quantityUnit").value = fee.quantityUnit || ""; + toggleQuantityFields(); + if (fee.isRequired) { + modalElement.querySelector("#feeEdit--isRequired").value = "1"; + } + los.populateAliases(modalElement); + }, onshown: (modalElement, closeModalFunction) => { bulmaJS.toggleHtmlClipped(); - modalElement.querySelector("#feeCategoryAdd--feeCategory").focus(); - addCloseModalFunction = closeModalFunction; - modalElement.querySelector("form").addEventListener("submit", doAddFeeCategory); + editCloseModalFunction = closeModalFunction; + modalElement.querySelector("form").addEventListener("submit", doUpdateFee); + bulmaJS.init(modalElement); + modalElement.querySelector(".button--deleteFee").addEventListener("click", confirmDeleteFee); }, onremoved: () => { bulmaJS.toggleHtmlClipped(); } }); - }); + }; + renderFeeCategories(); })(); diff --git a/public-typescript/adminFees.ts b/public-typescript/adminFees.ts index e4260d41..96b93940 100644 --- a/public-typescript/adminFees.ts +++ b/public-typescript/adminFees.ts @@ -24,6 +24,189 @@ declare const bulmaJS: BulmaJS; let feeCategories: recordTypes.FeeCategory[] = exports.feeCategories; + const renderFeeCategories = () => { + + if (feeCategories.length === 0) { + feeCategoriesContainerElement.innerHTML = "
" + + "

There are no available fees.

" + + "
"; + + return; + } + + feeCategoriesContainerElement.innerHTML = ""; + + for (const feeCategory of feeCategories) { + + const feeCategoryContainerElement = document.createElement("section"); + feeCategoryContainerElement.className = "container--feeCategory mb-5"; + feeCategoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString(); + + feeCategoryContainerElement.insertAdjacentHTML("beforeend", + "
" + + ("
" + + "
" + + "

" + cityssm.escapeHTML(feeCategory.feeCategory) + "

" + + "
" + + "
") + + ("
" + + (feeCategory.fees.length === 0 ? + "
" + + "" + + "
" : + "") + + "
" + + "" + + "
" + + "
" + + "" + + "
" + + "
") + + "
"); + + if (feeCategory.fees.length === 0) { + feeCategoryContainerElement.insertAdjacentHTML("beforeend", + "
" + + "

There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.

" + + "
"); + } else { + + const panelElement = document.createElement("div"); + panelElement.className = "panel"; + + for (const fee of feeCategory.fees) { + + const panelBlockElement = document.createElement("a"); + panelBlockElement.className = "panel-block is-block container--fee"; + panelBlockElement.dataset.feeId = fee.feeId.toString(); + + panelBlockElement.innerHTML = "
" + + ("
" + + "

" + + "" + cityssm.escapeHTML(fee.feeName) + "
" + + "" + cityssm.escapeHTML(fee.feeDescription).replace(/\n/g, "
") + "
" + + "

" + + "

" + + (fee.isRequired ? + "Required" : + "") + + (fee.occupancyTypeId ? + " " + + cityssm.escapeHTML(fee.occupancyType) + "" : + "") + + (fee.lotTypeId ? + " " + + cityssm.escapeHTML(fee.lotType) + "" : + "") + + "

" + + "
") + + ("
" + + (fee.feeFunction ? + cityssm.escapeHTML(fee.feeFunction) + "
" + + "Fee Function" : + "$" + fee.feeAmount.toFixed(2) + "
" + + "Fee") + + "
") + + ("
" + + (fee.taxPercentage ? + fee.taxPercentage + "%" : + "$" + fee.taxAmount.toFixed(2)) + + "
Tax" + + "
") + + ("
" + + (fee.includeQuantity ? + cityssm.escapeHTML(fee.quantityUnit) + "
" + + "Quantity" : + "") + + "
") + + "
"; + + panelBlockElement.addEventListener("click", openEditFee); + + panelElement.append(panelBlockElement); + } + + feeCategoryContainerElement.append(panelElement); + } + + feeCategoriesContainerElement.append(feeCategoryContainerElement); + } + + const deleteCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--deleteFeeCategory"); + + for (const deleteCategoryButtonElement of deleteCategoryButtonElements) { + deleteCategoryButtonElement.addEventListener("click", confirmDeleteFeeCategory); + } + + const editCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--editFeeCategory"); + + for (const editCategoryButtonElement of editCategoryButtonElements) { + editCategoryButtonElement.addEventListener("click", openEditFeeCategory); + } + + const addFeeButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--addFee"); + + for (const addFeeButtonElement of addFeeButtonElements) { + addFeeButtonElement.addEventListener("click", openAddFee); + } + }; + + + /* + * Fee Categories + */ + + document.querySelector("#button--addFeeCategory").addEventListener("click", () => { + + let addCloseModalFunction: () => void; + + const doAddFeeCategory = (submitEvent: SubmitEvent) => { + submitEvent.preventDefault(); + + cityssm.postJSON(urlPrefix + "/admin/doAddFeeCategory", + submitEvent.currentTarget, + (responseJSON: { + success: boolean;errorMessage ? : string;feeCategories: recordTypes.FeeCategory[]; + }) => { + + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } else { + bulmaJS.alert({ + title: "Error Creating Fee Category", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + + cityssm.openHtmlModal("adminFees-addFeeCategory", { + + onshown: (modalElement, closeModalFunction) => { + bulmaJS.toggleHtmlClipped(); + (modalElement.querySelector("#feeCategoryAdd--feeCategory") as HTMLInputElement).focus(); + + addCloseModalFunction = closeModalFunction; + modalElement.querySelector("form").addEventListener("submit", doAddFeeCategory); + }, + onremoved: () => { + bulmaJS.toggleHtmlClipped(); + } + }); + }); + const openEditFeeCategory = (clickEvent: Event) => { const feeCategoryId = Number.parseInt(((clickEvent.currentTarget as HTMLElement).closest(".container--feeCategory") as HTMLElement).dataset.feeCategoryId, 10); @@ -77,6 +260,45 @@ declare const bulmaJS: BulmaJS; }); }; + const confirmDeleteFeeCategory = (clickEvent: Event) => { + + const feeCategoryId = Number.parseInt(((clickEvent.currentTarget as HTMLElement).closest(".container--feeCategory") as HTMLElement).dataset.feeCategoryId, 10); + + const doDelete = () => { + + cityssm.postJSON(urlPrefix + "/admin/doDeleteFeeCategory", { + feeCategoryId + }, + (responseJSON: {success: boolean; errorMessage?: string; feeCategories?: recordTypes.FeeCategory[];}) => { + + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + renderFeeCategories(); + } else { + bulmaJS.alert({ + title: "Error Updating Fee Category", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + + bulmaJS.confirm({ + title: "Delete Fee Category?", + message: "Are you sure you want to delete this fee category?", + contextualColorName: "warning", + okButton: { + text: "Yes, Delete the Fee Category", + callbackFunction: doDelete + } + }) + }; + + /* + * Fees + */ + const openAddFee = (clickEvent: Event) => { const feeCategoryId = Number.parseInt(((clickEvent.currentTarget as HTMLElement).closest(".container--feeCategory") as HTMLElement).dataset.feeCategoryId, 10); @@ -85,6 +307,25 @@ declare const bulmaJS: BulmaJS; const doAddFee = (submitEvent: SubmitEvent) => { submitEvent.preventDefault(); + + cityssm.postJSON(urlPrefix + "/admin/doAddFee", + submitEvent.currentTarget, + (responseJSON: { + success: boolean;errorMessage ? : string;feeCategories: recordTypes.FeeCategory[]; + }) => { + + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + addCloseModalFunction(); + renderFeeCategories(); + } else { + bulmaJS.alert({ + title: "Error Adding Fee", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); }; cityssm.openHtmlModal("adminFees-addFee", { @@ -125,14 +366,61 @@ declare const bulmaJS: BulmaJS; lotTypeElement.append(optionElement); } + (modalElement.querySelector("#feeAdd--taxPercentage") as HTMLInputElement).value = (exports.taxPercentageDefault as number).toString(); + los.populateAliases(modalElement); }, onshown: (modalElement, closeModalFunction) => { bulmaJS.toggleHtmlClipped(); + addCloseModalFunction = closeModalFunction; modalElement.querySelector("form").addEventListener("submit", doAddFee); + + modalElement.querySelector("#feeAdd--feeFunction").addEventListener("change", () => { + + const feeAmountElement = modalElement.querySelector("#feeAdd--feeAmount") as HTMLInputElement; + const feeFunctionElement = modalElement.querySelector("#feeAdd--feeFunction") as HTMLSelectElement; + + if (feeFunctionElement.value === "") { + feeFunctionElement.closest(".select").classList.remove("is-success"); + + feeAmountElement.classList.add("is-success"); + feeAmountElement.disabled = false; + + } else { + feeFunctionElement.closest(".select").classList.add("is-success"); + + feeAmountElement.classList.remove("is-success"); + feeAmountElement.disabled = true; + } + }); + + modalElement.querySelector("#feeAdd--taxPercentage").addEventListener("keyup", () => { + + const taxAmountElement = modalElement.querySelector("#feeAdd--taxAmount") as HTMLInputElement; + const taxPercentageElement = modalElement.querySelector("#feeAdd--taxPercentage") as HTMLInputElement; + + if (taxPercentageElement.value === "") { + taxPercentageElement.classList.remove("is-success"); + + taxAmountElement.classList.add("is-success"); + taxAmountElement.disabled = false; + + } else { + taxPercentageElement.classList.add("is-success"); + + taxAmountElement.classList.remove("is-success"); + taxAmountElement.disabled = true; + } + }); + + modalElement.querySelector("#feeAdd--includeQuantity").addEventListener("change", () => { + + (modalElement.querySelector("#feeAdd--quantityUnit") as HTMLInputElement).disabled = + (modalElement.querySelector("#feeAdd--includeQuantity") as HTMLSelectElement).value === ""; + }); }, onremoved: () => { bulmaJS.toggleHtmlClipped(); @@ -140,102 +428,41 @@ declare const bulmaJS: BulmaJS; }); }; - const renderFeeCategories = () => { + const openEditFee = (clickEvent: Event) => { + clickEvent.preventDefault(); - if (feeCategories.length === 0) { - feeCategoriesContainerElement.innerHTML = "
" + - "

There are no available fees.

" + - "
"; + const feeId = Number.parseInt((clickEvent.currentTarget as HTMLElement).dataset.feeId, 10); + const feeCategoryId = Number.parseInt(((clickEvent.currentTarget as HTMLElement).closest(".container--feeCategory") as HTMLElement).dataset.feeCategoryId); - return; - } + const feeCategory = feeCategories.find((currentFeeCategory) => { + return currentFeeCategory.feeCategoryId === feeCategoryId; + }); - feeCategoriesContainerElement.innerHTML = ""; + const fee = feeCategory.fees.find((currentFee) => { + return currentFee.feeId === feeId; + }); - for (const feeCategory of feeCategories) { + let editCloseModalFunction: () => void; + let editModalElement: HTMLElement; - const feeCategoryContainerElement = document.createElement("section"); - feeCategoryContainerElement.className = "container--feeCategory"; - feeCategoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString(); - - feeCategoryContainerElement.insertAdjacentHTML("beforeend", - "
" + - ("
" + - "
" + - "

" + cityssm.escapeHTML(feeCategory.feeCategory) + "

" + - "
" + - "
") + - ("
" + - "
" + - "" + - "
" + - "
" + - "" + - "
" + - "
") + - "
"); - - if (feeCategory.fees.length === 0) { - feeCategoryContainerElement.insertAdjacentHTML("beforeend", - "
" + - "

There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.

" + - "
"); - } else { - - const panelElement = document.createElement("div"); - panelElement.className = "panel"; - - feeCategoryContainerElement.append(panelElement); - } - - feeCategoriesContainerElement.append(feeCategoryContainerElement); - } - - const editCategoryButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--editFeeCategory"); - - for (const editCategoryButtonElement of editCategoryButtonElements) { - editCategoryButtonElement.addEventListener("click", openEditFeeCategory); - } - - const addFeeButtonElements = feeCategoriesContainerElement.querySelectorAll(".button--addFee"); - - for (const addFeeButtonElement of addFeeButtonElements) { - addFeeButtonElement.addEventListener("click", openAddFee); - } - }; - - renderFeeCategories(); - - /* - * Fee Categories - */ - - document.querySelector("#button--addFeeCategory").addEventListener("click", () => { - - let addCloseModalFunction: () => void; - - const doAddFeeCategory = (submitEvent: SubmitEvent) => { + const doUpdateFee = (submitEvent: SubmitEvent) => { submitEvent.preventDefault(); - cityssm.postJSON(urlPrefix + "/admin/doAddFeeCategory", + cityssm.postJSON(urlPrefix + "/admin/doUpdateFee", submitEvent.currentTarget, (responseJSON: { - success: boolean;errorMessage ? : string;feeCategories: recordTypes.FeeCategory[]; + success: boolean; + errorMessage ? : string; + feeCategories ? : recordTypes.FeeCategory[]; }) => { if (responseJSON.success) { feeCategories = responseJSON.feeCategories; - addCloseModalFunction(); + editCloseModalFunction(); renderFeeCategories(); } else { bulmaJS.alert({ - title: "Error Creating Fee Category", + title: "Error Updating Fee", message: responseJSON.errorMessage, contextualColorName: "danger" }); @@ -243,18 +470,196 @@ declare const bulmaJS: BulmaJS; }); }; - cityssm.openHtmlModal("adminFees-addFeeCategory", { + const confirmDeleteFee = (clickEvent: Event) => { + clickEvent.preventDefault(); + const doDelete = () => { + + cityssm.postJSON(urlPrefix + "/admin/doDeleteFee", { + feeId + }, + (responseJSON: { + success: boolean; + errorMessage ? : string; + feeCategories ? : recordTypes.FeeCategory[]; + }) => { + + if (responseJSON.success) { + feeCategories = responseJSON.feeCategories; + editCloseModalFunction(); + renderFeeCategories(); + } else { + bulmaJS.alert({ + title: "Error Deleting Fee", + message: responseJSON.errorMessage, + contextualColorName: "danger" + }); + } + }); + }; + + bulmaJS.confirm({ + title: "Delete Fee?", + message: "Are you sure you want to delete this fee?", + contextualColorName: "warning", + okButton: { + text: "Yes, Delete the Fee", + callbackFunction: doDelete + } + }); + }; + + const toggleFeeFields = () => { + + const feeAmountElement = editModalElement.querySelector("#feeEdit--feeAmount") as HTMLInputElement; + const feeFunctionElement = editModalElement.querySelector("#feeEdit--feeFunction") as HTMLSelectElement; + + if (feeFunctionElement.value === "") { + feeFunctionElement.closest(".select").classList.remove("is-success"); + + feeAmountElement.classList.add("is-success"); + feeAmountElement.disabled = false; + + } else { + feeFunctionElement.closest(".select").classList.add("is-success"); + + feeAmountElement.classList.remove("is-success"); + feeAmountElement.disabled = true; + } + }; + + const toggleTaxFields = () => { + + const taxAmountElement = editModalElement.querySelector("#feeEdit--taxAmount") as HTMLInputElement; + const taxPercentageElement = editModalElement.querySelector("#feeEdit--taxPercentage") as HTMLInputElement; + + if (taxPercentageElement.value === "") { + taxPercentageElement.classList.remove("is-success"); + + taxAmountElement.classList.add("is-success"); + taxAmountElement.disabled = false; + + } else { + taxPercentageElement.classList.add("is-success"); + + taxAmountElement.classList.remove("is-success"); + taxAmountElement.disabled = true; + } + }; + + const toggleQuantityFields = () => { + + (editModalElement.querySelector("#feeEdit--quantityUnit") as HTMLInputElement).disabled = + (editModalElement.querySelector("#feeEdit--includeQuantity") as HTMLSelectElement).value === ""; + }; + + cityssm.openHtmlModal("adminFees-editFee", { + onshow: (modalElement) => { + + editModalElement = modalElement; + + (modalElement.querySelector("#feeEdit--feeId") as HTMLInputElement).value = fee.feeId.toString(); + + const feeCategoryElement = modalElement.querySelector("#feeEdit--feeCategoryId") as HTMLSelectElement; + + for (const feeCategory of feeCategories) { + + const optionElement = document.createElement("option"); + optionElement.value = feeCategory.feeCategoryId.toString(); + optionElement.textContent = feeCategory.feeCategory; + + if (feeCategory.feeCategoryId === feeCategoryId) { + optionElement.selected = true; + } + + feeCategoryElement.append(optionElement); + } + + (modalElement.querySelector("#feeEdit--feeName") as HTMLInputElement).value = fee.feeName; + (modalElement.querySelector("#feeEdit--feeDescription") as HTMLTextAreaElement).value = fee.feeDescription; + + const occupancyTypeElement = modalElement.querySelector("#feeEdit--occupancyTypeId") as HTMLSelectElement; + + for (const occupancyType of exports.occupancyTypes as recordTypes.OccupancyType[]) { + + const optionElement = document.createElement("option"); + optionElement.value = occupancyType.occupancyTypeId.toString(); + optionElement.textContent = occupancyType.occupancyType; + + if (occupancyType.occupancyTypeId === fee.occupancyTypeId) { + optionElement.selected = true; + } + + occupancyTypeElement.append(optionElement); + } + + const lotTypeElement = modalElement.querySelector("#feeEdit--lotTypeId") as HTMLSelectElement; + + for (const lotType of exports.lotTypes as recordTypes.LotType[]) { + + const optionElement = document.createElement("option"); + optionElement.value = lotType.lotTypeId.toString(); + optionElement.textContent = lotType.lotType; + + if (lotType.lotTypeId === fee.lotTypeId) { + optionElement.selected = true; + } + + lotTypeElement.append(optionElement); + } + + (modalElement.querySelector("#feeEdit--feeAmount") as HTMLInputElement).value = fee.feeAmount ? fee.feeAmount.toFixed(2) : ""; + modalElement.querySelector("#feeEdit--feeFunction").addEventListener("change", toggleFeeFields); + + toggleFeeFields(); + + (modalElement.querySelector("#feeEdit--taxAmount") as HTMLInputElement).value = fee.taxAmount ? fee.taxAmount.toFixed(2) : ""; + + const taxPercentageElement = modalElement.querySelector("#feeEdit--taxPercentage") as HTMLInputElement; + taxPercentageElement.value = fee.taxPercentage ? fee.taxPercentage.toString() : ""; + taxPercentageElement.addEventListener("keyup", toggleTaxFields); + + toggleTaxFields(); + + const includeQuantityElement = modalElement.querySelector("#feeEdit--includeQuantity") as HTMLSelectElement; + + if (fee.includeQuantity) { + includeQuantityElement.value = "1"; + } + + includeQuantityElement.addEventListener("change", toggleQuantityFields); + + (modalElement.querySelector("#feeEdit--quantityUnit") as HTMLInputElement).value = fee.quantityUnit || ""; + + toggleQuantityFields(); + + if (fee.isRequired) { + (modalElement.querySelector("#feeEdit--isRequired") as HTMLSelectElement).value = "1"; + } + + los.populateAliases(modalElement); + }, onshown: (modalElement, closeModalFunction) => { - bulmaJS.toggleHtmlClipped(); - (modalElement.querySelector("#feeCategoryAdd--feeCategory") as HTMLInputElement).focus(); - addCloseModalFunction = closeModalFunction; - modalElement.querySelector("form").addEventListener("submit", doAddFeeCategory); + bulmaJS.toggleHtmlClipped(); + + editCloseModalFunction = closeModalFunction; + + modalElement.querySelector("form").addEventListener("submit", doUpdateFee); + + bulmaJS.init(modalElement); + + modalElement.querySelector(".button--deleteFee").addEventListener("click", confirmDeleteFee); }, onremoved: () => { bulmaJS.toggleHtmlClipped(); } }); - }); + }; + + /* + * Initialize + */ + + renderFeeCategories(); })(); \ No newline at end of file diff --git a/public/html/adminFees-addFee.html b/public/html/adminFees-addFee.html index a3ef9bfe..9fcd269f 100644 --- a/public/html/adminFees-addFee.html +++ b/public/html/adminFees-addFee.html @@ -18,7 +18,7 @@
- +
@@ -29,6 +29,16 @@
+
+

+ Filters can be used to show or hide available filters depending on the + type and + type selected + when creating the + + record. +

+
@@ -55,12 +65,20 @@
+
+

+ Fees can be simply flat amounts, + or calculated by functions defined by the application administrator. + Note that if both an amount and a function are set, + the function will be used. +

+
- + @@ -72,18 +90,29 @@
- +
+
+

+ Taxes can be defined as flat amounts, + or as percentages of the fee amount. + Note that if both an amount and a percentage are set, + the percentage will be used. +

+
- + @@ -94,7 +123,7 @@
- + @@ -102,6 +131,40 @@
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+