development

deepsource-autofix-76c6eb20
Dan Gowans 2022-08-16 16:27:12 -04:00
parent fad2578773
commit 31d11de8d4
60 changed files with 1947 additions and 629 deletions

View File

@ -0,0 +1,16 @@
import { getLotTypes, getOccupancyTypes } from "../../helpers/functions.cache.js";
import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js";
export const handler = (_request, response) => {
const feeCategories = getFeeCategories({}, {
includeFees: true
});
const occupancyTypes = getOccupancyTypes();
const lotTypes = getLotTypes();
response.render("admin-fees", {
headTitle: "Fee Management",
feeCategories,
occupancyTypes,
lotTypes
});
};
export default handler;

View File

@ -0,0 +1,30 @@
import type {
RequestHandler
} from "express";
import { getLotTypes, getOccupancyTypes } from "../../helpers/functions.cache.js";
import {
getFeeCategories
} from "../../helpers/lotOccupancyDB/getFeeCategories.js";
export const handler: RequestHandler = (_request, response) => {
const feeCategories = getFeeCategories({}, {
includeFees: true
});
const occupancyTypes = getOccupancyTypes();
const lotTypes = getLotTypes();
response.render("admin-fees", {
headTitle: "Fee Management",
feeCategories,
occupancyTypes,
lotTypes
});
};
export default handler;

View File

@ -1,6 +0,0 @@
export const handler = (_request, response) => {
response.render("admin-licenceCategories", {
headTitle: "Licence Categories"
});
};
export default handler;

View File

@ -1,12 +0,0 @@
import type { RequestHandler } from "express";
export const handler: RequestHandler = (_request, response) => {
response.render("admin-licenceCategories", {
headTitle: "Licence Categories"
});
};
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,14 @@
import { addFeeCategory } from "../../helpers/lotOccupancyDB/addFeeCategory.js";
import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js";
export const handler = async (request, response) => {
const feeCategoryId = addFeeCategory(request.body, request.session);
const feeCategories = getFeeCategories({}, {
includeFees: true
});
response.json({
success: true,
feeCategoryId,
feeCategories
});
};
export default handler;

View File

@ -0,0 +1,31 @@
import type {
RequestHandler
} from "express";
import {
addFeeCategory
} from "../../helpers/lotOccupancyDB/addFeeCategory.js";
import {
getFeeCategories
} from "../../helpers/lotOccupancyDB/getFeeCategories.js";
export const handler: RequestHandler = async (request, response) => {
const feeCategoryId = addFeeCategory(request.body, request.session);
const feeCategories = getFeeCategories({}, {
includeFees: true
});
response.json({
success: true,
feeCategoryId,
feeCategories
});
};
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,13 @@
import { updateFeeCategory } from "../../helpers/lotOccupancyDB/updateFeeCategory.js";
import { getFeeCategories } from "../../helpers/lotOccupancyDB/getFeeCategories.js";
export const handler = async (request, response) => {
const success = updateFeeCategory(request.body, request.session);
const feeCategories = getFeeCategories({}, {
includeFees: true
});
response.json({
success,
feeCategories
});
};
export default handler;

View File

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

View File

@ -2,12 +2,16 @@ import type * as recordTypes from "../types/recordTypes";
export declare function getLotOccupantTypes(): recordTypes.LotOccupantType[];
export declare function getLotOccupantTypeById(lotOccupantTypeId: number): recordTypes.LotOccupantType;
export declare function getLotOccupantTypesByLotOccupantType(lotOccupantType: string): recordTypes.LotOccupantType;
export declare function clearLotOccupantTypesCache(): void;
export declare function getLotStatuses(): recordTypes.LotStatus[];
export declare function getLotStatusById(lotStatusId: number): recordTypes.LotStatus;
export declare function getLotStatusByLotStatus(lotStatus: string): recordTypes.LotStatus;
export declare function clearLotStatusesCache(): void;
export declare function getLotTypes(): recordTypes.LotType[];
export declare function getLotTypeById(lotTypeId: number): recordTypes.LotType;
export declare function getLotTypesByLotType(lotType: string): recordTypes.LotType;
export declare function clearLotTypesCache(): void;
export declare function getOccupancyTypes(): recordTypes.OccupancyType[];
export declare function getOccupancyTypeById(occupancyTypeId: number): recordTypes.OccupancyType;
export declare function getOccupancyTypeByOccupancyType(occupancyTypeString: string): recordTypes.OccupancyType;
export declare function clearOccupancyTypesCache(): void;

View File

@ -23,6 +23,9 @@ export function getLotOccupantTypesByLotOccupantType(lotOccupantType) {
return currentLotOccupantType.lotOccupantType.toLowerCase() === lotOccupantTypeLowerCase;
});
}
export function clearLotOccupantTypesCache() {
lotOccupantTypes = undefined;
}
let lotStatuses;
export function getLotStatuses() {
if (!lotStatuses) {
@ -43,6 +46,9 @@ export function getLotStatusByLotStatus(lotStatus) {
return currentLotStatus.lotStatus.toLowerCase() === lotStatusLowerCase;
});
}
export function clearLotStatusesCache() {
lotStatuses = undefined;
}
let lotTypes;
export function getLotTypes() {
if (!lotTypes) {
@ -63,6 +69,9 @@ export function getLotTypesByLotType(lotType) {
return currentLotType.lotType.toLowerCase() === lotTypeLowerCase;
});
}
export function clearLotTypesCache() {
lotTypes = undefined;
}
let occupancyTypes;
const occupancyTypeMap = new Map();
export function getOccupancyTypes() {
@ -89,3 +98,7 @@ export function getOccupancyTypeByOccupancyType(occupancyTypeString) {
}
return occupancyType;
}
export function clearOccupancyTypesCache() {
occupancyTypes = undefined;
occupancyTypeMap.clear();
}

View File

@ -1,10 +1,24 @@
import { getLotOccupantTypes as getLotOccupantTypesFromDatabase } from "./lotOccupancyDB/getLotOccupantTypes.js";
import { getLotStatuses as getLotStatusesFromDatabase } from "./lotOccupancyDB/getLotStatuses.js";
import { getLotTypes as getLotTypesFromDatabase } from "./lotOccupancyDB/getLotTypes.js";
import { getOccupancyTypes as getOccupancyTypesFromDatabase } from "./lotOccupancyDB/getOccupancyTypes.js";
import {
getLotOccupantTypes as getLotOccupantTypesFromDatabase
} from "./lotOccupancyDB/getLotOccupantTypes.js";
import {
getLotStatuses as getLotStatusesFromDatabase
} from "./lotOccupancyDB/getLotStatuses.js";
import {
getLotTypes as getLotTypesFromDatabase
} from "./lotOccupancyDB/getLotTypes.js";
import {
getOccupancyTypes as getOccupancyTypesFromDatabase
} from "./lotOccupancyDB/getOccupancyTypes.js";
import {
getOccupancyType
} from "./lotOccupancyDB/getOccupancyType.js";
import type * as recordTypes from "../types/recordTypes";
import { getOccupancyType } from "./lotOccupancyDB/getOccupancyType.js";
/*
* Lot Occupant Types
@ -41,42 +55,50 @@ export function getLotOccupantTypesByLotOccupantType(lotOccupantType: string) {
});
}
export function clearLotOccupantTypesCache() {
lotOccupantTypes = undefined;
}
/*
* Lot Statuses
*/
* Lot Statuses
*/
let lotStatuses: recordTypes.LotStatus[];
export function getLotStatuses () {
export function getLotStatuses() {
if (!lotStatuses) {
lotStatuses = getLotStatusesFromDatabase();
}
return lotStatuses;
}
export function getLotStatusById (lotStatusId: number) {
export function getLotStatusById(lotStatusId: number) {
const cachedLotStatuses = getLotStatuses();
return cachedLotStatuses.find((currentLotStatus) => {
return currentLotStatus.lotStatusId === lotStatusId;
});
}
export function getLotStatusByLotStatus (lotStatus: string) {
export function getLotStatusByLotStatus(lotStatus: string) {
const cachedLotStatuses = getLotStatuses();
const lotStatusLowerCase = lotStatus.toLowerCase();
return cachedLotStatuses.find((currentLotStatus) => {
return currentLotStatus.lotStatus.toLowerCase() === lotStatusLowerCase;
});
}
export function clearLotStatusesCache() {
lotStatuses = undefined;
}
/*
* Lot Types
*/
@ -112,14 +134,19 @@ export function getLotTypesByLotType(lotType: string) {
});
}
export function clearLotTypesCache() {
lotTypes = undefined;
}
/*
* Occupancy Types
*/
* Occupancy Types
*/
let occupancyTypes: recordTypes.OccupancyType[];
const occupancyTypeMap = new Map<number, recordTypes.OccupancyType>();
const occupancyTypeMap = new Map < number,
recordTypes.OccupancyType > ();
export function getOccupancyTypes () {
export function getOccupancyTypes() {
if (!occupancyTypes) {
occupancyTypes = getOccupancyTypesFromDatabase();
@ -128,7 +155,7 @@ export function getOccupancyTypes () {
return occupancyTypes;
}
export function getOccupancyTypeById (occupancyTypeId: number) {
export function getOccupancyTypeById(occupancyTypeId: number) {
if (!occupancyTypeMap.has(occupancyTypeId)) {
@ -139,8 +166,8 @@ export function getOccupancyTypeById (occupancyTypeId: number) {
return occupancyTypeMap.get(occupancyTypeId);
}
export function getOccupancyTypeByOccupancyType (occupancyTypeString: string) {
export function getOccupancyTypeByOccupancyType(occupancyTypeString: string) {
const cachedOccupancyTypes = getOccupancyTypes();
const occupancyTypeLowerCase = occupancyTypeString.toLowerCase();
@ -155,4 +182,9 @@ export function getOccupancyTypeByOccupancyType (occupancyTypeString: string) {
}
return occupancyType;
}
export function clearOccupancyTypesCache() {
occupancyTypes = undefined;
occupancyTypeMap.clear();
}

View File

@ -6,6 +6,7 @@ import { addOccupancyTypeField } from "./lotOccupancyDB/addOccupancyTypeField.js
import { addLotStatus } from "./lotOccupancyDB/addLotStatus.js";
import { addLotOccupantType } from "./lotOccupancyDB/addLotOccupantType.js";
import Debug from "debug";
import addFeeCategory from "./lotOccupancyDB/addFeeCategory.js";
const debug = Debug("lot-occupancy-system:initialize");
const session = {
user: {
@ -147,5 +148,25 @@ const initializeCemeteryDatabase = () => {
maximumLength: 100,
orderNumber: 21
}, session);
addFeeCategory({
feeCategory: "Interment Rights",
orderNumber: 1
}, session);
addFeeCategory({
feeCategory: "Cremation Services",
orderNumber: 2
}, session);
addFeeCategory({
feeCategory: "Burial Charges",
orderNumber: 3
}, session);
addFeeCategory({
feeCategory: "Disinterment of Human Remains",
orderNumber: 4
}, session);
addFeeCategory({
feeCategory: "Additional Services",
orderNumber: 5
}, session);
};
initializeCemeteryDatabase();

View File

@ -31,6 +31,7 @@ import type {
} from "../types/recordTypes.js";
import Debug from "debug";
import addFeeCategory from "./lotOccupancyDB/addFeeCategory.js";
const debug = Debug("lot-occupancy-system:initialize");
@ -220,6 +221,35 @@ const initializeCemeteryDatabase = () => {
maximumLength: 100,
orderNumber: 21
}, session);
/*
* Fee Categories
*/
addFeeCategory({
feeCategory: "Interment Rights",
orderNumber: 1
}, session);
addFeeCategory({
feeCategory: "Cremation Services",
orderNumber: 2
}, session);
addFeeCategory({
feeCategory: "Burial Charges",
orderNumber: 3
}, session);
addFeeCategory({
feeCategory: "Disinterment of Human Remains",
orderNumber: 4
}, session);
addFeeCategory({
feeCategory: "Additional Services",
orderNumber: 5
}, session);
};

View File

@ -172,16 +172,29 @@ export const initializeDatabase = () => {
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupancycomments_datetime" +
" on LotOccupancyComments (lotOccupancyId, lotOccupancyCommentDate, lotOccupancyCommentTime)").run();
lotOccupancyDB.prepare("create table if not exists FeeCategories (" +
"feeCategoryId integer not null primary key autoincrement," +
" feeCategory varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists Fees (" +
"feeId integer not null primary key autoincrement," +
" feeCategoryId integer not null," +
" feeName varchar(100) not null," +
" feeDescription text," +
" occupancyTypeId integer," +
" lotTypeId integer," +
" includeQuantity boolean not null default 0," +
" quantityUnit varchar(30)," +
" feeAmount decimal(6, 2)," +
" feeFunction varchar(100)," +
" taxAmount decimal(6, 2)," +
" taxPercentage decimal(5, 2)," +
" isRequired bit not null default 0," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (feeCategoryId) references FeeCategories (feeCategoryId)," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)" +
")").run();
@ -190,7 +203,9 @@ export const initializeDatabase = () => {
lotOccupancyDB.prepare("create table if not exists LotOccupancyFees (" +
"lotOccupancyId integer not null," +
" feeId integer not null," +
" quantity decimal(4, 1) not null default 1," +
" feeAmount decimal(6, 2) not null," +
" taxAmount decmial(6, 2) not null," +
recordColumns + "," +
" primary key (lotOccupancyId, feeId)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +

View File

@ -1,7 +1,7 @@
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
lotOccupancyDB as databasePath
} from "../data/databasePaths.js";
import debug from "debug";
@ -9,311 +9,327 @@ const debugSQL = debug("lot-occupancy-system:databaseInitializer");
const recordColumns = " recordCreate_userName varchar(30) not null," +
" recordCreate_timeMillis integer not null," +
" recordUpdate_userName varchar(30) not null," +
" recordUpdate_timeMillis integer not null," +
" recordDelete_userName varchar(30)," +
" recordDelete_timeMillis integer";
" recordCreate_timeMillis integer not null," +
" recordUpdate_userName varchar(30) not null," +
" recordUpdate_timeMillis integer not null," +
" recordDelete_userName varchar(30)," +
" recordDelete_timeMillis integer";
export const initializeDatabase = (): boolean => {
const lotOccupancyDB = sqlite(databasePath);
const lotOccupancyDB = sqlite(databasePath);
const row = lotOccupancyDB
.prepare("select name from sqlite_master where type = 'table' and name = 'Fees'")
.get();
const row = lotOccupancyDB
.prepare("select name from sqlite_master where type = 'table' and name = 'Fees'")
.get();
if (!row) {
if (!row) {
debugSQL("Creating " + databasePath);
debugSQL("Creating " + databasePath);
// Lot Types
// Lot Types
lotOccupancyDB.prepare("create table if not exists LotTypes (" +
"lotTypeId integer not null primary key autoincrement," +
" lotType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists LotTypes (" +
"lotTypeId integer not null primary key autoincrement," +
" lotType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lottypes_ordernumber" +
" on LotTypes (orderNumber, lotType)").run();
lotOccupancyDB.prepare("create index if not exists idx_lottypes_ordernumber" +
" on LotTypes (orderNumber, lotType)").run();
lotOccupancyDB.prepare("create table if not exists LotTypeFields (" +
"lotTypeFieldId integer not null primary key autoincrement," +
" lotTypeId integer not null," +
" lotTypeField varchar(100) not null," +
" lotTypeFieldValues text," +
" isRequired bit not null default 0," +
" pattern varchar(100)," +
" minimumLength smallint not null default 1 check (minimumLength >= 0)," +
" maximumLength smallint not null default 100 check (maximumLength >= 0)," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotTypeFields (" +
"lotTypeFieldId integer not null primary key autoincrement," +
" lotTypeId integer not null," +
" lotTypeField varchar(100) not null," +
" lotTypeFieldValues text," +
" isRequired bit not null default 0," +
" pattern varchar(100)," +
" minimumLength smallint not null default 1 check (minimumLength >= 0)," +
" maximumLength smallint not null default 100 check (maximumLength >= 0)," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)" +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lottypefields_ordernumber" +
" on LotTypeFields (lotTypeId, orderNumber, lotTypeField)").run();
lotOccupancyDB.prepare("create index if not exists idx_lottypefields_ordernumber" +
" on LotTypeFields (lotTypeId, orderNumber, lotTypeField)").run();
lotOccupancyDB.prepare("create table if not exists LotStatuses (" +
"lotStatusId integer not null primary key autoincrement," +
" lotStatus varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists LotStatuses (" +
"lotStatusId integer not null primary key autoincrement," +
" lotStatus varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lotstatuses_ordernumber" +
" on LotStatuses (orderNumber, lotStatus)").run();
lotOccupancyDB.prepare("create index if not exists idx_lotstatuses_ordernumber" +
" on LotStatuses (orderNumber, lotStatus)").run();
// Maps and Lots
// Maps and Lots
lotOccupancyDB.prepare("create table if not exists Maps (" +
"mapId integer not null primary key autoincrement," +
" mapName varchar(200) not null," +
" mapDescription text," +
lotOccupancyDB.prepare("create table if not exists Maps (" +
"mapId integer not null primary key autoincrement," +
" mapName varchar(200) not null," +
" mapDescription text," +
" mapLatitude decimal(10, 8) check (mapLatitude between -90 and 90)," +
" mapLongitude decimal(11, 8) check (mapLongitude between -180 and 180)," +
" mapLatitude decimal(10, 8) check (mapLatitude between -90 and 90)," +
" mapLongitude decimal(11, 8) check (mapLongitude between -180 and 180)," +
" mapSVG varchar(50)," +
" mapSVG varchar(50)," +
" mapAddress1 varchar(50)," +
" mapAddress2 varchar(50)," +
" mapCity varchar(20)," +
" mapProvince varchar(2)," +
" mapPostalCode varchar(7)," +
" mapPhoneNumber varchar(30)," +
" mapAddress1 varchar(50)," +
" mapAddress2 varchar(50)," +
" mapCity varchar(20)," +
" mapProvince varchar(2)," +
" mapPostalCode varchar(7)," +
" mapPhoneNumber varchar(30)," +
recordColumns +
")").run();
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists Lots (" +
"lotId integer not null primary key autoincrement," +
" lotTypeId integer not null," +
" lotName varchar(100)," +
" mapId integer," +
" mapKey varchar(100)," +
lotOccupancyDB.prepare("create table if not exists Lots (" +
"lotId integer not null primary key autoincrement," +
" lotTypeId integer not null," +
" lotName varchar(100)," +
" mapId integer," +
" mapKey varchar(100)," +
" lotLatitude decimal(10, 8) check (lotLatitude between -90 and 90)," +
" lotLongitude decimal(11, 8) check (lotLongitude between -180 and 180)," +
" lotLatitude decimal(10, 8) check (lotLatitude between -90 and 90)," +
" lotLongitude decimal(11, 8) check (lotLongitude between -180 and 180)," +
" lotStatusId integer," +
" lotStatusId integer," +
recordColumns + "," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)," +
" foreign key (mapId) references Maps (mapId)," +
" foreign key (lotStatusId) references LotStatuses (lotStatusId)" +
")").run();
recordColumns + "," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)," +
" foreign key (mapId) references Maps (mapId)," +
" foreign key (lotStatusId) references LotStatuses (lotStatusId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotFields (" +
"lotId integer not null," +
" lotTypeFieldId integer not null," +
" lotFieldValue text not null," +
recordColumns + "," +
" primary key (lotId, lotTypeFieldId)," +
" foreign key (lotId) references Lots (lotId)," +
" foreign key (lotTypeFieldId) references LotTypeFields (lotTypeFieldId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotFields (" +
"lotId integer not null," +
" lotTypeFieldId integer not null," +
" lotFieldValue text not null," +
recordColumns + "," +
" primary key (lotId, lotTypeFieldId)," +
" foreign key (lotId) references Lots (lotId)," +
" foreign key (lotTypeFieldId) references LotTypeFields (lotTypeFieldId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotComments (" +
"lotCommentId integer not null primary key autoincrement," +
" lotId integer not null," +
" lotCommentDate integer not null check (lotCommentDate > 0)," +
" lotCommentTime integer not null check (lotCommentTime >= 0)," +
" lotComment text not null," +
recordColumns + "," +
" foreign key (lotId) references Lots (lotId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotComments (" +
"lotCommentId integer not null primary key autoincrement," +
" lotId integer not null," +
" lotCommentDate integer not null check (lotCommentDate > 0)," +
" lotCommentTime integer not null check (lotCommentTime >= 0)," +
" lotComment text not null," +
recordColumns + "," +
" foreign key (lotId) references Lots (lotId)" +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lotcomments_datetime" +
" on LotComments (lotId, lotCommentDate, lotCommentTime)").run();
lotOccupancyDB.prepare("create index if not exists idx_lotcomments_datetime" +
" on LotComments (lotId, lotCommentDate, lotCommentTime)").run();
// Occupancies
// Occupancies
lotOccupancyDB.prepare("create table if not exists OccupancyTypes (" +
"occupancyTypeId integer not null primary key autoincrement," +
" occupancyType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists OccupancyTypes (" +
"occupancyTypeId integer not null primary key autoincrement," +
" occupancyType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_occupancytypes_ordernumber" +
" on OccupancyTypes (orderNumber, occupancyType)").run();
lotOccupancyDB.prepare("create index if not exists idx_occupancytypes_ordernumber" +
" on OccupancyTypes (orderNumber, occupancyType)").run();
lotOccupancyDB.prepare("create table if not exists OccupancyTypeFields (" +
"occupancyTypeFieldId integer not null primary key autoincrement," +
" occupancyTypeId integer not null," +
" occupancyTypeField varchar(100) not null," +
" occupancyTypeFieldValues text," +
" isRequired bit not null default 0," +
" pattern varchar(100)," +
" minimumLength smallint not null default 1 check (minimumLength >= 0)," +
" maximumLength smallint not null default 100 check (maximumLength >= 0)," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists OccupancyTypeFields (" +
"occupancyTypeFieldId integer not null primary key autoincrement," +
" occupancyTypeId integer not null," +
" occupancyTypeField varchar(100) not null," +
" occupancyTypeFieldValues text," +
" isRequired bit not null default 0," +
" pattern varchar(100)," +
" minimumLength smallint not null default 1 check (minimumLength >= 0)," +
" maximumLength smallint not null default 100 check (maximumLength >= 0)," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)" +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_occupancytypefields_ordernumber" +
" on OccupancyTypeFields (occupancyTypeId, orderNumber, occupancyTypeField)").run();
lotOccupancyDB.prepare("create index if not exists idx_occupancytypefields_ordernumber" +
" on OccupancyTypeFields (occupancyTypeId, orderNumber, occupancyTypeField)").run();
lotOccupancyDB.prepare("create table if not exists LotOccupantTypes (" +
"lotOccupantTypeId integer not null primary key autoincrement," +
" lotOccupantType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists LotOccupantTypes (" +
"lotOccupantTypeId integer not null primary key autoincrement," +
" lotOccupantType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupanttypes_ordernumber" +
" on LotOccupantTypes (orderNumber, lotOccupantType)").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupanttypes_ordernumber" +
" on LotOccupantTypes (orderNumber, lotOccupantType)").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancies (" +
"lotOccupancyId integer not null primary key autoincrement," +
" occupancyTypeId integer not null," +
" lotId integer," +
" occupancyStartDate integer not null check (occupancyStartDate > 0)," +
" occupancyEndDate integer check (occupancyEndDate > 0)," +
recordColumns + "," +
" foreign key (lotId) references Lots (lotId)," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancies (" +
"lotOccupancyId integer not null primary key autoincrement," +
" occupancyTypeId integer not null," +
" lotId integer," +
" occupancyStartDate integer not null check (occupancyStartDate > 0)," +
" occupancyEndDate integer check (occupancyEndDate > 0)," +
recordColumns + "," +
" foreign key (lotId) references Lots (lotId)," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyOccupants (" +
"lotOccupancyId integer not null," +
" lotOccupantIndex integer not null," +
" occupantName varchar(200) not null," +
" occupantAddress1 varchar(50)," +
" occupantAddress2 varchar(50)," +
" occupantCity varchar(20)," +
" occupantProvince varchar(2)," +
" occupantPostalCode varchar(7)," +
" occupantPhoneNumber varchar(30)," +
" lotOccupantTypeId integer not null," +
recordColumns + "," +
" primary key (lotOccupancyId, lotOccupantIndex)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (lotOccupantTypeId) references LotOccupantTypes (lotOccupantTypeId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyOccupants (" +
"lotOccupancyId integer not null," +
" lotOccupantIndex integer not null," +
" occupantName varchar(200) not null," +
" occupantAddress1 varchar(50)," +
" occupantAddress2 varchar(50)," +
" occupantCity varchar(20)," +
" occupantProvince varchar(2)," +
" occupantPostalCode varchar(7)," +
" occupantPhoneNumber varchar(30)," +
" lotOccupantTypeId integer not null," +
recordColumns + "," +
" primary key (lotOccupancyId, lotOccupantIndex)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (lotOccupantTypeId) references LotOccupantTypes (lotOccupantTypeId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyFields (" +
"lotOccupancyId integer not null," +
" occupancyTypeFieldId integer not null," +
" lotOccupancyFieldValue text not null," +
recordColumns + "," +
" primary key (lotOccupancyId, occupancyTypeFieldId)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (occupancyTypeFieldId) references OccupancyTypeFields (occupancyTypeFieldId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyFields (" +
"lotOccupancyId integer not null," +
" occupancyTypeFieldId integer not null," +
" lotOccupancyFieldValue text not null," +
recordColumns + "," +
" primary key (lotOccupancyId, occupancyTypeFieldId)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (occupancyTypeFieldId) references OccupancyTypeFields (occupancyTypeFieldId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyComments (" +
"lotOccupancyCommentId integer not null primary key autoincrement," +
" lotOccupancyId integer not null," +
" lotOccupancyCommentDate integer not null check (lotOccupancyCommentDate > 0)," +
" lotOccupancyCommentTime integer not null check (lotOccupancyCommentTime >= 0)," +
" lotOccupancyComment text not null," +
recordColumns + "," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyComments (" +
"lotOccupancyCommentId integer not null primary key autoincrement," +
" lotOccupancyId integer not null," +
" lotOccupancyCommentDate integer not null check (lotOccupancyCommentDate > 0)," +
" lotOccupancyCommentTime integer not null check (lotOccupancyCommentTime >= 0)," +
" lotOccupancyComment text not null," +
recordColumns + "," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)" +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupancycomments_datetime" +
" on LotOccupancyComments (lotOccupancyId, lotOccupancyCommentDate, lotOccupancyCommentTime)").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupancycomments_datetime" +
" on LotOccupancyComments (lotOccupancyId, lotOccupancyCommentDate, lotOccupancyCommentTime)").run();
// Occupancy Fees and Transactions
// Occupancy Fees and Transactions
lotOccupancyDB.prepare("create table if not exists Fees (" +
"feeId integer not null primary key autoincrement," +
" feeName varchar(100) not null," +
" occupancyTypeId integer," +
" lotTypeId integer," +
" feeAmount decimal(6, 2)," +
" feeFunction varchar(100)," +
" isRequired bit not null default 0," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists FeeCategories (" +
"feeCategoryId integer not null primary key autoincrement," +
" feeCategory varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_fees_ordernumber" +
" on Fees (orderNumber, feeName)").run();
lotOccupancyDB.prepare("create table if not exists Fees (" +
"feeId integer not null primary key autoincrement," +
" feeCategoryId integer not null," +
" feeName varchar(100) not null," +
" feeDescription text," +
" occupancyTypeId integer," +
" lotTypeId integer," +
" includeQuantity boolean not null default 0," +
" quantityUnit varchar(30)," +
" feeAmount decimal(6, 2)," +
" feeFunction varchar(100)," +
" taxAmount decimal(6, 2)," +
" taxPercentage decimal(5, 2)," +
" isRequired bit not null default 0," +
" orderNumber smallint not null default 0," +
recordColumns + "," +
" foreign key (feeCategoryId) references FeeCategories (feeCategoryId)," +
" foreign key (occupancyTypeId) references OccupancyTypes (occupancyTypeId)," +
" foreign key (lotTypeId) references LotTypes (lotTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyFees (" +
"lotOccupancyId integer not null," +
" feeId integer not null," +
" feeAmount decimal(6, 2) not null," +
recordColumns + "," +
" primary key (lotOccupancyId, feeId)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (feeId) references Fees (feeId)" +
") without rowid").run();
lotOccupancyDB.prepare("create index if not exists idx_fees_ordernumber" +
" on Fees (orderNumber, feeName)").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyTransactions (" +
"lotOccupancyId integer not null," +
" transactionIndex integer not null," +
" transactionDate integer not null check (transactionDate > 0)," +
" transactionTime integer not null check (transactionTime >= 0)," +
" transactionAmount decimal(6, 2) not null," +
" externalReceiptNumber varchar(100)," +
" transactionNote text," +
recordColumns + "," +
" primary key (lotOccupancyId, transactionIndex)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyFees (" +
"lotOccupancyId integer not null," +
" feeId integer not null," +
" quantity decimal(4, 1) not null default 1," +
" feeAmount decimal(6, 2) not null," +
" taxAmount decmial(6, 2) not null," +
recordColumns + "," +
" primary key (lotOccupancyId, feeId)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)," +
" foreign key (feeId) references Fees (feeId)" +
") without rowid").run();
lotOccupancyDB.prepare("create index if not exists idx_lotoccupancytransactions_ordernumber" +
" on LotOccupancyTransactions (lotOccupancyId, transactionDate, transactionTime)").run();
lotOccupancyDB.prepare("create table if not exists LotOccupancyTransactions (" +
"lotOccupancyId integer not null," +
" transactionIndex integer not null," +
" transactionDate integer not null check (transactionDate > 0)," +
" transactionTime integer not null check (transactionTime >= 0)," +
" transactionAmount decimal(6, 2) not null," +
" externalReceiptNumber varchar(100)," +
" transactionNote text," +
recordColumns + "," +
" primary key (lotOccupancyId, transactionIndex)," +
" foreign key (lotOccupancyId) references LotOccupancies (lotOccupancyId)" +
") without rowid").run();
// Work Orders
lotOccupancyDB.prepare("create index if not exists idx_lotoccupancytransactions_ordernumber" +
" on LotOccupancyTransactions (lotOccupancyId, transactionDate, transactionTime)").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderTypes (" +
"workOrderTypeId integer not null primary key autoincrement," +
" workOrderType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
// Work Orders
lotOccupancyDB.prepare("create index if not exists idx_workordertypes_ordernumber" +
" on WorkOrderTypes (orderNumber, workOrderType)").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderTypes (" +
"workOrderTypeId integer not null primary key autoincrement," +
" workOrderType varchar(100) not null," +
" orderNumber smallint not null default 0," +
recordColumns +
")").run();
lotOccupancyDB.prepare("create table if not exists WorkOrders (" +
"workOrderId integer not null primary key autoincrement," +
" workOrderTypeId integer not null," +
" workOrderNumber varchar(50) not null," +
" workOrderDescription text," +
" workOrderOpenDate integer check (workOrderOpenDate > 0)," +
" workOrderCloseDate integer check (workOrderCloseDate > 0)," +
recordColumns + "," +
" foreign key (workOrderTypeId) references WorkOrderTypes (workOrderTypeId)" +
")").run();
lotOccupancyDB.prepare("create index if not exists idx_workordertypes_ordernumber" +
" on WorkOrderTypes (orderNumber, workOrderType)").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderLots (" +
"workOrderId integer not null," +
" lotId integer not null," +
recordColumns + "," +
" primary key (workOrderId, lotId)," +
" foreign key (workOrderId) references WorkOrders (workOrderId)," +
" foreign key (lotId) references Lots (lotId)" +
") without rowid").run();
lotOccupancyDB.prepare("create table if not exists WorkOrders (" +
"workOrderId integer not null primary key autoincrement," +
" workOrderTypeId integer not null," +
" workOrderNumber varchar(50) not null," +
" workOrderDescription text," +
" workOrderOpenDate integer check (workOrderOpenDate > 0)," +
" workOrderCloseDate integer check (workOrderCloseDate > 0)," +
recordColumns + "," +
" foreign key (workOrderTypeId) references WorkOrderTypes (workOrderTypeId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderComments (" +
"workOrderCommentId integer not null primary key autoincrement," +
" workOrderId integer not null," +
" workOrderCommentDate integer not null check (workOrderCommentDate > 0)," +
" workOrderCommentTime integer not null check (workOrderCommentTime >= 0)," +
" workOrderComment text not null," +
recordColumns + "," +
" foreign key (workOrderId) references WorkOrders (workOrderId)" +
")").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderLots (" +
"workOrderId integer not null," +
" lotId integer not null," +
recordColumns + "," +
" primary key (workOrderId, lotId)," +
" foreign key (workOrderId) references WorkOrders (workOrderId)," +
" foreign key (lotId) references Lots (lotId)" +
") without rowid").run();
lotOccupancyDB.prepare("create index if not exists idx_workordercomments_datetime" +
" on WorkOrderComments (workOrderId, workOrderCommentDate, workOrderCommentTime)").run();
lotOccupancyDB.prepare("create table if not exists WorkOrderComments (" +
"workOrderCommentId integer not null primary key autoincrement," +
" workOrderId integer not null," +
" workOrderCommentDate integer not null check (workOrderCommentDate > 0)," +
" workOrderCommentTime integer not null check (workOrderCommentTime >= 0)," +
" workOrderComment text not null," +
recordColumns + "," +
" foreign key (workOrderId) references WorkOrders (workOrderId)" +
")").run();
lotOccupancyDB.close();
lotOccupancyDB.prepare("create index if not exists idx_workordercomments_datetime" +
" on WorkOrderComments (workOrderId, workOrderCommentDate, workOrderCommentTime)").run();
return true;
}
lotOccupancyDB.close();
return false;
};
return true;
}
return false;
};

View File

@ -0,0 +1,7 @@
import type * as recordTypes from "../../types/recordTypes";
interface AddFeeCategoryForm {
feeCategory: string;
orderNumber?: number;
}
export declare const addFeeCategory: (feeCategoryForm: AddFeeCategoryForm, requestSession: recordTypes.PartialSession) => number;
export default addFeeCategory;

View File

@ -0,0 +1,16 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const addFeeCategory = (feeCategoryForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("insert into FeeCategories (" +
"feeCategory, orderNumber," +
" recordCreate_userName, recordCreate_timeMillis," +
" recordUpdate_userName, recordUpdate_timeMillis)" +
" values (?, ?, ?, ?, ?, ?)")
.run(feeCategoryForm.feeCategory, (feeCategoryForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
return result.lastInsertRowid;
};
export default addFeeCategory;

View File

@ -0,0 +1,39 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
interface AddFeeCategoryForm {
feeCategory: string;
orderNumber?: number;
}
export const addFeeCategory =
(feeCategoryForm: AddFeeCategoryForm, requestSession: recordTypes.PartialSession): number => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("insert into FeeCategories (" +
"feeCategory, orderNumber," +
" recordCreate_userName, recordCreate_timeMillis," +
" recordUpdate_userName, recordUpdate_timeMillis)" +
" values (?, ?, ?, ?, ?, ?)")
.run(feeCategoryForm.feeCategory,
(feeCategoryForm.orderNumber || 0),
requestSession.user.userName,
rightNowMillis,
requestSession.user.userName,
rightNowMillis);
database.close();
return result.lastInsertRowid as number;
};
export default addFeeCategory;

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { clearLotOccupantTypesCache } from "../functions.cache.js";
export const addLotOccupantType = (lotOccupantTypeForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
@ -11,6 +12,7 @@ export const addLotOccupantType = (lotOccupantTypeForm, requestSession) => {
" values (?, ?, ?, ?, ?, ?)")
.run(lotOccupantTypeForm.lotOccupantType, (lotOccupantTypeForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
clearLotOccupantTypesCache();
return result.lastInsertRowid;
};
export default addLotOccupantType;

View File

@ -5,6 +5,7 @@ import {
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
import { clearLotOccupantTypesCache } from "../functions.cache.js";
interface AddLotOccupantTypeForm {
@ -35,6 +36,8 @@ export const addLotOccupantType =
database.close();
clearLotOccupantTypesCache();
return result.lastInsertRowid as number;
};

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { clearLotStatusesCache } from "../functions.cache.js";
export const addLotStatus = (lotStatusForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
@ -11,6 +12,7 @@ export const addLotStatus = (lotStatusForm, requestSession) => {
" values (?, ?, ?, ?, ?, ?)")
.run(lotStatusForm.lotStatus, (lotStatusForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
clearLotStatusesCache();
return result.lastInsertRowid;
};
export default addLotStatus;

View File

@ -5,6 +5,7 @@ import {
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
import { clearLotStatusesCache } from "../functions.cache.js";
interface AddLotStatusForm {
@ -35,6 +36,8 @@ export const addLotStatus =
database.close();
clearLotStatusesCache();
return result.lastInsertRowid as number;
};

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { clearLotTypesCache } from "../functions.cache.js";
export const addLotType = (lotTypeForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
@ -11,6 +12,7 @@ export const addLotType = (lotTypeForm, requestSession) => {
" values (?, ?, ?, ?, ?, ?)")
.run(lotTypeForm.lotType, (lotTypeForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
clearLotTypesCache();
return result.lastInsertRowid;
};
export default addLotType;

View File

@ -5,6 +5,7 @@ import {
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
import { clearLotTypesCache } from "../functions.cache.js";
interface AddLotTypeForm {
@ -35,6 +36,8 @@ export const addLotType =
database.close();
clearLotTypesCache();
return result.lastInsertRowid as number;
};

View File

@ -1,5 +1,6 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { clearOccupancyTypesCache } from "../functions.cache.js";
export const addOccupancyType = (occupancyTypeForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
@ -11,6 +12,7 @@ export const addOccupancyType = (occupancyTypeForm, requestSession) => {
" values (?, ?, ?, ?, ?, ?)")
.run(occupancyTypeForm.occupancyType, (occupancyTypeForm.orderNumber || 0), requestSession.user.userName, rightNowMillis, requestSession.user.userName, rightNowMillis);
database.close();
clearOccupancyTypesCache();
return result.lastInsertRowid;
};
export default addOccupancyType;

View File

@ -5,6 +5,7 @@ import {
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
import { clearOccupancyTypesCache } from "../functions.cache.js";
interface AddOccupancyTypeForm {
@ -35,6 +36,8 @@ export const addOccupancyType =
database.close();
clearOccupancyTypesCache();
return result.lastInsertRowid as number;
};

View File

@ -0,0 +1,10 @@
import type * as recordTypes from "../../types/recordTypes";
interface GetFeeCategoriesFilters {
occupancyTypeId?: number | string;
lotTypeId?: number | string;
}
interface GetFeeCategoriesOptions {
includeFees?: boolean;
}
export declare const getFeeCategories: (filters?: GetFeeCategoriesFilters, options?: GetFeeCategoriesOptions) => recordTypes.FeeCategory[];
export default getFeeCategories;

View File

@ -0,0 +1,55 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const getFeeCategories = (filters, options) => {
const database = sqlite(databasePath, {
readonly: true
});
let sql = "select feeCategoryId, feeCategory" +
" from FeeCategories" +
" where recordDelete_timeMillis is null";
let sqlParameters = [];
if (filters.occupancyTypeId) {
sql += " and feeCategoryId in (" +
"select feeCategoryId from Fees" +
" where recordDelete_timeMillis is null" +
" and (occupancyTypeId is null or occupancyTypeId = ?))";
sqlParameters.push(filters.occupancyTypeId);
}
if (filters.lotTypeId) {
sql += " and feeCategoryId in (" +
"select feeCategoryId from Fees" +
" where recordDelete_timeMillis is null" +
" and (lotTypeId is null or lotTypeId = ?))";
sqlParameters.push(filters.lotTypeId);
}
const feeCategories = database.prepare(sql +
" 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) {
sqlParameters.push(feeCategory.feeCategoryId);
if (filters.occupancyTypeId) {
sql += " and (occupancyTypeId is null or occupancyTypeId = ?)";
sqlParameters.push(filters.occupancyTypeId);
}
if (filters.lotTypeId) {
sql += " and (lotTypeId is null or lotTypeId = ?)";
sqlParameters.push(filters.lotTypeId);
}
feeCategory.fees = database.prepare(sql +
" order by orderNumber, feeName")
.all(sqlParameters);
}
}
database.close();
return feeCategories;
};
export default getFeeCategories;

View File

@ -0,0 +1,94 @@
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
} from "../../data/databasePaths.js";
import type * as recordTypes from "../../types/recordTypes";
interface GetFeeCategoriesFilters {
occupancyTypeId ? : number | string;
lotTypeId ? : number | string;
}
interface GetFeeCategoriesOptions {
includeFees ? : boolean;
}
export const getFeeCategories = (filters ? : GetFeeCategoriesFilters, options ? : GetFeeCategoriesOptions): recordTypes.FeeCategory[] => {
const database = sqlite(databasePath, {
readonly: true
});
let sql = "select feeCategoryId, feeCategory" +
" from FeeCategories" +
" where recordDelete_timeMillis is null";
let sqlParameters = [];
if (filters.occupancyTypeId) {
sql += " and feeCategoryId in (" +
"select feeCategoryId from Fees" +
" where recordDelete_timeMillis is null" +
" and (occupancyTypeId is null or occupancyTypeId = ?))";
sqlParameters.push(filters.occupancyTypeId);
}
if (filters.lotTypeId) {
sql += " and feeCategoryId in (" +
"select feeCategoryId from Fees" +
" where recordDelete_timeMillis is null" +
" and (lotTypeId is null or lotTypeId = ?))";
sqlParameters.push(filters.lotTypeId);
}
const feeCategories: recordTypes.FeeCategory[] = database.prepare(sql +
" 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) {
sqlParameters.push(feeCategory.feeCategoryId);
if (filters.occupancyTypeId) {
sql += " and (occupancyTypeId is null or occupancyTypeId = ?)";
sqlParameters.push(filters.occupancyTypeId);
}
if (filters.lotTypeId) {
sql += " and (lotTypeId is null or lotTypeId = ?)";
sqlParameters.push(filters.lotTypeId);
}
feeCategory.fees = database.prepare(sql +
" order by orderNumber, feeName")
.all(sqlParameters);
}
}
database.close();
return feeCategories;
};
export default getFeeCategories;

View File

@ -1,6 +1,6 @@
import { dateToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js";
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
import { dateToInteger } from "@cityssm/expressjs-server-js/dateTimeFns.js";
export const getLots = (filters, options) => {
const database = sqlite(databasePath, {
readonly: true

View File

@ -1,11 +1,13 @@
import {
dateToInteger
} from "@cityssm/expressjs-server-js/dateTimeFns.js";
import sqlite from "better-sqlite3";
import {
lotOccupancyDB as databasePath
} from "../../data/databasePaths.js";
import {
dateToInteger
} from "@cityssm/expressjs-server-js/dateTimeFns.js";
import type * as recordTypes from "../../types/recordTypes";

View File

@ -0,0 +1,7 @@
import type * as recordTypes from "../../types/recordTypes";
interface UpdateFeeCategoryForm {
feeCategoryId: number | string;
feeCategory: string;
}
export declare const updateFeeCategory: (feeCategoryForm: UpdateFeeCategoryForm, requestSession: recordTypes.PartialSession) => boolean;
export default updateFeeCategory;

View File

@ -0,0 +1,17 @@
import sqlite from "better-sqlite3";
import { lotOccupancyDB as databasePath } from "../../data/databasePaths.js";
export const updateFeeCategory = (feeCategoryForm, requestSession) => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update FeeCategories" +
" set feeCategory = ?," +
" recordUpdate_userName = ?," +
" recordUpdate_timeMillis = ?" +
" where recordDelete_timeMillis is null" +
" and feeCategoryId = ?")
.run(feeCategoryForm.feeCategory, requestSession.user.userName, rightNowMillis, feeCategoryForm.feeCategoryId);
database.close();
return result.changes > 0;
};
export default updateFeeCategory;

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";
interface UpdateFeeCategoryForm {
feeCategoryId: number | string;
feeCategory: string;
}
export const updateFeeCategory =
(feeCategoryForm: UpdateFeeCategoryForm, requestSession: recordTypes.PartialSession): boolean => {
const database = sqlite(databasePath);
const rightNowMillis = Date.now();
const result = database
.prepare("update FeeCategories" +
" set feeCategory = ?," +
" recordUpdate_userName = ?," +
" recordUpdate_timeMillis = ?" +
" where recordDelete_timeMillis is null" +
" and feeCategoryId = ?")
.run(feeCategoryForm.feeCategory,
requestSession.user.userName,
rightNowMillis,
feeCategoryForm.feeCategoryId);
database.close();
return result.changes > 0;
};
export default updateFeeCategory;

160
package-lock.json generated
View File

@ -53,8 +53,8 @@
"@types/node-windows": "^0.1.2",
"@types/papaparse": "^5.3.3",
"@types/session-file-store": "^1.2.2",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"bulma": "^0.9.4",
"eslint": "^8.22.0",
"eslint-plugin-import": "^2.26.0",
@ -1147,14 +1147,14 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz",
"integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz",
"integrity": "sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/type-utils": "5.33.0",
"@typescript-eslint/utils": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/type-utils": "5.33.1",
"@typescript-eslint/utils": "5.33.1",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
@ -1180,14 +1180,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz",
"integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.1.tgz",
"integrity": "sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/typescript-estree": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/typescript-estree": "5.33.1",
"debug": "^4.3.4"
},
"engines": {
@ -1207,13 +1207,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz",
"integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz",
"integrity": "sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/visitor-keys": "5.33.0"
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/visitor-keys": "5.33.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1224,12 +1224,12 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz",
"integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz",
"integrity": "sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "5.33.0",
"@typescript-eslint/utils": "5.33.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -1250,9 +1250,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz",
"integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.1.tgz",
"integrity": "sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1263,13 +1263,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz",
"integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz",
"integrity": "sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/visitor-keys": "5.33.0",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/visitor-keys": "5.33.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -1290,15 +1290,15 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz",
"integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.1.tgz",
"integrity": "sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/typescript-estree": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/typescript-estree": "5.33.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@ -1314,12 +1314,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz",
"integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz",
"integrity": "sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/types": "5.33.1",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -11632,14 +11632,14 @@
}
},
"@typescript-eslint/eslint-plugin": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz",
"integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz",
"integrity": "sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/type-utils": "5.33.0",
"@typescript-eslint/utils": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/type-utils": "5.33.1",
"@typescript-eslint/utils": "5.33.1",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
@ -11649,52 +11649,52 @@
}
},
"@typescript-eslint/parser": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz",
"integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.1.tgz",
"integrity": "sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/typescript-estree": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/typescript-estree": "5.33.1",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz",
"integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz",
"integrity": "sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/visitor-keys": "5.33.0"
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/visitor-keys": "5.33.1"
}
},
"@typescript-eslint/type-utils": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz",
"integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz",
"integrity": "sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "5.33.0",
"@typescript-eslint/utils": "5.33.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz",
"integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.1.tgz",
"integrity": "sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz",
"integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz",
"integrity": "sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/visitor-keys": "5.33.0",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/visitor-keys": "5.33.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -11703,26 +11703,26 @@
}
},
"@typescript-eslint/utils": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz",
"integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.1.tgz",
"integrity": "sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.33.0",
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/typescript-estree": "5.33.0",
"@typescript-eslint/scope-manager": "5.33.1",
"@typescript-eslint/types": "5.33.1",
"@typescript-eslint/typescript-estree": "5.33.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.33.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz",
"integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==",
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz",
"integrity": "sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.33.0",
"@typescript-eslint/types": "5.33.1",
"eslint-visitor-keys": "^3.3.0"
}
},

View File

@ -74,8 +74,8 @@
"@types/node-windows": "^0.1.2",
"@types/papaparse": "^5.3.3",
"@types/session-file-store": "^1.2.2",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"bulma": "^0.9.4",
"eslint": "^8.22.0",
"eslint-plugin-import": "^2.26.0",

View File

@ -125,4 +125,14 @@ fieldset:enabled .is-hidden-enabled {
.select option:disabled {
display: none;
}
}
/*
* Modal Size Fix
*/
.modal-card {
max-width: 100%;
}

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,176 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
(() => {
const los = exports.los;
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const feeCategoriesContainerElement = document.querySelector("#container--feeCategories");
let feeCategories = exports.feeCategories;
const openEditFeeCategory = (clickEvent) => {
const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10);
const feeCategory = feeCategories.find((currentFeeCategory) => {
return currentFeeCategory.feeCategoryId === feeCategoryId;
});
let editCloseModalFunction;
const doUpdateFeeCategory = (submitEvent) => {
submitEvent.preventDefault();
cityssm.postJSON(urlPrefix + "/admin/doUpdateFeeCategory", submitEvent.currentTarget, (responseJSON) => {
if (responseJSON.success) {
feeCategories = responseJSON.feeCategories;
editCloseModalFunction();
renderFeeCategories();
}
else {
bulmaJS.alert({
title: "Error Updating Fee Category",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
};
cityssm.openHtmlModal("adminFees-editFeeCategory", {
onshow: (modalElement) => {
modalElement.querySelector("#feeCategoryEdit--feeCategoryId").value = feeCategory.feeCategoryId.toString();
modalElement.querySelector("#feeCategoryEdit--feeCategory").value = feeCategory.feeCategory;
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
editCloseModalFunction = closeModalFunction;
modalElement.querySelector("form").addEventListener("submit", doUpdateFeeCategory);
},
onremoved: () => {
bulmaJS.toggleHtmlClipped();
}
});
};
const openAddFee = (clickEvent) => {
const feeCategoryId = Number.parseInt(clickEvent.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId, 10);
let addCloseModalFunction;
const doAddFee = (submitEvent) => {
submitEvent.preventDefault();
};
cityssm.openHtmlModal("adminFees-addFee", {
onshow: (modalElement) => {
const feeCategoryElement = modalElement.querySelector("#feeAdd--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);
}
const occupancyTypeElement = modalElement.querySelector("#feeAdd--occupancyTypeId");
for (const occupancyType of exports.occupancyTypes) {
const optionElement = document.createElement("option");
optionElement.value = occupancyType.occupancyTypeId.toString();
optionElement.textContent = occupancyType.occupancyType;
occupancyTypeElement.append(optionElement);
}
const lotTypeElement = modalElement.querySelector("#feeAdd--lotTypeId");
for (const lotType of exports.lotTypes) {
const optionElement = document.createElement("option");
optionElement.value = lotType.lotTypeId.toString();
optionElement.textContent = lotType.lotType;
lotTypeElement.append(optionElement);
}
los.populateAliases(modalElement);
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
addCloseModalFunction = closeModalFunction;
modalElement.querySelector("form").addEventListener("submit", doAddFee);
},
onremoved: () => {
bulmaJS.toggleHtmlClipped();
}
});
};
const renderFeeCategories = () => {
if (feeCategories.length === 0) {
feeCategoriesContainerElement.innerHTML = "<div class=\"message is-warning\">" +
"<p class=\"message-body\">There are no available fees.</p>" +
"</div>";
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", "<div class=\"level is-mobile\">" +
("<div class=\"level-left\">" +
"<div class=\"level-item\">" +
"<h2 class=\"title is-4\">" + cityssm.escapeHTML(feeCategory.feeCategory) + "</h2>" +
"</div>" +
"</div>") +
("<div class=\"level-right\">" +
"<div class=\"level-item\">" +
"<button class=\"button is-small is-primary button--editFeeCategory\" type=\"button\">" +
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
"<span>Edit Category</span>" +
"</button>" +
"</div>" +
"<div class=\"level-item\">" +
"<button class=\"button is-small is-success 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>");
if (feeCategory.fees.length === 0) {
feeCategoryContainerElement.insertAdjacentHTML("beforeend", "<div class=\"message is-info\">" +
"<p class=\"message-body\">There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.</p>" +
"</div>");
}
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) => {
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();
}
});
});
})();

View File

@ -0,0 +1,260 @@
/* eslint-disable unicorn/prefer-module */
import type * as globalTypes from "../types/globalTypes";
import type * as recordTypes from "../types/recordTypes";
import type {
cityssmGlobal
} from "@cityssm/bulma-webapp-js/src/types";
import type {
BulmaJS
} from "@cityssm/bulma-js/types";
declare const cityssm: cityssmGlobal;
declare const bulmaJS: BulmaJS;
(() => {
const los = (exports.los as globalTypes.LOS);
const urlPrefix = document.querySelector("main").dataset.urlPrefix;
const feeCategoriesContainerElement = document.querySelector("#container--feeCategories") as HTMLElement;
let feeCategories: recordTypes.FeeCategory[] = exports.feeCategories;
const openEditFeeCategory = (clickEvent: Event) => {
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;
});
let editCloseModalFunction: () => void;
const doUpdateFeeCategory = (submitEvent: SubmitEvent) => {
submitEvent.preventDefault();
cityssm.postJSON(urlPrefix + "/admin/doUpdateFeeCategory",
submitEvent.currentTarget,
(responseJSON: {
success: boolean;
errorMessage ? : string;
feeCategories: recordTypes.FeeCategory[];
}) => {
if (responseJSON.success) {
feeCategories = responseJSON.feeCategories;
editCloseModalFunction();
renderFeeCategories();
} else {
bulmaJS.alert({
title: "Error Updating Fee Category",
message: responseJSON.errorMessage,
contextualColorName: "danger"
});
}
});
}
cityssm.openHtmlModal("adminFees-editFeeCategory", {
onshow: (modalElement) => {
(modalElement.querySelector("#feeCategoryEdit--feeCategoryId") as HTMLInputElement).value = feeCategory.feeCategoryId.toString();
(modalElement.querySelector("#feeCategoryEdit--feeCategory") as HTMLInputElement).value = feeCategory.feeCategory;
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
editCloseModalFunction = closeModalFunction;
modalElement.querySelector("form").addEventListener("submit", doUpdateFeeCategory);
},
onremoved: () => {
bulmaJS.toggleHtmlClipped();
}
});
};
const openAddFee = (clickEvent: Event) => {
const feeCategoryId = Number.parseInt(((clickEvent.currentTarget as HTMLElement).closest(".container--feeCategory") as HTMLElement).dataset.feeCategoryId, 10);
let addCloseModalFunction: () => void;
const doAddFee = (submitEvent: SubmitEvent) => {
submitEvent.preventDefault();
};
cityssm.openHtmlModal("adminFees-addFee", {
onshow: (modalElement) => {
const feeCategoryElement = modalElement.querySelector("#feeAdd--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);
}
const occupancyTypeElement = modalElement.querySelector("#feeAdd--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;
occupancyTypeElement.append(optionElement);
}
const lotTypeElement = modalElement.querySelector("#feeAdd--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;
lotTypeElement.append(optionElement);
}
los.populateAliases(modalElement);
},
onshown: (modalElement, closeModalFunction) => {
bulmaJS.toggleHtmlClipped();
addCloseModalFunction = closeModalFunction;
modalElement.querySelector("form").addEventListener("submit", doAddFee);
},
onremoved: () => {
bulmaJS.toggleHtmlClipped();
}
});
};
const renderFeeCategories = () => {
if (feeCategories.length === 0) {
feeCategoriesContainerElement.innerHTML = "<div class=\"message is-warning\">" +
"<p class=\"message-body\">There are no available fees.</p>" +
"</div>";
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",
"<div class=\"level is-mobile\">" +
("<div class=\"level-left\">" +
"<div class=\"level-item\">" +
"<h2 class=\"title is-4\">" + cityssm.escapeHTML(feeCategory.feeCategory) + "</h2>" +
"</div>" +
"</div>") +
("<div class=\"level-right\">" +
"<div class=\"level-item\">" +
"<button class=\"button is-small is-primary button--editFeeCategory\" type=\"button\">" +
"<span class=\"icon is-small\"><i class=\"fas fa-pencil-alt\" aria-hidden=\"true\"></i></span>" +
"<span>Edit Category</span>" +
"</button>" +
"</div>" +
"<div class=\"level-item\">" +
"<button class=\"button is-small is-success 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>");
if (feeCategory.fees.length === 0) {
feeCategoryContainerElement.insertAdjacentHTML("beforeend",
"<div class=\"message is-info\">" +
"<p class=\"message-body\">There are no fees in the \"" + cityssm.escapeHTML(feeCategory.feeCategory) + "\" category.</p>" +
"</div>");
} 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) => {
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();
}
});
});
})();

View File

@ -51,11 +51,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
case "lot":
aliasElement.textContent = exports.aliases.lot.toLowerCase();
break;
case "Occupancy":
aliasElement.textContent = exports.aliases.occupancy;
break;
case "occupancy":
aliasElement.textContent = exports.aliases.occupancy.toLowerCase();
break;
case "Occupant":
aliasElement.textContent = exports.aliases.occupant;
break;
case "occupant":
aliasElement.textContent = exports.aliases.lot.toLowerCase();
aliasElement.textContent = exports.aliases.occupant.toLowerCase();
break;
}
}

View File

@ -84,12 +84,20 @@ import type * as globalTypes from "../types/globalTypes";
aliasElement.textContent = exports.aliases.lot.toLowerCase();
break;
case "Occupancy":
aliasElement.textContent = exports.aliases.occupancy;
break;
case "occupancy":
aliasElement.textContent = exports.aliases.occupancy.toLowerCase();
break;
case "Occupant":
aliasElement.textContent = exports.aliases.occupant;
break;
case "occupant":
aliasElement.textContent = exports.aliases.lot.toLowerCase();
aliasElement.textContent = exports.aliases.occupant.toLowerCase();
break;
}
}

View File

@ -0,0 +1,115 @@
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card" style="width:900px">
<header class="modal-card-head">
<h3 class="modal-card-title">
Add Fee
</h3>
<button class="delete is-close-modal-button" aria-label="close" type="button"></button>
</header>
<section class="modal-card-body">
<form id="form--feeAdd">
<div class="field">
<label class="label" for="feeAdd--feeCategoryId">Fee Category</label>
<div class="control">
<div class="select is-fullwidth">
<select id="feeAdd--feeCategoryId" name="feeCategoryId" required></select>
</div>
</div>
</div>
<div class="field">
<label class="label" for="feeAdd--feeName">Fee</label>
<div class="control">
<input class="input" id="feeAdd--feeName" name="feeName" maxlength="100" required />
</div>
</div>
<div class="field">
<label class="label" for="feeAdd--feeDescription">Fee Description</label>
<div class="control">
<textarea class="textarea" id="feeAdd--feeDescription" name="feeDescription"></textarea>
</div>
</div>
<div class="columns">
<div class="column">
<div class="field">
<label class="label" for="feeAdd--occupancyTypeId"><span class="alias" data-alias="Occupancy"></span> Type Filter</label>
<div class="control">
<div class="select is-fullwidth">
<select id="feeAdd--occupancyTypeId" name="occupancyTypeId">
<option value="">(All Types)</option>
</select>
</div>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="feeAdd--lotTypeId"><span class="alias" data-alias="Lot"></span> Type Filter</label>
<div class="control">
<div class="select is-fullwidth">
<select id="feeAdd--lotTypeId" name="lotTypeId">
<option value="">(All Types)</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="field">
<label class="label" for="feeAdd--feeAmount">Fee Amount</label>
<div class="control has-icons-left">
<input class="input has-text-right" id="feeAdd--feeAmount" name="feeAmount" type="number" step="0.01" min="0" max="9999.99" value="0" onwheel="return false" />
<span class="icon is-small is-left">
<i class="fas fa-dollar-sign" aria-hidden="true"></i>
</span>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="feeAdd--feeFunction">Fee Function</label>
<div class="control">
<div class="select is-fullwidth">
<select id="feeAdd--feeFunction" name="feeFunction"></select>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="field">
<label class="label" for="feeAdd--taxAmount">Tax Amount</label>
<div class="control has-icons-left">
<input class="input has-text-right" id="feeAdd--taxAmount" name="taxAmount" type="number" step="0.01" min="0" max="9999.99" onwheel="return false" />
<span class="icon is-small is-left">
<i class="fas fa-dollar-sign" aria-hidden="true"></i>
</span>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="feeAdd--taxPercentage">Tax Percentage</label>
<div class="control has-icons-right">
<input class="input has-text-right" id="feeAdd--taxPercentage" name="taxPercentage" type="number" step="0.01" min="0" max="100" value="0" onwheel="return false" />
<span class="icon is-small is-right">
<i class="fas fa-percent" aria-hidden="true"></i>
</span>
</div>
</div>
</div>
</div>
</form>
</section>
<footer class="modal-card-foot justify-right">
<button class="button is-success" type="submit" form="form--feeAdd">
<span class="icon"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee</span>
</button>
<button class="button is-close-modal-button" type="button">Cancel</button>
</footer>
</div>
</div>

View File

@ -0,0 +1,28 @@
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<h3 class="modal-card-title">
Add Fee Category
</h3>
<button class="delete is-close-modal-button" aria-label="close" type="button"></button>
</header>
<section class="modal-card-body">
<form id="form--feeCategoryAdd">
<div class="field">
<label class="label" for="feeCategoryAdd--feeCategory">New Fee Category</label>
<div class="control">
<input class="input" id="feeCategoryAdd--feeCategory" name="feeCategory" maxlength="100" required />
</div>
</div>
</form>
</section>
<footer class="modal-card-foot justify-right">
<button class="button is-success" type="submit" form="form--feeCategoryAdd">
<span class="icon"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee Category</span>
</button>
<button class="button is-close-modal-button" type="button">Cancel</button>
</footer>
</div>
</div>

View File

@ -0,0 +1,29 @@
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<h3 class="modal-card-title">
Update Fee Category
</h3>
<button class="delete is-close-modal-button" aria-label="close" type="button"></button>
</header>
<section class="modal-card-body">
<form id="form--feeCategoryEdit">
<input id="feeCategoryEdit--feeCategoryId" name="feeCategoryId" type="hidden" />
<div class="field">
<label class="label" for="feeCategoryEdit--feeCategory">Fee Category</label>
<div class="control">
<input class="input" id="feeCategoryEdit--feeCategory" name="feeCategory" maxlength="100" required />
</div>
</div>
</form>
</section>
<footer class="modal-card-foot justify-right">
<button class="button is-success" type="submit" form="form--feeCategoryEdit">
<span class="icon"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Update Fee Category</span>
</button>
<button class="button is-close-modal-button" type="button">Cancel</button>
</footer>
</div>
</div>

View File

@ -0,0 +1 @@
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=exports.los,t=document.querySelector("main").dataset.urlPrefix,o=document.querySelector("#container--feeCategories");let s=exports.feeCategories;const a=e=>{const o=Number.parseInt(e.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId,10),a=s.find(e=>e.feeCategoryId===o);let r;const l=e=>{e.preventDefault(),cityssm.postJSON(t+"/admin/doUpdateFeeCategory",e.currentTarget,e=>{e.success?(s=e.feeCategories,r(),n()):bulmaJS.alert({title:"Error Updating Fee Category",message:e.errorMessage,contextualColorName:"danger"})})};cityssm.openHtmlModal("adminFees-editFeeCategory",{onshow:e=>{e.querySelector("#feeCategoryEdit--feeCategoryId").value=a.feeCategoryId.toString(),e.querySelector("#feeCategoryEdit--feeCategory").value=a.feeCategory},onshown:(e,t)=>{bulmaJS.toggleHtmlClipped(),r=t,e.querySelector("form").addEventListener("submit",l)},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})},r=t=>{const o=Number.parseInt(t.currentTarget.closest(".container--feeCategory").dataset.feeCategoryId,10);let a;const r=e=>{e.preventDefault()};cityssm.openHtmlModal("adminFees-addFee",{onshow:t=>{const a=t.querySelector("#feeAdd--feeCategoryId");for(const e of s){const t=document.createElement("option");t.value=e.feeCategoryId.toString(),t.textContent=e.feeCategory,e.feeCategoryId===o&&(t.selected=!0),a.append(t)}const r=t.querySelector("#feeAdd--occupancyTypeId");for(const e of exports.occupancyTypes){const t=document.createElement("option");t.value=e.occupancyTypeId.toString(),t.textContent=e.occupancyType,r.append(t)}const n=t.querySelector("#feeAdd--lotTypeId");for(const e of exports.lotTypes){const t=document.createElement("option");t.value=e.lotTypeId.toString(),t.textContent=e.lotType,n.append(t)}e.populateAliases(t)},onshown:(e,t)=>{bulmaJS.toggleHtmlClipped(),a=t,e.querySelector("form").addEventListener("submit",r)},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})},n=()=>{if(0===s.length)return void(o.innerHTML='<div class="message is-warning"><p class="message-body">There are no available fees.</p></div>');o.innerHTML="";for(const e of s){const t=document.createElement("section");if(t.className="container--feeCategory",t.dataset.feeCategoryId=e.feeCategoryId.toString(),t.insertAdjacentHTML("beforeend",'<div class="level is-mobile"><div class="level-left"><div class="level-item"><h2 class="title is-4">'+cityssm.escapeHTML(e.feeCategory)+'</h2></div></div><div class="level-right"><div class="level-item"><button class="button is-small is-primary button--editFeeCategory" type="button"><span class="icon is-small"><i class="fas fa-pencil-alt" aria-hidden="true"></i></span><span>Edit Category</span></button></div><div class="level-item"><button class="button is-small is-success 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>'),0===e.fees.length)t.insertAdjacentHTML("beforeend",'<div class="message is-info"><p class="message-body">There are no fees in the "'+cityssm.escapeHTML(e.feeCategory)+'" category.</p></div>');else{const e=document.createElement("div");e.className="panel",t.append(e)}o.append(t)}const e=o.querySelectorAll(".button--editFeeCategory");for(const t of e)t.addEventListener("click",a);const t=o.querySelectorAll(".button--addFee");for(const e of t)e.addEventListener("click",r)};n(),document.querySelector("#button--addFeeCategory").addEventListener("click",()=>{let e;const o=o=>{o.preventDefault(),cityssm.postJSON(t+"/admin/doAddFeeCategory",o.currentTarget,t=>{t.success?(s=t.feeCategories,e(),n()):bulmaJS.alert({title:"Error Creating Fee Category",message:t.errorMessage,contextualColorName:"danger"})})};cityssm.openHtmlModal("adminFees-addFeeCategory",{onshown:(t,s)=>{bulmaJS.toggleHtmlClipped(),t.querySelector("#feeCategoryAdd--feeCategory").focus(),e=s,t.querySelector("form").addEventListener("submit",o)},onremoved:()=>{bulmaJS.toggleHtmlClipped()}})})})();

View File

@ -1 +1 @@
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=e=>{const t=e.currentTarget.closest(".field").querySelector("input, select");if("INPUT"===t.tagName)t.disabled=!1;else{const e=t.querySelectorAll("option");for(const t of e)t.disabled=!1}t.focus()},t={highlightMap:(e,t,o)=>{let l,s=t;for(;!(l=e.querySelector("#"+s))&&s.includes("-");)s=s.slice(0,Math.max(0,s.lastIndexOf("-"))),console.log(s);if(l){l.style.fill=null,l.classList.add("highlight","is-"+o);const e=l.querySelectorAll("path");for(const t of e)t.style.fill=null}},initializeUnlockFieldButtons:t=>{const o=t.querySelectorAll(".is-unlock-field-button");for(const t of o)t.addEventListener("click",e)},populateAliases:e=>{const t=e.querySelectorAll(".alias");for(const e of t)switch(e.dataset.alias){case"Lot":e.textContent=exports.aliases.lot;break;case"lot":e.textContent=exports.aliases.lot.toLowerCase();break;case"Occupant":e.textContent=exports.aliases.occupant;break;case"occupant":e.textContent=exports.aliases.lot.toLowerCase()}}};exports.los=t})();
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),(()=>{const e=e=>{const t=e.currentTarget.closest(".field").querySelector("input, select");if("INPUT"===t.tagName)t.disabled=!1;else{const e=t.querySelectorAll("option");for(const t of e)t.disabled=!1}t.focus()},t={highlightMap:(e,t,o)=>{let s,l=t;for(;!(s=e.querySelector("#"+l))&&l.includes("-");)l=l.slice(0,Math.max(0,l.lastIndexOf("-"))),console.log(l);if(s){s.style.fill=null,s.classList.add("highlight","is-"+o);const e=s.querySelectorAll("path");for(const t of e)t.style.fill=null}},initializeUnlockFieldButtons:t=>{const o=t.querySelectorAll(".is-unlock-field-button");for(const t of o)t.addEventListener("click",e)},populateAliases:e=>{const t=e.querySelectorAll(".alias");for(const e of t)switch(e.dataset.alias){case"Lot":e.textContent=exports.aliases.lot;break;case"lot":e.textContent=exports.aliases.lot.toLowerCase();break;case"Occupancy":e.textContent=exports.aliases.occupancy;break;case"occupancy":e.textContent=exports.aliases.occupancy.toLowerCase();break;case"Occupant":e.textContent=exports.aliases.occupant;break;case"occupant":e.textContent=exports.aliases.occupant.toLowerCase()}}};exports.los=t})();

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,10 @@
import { Router } from "express";
import * as permissionHandlers from "../handlers/permissions.js";
import handler_licenceCategories from "../handlers/admin-get/licenceCategories.js";
import handler_fees from "../handlers/admin-get/fees.js";
import handler_doAddFeeCategory from "../handlers/admin-post/doAddFeeCategory.js";
import handler_doUpdateFeeCategory from "../handlers/admin-post/doUpdateFeeCategory.js";
export const router = Router();
router.get("/licenceCategories", permissionHandlers.adminGetHandler, handler_licenceCategories);
router.get("/fees", permissionHandlers.adminGetHandler, handler_fees);
router.post("/doAddFeeCategory", permissionHandlers.adminPostHandler, handler_doAddFeeCategory);
router.post("/doUpdateFeeCategory", permissionHandlers.adminPostHandler, handler_doUpdateFeeCategory);
export default router;

View File

@ -1,18 +1,30 @@
import { Router } from "express";
import {
Router
} from "express";
import * as permissionHandlers from "../handlers/permissions.js";
import handler_licenceCategories from "../handlers/admin-get/licenceCategories.js";
import handler_fees from "../handlers/admin-get/fees.js";
import handler_doAddFeeCategory from "../handlers/admin-post/doAddFeeCategory.js";
import handler_doUpdateFeeCategory from "../handlers/admin-post/doUpdateFeeCategory.js";
export const router = Router();
// Licence Categories
// Fees
router.get("/licenceCategories",
permissionHandlers.adminGetHandler,
handler_licenceCategories);
router.get("/fees",
permissionHandlers.adminGetHandler,
handler_fees);
router.post("/doAddFeeCategory",
permissionHandlers.adminPostHandler,
handler_doAddFeeCategory);
router.post("/doUpdateFeeCategory",
permissionHandlers.adminPostHandler,
handler_doUpdateFeeCategory);
export default router;
export default router;

View File

@ -4,10 +4,11 @@ import * as authenticationFunctions from "../helpers/functions.authentication.js
export const router = Router();
const getSafeRedirectURL = (possibleRedirectURL = "") => {
const urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix");
const urlToCheck = (possibleRedirectURL.startsWith(urlPrefix)
? possibleRedirectURL.slice(urlPrefix.length)
: possibleRedirectURL).toLowerCase();
const urlToCheck = (possibleRedirectURL.startsWith(urlPrefix) ?
possibleRedirectURL.slice(urlPrefix.length) :
possibleRedirectURL).toLowerCase();
switch (urlToCheck) {
case "/admin/fees":
case "/lotOccupancies":
case "/lots":
case "/maps":

View File

@ -1,4 +1,6 @@
import { Router } from "express";
import {
Router
} from "express";
import * as configFunctions from "../helpers/functions.config.js";
@ -11,102 +13,103 @@ export const router = Router();
const getSafeRedirectURL = (possibleRedirectURL = "") => {
const urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix");
const urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix");
const urlToCheck = (possibleRedirectURL.startsWith(urlPrefix)
? possibleRedirectURL.slice(urlPrefix.length)
: possibleRedirectURL).toLowerCase();
const urlToCheck = (possibleRedirectURL.startsWith(urlPrefix) ?
possibleRedirectURL.slice(urlPrefix.length) :
possibleRedirectURL).toLowerCase();
switch (urlToCheck) {
case "/lotOccupancies":
case "/lots":
case "/maps":
case "/workOrders":
case "/reports":
switch (urlToCheck) {
case "/admin/fees":
case "/lotOccupancies":
case "/lots":
case "/maps":
case "/workOrders":
case "/reports":
return urlPrefix + urlToCheck;
}
return urlPrefix + urlToCheck;
}
return urlPrefix + "/dashboard";
return urlPrefix + "/dashboard";
};
router.route("/")
.get((request, response) => {
.get((request, response) => {
const sessionCookieName = configFunctions.getProperty("session.cookieName");
const sessionCookieName = configFunctions.getProperty("session.cookieName");
if (request.session.user && request.cookies[sessionCookieName]) {
if (request.session.user && request.cookies[sessionCookieName]) {
const redirectURL = getSafeRedirectURL((request.query.redirect || "") as string);
const redirectURL = getSafeRedirectURL((request.query.redirect || "") as string);
response.redirect(redirectURL);
response.redirect(redirectURL);
} else {
} else {
response.render("login", {
userName: "",
message: "",
redirect: request.query.redirect
});
}
})
.post(async (request, response) => {
response.render("login", {
userName: "",
message: "",
redirect: request.query.redirect
});
}
})
.post(async (request, response) => {
const userName = request.body.userName as string;
const passwordPlain = request.body.password as string;
const userName = request.body.userName as string;
const passwordPlain = request.body.password as string;
const redirectURL = getSafeRedirectURL(request.body.redirect);
const redirectURL = getSafeRedirectURL(request.body.redirect);
const isAuthenticated = await authenticationFunctions.authenticate(userName, passwordPlain)
let userObject: recordTypes.User;
const isAuthenticated = await authenticationFunctions.authenticate(userName, passwordPlain)
let userObject: recordTypes.User;
if (isAuthenticated) {
if (isAuthenticated) {
const userNameLowerCase = userName.toLowerCase();
const userNameLowerCase = userName.toLowerCase();
const canLogin = configFunctions.getProperty("users.canLogin")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
const canLogin = configFunctions.getProperty("users.canLogin")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
if (canLogin) {
if (canLogin) {
const canUpdate = configFunctions.getProperty("users.canUpdate")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
const canUpdate = configFunctions.getProperty("users.canUpdate")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
const isAdmin = configFunctions.getProperty("users.isAdmin")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
const isAdmin = configFunctions.getProperty("users.isAdmin")
.some((currentUserName) => {
return userNameLowerCase === currentUserName.toLowerCase();
});
userObject = {
userName: userNameLowerCase,
userProperties: {
canUpdate,
isAdmin
}
};
}
}
userObject = {
userName: userNameLowerCase,
userProperties: {
canUpdate,
isAdmin
}
};
}
}
if (isAuthenticated && userObject) {
if (isAuthenticated && userObject) {
request.session.user = userObject;
request.session.user = userObject;
response.redirect(redirectURL);
response.redirect(redirectURL);
} else {
} else {
response.render("login", {
userName,
message: "Login Failed",
redirect: redirectURL
});
}
});
response.render("login", {
userName,
message: "Login Failed",
redirect: redirectURL
});
}
});
export default router;
export default router;

View File

@ -27,14 +27,15 @@ import * as permissionHandlers from "../handlers/permissions.js";
export const router = Router();
// Search
router.get("/",
handler_search);
router.post("/doSearchLotOccupancies",
handler_doSearchLotOccupancies);
// Create
router.get("/new",
permissionHandlers.updateGetHandler,
@ -48,10 +49,12 @@ router.post("/doCreateLotOccupancy",
permissionHandlers.updatePostHandler,
handler_doCreateLotOccupancy);
// View
router.get("/:lotOccupancyId",
handler_view);
// Edit
router.get("/:lotOccupancyId/edit",
permissionHandlers.updateGetHandler,
@ -61,6 +64,8 @@ router.post("/doUpdateLotOccupancy",
permissionHandlers.updatePostHandler,
handler_doUpdateLotOccupancy);
// Occupants
router.post("/doAddLotOccupancyOccupant",
permissionHandlers.updatePostHandler,
handler_doAddLotOccupancyOccupant);
@ -73,6 +78,8 @@ router.post("/doDeleteLotOccupancyOccupant",
permissionHandlers.updatePostHandler,
handler_doDeleteLotOccupancyOccupant);
// Comments
router.post("/doAddLotOccupancyComment",
permissionHandlers.updatePostHandler,
handler_doAddLotOccupancyComment);

View File

@ -97,18 +97,34 @@ export interface LotOccupantType extends Record {
lotOccupantType?: string;
orderNumber?: number;
}
export interface FeeCategory extends Record {
feeCategoryId?: number;
feeCategory?: string;
fees?: Fee[];
}
export interface Fee extends Record {
feeId?: number;
feeCategoryId?: number;
feeCategory?: string;
feeName?: string;
feeDescription?: string;
occupancyTypeId?: number;
lotTypeId?: number;
includeQuantity?: boolean;
quantityUnit?: string;
feeAmount?: number;
feeFunction?: string;
taxAmount?: number;
taxPercentage?: number;
isRequired?: boolean;
}
export interface LotOccupancyFee extends Fee, Record {
lotOccupancyId?: number;
feeId?: number;
feeName?: string;
quantity?: number;
feeAmount?: number;
taxAmount?: number;
}
export interface LotOccupancyTransaction extends Record {
lotOccupancyId?: number;

View File

@ -139,36 +139,60 @@ export interface LotOccupantType extends Record {
}
export interface FeeCategory extends Record {
feeCategoryId ? : number;
feeCategory ? : string;
fees ? : Fee[];
}
export interface Fee extends Record {
feeId?: number;
feeName?: string;
feeId ? : number;
occupancyTypeId?: number;
lotTypeId?: number;
feeCategoryId ? : number;
feeCategory ? : string;
feeAmount?: number;
feeFunction?: string;
feeName ? : string;
feeDescription ? : string;
isRequired?: boolean;
occupancyTypeId ? : number;
lotTypeId ? : number;
includeQuantity ? : boolean;
quantityUnit ? : string;
feeAmount ? : number;
feeFunction ? : string;
taxAmount ? : number;
taxPercentage ? : number;
isRequired ? : boolean;
}
export interface LotOccupancyFee extends Fee, Record {
lotOccupancyId?: number;
feeAmount?: number;
lotOccupancyId ? : number;
feeId ? : number;
feeName ? : string;
quantity ? : number;
feeAmount ? : number;
taxAmount ? : number;
}
export interface LotOccupancyTransaction extends Record {
lotOccupancyId?: number;
transactionIndex?: number;
transactionDate?: number;
transactionDateString?: string;
transactionTime?: number;
transactionTimeString?: string;
tranactionAmount?: number;
externalReceiptNumber?: string;
transactionNote?: string;
lotOccupancyId ? : number;
transactionIndex ? : number;
transactionDate ? : number;
transactionDateString ? : string;
transactionTime ? : number;
transactionTimeString ? : string;
tranactionAmount ? : number;
externalReceiptNumber ? : string;
transactionNote ? : string;
}
@ -204,9 +228,9 @@ export interface LotOccupancyComment extends Record {
export interface LotOccupancyField extends OccupancyTypeField, Record {
lotOccupancyId?: number;
occupancyTypeFieldId?: number;
lotOccupancyFieldValue?: string;
lotOccupancyId ? : number;
occupancyTypeFieldId ? : number;
lotOccupancyFieldValue ? : string;
}
@ -224,15 +248,15 @@ export interface LotOccupancy extends Record {
occupancyStartDate ? : number;
occupancyStartDateString ? : string;
occupancyEndDate ? : number;
occupancyEndDateString ? : string;
lotOccupancyFields? : LotOccupancyField[];
lotOccupancyFields ? : LotOccupancyField[];
lotOccupancyComments ? : LotOccupancyComment[];
lotOccupancyOccupants ? : LotOccupancyOccupant[];
lotOccupancyFees?: LotOccupancyFee[];
lotOccupancyTransactions?: LotOccupancyTransaction[];
lotOccupancyFees ? : LotOccupancyFee[];
lotOccupancyTransactions ? : LotOccupancyTransaction[];
}

View File

@ -0,0 +1,51 @@
<%- include('_header'); -%>
<nav class="breadcrumb">
<ul>
<li><a href="<%= urlPrefix %>/dashboard">Home</a></li>
<li>
<a href="#">
<span class="icon is-small"><i class="fas fa-cog" aria-hidden="true"></i></span>
<span>Administrator Tools</span>
</a>
</li>
<li class="is-active">
<a href="#" aria-current="page">
Fee Management
</a>
</li>
</ul>
</nav>
<div class="level is-mobile">
<div class="level-left">
<div class="level-item">
<h1 class="title is-1">
Fee Management
</h1>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-success" id="button--addFeeCategory" type="button">
<span class="icon is-small"><i class="fas fa-plus" aria-hidden="true"></i></span>
<span>Add Fee Category</span>
</button>
</div>
</div>
</div>
<div id="container--feeCategories"></div>
<%- include('_footerA'); -%>
<script>
exports.feeCategories = <%- JSON.stringify(feeCategories) %>;
exports.occupancyTypes = <%- JSON.stringify(occupancyTypes) %>;
exports.lotTypes = <%- JSON.stringify(lotTypes) %>;
</script>
<script src="<%= urlPrefix %>/javascripts/adminFees.min.js"></script>
<%- include('_footerB'); -%>

View File

@ -1,146 +1,154 @@
<%- include('_header'); -%>
<div class="level">
<div class="level-left has-flex-shrink-1">
<h1 class="title is-1">
<%= configFunctions.getProperty("application.applicationName") %>
</h1>
</div>
</div>
<h1 class="title is-1">
<%= configFunctions.getProperty("application.applicationName") %>
</h1>
<div class="columns">
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<span class="fa-layers fa-3x fa-fw" aria-hidden="true">
<i class="fas fa-vector-square"></i>
<i class="fas fa-user" data-fa-transform="shrink-10"></i>
</span>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/lotOccupancies"><%= configFunctions.getProperty("aliases.lot") %> <%= configFunctions.getProperty("aliases.occupancies") %></a>
</h2>
<p>View and maintain current and past <%= configFunctions.getProperty("aliases.lot").toLowerCase() %> <%= configFunctions.getProperty("aliases.occupancies").toLowerCase() %>.</p>
</div>
</div>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-hard-hat" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/workOrders">Work Orders</a>
</h2>
<p>View and maintain work orders.</p>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-vector-square" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/lots"><%= configFunctions.getProperty("aliases.lots") %></a>
</h2>
<p>View and maintain <%= configFunctions.getProperty("aliases.lots").toLowerCase() %> within a <%= configFunctions.getProperty("aliases.map").toLowerCase() %>.</p>
</div>
</div>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="far fa-3x fa-fw fa-map" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/maps"><%= configFunctions.getProperty("aliases.maps") %></a>
</h2>
<p>View and maintain <%= configFunctions.getProperty("aliases.maps").toLowerCase() %>.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-file" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/reports">Report Library</a>
</h2>
<p>Produce reports and export data.</p>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-question-circle" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="https://cityssm.github.io/lot-occupancy-system/" target="_blank" rel="noopener noreferrer">Help Documentation</a>
</h2>
<p>Instructions on how to use this application.</p>
</div>
</div>
</div>
<div class="card-footer">
<a class="card-footer-item has-tooltip-bottom" data-tooltip="Latest Updates, Issue Tracker, Say Hello" href="https://github.com/cityssm/lot-occupancy-system" target="_blank" rel="noreferrer">
<span class="icon">
<i class="fab fa-github" aria-hidden="true"></i>
</span>
GitHub
</a>
</div>
</div>
</div>
</div>
<% if (false && user.userProperties.isAdmin) { %>
<h2 class="title is-3">Administrator Tools</h2>
<div class="columns">
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-cogs" aria-hidden="true"></i>
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<span class="fa-layers fa-3x fa-fw" aria-hidden="true">
<i class="fas fa-vector-square"></i>
<i class="fas fa-user" data-fa-transform="shrink-10"></i>
</span>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/lotOccupancies"><%= configFunctions.getProperty("aliases.lot") %>
<%= configFunctions.getProperty("aliases.occupancies") %></a>
</h2>
<p>View and maintain current and past
<%= configFunctions.getProperty("aliases.lot").toLowerCase() %>
<%= configFunctions.getProperty("aliases.occupancies").toLowerCase() %>.</p>
</div>
</div>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/admin/licenceCategories">Licence Categories</a>
</h2>
<p>Add new licence types. Maintain existing licence types.</p>
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-hard-hat" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/workOrders">Work Orders</a>
</h2>
<p>View and maintain work orders.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-vector-square" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/lots"><%= configFunctions.getProperty("aliases.lots") %></a>
</h2>
<p>View and maintain <%= configFunctions.getProperty("aliases.lots").toLowerCase() %> within a
<%= configFunctions.getProperty("aliases.map").toLowerCase() %>.</p>
</div>
</div>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="far fa-3x fa-fw fa-map" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/maps"><%= configFunctions.getProperty("aliases.maps") %></a>
</h2>
<p>View and maintain <%= configFunctions.getProperty("aliases.maps").toLowerCase() %>.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-file" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/reports">Report Library</a>
</h2>
<p>Produce reports and export data.</p>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-question-circle" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="https://cityssm.github.io/lot-occupancy-system/" target="_blank"
rel="noopener noreferrer">Help Documentation</a>
</h2>
<p>Instructions on how to use this application.</p>
</div>
</div>
</div>
<div class="card-footer">
<a class="card-footer-item has-tooltip-bottom" data-tooltip="Latest Updates, Issue Tracker, Say Hello"
href="https://github.com/cityssm/lot-occupancy-system" target="_blank" rel="noreferrer">
<span class="icon">
<i class="fab fa-github" aria-hidden="true"></i>
</span>
GitHub
</a>
</div>
</div>
</div>
</div>
<% if (user.userProperties.isAdmin) { %>
<h2 class="title is-3">Administrator Tools</h2>
<div class="columns">
<div class="column">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<i class="fas fa-3x fa-fw fa-dollar-sign" aria-hidden="true"></i>
</div>
<div class="media-content has-text-black">
<h2 class="title is-4 is-marginless">
<a href="<%= urlPrefix %>/admin/fees">Fee Management</a>
</h2>
<p>
Manage fees for
<%= configFunctions.getProperty("aliases.lot").toLowerCase() %>
<%= configFunctions.getProperty("aliases.occupancy").toLowerCase() %>
and specific
<%= configFunctions.getProperty("aliases.lot").toLowerCase() %> types.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<% } %>
<%- include('_footerA'); -%>
<%- include('_footerB'); -%>
<%- include('_footerB'); -%>