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 ?
+ "
" +
+ "" +
+ " " +
+ "Delete Category " +
+ " " +
+ "
" :
+ "") +
+ "
" +
+ "" +
+ " " +
+ "Edit Category " +
+ " " +
+ "
" +
+ "
" +
+ "" +
+ " " +
+ "Add Fee " +
+ " " +
+ "
" +
+ "
") +
+ "
");
+ 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) + " " +
- "" +
- "
") +
- ("
" +
- "
" +
- "" +
- " " +
- "Edit Category " +
- " " +
- "
" +
- "
" +
- "" +
- " " +
- "Add Fee " +
- " " +
- "
" +
- "
") +
- "
");
- 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 ?
+ "
" +
+ "" +
+ " " +
+ "Delete Category " +
+ " " +
+ "
" :
+ "") +
+ "
" +
+ "" +
+ " " +
+ "Edit Category " +
+ " " +
+ "
" +
+ "
" +
+ "" +
+ " " +
+ "Add Fee " +
+ " " +
+ "
" +
+ "
") +
+ "
");
+
+ 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) + " " +
- "" +
- "
") +
- ("
" +
- "
" +
- "" +
- " " +
- "Edit Category " +
- " " +
- "
" +
- "
" +
- "" +
- " " +
- "Add Fee " +
- " " +
- "
" +
- "
") +
- "
");
-
- 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 @@
-
Fee
+
Fee Name
@@ -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.
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
Tax Amount
+
+
+
+
Include Quantity
+
+
+
+ No Quantity
+ Quantity Available
+
+
+
+
+
+
+
+
+
Fee Is Required
+
+
+
+ No, Fee is Optional
+ Yes, Fee is Required
+
+
+
+