development

primarily lot occupancy fees
deepsource-autofix-76c6eb20
Dan Gowans 2022-08-18 16:16:33 -04:00
parent de02b0d496
commit 14f996233d
35 changed files with 1415 additions and 309 deletions

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,11 @@
import { addLotOccupancyFee } from "../../helpers/lotOccupancyDB/addLotOccupancyFee.js";
import { getLotOccupancyFees } from "../../helpers/lotOccupancyDB/getLotOccupancyFees.js";
export const handler = async (request, response) => {
addLotOccupancyFee(request.body, request.session);
const lotOccupancyFees = getLotOccupancyFees(request.body.lotOccupancyId);
response.json({
success: true,
lotOccupancyFees
});
};
export default handler;

View File

@ -0,0 +1,27 @@
import type {
RequestHandler
} from "express";
import {
addLotOccupancyFee
} from "../../helpers/lotOccupancyDB/addLotOccupancyFee.js";
import {
getLotOccupancyFees
} from "../../helpers/lotOccupancyDB/getLotOccupancyFees.js";
export const handler: RequestHandler = async (request, response) => {
addLotOccupancyFee(request.body, request.session);
const lotOccupancyFees = getLotOccupancyFees(request.body.lotOccupancyId);
response.json({
success: true,
lotOccupancyFees
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,8 @@
import { deleteLotOccupancy } from "../../helpers/lotOccupancyDB/deleteLotOccupancy.js";
export const handler = async (request, response) => {
const success = deleteLotOccupancy(request.body.lotOccupancyId, request.session);
response.json({
success
});
};
export default handler;

View File

@ -0,0 +1,20 @@
import type {
RequestHandler
} from "express";
import {
deleteLotOccupancy
} from "../../helpers/lotOccupancyDB/deleteLotOccupancy.js";
export const handler: RequestHandler = async (request, response) => {
const success = deleteLotOccupancy(request.body.lotOccupancyId, request.session);
response.json({
success
});
};
export default handler;

View File

@ -0,0 +1,3 @@
import type { RequestHandler } from "express";
export declare const handler: RequestHandler;
export default handler;

View File

@ -0,0 +1,11 @@
import { deleteLotOccupancyFee } from "../../helpers/lotOccupancyDB/deleteLotOccupancyFee.js";
import { getLotOccupancyFees } from "../../helpers/lotOccupancyDB/getLotOccupancyFees.js";
export const handler = async (request, response) => {
const success = deleteLotOccupancyFee(request.body.lotOccupancyId, request.body.feeId, request.session);
const lotOccupancyFees = getLotOccupancyFees(request.body.lotOccupancyId);
response.json({
success,
lotOccupancyFees
});
};
export default handler;

View File

@ -0,0 +1,27 @@
import type {
RequestHandler
} from "express";
import {
deleteLotOccupancyFee
} from "../../helpers/lotOccupancyDB/deleteLotOccupancyFee.js";
import {
getLotOccupancyFees
} from "../../helpers/lotOccupancyDB/getLotOccupancyFees.js";
export const handler: RequestHandler = async (request, response) => {
const success = deleteLotOccupancyFee(request.body.lotOccupancyId, request.body.feeId, request.session);
const lotOccupancyFees = getLotOccupancyFees(request.body.lotOccupancyId);
response.json({
success,
lotOccupancyFees
});
};
export default handler;

3
helpers/functions.fee.d.ts vendored 100644
View File

@ -0,0 +1,3 @@
import * as recordTypes from "../types/recordTypes";
export declare const calculateFeeAmount: (fee: recordTypes.Fee, lotOccupancy: recordTypes.LotOccupancy) => number;
export declare const calculateTaxAmount: (fee: recordTypes.Fee, feeAmount: number) => number;

View File

@ -0,0 +1,10 @@
export const calculateFeeAmount = (fee, lotOccupancy) => {
return fee.feeFunction ?
0 :
(fee.feeAmount || 0);
};
export const calculateTaxAmount = (fee, feeAmount) => {
return fee.taxPercentage ?
feeAmount * (fee.taxPercentage / 100) :
(fee.taxAmount || 0);
};

View File

@ -0,0 +1,18 @@
import e from "express";
import * as recordTypes from "../types/recordTypes";
export const calculateFeeAmount = (fee: recordTypes.Fee, lotOccupancy: recordTypes.LotOccupancy): number => {
return fee.feeFunction ?
0 :
(fee.feeAmount || 0);
};
export const calculateTaxAmount = (fee: recordTypes.Fee, feeAmount: number) => {
return fee.taxPercentage ?
feeAmount * (fee.taxPercentage / 100) :
(fee.taxAmount || 0);
};

View File

@ -0,0 +1,8 @@
import type * as recordTypes from "../../types/recordTypes";
interface AddLotOccupancyFeeForm {
lotOccupancyId: string;
feeId: string;
quantity: number | string;
}
export declare const addLotOccupancyFee: (lotOccupancyFeeForm: AddLotOccupancyFeeForm, requestSession: recordTypes.PartialSession) => boolean;
export default addLotOccupancyFee;

View File

@ -0,0 +1,42 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { calculateFeeAmount, calculateTaxAmount } from "../functions.fee.js";
import { getFee } from "./getFee.js";
import { getLotOccupancy } from "./getLotOccupancy.js";
export const addLotOccupancyFee = (lotOccupancyFeeForm, requestSession) => {
const database = sqlite(databasePath);
const record = database.prepare("select recordDelete_timeMillis" +
" from LotOccupancyFees" +
" where lotOccupancyId = ?" +
" and feeId = ?")
.get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
if (record) {
if (record.recordDelete_timeMillis) {
database.prepare("delete from LotOccupancyFees" +
" where recordDelete_timeMillis is not null" +
" and lotOccupancyId = ?" +
" and feeId = ?")
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
}
else {
database.close();
return false;
}
}
const lotOccupancy = getLotOccupancy(lotOccupancyFeeForm.lotOccupancyId);
const fee = getFee(lotOccupancyFeeForm.feeId);
const feeAmount = calculateFeeAmount(fee, lotOccupancy);
const taxAmount = calculateTaxAmount(fee, feeAmount);
const rightNowMillis = Date.now();
const result = database
.prepare("insert into LotOccupancyFees (" +
"lotOccupancyId, feeId," +
" quantity, feeAmount, taxAmount," +
" recordCreate_userName, recordCreate_timeMillis," +
" recordUpdate_userName, recordUpdate_timeMillis)" +
" values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId, lotOccupancyFeeForm.quantity, feeAmount, taxAmount, requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
return result.changes > 0;
};
export default addLotOccupancyFee;

View File

@ -0,0 +1,92 @@
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
} from "../../data/databasePaths.js";
import {
calculateFeeAmount,
calculateTaxAmount
} from "../functions.fee.js";
import {
getFee
} from "./getFee.js";
import {
getLotOccupancy
} from "./getLotOccupancy.js";
import type * as recordTypes from "../../types/recordTypes";
interface AddLotOccupancyFeeForm {
lotOccupancyId: string;
feeId: string;
quantity: number | string;
}
export const addLotOccupancyFee =
(lotOccupancyFeeForm: AddLotOccupancyFeeForm, requestSession: recordTypes.PartialSession): boolean => {
const database = sqlite(databasePath);
// Check if record already exists
const record: {
recordDelete_timeMillis?: number
} = database.prepare("select recordDelete_timeMillis" +
" from LotOccupancyFees" +
" where lotOccupancyId = ?" +
" and feeId = ?")
.get(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
if (record) {
if (record.recordDelete_timeMillis) {
database.prepare("delete from LotOccupancyFees" +
" where recordDelete_timeMillis is not null" +
" and lotOccupancyId = ?" +
" and feeId = ?")
.run(lotOccupancyFeeForm.lotOccupancyId, lotOccupancyFeeForm.feeId);
} else {
database.close();
return false;
}
}
// Create new record
const lotOccupancy = getLotOccupancy(lotOccupancyFeeForm.lotOccupancyId);
const fee = getFee(lotOccupancyFeeForm.feeId);
const feeAmount = calculateFeeAmount(fee, lotOccupancy);
const taxAmount = calculateTaxAmount(fee, feeAmount);
const rightNowMillis = Date.now();
const result = database
.prepare("insert into LotOccupancyFees (" +
"lotOccupancyId, feeId," +
" quantity, feeAmount, taxAmount," +
" recordCreate_userName, recordCreate_timeMillis," +
" recordUpdate_userName, recordUpdate_timeMillis)" +
" values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run(lotOccupancyFeeForm.lotOccupancyId,
lotOccupancyFeeForm.feeId,
lotOccupancyFeeForm.quantity,
feeAmount,
taxAmount,
requestSession.user.userName,
rightNowMillis,
requestSession.user.userName,
rightNowMillis);
database.close();
return result.changes > 0
};
export default addLotOccupancyFee;

View File

@ -0,0 +1,3 @@
import type * as recordTypes from "../../types/recordTypes";
export declare const deleteLotOccupancy: (lotOccupancyId: number | string, requestSession: recordTypes.PartialSession) => boolean;
export default deleteLotOccupancy;

View File

@ -0,0 +1,15 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const deleteLotOccupancy = (lotOccupancyId, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update LotOccupancies" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where lotOccupancyId = ?")
.run(requestSession.user.userName, rightNowMillis, lotOccupancyId);
database.close();
return (result.changes > 0);
};
export default deleteLotOccupancy;

View File

@ -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 deleteLotOccupancy =
(lotOccupancyId: number | string,
requestSession: recordTypes.PartialSession): boolean => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update LotOccupancies" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where lotOccupancyId = ?")
.run(requestSession.user.userName,
rightNowMillis,
lotOccupancyId);
database.close();
return (result.changes > 0);
};
export default deleteLotOccupancy;

View File

@ -0,0 +1,3 @@
import type * as recordTypes from "../../types/recordTypes";
export declare const deleteLotOccupancyFee: (lotOccupancyId: number | string, feeId: number | string, requestSession: recordTypes.PartialSession) => boolean;
export default deleteLotOccupancyFee;

View File

@ -0,0 +1,16 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const deleteLotOccupancyFee = (lotOccupancyId, feeId, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update LotOccupancyFees" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where lotOccupancyId = ?" +
" and feeId = ?")
.run(requestSession.user.userName, rightNowMillis, lotOccupancyId, feeId);
database.close();
return (result.changes > 0);
};
export default deleteLotOccupancyFee;

View File

@ -0,0 +1,36 @@
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
export const deleteLotOccupancyFee =
(lotOccupancyId: number | string,
feeId: number | string,
requestSession: recordTypes.PartialSession): boolean => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update LotOccupancyFees" +
" set recordDelete_userName = ?," +
" recordDelete_timeMillis = ?" +
" where lotOccupancyId = ?" +
" and feeId = ?")
.run(requestSession.user.userName,
rightNowMillis,
lotOccupancyId,
feeId);
database.close();
return (result.changes > 0);
};
export default deleteLotOccupancyFee;

View File

@ -0,0 +1,3 @@
import type * as recordTypes from "../../types/recordTypes";
export declare const getFee: (feeId: number | string) => recordTypes.Fee;
export default getFee;

View File

@ -0,0 +1,25 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const getFee = (feeId) => {
const database = sqlite(databasePath, {
readonly: true
});
const fee = database.prepare("select f.feeId," +
" f.feeCategoryId, c.feeCategory," +
" 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 FeeCategories c on f.feeCategoryId = c.feeCategoryId" +
" 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.feeId = ?")
.get(feeId);
database.close();
return fee;
};
export default getFee;

View File

@ -0,0 +1,38 @@
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
export const getFee = (feeId: number | string): recordTypes.Fee => {
const database = sqlite(databasePath, {
readonly: true
});
const fee = database.prepare("select f.feeId," +
" f.feeCategoryId, c.feeCategory," +
" 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 FeeCategories c on f.feeCategoryId = c.feeCategoryId" +
" 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.feeId = ?")
.get(feeId);
database.close();
return fee;
};
export default getFee;

View File

@ -5,10 +5,12 @@ export const getLotOccupancyFees = (lotOccupancyId, connectedDatabase) => {
readonly: true
});
const lotOccupancyFees = database
.prepare("select o.lotOccupancyId, o.feeId, o.feeAmount," +
" f.feeName" +
.prepare("select o.lotOccupancyId," +
" o.feeId, c.feeCategory, f.feeName," +
" o.feeAmount, o.taxAmount, o.quantity" +
" from LotOccupancyFees o" +
" left join Fees f on o.feeId = f.feeId" +
" left join FeeCategories c on f.feeCategoryId = c.feeCategoryId" +
" where o.recordDelete_timeMillis is null" +
" and o.lotOccupancyId = ?" +
" order by o.recordCreate_timeMillis")

View File

@ -15,10 +15,12 @@ export const getLotOccupancyFees = (lotOccupancyId: number | string,
});
const lotOccupancyFees: recordTypes.LotOccupancyFee[] = database
.prepare("select o.lotOccupancyId, o.feeId, o.feeAmount," +
" f.feeName" +
.prepare("select o.lotOccupancyId," +
" o.feeId, c.feeCategory, f.feeName," +
" o.feeAmount, o.taxAmount, o.quantity" +
" from LotOccupancyFees o" +
" left join Fees f on o.feeId = f.feeId" +
" left join FeeCategories c on f.feeCategoryId = c.feeCategoryId" +
" where o.recordDelete_timeMillis is null" +
" and o.lotOccupancyId = ?" +
" order by o.recordCreate_timeMillis")

View File

@ -46,6 +46,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
for (const formInputElement of formInputElements) {
formInputElement.addEventListener("change", setUnsavedChanges);
}
if (!isCreate) {
document.querySelector("#button--deleteLotOccupancy").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doDeleteLotOccupancy", {
lotOccupancyId
}, (responseJSON) => {
if (responseJSON.success) {
window.location.href = urlPrefix + "/lotOccupancies?t=" + Date.now();
}
else {
bulmaJS.alert({
title: "Error Deleting Record",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
bulmaJS.confirm({
title: "Delete " + exports.aliases.occupancy + " Record",
message: "Are you sure you want to delete this record?",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete",
callbackFunction: doDelete
}
});
});
}
const occupancyTypeIdElement = document.querySelector("#lotOccupancy--occupancyTypeId");
if (isCreate) {
const lotOccupancyFieldsContainerElement = document.querySelector("#container--lotOccupancyFields");
@ -261,6 +291,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
modalElement.querySelector("#lotOccupancyOccupantEdit--lotOccupantTypeId").focus();
editFormElement = modalElement.querySelector("form");
editFormElement.addEventListener("submit", editOccupant);
editCloseModalFunction = closeModalFunction;
@ -337,7 +368,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
" <span>Edit</span>" +
"</button>") +
("<button class=\"button is-light is-danger button--delete\" type=\"button\" aria-label=\"Delete\">" +
("<button class=\"button is-light is-danger button--delete\" data-tooltip=\"Delete " + cityssm.escapeHTML(exports.aliases.occupant) + "\" type=\"button\" aria-label=\"Delete\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>") +
"</div>" +
@ -384,6 +415,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
modalElement.querySelector("#lotOccupancyOccupantAdd--lotOccupantTypeId").focus();
addFormElement = modalElement.querySelector("form");
addFormElement.addEventListener("submit", addOccupant);
addCloseModalFunction = closeModalFunction;
@ -432,6 +464,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
modalElement.querySelector("#lotOccupancyCommentEdit--lotOccupancyComment").focus();
editFormElement = modalElement.querySelector("form");
editFormElement.addEventListener("submit", editComment);
editCloseModalFunction = closeModalFunction;
@ -503,7 +536,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
" <span>Edit</span>" +
"</button>") +
("<button class=\"button is-light is-danger button--delete\" type=\"button\" aria-label=\"Delete\">" +
("<button class=\"button is-light is-danger button--delete\" data-tooltip=\"Delete Comment\" type=\"button\" aria-label=\"Delete\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>") +
"</div>" +
@ -542,6 +575,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
modalElement.querySelector("#lotOccupancyCommentAdd--lotOccupancyComment").focus();
addFormElement = modalElement.querySelector("form");
addFormElement.addEventListener("submit", addComment);
addCloseModalFunction = closeModalFunction;
@ -556,7 +590,89 @@ Object.defineProperty(exports, "__esModule", { value: true });
if (!isCreate) {
let lotOccupancyFees = exports.lotOccupancyFees;
const lotOccupancyFeesContainerElement = document.querySelector("#container--lotOccupancyFees");
const deleteLotOccupancyFee = (clickEvent) => {
const feeId = clickEvent.currentTarget.closest(".container--lotOccupancyFee").dataset.feeId;
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doDeleteLotOccupancyFee", {
lotOccupancyId,
feeId
}, (responseJSON) => {
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
}
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 Fee",
callbackFunction: doDelete,
}
});
};
const renderLotOccupancyFees = () => {
if (lotOccupancyFees.length === 0) {
lotOccupancyFeesContainerElement.innerHTML = "<div class=\"message is-info\">" +
"<p class=\"message-body\">There are no fees associated with this record.</p>" +
"</div>";
return;
}
lotOccupancyFeesContainerElement.innerHTML = "<table class=\"table is-fullwidth is-striped is-hoverable\">" +
("<thead><tr>" +
"<th>Fee</th>" +
"<th><span class=\"is-sr-only\">Unit Cost</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">&times;</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">Quantity</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">equals</span></th>" +
"<th class=\"has-width-1 has-text-right\">Total</th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">Options</span></th>" +
"</tr></thead>") +
"<tbody></tbody>" +
("<tfoot>" +
"<tr><th colspan=\"5\">Subtotal</th><td class=\"has-text-weight-bold has-text-right\" id=\"lotOccupancyFees--feeAmountTotal\"></td><td></td></tr>" +
"<tr><th colspan=\"5\">Tax</th><td class=\"has-text-right\" id=\"lotOccupancyFees--taxAmountTotal\"></td><td></td></tr>" +
"<tr><th colspan=\"5\">Grand Total</th><td class=\"has-text-weight-bold has-text-right\" id=\"lotOccupancyFees--grandTotal\"></td><td></td></tr>" +
"</tfoot>") +
"</table>";
let feeAmountTotal = 0;
let taxAmountTotal = 0;
for (const lotOccupancyFee of lotOccupancyFees) {
const tableRowElement = document.createElement("tr");
tableRowElement.className = "container--lotOccupancyFee";
tableRowElement.dataset.feeId = lotOccupancyFee.feeId.toString();
tableRowElement.innerHTML = ("<td colspan=\"" + (lotOccupancyFee.quantity === 1 ? "5" : "1") + "\">" +
cityssm.escapeHTML(lotOccupancyFee.feeName) +
"</td>") +
(lotOccupancyFee.quantity === 1 ?
"" :
"<td class=\"has-text-right\">$" + lotOccupancyFee.feeAmount.toFixed(2) + "</td>" +
"<td>&times;</td>" +
"<td class=\"has-text-right\">" + lotOccupancyFee.quantity + "</td>" +
"<td>=</td>") +
"<td class=\"has-text-right\">$" + (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity).toFixed(2) + "</td>" +
("<td>" +
"<button class=\"button is-small is-danger is-light\" data-tooltip=\"Delete Fee\" type=\"button\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>" +
"</td>");
tableRowElement.querySelector("button").addEventListener("click", deleteLotOccupancyFee);
lotOccupancyFeesContainerElement.querySelector("tbody").append(tableRowElement);
feeAmountTotal += (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity);
taxAmountTotal += (lotOccupancyFee.taxAmount * lotOccupancyFee.quantity);
}
lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--feeAmountTotal").textContent = "$" + feeAmountTotal.toFixed(2);
lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--taxAmountTotal").textContent = "$" + taxAmountTotal.toFixed(2);
lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--grandTotal").textContent = "$" + (feeAmountTotal + taxAmountTotal).toFixed(2);
};
document.querySelector("#button--addFee").addEventListener("click", () => {
if (hasUnsavedChanges) {
@ -568,16 +684,110 @@ Object.defineProperty(exports, "__esModule", { value: true });
}
let feeCategories;
let feeFilterElement;
let feeFilterResultsElement;
const doAddFee = (feeId, quantity = 1) => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doAddLotOccupancyFee", {
lotOccupancyId,
feeId,
quantity
}, (responseJSON) => {
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
filterFees();
}
else {
bulmaJS.alert({
title: "Error Adding Fee",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
const doSetQuantityAndAddFee = (fee) => {
let quantityElement;
let quantityCloseModalFunction;
const doSetQuantity = (submitEvent) => {
submitEvent.preventDefault();
doAddFee(fee.feeId, quantityElement.value);
quantityCloseModalFunction();
};
cityssm.openHtmlModal("lotOccupancy-setFeeQuantity", {
onshow: (modalElement) => {
modalElement.querySelector("#lotOccupancyFeeQuantity--quantityUnit").textContent = fee.quantityUnit;
},
onshown: (modalElement, closeModalFunction) => {
quantityCloseModalFunction = closeModalFunction;
quantityElement = modalElement.querySelector("#lotOccupancyFeeQuantity--quantity");
modalElement.querySelector("form").addEventListener("submit", doSetQuantity);
}
});
};
const tryAddFee = (clickEvent) => {
clickEvent.preventDefault();
const feeId = Number.parseInt(clickEvent.currentTarget.dataset.feeId, 10);
const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10);
const feeCategory = feeCategories.find((currentFeeCategory) => {
return currentFeeCategory.feeCategoryId === feeCategoryId;
});
const fee = feeCategory.fees.find((currentFee) => {
return currentFee.feeId === feeId;
});
if (fee.includeQuantity) {
doSetQuantityAndAddFee(fee);
}
else {
doAddFee(feeId);
}
};
const filterFees = () => {
const filterStringPieces = feeFilterElement.value.trim().toLowerCase().split(" ");
feeFilterResultsElement.innerHTML = "";
for (const feeCategory of feeCategories) {
const categoryContainerElement = document.createElement("div");
categoryContainerElement.className = "container--feeCategory";
categoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString();
categoryContainerElement.innerHTML = "<h4 class=\"title is-5\">" + cityssm.escapeHTML(feeCategory.feeCategory) + "</h4>" +
"<div class=\"panel\"></div>";
let hasFees = false;
for (const fee of feeCategory.fees) {
if (lotOccupancyFeesContainerElement.querySelector(".container--lotOccupancyFee[data-fee-id='" + fee.feeId + "']")) {
continue;
}
let includeFee = true;
for (const filterStringPiece of filterStringPieces) {
if (!fee.feeName.toLowerCase().includes(filterStringPiece)) {
includeFee = false;
break;
}
}
if (!includeFee) {
continue;
}
hasFees = true;
const panelBlockElement = document.createElement("a");
panelBlockElement.className = "panel-block is-block container--fee";
panelBlockElement.dataset.feeId = fee.feeId.toString();
panelBlockElement.href = "#";
panelBlockElement.innerHTML = "<strong>" + cityssm.escapeHTML(fee.feeName) + "</strong><br />" +
"<small>" + cityssm.escapeHTML(fee.feeDescription).replace(/\n/g, "<br />") + "</small>";
panelBlockElement.addEventListener("click", tryAddFee);
categoryContainerElement.querySelector(".panel").append(panelBlockElement);
}
if (hasFees) {
feeFilterResultsElement.append(categoryContainerElement);
}
}
};
cityssm.openHtmlModal("lotOccupancy-addFee", {
onshow: (modalElement) => {
feeFilterElement = modalElement.querySelector("#feeSelect--feeName");
feeFilterResultsElement = modalElement.querySelector("#resultsContainer--feeSelect");
cityssm.postJSON(urlPrefix + "/lotOccupancies/doGetFees", {
lotOccupancyId
}, (responseJSON) => {
feeCategories = responseJSON.feeCategories;
feeFilterElement = modalElement.querySelector("#feeSelect--feeName");
feeFilterElement.disabled = false;
feeFilterElement.addEventListener("keyup", filterFees);
feeFilterElement.focus();

View File

@ -84,6 +84,40 @@ declare const bulmaJS: BulmaJS;
formInputElement.addEventListener("change", setUnsavedChanges);
}
if (!isCreate) {
document.querySelector("#button--deleteLotOccupancy").addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doDeleteLotOccupancy", {
lotOccupancyId
},
(responseJSON: {success: boolean; errorMessage?: string;}) => {
if (responseJSON.success) {
window.location.href = urlPrefix + "/lotOccupancies?t=" + Date.now();
} else {
bulmaJS.alert({
title: "Error Deleting Record",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
}
bulmaJS.confirm({
title: "Delete " + exports.aliases.occupancy + " Record",
message: "Are you sure you want to delete this record?",
contextualColorName: "warning",
okButton: {
text: "Yes, Delete",
callbackFunction: doDelete
}
});
});
}
// Occupancy Type
const occupancyTypeIdElement = document.querySelector("#lotOccupancy--occupancyTypeId") as HTMLSelectElement;
@ -397,6 +431,8 @@ declare const bulmaJS: BulmaJS;
bulmaJS.toggleHtmlClipped();
(modalElement.querySelector("#lotOccupancyOccupantEdit--lotOccupantTypeId") as HTMLInputElement).focus();
editFormElement = modalElement.querySelector("form");
editFormElement.addEventListener("submit", editOccupant);
@ -490,7 +526,7 @@ declare const bulmaJS: BulmaJS;
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
" <span>Edit</span>" +
"</button>") +
("<button class=\"button is-light is-danger button--delete\" type=\"button\" aria-label=\"Delete\">" +
("<button class=\"button is-light is-danger button--delete\" data-tooltip=\"Delete " + cityssm.escapeHTML(exports.aliases.occupant) + "\" type=\"button\" aria-label=\"Delete\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>") +
"</div>" +
@ -556,6 +592,8 @@ declare const bulmaJS: BulmaJS;
bulmaJS.toggleHtmlClipped();
(modalElement.querySelector("#lotOccupancyOccupantAdd--lotOccupantTypeId") as HTMLInputElement).focus();
addFormElement = modalElement.querySelector("form");
addFormElement.addEventListener("submit", addOccupant);
@ -629,6 +667,8 @@ declare const bulmaJS: BulmaJS;
bulmaJS.toggleHtmlClipped();
(modalElement.querySelector("#lotOccupancyCommentEdit--lotOccupancyComment") as HTMLTextAreaElement).focus();
editFormElement = modalElement.querySelector("form");
editFormElement.addEventListener("submit", editComment);
@ -716,7 +756,7 @@ declare const bulmaJS: BulmaJS;
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
" <span>Edit</span>" +
"</button>") +
("<button class=\"button is-light is-danger button--delete\" type=\"button\" aria-label=\"Delete\">" +
("<button class=\"button is-light is-danger button--delete\" data-tooltip=\"Delete Comment\" type=\"button\" aria-label=\"Delete\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>") +
"</div>" +
@ -774,6 +814,8 @@ declare const bulmaJS: BulmaJS;
bulmaJS.toggleHtmlClipped();
(modalElement.querySelector("#lotOccupancyCommentAdd--lotOccupancyComment") as HTMLTextAreaElement).focus();
addFormElement = modalElement.querySelector("form");
addFormElement.addEventListener("submit", addComment);
@ -794,11 +836,111 @@ declare const bulmaJS: BulmaJS;
*/
if (!isCreate) {
let lotOccupancyFees: recordTypes.LotOccupancyFee[] = exports.lotOccupancyFees;
const lotOccupancyFeesContainerElement = document.querySelector("#container--lotOccupancyFees") as HTMLElement;
const deleteLotOccupancyFee = (clickEvent: Event) => {
const feeId = ((clickEvent.currentTarget as HTMLElement).closest(".container--lotOccupancyFee") as HTMLElement).dataset.feeId;
const doDelete = () => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doDeleteLotOccupancyFee", {
lotOccupancyId,
feeId
},
(responseJSON: {
success: boolean;errorMessage ? : string;lotOccupancyFees ? : recordTypes.LotOccupancyFee[];
}) => {
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
} 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 Fee",
callbackFunction: doDelete,
}
})
};
const renderLotOccupancyFees = () => {
if (lotOccupancyFees.length === 0) {
lotOccupancyFeesContainerElement.innerHTML = "<div class=\"message is-info\">" +
"<p class=\"message-body\">There are no fees associated with this record.</p>" +
"</div>";
return;
}
lotOccupancyFeesContainerElement.innerHTML = "<table class=\"table is-fullwidth is-striped is-hoverable\">" +
("<thead><tr>" +
"<th>Fee</th>" +
"<th><span class=\"is-sr-only\">Unit Cost</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">&times;</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">Quantity</span></th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">equals</span></th>" +
"<th class=\"has-width-1 has-text-right\">Total</th>" +
"<th class=\"has-width-1\"><span class=\"is-sr-only\">Options</span></th>" +
"</tr></thead>") +
"<tbody></tbody>" +
("<tfoot>" +
"<tr><th colspan=\"5\">Subtotal</th><td class=\"has-text-weight-bold has-text-right\" id=\"lotOccupancyFees--feeAmountTotal\"></td><td></td></tr>" +
"<tr><th colspan=\"5\">Tax</th><td class=\"has-text-right\" id=\"lotOccupancyFees--taxAmountTotal\"></td><td></td></tr>" +
"<tr><th colspan=\"5\">Grand Total</th><td class=\"has-text-weight-bold has-text-right\" id=\"lotOccupancyFees--grandTotal\"></td><td></td></tr>" +
"</tfoot>") +
"</table>";
let feeAmountTotal = 0;
let taxAmountTotal = 0;
for (const lotOccupancyFee of lotOccupancyFees) {
const tableRowElement = document.createElement("tr");
tableRowElement.className = "container--lotOccupancyFee";
tableRowElement.dataset.feeId = lotOccupancyFee.feeId.toString();
tableRowElement.innerHTML = ("<td colspan=\"" + (lotOccupancyFee.quantity === 1 ? "5" : "1") + "\">" +
cityssm.escapeHTML(lotOccupancyFee.feeName) +
"</td>") +
(lotOccupancyFee.quantity === 1 ?
"" :
"<td class=\"has-text-right\">$" + lotOccupancyFee.feeAmount.toFixed(2) + "</td>" +
"<td>&times;</td>" +
"<td class=\"has-text-right\">" + lotOccupancyFee.quantity + "</td>" +
"<td>=</td>") +
"<td class=\"has-text-right\">$" + (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity).toFixed(2) + "</td>" +
("<td>" +
"<button class=\"button is-small is-danger is-light\" data-tooltip=\"Delete Fee\" type=\"button\">" +
"<i class=\"fas fa-trash\" aria-hidden=\"true\"></i>" +
"</button>" +
"</td>");
tableRowElement.querySelector("button").addEventListener("click", deleteLotOccupancyFee);
lotOccupancyFeesContainerElement.querySelector("tbody").append(tableRowElement);
feeAmountTotal += (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity);
taxAmountTotal += (lotOccupancyFee.taxAmount * lotOccupancyFee.quantity);
}
(lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--feeAmountTotal") as HTMLElement).textContent = "$" + feeAmountTotal.toFixed(2);
(lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--taxAmountTotal") as HTMLElement).textContent = "$" + taxAmountTotal.toFixed(2);
(lotOccupancyFeesContainerElement.querySelector("#lotOccupancyFees--grandTotal") as HTMLElement).textContent = "$" + (feeAmountTotal + taxAmountTotal).toFixed(2);
};
document.querySelector("#button--addFee").addEventListener("click", () => {
@ -814,30 +956,157 @@ declare const bulmaJS: BulmaJS;
let feeCategories: recordTypes.FeeCategory[];
let feeFilterElement: HTMLInputElement;
let feeFilterResultsElement: HTMLElement;
const doAddFee = (feeId: number, quantity: number | string = 1) => {
cityssm.postJSON(urlPrefix + "/lotOccupancies/doAddLotOccupancyFee", {
lotOccupancyId,
feeId,
quantity
},
(responseJSON: {
success: boolean;
errorMessage ? : string;
lotOccupancyFees ? : recordTypes.LotOccupancyFee[];
}) => {
if (responseJSON.success) {
lotOccupancyFees = responseJSON.lotOccupancyFees;
renderLotOccupancyFees();
filterFees();
} else {
bulmaJS.alert({
title: "Error Adding Fee",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
const doSetQuantityAndAddFee = (fee: recordTypes.Fee) => {
let quantityElement: HTMLInputElement;
let quantityCloseModalFunction: () => void;
const doSetQuantity = (submitEvent: SubmitEvent) => {
submitEvent.preventDefault();
doAddFee(fee.feeId, quantityElement.value);
quantityCloseModalFunction();
};
cityssm.openHtmlModal("lotOccupancy-setFeeQuantity", {
onshow: (modalElement) => {
(modalElement.querySelector("#lotOccupancyFeeQuantity--quantityUnit") as HTMLElement).textContent = fee.quantityUnit;
},
onshown: (modalElement, closeModalFunction) => {
quantityCloseModalFunction = closeModalFunction;
quantityElement = modalElement.querySelector("#lotOccupancyFeeQuantity--quantity");
modalElement.querySelector("form").addEventListener("submit", doSetQuantity);
}
});
};
const tryAddFee = (clickEvent: Event) => {
clickEvent.preventDefault();
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, 10);
const feeCategory = feeCategories.find((currentFeeCategory) => {
return currentFeeCategory.feeCategoryId === feeCategoryId;
});
const fee = feeCategory.fees.find((currentFee) => {
return currentFee.feeId === feeId;
});
if (fee.includeQuantity) {
doSetQuantityAndAddFee(fee);
} else {
doAddFee(feeId);
}
};
const filterFees = () => {
const filterStringPieces = feeFilterElement.value.trim().toLowerCase().split(" ");
feeFilterResultsElement.innerHTML = "";
for (const feeCategory of feeCategories) {
const categoryContainerElement = document.createElement("div");
categoryContainerElement.className = "container--feeCategory";
categoryContainerElement.dataset.feeCategoryId = feeCategory.feeCategoryId.toString();
categoryContainerElement.innerHTML = "<h4 class=\"title is-5\">" + cityssm.escapeHTML(feeCategory.feeCategory) + "</h4>" +
"<div class=\"panel\"></div>";
let hasFees = false;
for (const fee of feeCategory.fees) {
if (lotOccupancyFeesContainerElement.querySelector(".container--lotOccupancyFee[data-fee-id='" + fee.feeId + "']")) {
continue;
}
let includeFee = true;
for (const filterStringPiece of filterStringPieces) {
if (!fee.feeName.toLowerCase().includes(filterStringPiece)) {
includeFee = false;
break;
}
}
if (!includeFee) {
continue;
}
hasFees = true;
const panelBlockElement = document.createElement("a");
panelBlockElement.className = "panel-block is-block container--fee";
panelBlockElement.dataset.feeId = fee.feeId.toString();
panelBlockElement.href = "#";
panelBlockElement.innerHTML = "<strong>" + cityssm.escapeHTML(fee.feeName) + "</strong><br />" +
"<small>" + cityssm.escapeHTML(fee.feeDescription).replace(/\n/g, "<br />") + "</small>";
panelBlockElement.addEventListener("click", tryAddFee);
categoryContainerElement.querySelector(".panel").append(panelBlockElement);
}
if (hasFees) {
feeFilterResultsElement.append(categoryContainerElement);
}
}
};
cityssm.openHtmlModal("lotOccupancy-addFee", {
onshow: (modalElement) => {
feeFilterElement = modalElement.querySelector("#feeSelect--feeName");
feeFilterResultsElement = modalElement.querySelector("#resultsContainer--feeSelect");
cityssm.postJSON(urlPrefix + "/lotOccupancies/doGetFees", {
lotOccupancyId
},
(responseJSON: { feeCategories: recordTypes.FeeCategory[]}) => {
feeCategories = responseJSON.feeCategories;
lotOccupancyId
},
(responseJSON: {
feeCategories: recordTypes.FeeCategory[]
}) => {
feeCategories = responseJSON.feeCategories;
feeFilterElement = modalElement.querySelector("#feeSelect--feeName");
feeFilterElement.disabled = false;
feeFilterElement.addEventListener("keyup", filterFees);
feeFilterElement.focus();
feeFilterElement.disabled = false;
feeFilterElement.addEventListener("keyup", filterFees);
feeFilterElement.focus();
filterFees();
});
filterFees();
});
},
onshown: () => {
bulmaJS.toggleHtmlClipped();

View File

@ -92,7 +92,6 @@
<div class="select is-fullwidth">
<select id="feeAdd--feeFunction" name="feeFunction">
<option value="">(No Function Selected)</option>
<option value="t">test</option>
</select>
</div>
</div>

View File

@ -0,0 +1,31 @@
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<h3 class="modal-card-title">
Set Quantity
</h3>
<button class="delete is-close-modal-button" aria-label="close" type="button"></button>
</header>
<section class="modal-card-body">
<form id="form--lotOccupancyFeeQuantity">
<label class="label" for="lotOccupancyFeeQuantity--quantity">Quantity</label>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input" id="lotOccupancyFeeQuantity--quantity" name="quantity" type="number" value="1" min="0.1" max="999.9" step="0.1" required />
</div>
<div class="control">
<span class="button is-static" id="lotOccupancyFeeQuantity--quantityUnit"></span>
</div>
</div>
</form>
</section>
<footer class="modal-card-foot justify-right">
<button class="button is-success" type="submit" form="form--lotOccupancyFeeQuantity">
<span class="icon"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Set Quantity and Add Fee</span>
</button>
<button class="button is-close-modal-button" type="button">Cancel</button>
</footer>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ import handler_doGetOccupancyTypeFields from "../handlers/lotOccupancies-post/do
import handler_doCreateLotOccupancy from "../handlers/lotOccupancies-post/doCreateLotOccupancy.js";
import handler_edit from "../handlers/lotOccupancies-get/edit.js";
import handler_doUpdateLotOccupancy from "../handlers/lotOccupancies-post/doUpdateLotOccupancy.js";
import handler_doDeleteLotOccupancy from "../handlers/lotOccupancies-post/doDeleteLotOccupancy.js";
import handler_doAddLotOccupancyOccupant from "../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js";
import handler_doUpdateLotOccupancyOccupant from "../handlers/lotOccupancies-post/doUpdateLotOccupancyOccupant.js";
import handler_doDeleteLotOccupancyOccupant from "../handlers/lotOccupancies-post/doDeleteLotOccupancyOccupant.js";
@ -14,6 +15,8 @@ import handler_doAddLotOccupancyComment from "../handlers/lotOccupancies-post/do
import handler_doUpdateLotOccupancyComment from "../handlers/lotOccupancies-post/doUpdateLotOccupancyComment.js";
import handler_doDeleteLotOccupancyComment from "../handlers/lotOccupancies-post/doDeleteLotOccupancyComment.js";
import handler_doGetFees from "../handlers/lotOccupancies-post/doGetFees.js";
import handler_doAddLotOccupancyFee from "../handlers/lotOccupancies-post/doAddLotOccupancyFee.js";
import handler_doDeleteLotOccupancyFee from "../handlers/lotOccupancies-post/doDeleteLotOccupancyFee.js";
import * as permissionHandlers from "../handlers/permissions.js";
export const router = Router();
router.get("/", handler_search);
@ -24,6 +27,7 @@ router.post("/doCreateLotOccupancy", permissionHandlers.updatePostHandler, handl
router.get("/:lotOccupancyId", handler_view);
router.get("/:lotOccupancyId/edit", permissionHandlers.updateGetHandler, handler_edit);
router.post("/doUpdateLotOccupancy", permissionHandlers.updatePostHandler, handler_doUpdateLotOccupancy);
router.post("/doDeleteLotOccupancy", permissionHandlers.updatePostHandler, handler_doDeleteLotOccupancy);
router.post("/doAddLotOccupancyOccupant", permissionHandlers.updatePostHandler, handler_doAddLotOccupancyOccupant);
router.post("/doUpdateLotOccupancyOccupant", permissionHandlers.updatePostHandler, handler_doUpdateLotOccupancyOccupant);
router.post("/doDeleteLotOccupancyOccupant", permissionHandlers.updatePostHandler, handler_doDeleteLotOccupancyOccupant);
@ -31,4 +35,6 @@ router.post("/doAddLotOccupancyComment", permissionHandlers.updatePostHandler, h
router.post("/doUpdateLotOccupancyComment", permissionHandlers.updatePostHandler, handler_doUpdateLotOccupancyComment);
router.post("/doDeleteLotOccupancyComment", permissionHandlers.updatePostHandler, handler_doDeleteLotOccupancyComment);
router.post("/doGetFees", permissionHandlers.updatePostHandler, handler_doGetFees);
router.post("/doAddLotOccupancyFee", permissionHandlers.updatePostHandler, handler_doAddLotOccupancyFee);
router.post("/doDeleteLotOccupancyFee", permissionHandlers.updatePostHandler, handler_doDeleteLotOccupancyFee);
export default router;

View File

@ -13,6 +13,7 @@ import handler_doCreateLotOccupancy from "../handlers/lotOccupancies-post/doCrea
import handler_edit from "../handlers/lotOccupancies-get/edit.js";
import handler_doUpdateLotOccupancy from "../handlers/lotOccupancies-post/doUpdateLotOccupancy.js";
import handler_doDeleteLotOccupancy from "../handlers/lotOccupancies-post/doDeleteLotOccupancy.js";
import handler_doAddLotOccupancyOccupant from "../handlers/lotOccupancies-post/doAddLotOccupancyOccupant.js";
import handler_doUpdateLotOccupancyOccupant from "../handlers/lotOccupancies-post/doUpdateLotOccupancyOccupant.js";
@ -23,6 +24,8 @@ import handler_doUpdateLotOccupancyComment from "../handlers/lotOccupancies-post
import handler_doDeleteLotOccupancyComment from "../handlers/lotOccupancies-post/doDeleteLotOccupancyComment.js";
import handler_doGetFees from "../handlers/lotOccupancies-post/doGetFees.js";
import handler_doAddLotOccupancyFee from "../handlers/lotOccupancies-post/doAddLotOccupancyFee.js";
import handler_doDeleteLotOccupancyFee from "../handlers/lotOccupancies-post/doDeleteLotOccupancyFee.js";
import * as permissionHandlers from "../handlers/permissions.js";
@ -66,6 +69,10 @@ router.post("/doUpdateLotOccupancy",
permissionHandlers.updatePostHandler,
handler_doUpdateLotOccupancy);
router.post("/doDeleteLotOccupancy",
permissionHandlers.updatePostHandler,
handler_doDeleteLotOccupancy);
// Occupants
router.post("/doAddLotOccupancyOccupant",
@ -100,5 +107,13 @@ router.post("/doGetFees",
permissionHandlers.updatePostHandler,
handler_doGetFees);
router.post("/doAddLotOccupancyFee",
permissionHandlers.updatePostHandler,
handler_doAddLotOccupancyFee);
router.post("/doDeleteLotOccupancyFee",
permissionHandlers.updatePostHandler,
handler_doDeleteLotOccupancyFee);
export default router;

View File

@ -45,204 +45,249 @@
<input id="lotOccupancy--lotOccupancyId" name="lotOccupancyId" type="hidden" value="<%= lotOccupancy.lotOccupancyId %>" />
<div class="columns">
<div class="column">
<label class="label" for="lotOccupancy--occupancyTypeId">
<%= configFunctions.getProperty("aliases.occupancy") %> Type
</label>
<div class="field has-addons">
<div class="control is-expanded">
<div class="select is-fullwidth">
<select id="lotOccupancy--occupancyTypeId" name="occupancyTypeId" required>
<% if (isCreate) { %>
<option value="">(No Type)</option>
<% } %>
<% let typeIsFound = false; %>
<% for (const occupancyType of occupancyTypes) { %>
<%
if (lotOccupancy.occupancyTypeId === occupancyType.occupancyTypeId) {
typeIsFound = true;
}
%>
<option value="<%= occupancyType.occupancyTypeId %>"
<%= (lotOccupancy.occupancyTypeId === occupancyType.occupancyTypeId ? " selected" : "") %>
<%= (!isCreate && lotOccupancy.occupancyTypeId !== occupancyType.occupancyTypeId ? " disabled" : "") %>>
<%= occupancyType.occupancyType %>
</option>
<% } %>
<% if (lotOccupancy.occupancyTypeId && !typeIsFound) { %>
<option value="<%= lotOccupancy.occupancyTypeId %>" selected>
<%= lotOccupancy.occupancyType %>
</option>
<% } %>
</select>
</div>
</div>
<div class="control">
<button class="button is-unlock-field-button" type="button" title="Unlock Field">
<i class="fas fa-unlock" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="column">
<input id="lotOccupancy--lotId" name="lotId" type="hidden" value="<%= lotOccupancy.lotId %>" />
<label class="label" for="lotOccupancy--lotName">
<%= configFunctions.getProperty("aliases.lot") %>
</label>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input has-text-left" id="lotOccupancy--lotName" type="button" value="<%= lotOccupancy.lotName %>"
<%= (configFunctions.getProperty("settings.lotOccupancy.lotIdIsRequired") ? " required" : "") %>
<%= (isCreate ? "" : " disabled") %> />
</div>
<div class="control">
<button class="button is-unlock-field-button" type="button" title="Unlock Field">
<i class="fas fa-unlock" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="lotOccupancy--occupancyStartDateString">Start Date</label>
<div class="control">
<input class="input" id="lotOccupancy--occupancyStartDateString" name="occupancyStartDateString" type="date"
value="<%= lotOccupancy.occupancyStartDateString %>" required />
</div>
</div>
<div class="field">
<label class="label" for="lotOccupancy--occupancyEndDateString">End Date</label>
<div class="control">
<input class="input" id="lotOccupancy--occupancyEndDateString" name="occupancyEndDateString" type="date"
value="<%= lotOccupancy.occupancyEndDateString %>"
min="<%= lotOccupancy.occupancyStartDateString %>"
<%= (configFunctions.getProperty("settings.lotOccupancy.occupancyEndDateIsRequired") ? " required" : "") %> />
</div>
</div>
</div>
<div class="column">
<div id="container--lotOccupancyFields">
<% if (isCreate) { %>
<div class="message is-info">
<p class="message-body">
Select the <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> type to load the available fields.
</p>
</div>
<% } else if (lotOccupancy.lotOccupancyFields.length === 0) { %>
<div class="message is-info">
<p class="message-body">
The current <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> type has no additional fields.
</p>
</div>
<% } else { %>
<% let occupancyTypeFieldIds = ""; %>
<% for (const lotOccupancyField of lotOccupancy.lotOccupancyFields) { %>
<% occupancyTypeFieldIds += "," + lotOccupancyField.occupancyTypeFieldId; %>
<div class="field">
<label class="label" for="lotOccupancy--lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>">
<%= lotOccupancyField.occupancyTypeField %>
</label>
<div class="control">
<input class="input"
id="lotOccupancy--lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>"
name="lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>"
type="text"
value="<%= lotOccupancyField.lotOccupancyFieldValue %>"
<% if (lotOccupancyField.pattern !== "") { %>
pattern="<%= lotOccupancyField.pattern %>"
<div class="panel">
<div class="panel-block is-block">
<div class="columns">
<div class="column">
<label class="label" for="lotOccupancy--occupancyTypeId">
<%= configFunctions.getProperty("aliases.occupancy") %> Type
</label>
<div class="field has-addons">
<div class="control is-expanded">
<div class="select is-fullwidth">
<select id="lotOccupancy--occupancyTypeId" name="occupancyTypeId" required>
<% if (isCreate) { %>
<option value="">(No Type)</option>
<% } %>
minlength="<%= lotOccupancyField.minimumLength %>"
maxlength="<%= lotOccupancyField.maximumLength %>" />
<% let typeIsFound = false; %>
<% for (const occupancyType of occupancyTypes) { %>
<%
if (lotOccupancy.occupancyTypeId === occupancyType.occupancyTypeId) {
typeIsFound = true;
}
%>
<option value="<%= occupancyType.occupancyTypeId %>"
<%= (lotOccupancy.occupancyTypeId === occupancyType.occupancyTypeId ? " selected" : "") %>
<%= (!isCreate && lotOccupancy.occupancyTypeId !== occupancyType.occupancyTypeId ? " disabled" : "") %>>
<%= occupancyType.occupancyType %>
</option>
<% } %>
<% if (lotOccupancy.occupancyTypeId && !typeIsFound) { %>
<option value="<%= lotOccupancy.occupancyTypeId %>" selected>
<%= lotOccupancy.occupancyType %>
</option>
<% } %>
</select>
</div>
</div>
<% } %>
<input id="lotOccupancy--occupancyTypeFieldIds" name="occupancyTypeFieldIds" type="hidden" value="<%= occupancyTypeFieldIds.slice(1) %>" />
<% } %>
<div class="control">
<button class="button is-unlock-field-button" data-tooltip="Unlock Field" type="button" aria-label="Unlock <%= configFunctions.getProperty("aliases.occupancy") %> Type Field">
<i class="fas fa-unlock" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="column">
<input id="lotOccupancy--lotId" name="lotId" type="hidden" value="<%= lotOccupancy.lotId %>" />
<label class="label" for="lotOccupancy--lotName">
<%= configFunctions.getProperty("aliases.lot") %>
</label>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input has-text-left" id="lotOccupancy--lotName" type="button" value="<%= lotOccupancy.lotName %>"
<%= (configFunctions.getProperty("settings.lotOccupancy.lotIdIsRequired") ? " required" : "") %>
<%= (isCreate ? "" : " disabled") %> />
</div>
<div class="control">
<button class="button is-unlock-field-button" data-tooltip="Unlock Field" type="button" aria-label="Unlock <%= configFunctions.getProperty("aliases.lot") %></button> Field">
<i class="fas fa-unlock" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="lotOccupancy--occupancyStartDateString">Start Date</label>
<div class="control">
<input class="input" id="lotOccupancy--occupancyStartDateString" name="occupancyStartDateString" type="date"
value="<%= lotOccupancy.occupancyStartDateString %>" required />
</div>
</div>
<div class="field">
<label class="label" for="lotOccupancy--occupancyEndDateString">End Date</label>
<div class="control">
<input class="input" id="lotOccupancy--occupancyEndDateString" name="occupancyEndDateString" type="date"
value="<%= lotOccupancy.occupancyEndDateString %>"
min="<%= lotOccupancy.occupancyStartDateString %>"
<%= (configFunctions.getProperty("settings.lotOccupancy.occupancyEndDateIsRequired") ? " required" : "") %> />
</div>
</div>
</div>
<div class="column">
<div id="container--lotOccupancyFields">
<% if (isCreate) { %>
<div class="message is-info">
<p class="message-body">
Select the <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> type to load the available fields.
</p>
</div>
<% } else if (lotOccupancy.lotOccupancyFields.length === 0) { %>
<div class="message is-info">
<p class="message-body">
The current <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> type has no additional fields.
</p>
</div>
<% } else { %>
<% let occupancyTypeFieldIds = ""; %>
<% for (const lotOccupancyField of lotOccupancy.lotOccupancyFields) { %>
<% occupancyTypeFieldIds += "," + lotOccupancyField.occupancyTypeFieldId; %>
<div class="field">
<label class="label" for="lotOccupancy--lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>">
<%= lotOccupancyField.occupancyTypeField %>
</label>
<div class="control">
<input class="input"
id="lotOccupancy--lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>"
name="lotOccupancyFieldValue_<%= lotOccupancyField.occupancyTypeFieldId %>"
type="text"
value="<%= lotOccupancyField.lotOccupancyFieldValue %>"
<% if (lotOccupancyField.pattern !== "") { %>
pattern="<%= lotOccupancyField.pattern %>"
<% } %>
minlength="<%= lotOccupancyField.minimumLength %>"
maxlength="<%= lotOccupancyField.maximumLength %>" />
</div>
</div>
<% } %>
<input id="lotOccupancy--occupancyTypeFieldIds" name="occupancyTypeFieldIds" type="hidden" value="<%= occupancyTypeFieldIds.slice(1) %>" />
<% } %>
</div>
</div>
</div>
</div>
</div>
<div class="has-text-right">
<button class="button is-primary" type="submit">
<span class="icon is-small"><i class="fas fa-save" aria-hidden="true"></i></span>
<span>
<%= (isCreate ? "Create" : "Update") %>
<%= configFunctions.getProperty("aliases.occupancy") %>
Record
</span>
</button>
<div class="panel-block is-justify-content-right">
<button class="button is-primary" type="submit">
<span class="icon is-small"><i class="fas fa-save" aria-hidden="true"></i></span>
<span>
<%= (isCreate ? "Create" : "Update") %>
<%= configFunctions.getProperty("aliases.occupancy") %>
Record
</span>
</button>
<% if (!isCreate) { %>
<div class="dropdown is-right ml-2">
<div class="dropdown-trigger">
<button class="button" type="button">
<span>More Options</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu">
<div class="dropdown-content">
<a class="dropdown-item" id="button--deleteLotOccupancy" href="#">
<span class="icon is-small"><i class="fas fa-trash has-text-danger" aria-hidden="true"></i></span>
<span>Delete <%= configFunctions.getProperty("aliases.occupancy") %> Record</span>
</a>
</div>
</div>
</div>
<% } %>
</div>
</div>
</form>
<% if (isCreate) { %>
<% } else { %>
<hr />
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4">
<%= configFunctions.getProperty("aliases.occupants") %>
</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addOccupant" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add <%= configFunctions.getProperty("aliases.occupant") %></span>
</button>
</div>
</div>
</div>
<div id="container--lotOccupancyOccupants"></div>
<hr />
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4 mt-2">Comments</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addComment" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Comment</span>
</button>
</div>
</div>
</div>
<div id="container--lotOccupancyComments"></div>
<hr />
<div class="columns">
<div class="column">
<div class="panel mt-5">
<div class="panel-heading">
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4">Fees</h2>
<h2 class="title is-4">
<%= configFunctions.getProperty("aliases.occupants") %>
</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addFee" type="button">
<button class="button is-small is-success" id="button--addOccupant" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee</span>
<span>Add <%= configFunctions.getProperty("aliases.occupant") %></span>
</button>
</div>
</div>
</div>
<div id="container--lotOccupancyFees"></div>
</div>
<div class="panel-block is-block" id="container--lotOccupancyOccupants"></div>
</div>
<div class="panel">
<div class="panel-heading">
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4">Comments</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addComment" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Comment</span>
</button>
</div>
</div>
</div>
</div>
<div class="panel-block is-block" id="container--lotOccupancyComments"></div>
</div>
<div class="columns">
<div class="column">
<div class="panel">
<div class="panel-heading">
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4">Fees</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addFee" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee</span>
</button>
</div>
</div>
</div>
</div>
<div class="panel-block is-block" id="container--lotOccupancyFees"></div>
</div>
</div>
<div class="column">
<h2 class="title is-4">Transactions</h2>
<div class="panel">
<div class="panel-heading">
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h2 class="title is-4">Transactions</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-small is-success" id="button--addTransaction" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Transaction</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<% } %>

View File

@ -38,137 +38,206 @@
</div>
<% } %>
<div class="columns">
<div class="column">
<p>
<strong><%= configFunctions.getProperty("aliases.occupancy") %> Type</strong><br />
<%= lotOccupancy.occupancyType %>
</p>
</div>
<div class="column">
<p class="mb-2">
<strong><%= configFunctions.getProperty("aliases.lot") %></strong><br />
<a href="<%= urlPrefix %>/lots/<%= lotOccupancy.lotId %>"><%= lotOccupancy.lotName %></a>
</p>
<p>
<strong><%= configFunctions.getProperty("aliases.map") %></strong><br />
<a href="<%= urlPrefix %>/maps/<%= lotOccupancy.mapId %>"><%= lotOccupancy.mapName %></a>
</p>
</div>
<div class="column">
<p class="mb-2">
<strong>Start Date</strong><br />
<%= lotOccupancy.occupancyStartDateString %>
</p>
<p>
<strong>End Date</strong><br />
<%= (lotOccupancy.occupancyEndDateString === "" ? "(No End Date)" : lotOccupancy.occupancyEndDateString) %>
</p>
</div>
<% if (lotOccupancy.lotOccupancyFields.length > 0) { %>
<div class="column">
<% for (const lotOccupancyField of lotOccupancy.lotOccupancyFields) { %>
<p class="mb-2">
<strong><%= lotOccupancyField.occupancyTypeField %></strong><br />
<%= lotOccupancyField.lotOccupancyFieldValue || "(No Value)" %>
<div class="panel">
<div class="panel-block is-block">
<div class="columns">
<div class="column">
<p>
<strong><%= configFunctions.getProperty("aliases.occupancy") %> Type</strong><br />
<%= lotOccupancy.occupancyType %>
</p>
</div>
<div class="column">
<p class="mb-2">
<strong><%= configFunctions.getProperty("aliases.lot") %></strong><br />
<a href="<%= urlPrefix %>/lots/<%= lotOccupancy.lotId %>"><%= lotOccupancy.lotName %></a>
</p>
<p>
<strong><%= configFunctions.getProperty("aliases.map") %></strong><br />
<a href="<%= urlPrefix %>/maps/<%= lotOccupancy.mapId %>"><%= lotOccupancy.mapName %></a>
</p>
</div>
<div class="column">
<p class="mb-2">
<strong>Start Date</strong><br />
<%= lotOccupancy.occupancyStartDateString %>
</p>
<p>
<strong>End Date</strong><br />
<%= (lotOccupancy.occupancyEndDateString === "" ? "(No End Date)" : lotOccupancy.occupancyEndDateString) %>
</p>
</div>
<% if (lotOccupancy.lotOccupancyFields.length > 0) { %>
<div class="column">
<% for (const lotOccupancyField of lotOccupancy.lotOccupancyFields) { %>
<p class="mb-2">
<strong><%= lotOccupancyField.occupancyTypeField %></strong><br />
<%= lotOccupancyField.lotOccupancyFieldValue || "(No Value)" %>
</p>
<% } %>
</div>
<% } %>
</div>
<% } %>
</div>
</div>
<h2 class="title is-4"><%= configFunctions.getProperty("aliases.occupants") %></h2>
<% if (lotOccupancy.lotOccupancyOccupants.length === 0) { %>
<div class="message is-warning">
<p class="message-body">
There are no <%= configFunctions.getProperty("aliases.occupants").toLowerCase() %>
associated with this record.
</p>
</div>
<% } else { %>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th><%= configFunctions.getProperty("aliases.occupant") %> Type</th>
<th><%= configFunctions.getProperty("aliases.occupant") %></th>
<th>Address</th>
<th>Phone Number</th>
</tr>
</thead>
<tbody>
<% for (const lotOccupancyOccupant of lotOccupancy.lotOccupancyOccupants) { %>
<tr>
<td><%= lotOccupancyOccupant.lotOccupantType %></td>
<td><%= lotOccupancyOccupant.occupantName %></td>
<td>
<%= lotOccupancyOccupant.occupantAddress1 %><br />
<% if (lotOccupancyOccupant.occupantAddress2 && lotOccupancyOccupant.occupantAddress2 !== "") { %>
<%= lotOccupancyOccupant.occupantAddress2 %><br />
<div class="panel">
<h2 class="panel-heading">
<%= configFunctions.getProperty("aliases.occupants") %>
</h2>
<div class="panel-block is-block">
<% if (lotOccupancy.lotOccupancyOccupants.length === 0) { %>
<div class="message is-warning">
<p class="message-body">
There are no <%= configFunctions.getProperty("aliases.occupants").toLowerCase() %>
associated with this record.
</p>
</div>
<% } else { %>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th><%= configFunctions.getProperty("aliases.occupant") %> Type</th>
<th><%= configFunctions.getProperty("aliases.occupant") %></th>
<th>Address</th>
<th>Phone Number</th>
</tr>
</thead>
<tbody>
<% for (const lotOccupancyOccupant of lotOccupancy.lotOccupancyOccupants) { %>
<tr>
<td><%= lotOccupancyOccupant.lotOccupantType %></td>
<td><%= lotOccupancyOccupant.occupantName %></td>
<td>
<%= lotOccupancyOccupant.occupantAddress1 %><br />
<% if (lotOccupancyOccupant.occupantAddress2 && lotOccupancyOccupant.occupantAddress2 !== "") { %>
<%= lotOccupancyOccupant.occupantAddress2 %><br />
<% } %>
<%= lotOccupancyOccupant.occupantCity %>, <%= lotOccupancyOccupant.occupantProvince %><br />
<%= lotOccupancyOccupant.occupantPostalCode %>
</td>
<td>
<%= lotOccupancyOccupant.occupantPhoneNumber %>
</td>
</tr>
<% } %>
<%= lotOccupancyOccupant.occupantCity %>, <%= lotOccupancyOccupant.occupantProvince %><br />
<%= lotOccupancyOccupant.occupantPostalCode %>
</td>
<td>
<%= lotOccupancyOccupant.occupantPhoneNumber %>
</td>
</tr>
</tbody>
</table>
<% } %>
</tbody>
</table>
<% } %>
</div>
</div>
<% if (lotOccupancy.lotOccupancyComments.length > 0) { %>
<h2 class="title is-4">Comments</h2>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>Commentor</th>
<th>Comment Date</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<% for (const lotOccupancyComment of lotOccupancy.lotOccupancyComments) { %>
<tr>
<td><%= lotOccupancyComment.recordCreate_userName %></td>
<td>
<%= lotOccupancyComment.lotOccupancyCommentDateString %>
<%= (lotOccupancyComment.lotOccupancyCommentTime === 0 ? "" : lotOccupancyComment.lotOccupancyCommentTimeString) %>
</td>
<td><%= lotOccupancyComment.lotOccupancyComment %></td>
</tr>
<% } %>
</tbody>
</table>
<div class="panel">
<h2 class="panel-heading">Comments</h2>
<div class="panel-block is-block">
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>Commentor</th>
<th>Comment Date</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<% for (const lotOccupancyComment of lotOccupancy.lotOccupancyComments) { %>
<tr>
<td><%= lotOccupancyComment.recordCreate_userName %></td>
<td>
<%= lotOccupancyComment.lotOccupancyCommentDateString %>
<%= (lotOccupancyComment.lotOccupancyCommentTime === 0 ? "" : lotOccupancyComment.lotOccupancyCommentTimeString) %>
</td>
<td><%= lotOccupancyComment.lotOccupancyComment %></td>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
<% } %>
<div class="columns">
<div class="column">
<h2 class="title is-4">Fees</h2>
<% if (lotOccupancy.lotOccupancyFees.length === 0) { %>
<div class="message is-info">
<p class="message-body">
There are no fees applied to this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record.
</p>
<div class="panel">
<h2 class="panel-heading">Fees</h2>
<div class="panel-block is-block">
<% if (lotOccupancy.lotOccupancyFees.length === 0) { %>
<div class="message is-info">
<p class="message-body">
There are no fees applied to this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record.
</p>
</div>
<% } else { %>
<%
let feeAmountTotal = 0;
let taxAmountTotal = 0;
%>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>Fee</th>
<th class="has-text-right"><span class="is-sr-only">Unit Cost</span></th>
<th class="has-width-1"><span class="is-sr-only">&times;</span></th>
<th class="has-width-1 has-text-right"><span class="is-sr-only">Quantity</span></th>
<th class="has-width-1"><span class="is-sr-only">=</span></th>
<th class="has-width-1 has-text-right">Total</th>
</tr>
</thead>
<tbody>
<% for (const lotOccupancyFee of lotOccupancy.lotOccupancyFees) { %>
<%
feeAmountTotal += (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity);
taxAmountTotal += (lotOccupancyFee.taxAmount * lotOccupancyFee.quantity);
%>
<tr>
<td colspan="<%= (lotOccupancyFee.quantity === 1 ? "5" : "1") %>">
<%= lotOccupancyFee.feeName %>
</td>
<% if (lotOccupancyFee.quantity !== 1) { %>
<td class="has-text-right">$<%= lotOccupancyFee.feeAmount.toFixed(2) %></td>
<td>&times;</td>
<td class="has-text-right"><%= lotOccupancyFee.quantity %></td>
<td>=</td>
<% } %>
<td class="has-text-right">$<%= (lotOccupancyFee.feeAmount * lotOccupancyFee.quantity).toFixed(2) %></td>
</tr>
<% } %>
</tbody>
<tfoot>
<tr>
<th colspan="5">Subtotal</th>
<td class="has-text-right has-text-weight-bold">$<%= feeAmountTotal.toFixed(2) %></td>
</tr>
<tr>
<th colspan="5">Tax</th>
<td class="has-text-right">$<%= taxAmountTotal.toFixed(2) %></td>
</tr>
<tr>
<th colspan="5">Grand Total</th>
<td class="has-text-right has-text-weight-bold">$<%= (feeAmountTotal + taxAmountTotal).toFixed(2) %></td>
</tr>
</tfoot>
</table>
<% } %>
</div>
<% } else { %>
<% } %>
</div>
</div>
<div class="column">
<h2 class="title is-4">Transactions</h2>
<% if (lotOccupancy.lotOccupancyTransactions.length === 0) { %>
<div class="message is-info">
<p class="message-body">
There are no transactions associated with this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record.
</p>
<div class="panel">
<h2 class="panel-heading">Transactions</h2>
<div class="panel-block is-block">
<% if (lotOccupancy.lotOccupancyTransactions.length === 0) { %>
<div class="message is-info">
<p class="message-body">
There are no transactions associated with this <%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %> record.
</p>
</div>
<% } else { %>
<% } %>
</div>
<% } else { %>
<% } %>
</div>
</div>
</div>