@7365admin1/core 3.52.2-staging.234 → 3.52.2-staging.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/vehicle-anpr-revoke-safety.md +11 -0
- package/dist/handlebars/approve-drive-in.hbs +154 -67
- package/dist/handlebars/approve-walk-in.hbs +154 -67
- package/dist/index.d.ts +46 -6
- package/dist/index.js +211 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +211 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/anpr-revoke.test.mjs +81 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Vehicle management: make deleting and editing a vehicle safe against the ANPR cameras.
|
|
6
|
+
|
|
7
|
+
Deleting a vehicle no longer reports success when the plate is still on a camera. `bypass` now defaults to false, `removePlateNumber` returns an explicit per-camera outcome instead of swallowing its error, and a delete that cannot be confirmed on every camera holding the plate is refused with the record left intact so it can be retried. A site with no ANPR camera has nothing to revoke and now deletes successfully instead of failing with "No site cameras found."
|
|
8
|
+
|
|
9
|
+
Each camera is asked for its own record id before the removal, because a Dahua `recNo` is a per-device sequence number and the one stored on the vehicle only matches whichever camera was written last. Editing only the vehicle's name now removes the old whitelist record before inserting the new one, instead of leaving the previous entry on the camera for good.
|
|
10
|
+
|
|
11
|
+
Also: `pullVehicleByRecNo` is scoped to the person or site the plate belongs to (it matched on the record id alone, which is not unique across devices), the paginated camera lookups no longer skip their last page, and editing or deleting a vehicle now requires `vehicle-mgmt:update-vehicle` / `vehicle-mgmt:delete-vehicle` on the server, matching the gate the apps already apply in the browser.
|
|
@@ -1,79 +1,166 @@
|
|
|
1
|
-
|
|
1
|
+
{{! Visitor invitation email.
|
|
2
|
+
|
|
3
|
+
Email-client rules this file follows -- please keep them when editing:
|
|
4
|
+
* layout is <table>-based; flexbox and grid do not work in Outlook
|
|
5
|
+
* every style is inline; <style> blocks are stripped by Gmail
|
|
6
|
+
* no external CSS or web fonts
|
|
7
|
+
* `isInviteEmail` gates the resident-invite rows. The legacy
|
|
8
|
+
`residentAppInvitePreRegister` sender renders this same file from a
|
|
9
|
+
different document shape and sets none of those keys -- Handlebars
|
|
10
|
+
prints a missing key as an empty string, so without the guard that
|
|
11
|
+
email would grow a column of blank cells.
|
|
12
|
+
}}
|
|
13
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #eef1f4; margin: 0; padding: 24px 12px;">
|
|
2
14
|
<tr>
|
|
3
|
-
<td style="vertical-align:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<div style="display: flex; justify-content: center;">
|
|
7
|
-
<img src="{{logo}}" style="width: 190px; height: 50px;" />
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
<div style="margin-bottom: 16px; color: #052439; ">
|
|
11
|
-
<p style="color: #052439; font-weight: 600; font-size: 32px; line-height: 30px; text-align: center; padding: 16px;">Hey
|
|
12
|
-
{{name}},</p>
|
|
13
|
-
<p style="text-align: center;">You are now ready to check in upon arrival on {{arrivalDate}}.</p>
|
|
14
|
-
</div>
|
|
15
|
-
<a href="{{qrLink}}" style="text-decoration: none"><div
|
|
16
|
-
style="margin-top: 16px; padding: 5px; background-color: #4caf50; border-radius: 16px; text-align: center;"
|
|
17
|
-
>
|
|
18
|
-
<p style="color: white; font-weight: bold;">QR code Link (Click Here)</p>
|
|
19
|
-
</div></a>
|
|
15
|
+
<td align="center" style="vertical-align: top;">
|
|
16
|
+
|
|
17
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="width: 100%; max-width: 600px; background-color: #ffffff; border-radius: 14px; overflow: hidden; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
|
|
20
18
|
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
19
|
+
<tr>
|
|
20
|
+
<td align="center" style="background-color: #052439; padding: 22px 24px;">
|
|
21
|
+
<img src="{{logo}}" alt="Seven365" width="190" height="50" style="width: 190px; height: 50px; border: 0; display: block; color: #ffffff; font-family: Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 700;" />
|
|
22
|
+
</td>
|
|
23
|
+
</tr>
|
|
24
24
|
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
25
|
+
<tr>
|
|
26
|
+
<td style="padding: 32px 28px 8px 28px; text-align: center;">
|
|
27
|
+
<p style="margin: 0; color: #052439; font-weight: 700; font-size: 27px; line-height: 34px;">Hey {{name}},</p>
|
|
28
|
+
<p style="margin: 10px 0 0 0; color: #5b6b78; font-size: 15px; line-height: 22px;">You are now ready to check in upon arrival on <span style="color: #052439; font-weight: 600;">{{arrivalDate}}</span>.</p>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
28
31
|
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
<tr>
|
|
33
|
+
<td style="padding: 22px 28px 0 28px;">
|
|
34
|
+
<a href="{{qrLink}}" style="display: block; background-color: #35a745; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none; text-align: center; padding: 15px 20px; border-radius: 10px;">View My QR Code ( Click Here )</a>
|
|
35
|
+
{{#if statusLabel}}
|
|
36
|
+
{{! Whether the QR is usable yet. Sits under the button so it is read
|
|
37
|
+
before the tap, not after. `statusColor` is inlined from the
|
|
38
|
+
context because Handlebars has no helpers registered here. }}
|
|
39
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%;">
|
|
40
|
+
<tr>
|
|
41
|
+
<td align="center" style="padding: 14px 0 0 0;">
|
|
42
|
+
<span style="color: #7b8a95; font-size: 12px; letter-spacing: 0.4px; text-transform: uppercase; font-weight: 700;">QR Status</span>
|
|
43
|
+
<span style="display: inline-block; background-color: {{statusColor}}; color: #ffffff; font-size: 13px; font-weight: 700; padding: 5px 14px; border-radius: 999px; margin-left: 8px;">{{statusLabel}}</span>
|
|
44
|
+
</td>
|
|
35
45
|
</tr>
|
|
46
|
+
</table>
|
|
47
|
+
{{/if}}
|
|
48
|
+
<p style="margin: 12px 0 0 0; color: #7b8a95; font-size: 13px; line-height: 19px; text-align: center;">Please present the QR code at the pedestrian entrance for access.</p>
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
|
|
52
|
+
<tr>
|
|
53
|
+
<td style="padding: 26px 28px 0 28px;">
|
|
54
|
+
<p style="margin: 0 0 10px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Visitor Information</p>
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<td style="
|
|
56
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
|
|
57
|
+
<tr>
|
|
58
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Name</td>
|
|
59
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{name}}</td>
|
|
60
|
+
</tr>
|
|
61
|
+
{{#if isInviteEmail}}{{#if isContractor}}
|
|
62
|
+
<tr>
|
|
63
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Company</td>
|
|
64
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{company}}</td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contractor Type</td>
|
|
68
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contractorTypeLabel}}</td>
|
|
69
|
+
</tr>
|
|
70
|
+
{{/if}}{{/if}}
|
|
71
|
+
<tr>
|
|
72
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Tenant/Resident Address</td>
|
|
73
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{address}}</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr>
|
|
76
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contact No.</td>
|
|
77
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contactNumber}}</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Email</td>
|
|
81
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right; word-break: break-all;">{{email}}</td>
|
|
82
|
+
</tr>
|
|
83
|
+
{{#if isInviteEmail}}
|
|
84
|
+
<tr>
|
|
85
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Vehicle Plate No.</td>
|
|
86
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr>
|
|
89
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Overnight Parking</td>
|
|
90
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{overnightParking}}</td>
|
|
40
91
|
</tr>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<td style="
|
|
92
|
+
{{/if}}
|
|
93
|
+
<tr>
|
|
94
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Date</td>
|
|
95
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalDate}}</td>
|
|
44
96
|
</tr>
|
|
45
|
-
<tr
|
|
46
|
-
<td style="
|
|
47
|
-
<td style="
|
|
97
|
+
<tr>
|
|
98
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Time</td>
|
|
99
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalTime}}</td>
|
|
48
100
|
</tr>
|
|
101
|
+
<tr>
|
|
102
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Duration</td>
|
|
103
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{duration}}</td>
|
|
104
|
+
</tr>
|
|
105
|
+
{{#if isInviteEmail}}
|
|
106
|
+
<tr>
|
|
107
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Number of Passenger</td>
|
|
108
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{numberOfPassengers}}</td>
|
|
109
|
+
</tr>
|
|
110
|
+
{{/if}}
|
|
111
|
+
{{! Stacked rather than right-aligned: a purpose can run long, and a
|
|
112
|
+
wrapped right-aligned value reads as ragged noise. }}
|
|
113
|
+
<tr>
|
|
114
|
+
<td colspan="2" style="padding: 11px 0 0 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Purpose of Visit</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td colspan="2" style="padding: 3px 0 11px 0; color: #052439; font-size: 14px; font-weight: 600; line-height: 21px;">{{purposeOfVisit}}</td>
|
|
118
|
+
</tr>
|
|
119
|
+
</table>
|
|
120
|
+
</td>
|
|
121
|
+
</tr>
|
|
122
|
+
|
|
123
|
+
{{#if hasMembers}}
|
|
124
|
+
<tr>
|
|
125
|
+
<td style="padding: 26px 28px 0 28px;">
|
|
126
|
+
<p style="margin: 0 0 12px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Accompanying Members</p>
|
|
127
|
+
|
|
128
|
+
{{#each members}}
|
|
129
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; background-color: #f5f8fa; border: 1px solid #e7ecf0; border-radius: 10px; margin-bottom: 10px;">
|
|
130
|
+
<tr>
|
|
131
|
+
<td style="padding: 14px 16px;">
|
|
132
|
+
<p style="margin: 0 0 2px 0; color: #35a745; font-size: 11px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase;">Member {{position}}</p>
|
|
133
|
+
<p style="margin: 0 0 8px 0; color: #052439; font-size: 16px; font-weight: 700;">{{name}}</p>
|
|
134
|
+
|
|
135
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
|
|
136
|
+
<tr>
|
|
137
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">NRIC / Passport No.</td>
|
|
138
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{nric}}</td>
|
|
139
|
+
</tr>
|
|
140
|
+
<tr>
|
|
141
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Contact No.</td>
|
|
142
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{contact}}</td>
|
|
143
|
+
</tr>
|
|
144
|
+
<tr>
|
|
145
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Vehicle Plate No.</td>
|
|
146
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
|
|
147
|
+
</tr>
|
|
148
|
+
</table>
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
</table>
|
|
152
|
+
{{/each}}
|
|
153
|
+
</td>
|
|
154
|
+
</tr>
|
|
155
|
+
{{/if}}
|
|
156
|
+
|
|
157
|
+
<tr>
|
|
158
|
+
<td style="padding: 26px 28px 28px 28px;">
|
|
159
|
+
<p style="margin: 0; padding-top: 18px; border-top: 1px solid #e7ecf0; color: #90a0ab; font-size: 12px; line-height: 18px; text-align: center;">This invitation was sent to you by your host.<br />© Seven365 Pte. Ltd.</p>
|
|
160
|
+
</td>
|
|
161
|
+
</tr>
|
|
49
162
|
|
|
50
|
-
|
|
51
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Date</td>
|
|
52
|
-
<td style="text-align: right; padding: 8px;">{{arrivalDate}}</td>
|
|
53
|
-
</tr>
|
|
54
|
-
<tr style="border-bottom: 1px solid gray; padding: 20px;">
|
|
55
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Time</td>
|
|
56
|
-
<td style="text-align: right; padding: 8px;">{{arrivalTime}}</td>
|
|
57
|
-
</tr>
|
|
58
|
-
<tr style="border-bottom: 1px solid gray; padding: 20px;">
|
|
59
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Duration</td>
|
|
60
|
-
<td style="text-align: right; padding: 8px;">{{duration}}</td>
|
|
61
|
-
</tr>
|
|
62
|
-
<tr style=" padding: 20px;">
|
|
63
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Purpose of Visit</td>
|
|
64
|
-
</tr>
|
|
65
|
-
<tr style="padding: 20px;"><td style="text-align: right; padding: 8px;">{{purposeOfVisit}}</td></tr>
|
|
66
|
-
</tbody>
|
|
67
|
-
</table>
|
|
68
|
-
</div>
|
|
69
|
-
<footer height="70" style="padding: 0; bottom: 0; left: 0; right: 0; z-index: 10; background-color: #fff;">
|
|
70
|
-
<div style="padding: 12px; max-width: 600px; margin-left: auto; margin-right: auto;">
|
|
71
|
-
<div style="text-align: center;">
|
|
72
|
-
<span style="font-size: 16px;">© 2024 powered by Seven365 Pte. Ltd. v.1</span>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</footer>
|
|
76
|
-
</div>
|
|
163
|
+
</table>
|
|
77
164
|
</td>
|
|
78
165
|
</tr>
|
|
79
|
-
</table>
|
|
166
|
+
</table>
|
|
@@ -1,79 +1,166 @@
|
|
|
1
|
-
|
|
1
|
+
{{! Visitor invitation email.
|
|
2
|
+
|
|
3
|
+
Email-client rules this file follows -- please keep them when editing:
|
|
4
|
+
* layout is <table>-based; flexbox and grid do not work in Outlook
|
|
5
|
+
* every style is inline; <style> blocks are stripped by Gmail
|
|
6
|
+
* no external CSS or web fonts
|
|
7
|
+
* `isInviteEmail` gates the resident-invite rows. The legacy
|
|
8
|
+
`residentAppInvitePreRegister` sender renders this same file from a
|
|
9
|
+
different document shape and sets none of those keys -- Handlebars
|
|
10
|
+
prints a missing key as an empty string, so without the guard that
|
|
11
|
+
email would grow a column of blank cells.
|
|
12
|
+
}}
|
|
13
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #eef1f4; margin: 0; padding: 24px 12px;">
|
|
2
14
|
<tr>
|
|
3
|
-
<td style="vertical-align:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<div style="display: flex; justify-content: center;">
|
|
7
|
-
<img src="{{logo}}" style="width: 190px; height: 50px;" />
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
<div style="margin-bottom: 16px; color: #052439; ">
|
|
11
|
-
<p style="color: #052439; font-weight: 600; font-size: 32px; line-height: 30px; text-align: center; padding: 16px;">Hey
|
|
12
|
-
{{name}},</p>
|
|
13
|
-
<p style="text-align: center;">You are now ready to check in upon arrival on {{arrivalDate}}.</p>
|
|
14
|
-
</div>
|
|
15
|
-
<a href="{{qrLink}}" style="text-decoration: none"><div
|
|
16
|
-
style="margin-top: 16px; padding: 5px; background-color: #4caf50; border-radius: 16px; text-align: center;"
|
|
17
|
-
>
|
|
18
|
-
<p style="color: white; font-weight: bold;">QR code Link (Click Here)</p>
|
|
19
|
-
</div></a>
|
|
15
|
+
<td align="center" style="vertical-align: top;">
|
|
16
|
+
|
|
17
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="width: 100%; max-width: 600px; background-color: #ffffff; border-radius: 14px; overflow: hidden; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
|
|
20
18
|
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
19
|
+
<tr>
|
|
20
|
+
<td align="center" style="background-color: #052439; padding: 22px 24px;">
|
|
21
|
+
<img src="{{logo}}" alt="Seven365" width="190" height="50" style="width: 190px; height: 50px; border: 0; display: block; color: #ffffff; font-family: Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 700;" />
|
|
22
|
+
</td>
|
|
23
|
+
</tr>
|
|
24
24
|
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
25
|
+
<tr>
|
|
26
|
+
<td style="padding: 32px 28px 8px 28px; text-align: center;">
|
|
27
|
+
<p style="margin: 0; color: #052439; font-weight: 700; font-size: 27px; line-height: 34px;">Hey {{name}},</p>
|
|
28
|
+
<p style="margin: 10px 0 0 0; color: #5b6b78; font-size: 15px; line-height: 22px;">You are now ready to check in upon arrival on <span style="color: #052439; font-weight: 600;">{{arrivalDate}}</span>.</p>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
28
31
|
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
<tr>
|
|
33
|
+
<td style="padding: 22px 28px 0 28px;">
|
|
34
|
+
<a href="{{qrLink}}" style="display: block; background-color: #35a745; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none; text-align: center; padding: 15px 20px; border-radius: 10px;">View My QR Code ( Click Here )</a>
|
|
35
|
+
{{#if statusLabel}}
|
|
36
|
+
{{! Whether the QR is usable yet. Sits under the button so it is read
|
|
37
|
+
before the tap, not after. `statusColor` is inlined from the
|
|
38
|
+
context because Handlebars has no helpers registered here. }}
|
|
39
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%;">
|
|
40
|
+
<tr>
|
|
41
|
+
<td align="center" style="padding: 14px 0 0 0;">
|
|
42
|
+
<span style="color: #7b8a95; font-size: 12px; letter-spacing: 0.4px; text-transform: uppercase; font-weight: 700;">QR Status</span>
|
|
43
|
+
<span style="display: inline-block; background-color: {{statusColor}}; color: #ffffff; font-size: 13px; font-weight: 700; padding: 5px 14px; border-radius: 999px; margin-left: 8px;">{{statusLabel}}</span>
|
|
44
|
+
</td>
|
|
35
45
|
</tr>
|
|
46
|
+
</table>
|
|
47
|
+
{{/if}}
|
|
48
|
+
<p style="margin: 12px 0 0 0; color: #7b8a95; font-size: 13px; line-height: 19px; text-align: center;">Please present the QR code at the pedestrian entrance for access.</p>
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
|
|
52
|
+
<tr>
|
|
53
|
+
<td style="padding: 26px 28px 0 28px;">
|
|
54
|
+
<p style="margin: 0 0 10px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Visitor Information</p>
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<td style="
|
|
56
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
|
|
57
|
+
<tr>
|
|
58
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Name</td>
|
|
59
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{name}}</td>
|
|
60
|
+
</tr>
|
|
61
|
+
{{#if isInviteEmail}}{{#if isContractor}}
|
|
62
|
+
<tr>
|
|
63
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Company</td>
|
|
64
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{company}}</td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contractor Type</td>
|
|
68
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contractorTypeLabel}}</td>
|
|
69
|
+
</tr>
|
|
70
|
+
{{/if}}{{/if}}
|
|
71
|
+
<tr>
|
|
72
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Tenant/Resident Address</td>
|
|
73
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{address}}</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr>
|
|
76
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contact No.</td>
|
|
77
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contactNumber}}</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Email</td>
|
|
81
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right; word-break: break-all;">{{email}}</td>
|
|
82
|
+
</tr>
|
|
83
|
+
{{#if isInviteEmail}}
|
|
84
|
+
<tr>
|
|
85
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Vehicle Plate No.</td>
|
|
86
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr>
|
|
89
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Overnight Parking</td>
|
|
90
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{overnightParking}}</td>
|
|
40
91
|
</tr>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<td style="
|
|
92
|
+
{{/if}}
|
|
93
|
+
<tr>
|
|
94
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Date</td>
|
|
95
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalDate}}</td>
|
|
44
96
|
</tr>
|
|
45
|
-
<tr
|
|
46
|
-
<td style="
|
|
47
|
-
<td style="
|
|
97
|
+
<tr>
|
|
98
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Time</td>
|
|
99
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalTime}}</td>
|
|
48
100
|
</tr>
|
|
101
|
+
<tr>
|
|
102
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Duration</td>
|
|
103
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{duration}}</td>
|
|
104
|
+
</tr>
|
|
105
|
+
{{#if isInviteEmail}}
|
|
106
|
+
<tr>
|
|
107
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Number of Passenger</td>
|
|
108
|
+
<td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{numberOfPassengers}}</td>
|
|
109
|
+
</tr>
|
|
110
|
+
{{/if}}
|
|
111
|
+
{{! Stacked rather than right-aligned: a purpose can run long, and a
|
|
112
|
+
wrapped right-aligned value reads as ragged noise. }}
|
|
113
|
+
<tr>
|
|
114
|
+
<td colspan="2" style="padding: 11px 0 0 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Purpose of Visit</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td colspan="2" style="padding: 3px 0 11px 0; color: #052439; font-size: 14px; font-weight: 600; line-height: 21px;">{{purposeOfVisit}}</td>
|
|
118
|
+
</tr>
|
|
119
|
+
</table>
|
|
120
|
+
</td>
|
|
121
|
+
</tr>
|
|
122
|
+
|
|
123
|
+
{{#if hasMembers}}
|
|
124
|
+
<tr>
|
|
125
|
+
<td style="padding: 26px 28px 0 28px;">
|
|
126
|
+
<p style="margin: 0 0 12px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Accompanying Members</p>
|
|
127
|
+
|
|
128
|
+
{{#each members}}
|
|
129
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; background-color: #f5f8fa; border: 1px solid #e7ecf0; border-radius: 10px; margin-bottom: 10px;">
|
|
130
|
+
<tr>
|
|
131
|
+
<td style="padding: 14px 16px;">
|
|
132
|
+
<p style="margin: 0 0 2px 0; color: #35a745; font-size: 11px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase;">Member {{position}}</p>
|
|
133
|
+
<p style="margin: 0 0 8px 0; color: #052439; font-size: 16px; font-weight: 700;">{{name}}</p>
|
|
134
|
+
|
|
135
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
|
|
136
|
+
<tr>
|
|
137
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">NRIC / Passport No.</td>
|
|
138
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{nric}}</td>
|
|
139
|
+
</tr>
|
|
140
|
+
<tr>
|
|
141
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Contact No.</td>
|
|
142
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{contact}}</td>
|
|
143
|
+
</tr>
|
|
144
|
+
<tr>
|
|
145
|
+
<td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Vehicle Plate No.</td>
|
|
146
|
+
<td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
|
|
147
|
+
</tr>
|
|
148
|
+
</table>
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
</table>
|
|
152
|
+
{{/each}}
|
|
153
|
+
</td>
|
|
154
|
+
</tr>
|
|
155
|
+
{{/if}}
|
|
156
|
+
|
|
157
|
+
<tr>
|
|
158
|
+
<td style="padding: 26px 28px 28px 28px;">
|
|
159
|
+
<p style="margin: 0; padding-top: 18px; border-top: 1px solid #e7ecf0; color: #90a0ab; font-size: 12px; line-height: 18px; text-align: center;">This invitation was sent to you by your host.<br />© Seven365 Pte. Ltd.</p>
|
|
160
|
+
</td>
|
|
161
|
+
</tr>
|
|
49
162
|
|
|
50
|
-
|
|
51
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Date</td>
|
|
52
|
-
<td style="text-align: right; padding: 8px;">{{arrivalDate}}</td>
|
|
53
|
-
</tr>
|
|
54
|
-
<tr style="border-bottom: 1px solid gray; padding: 20px;">
|
|
55
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Time</td>
|
|
56
|
-
<td style="text-align: right; padding: 8px;">{{arrivalTime}}</td>
|
|
57
|
-
</tr>
|
|
58
|
-
<tr style="border-bottom: 1px solid gray; padding: 20px;">
|
|
59
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Duration</td>
|
|
60
|
-
<td style="text-align: right; padding: 8px;">{{duration}}</td>
|
|
61
|
-
</tr>
|
|
62
|
-
<tr style=" padding: 20px;">
|
|
63
|
-
<td style="text-align: left; font-weight: bold; padding: 8px;">Purpose of Visit</td>
|
|
64
|
-
</tr>
|
|
65
|
-
<tr style="padding: 20px;"><td style="text-align: right; padding: 8px;">{{purposeOfVisit}}</td></tr>
|
|
66
|
-
</tbody>
|
|
67
|
-
</table>
|
|
68
|
-
</div>
|
|
69
|
-
<footer height="70" style="padding: 0; bottom: 0; left: 0; right: 0; z-index: 10; background-color: #fff;">
|
|
70
|
-
<div style="padding: 12px; max-width: 600px; margin-left: auto; margin-right: auto;">
|
|
71
|
-
<div style="text-align: center;">
|
|
72
|
-
<span style="font-size: 16px;">© 2024 powered by Seven365 Pte. Ltd. v.1</span>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</footer>
|
|
76
|
-
</div>
|
|
163
|
+
</table>
|
|
77
164
|
</td>
|
|
78
165
|
</tr>
|
|
79
|
-
</table>
|
|
166
|
+
</table>
|
package/dist/index.d.ts
CHANGED
|
@@ -2505,6 +2505,31 @@ declare function useBuildingUnitController(): {
|
|
|
2505
2505
|
getAllLevelsWithUnits: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
2506
2506
|
};
|
|
2507
2507
|
|
|
2508
|
+
/**
|
|
2509
|
+
* "Was this plate actually taken off the camera?" — the one place that answers
|
|
2510
|
+
* it, for the three paths that revoke an ANPR whitelist/blocklist record:
|
|
2511
|
+
* deleting a vehicle, and the two branches of editing one.
|
|
2512
|
+
*
|
|
2513
|
+
* The reason this file exists: `removePlateNumber` used to `catch`, log, and
|
|
2514
|
+
* return `undefined`, so a device that refused, timed out or was unreachable
|
|
2515
|
+
* came back looking exactly like one that had removed the record. The delete
|
|
2516
|
+
* path then reported "Vehicle deleted successfully." while the plate was still
|
|
2517
|
+
* on the barrier — the vehicle could keep driving in after the record was gone
|
|
2518
|
+
* from the system, and there was nothing on screen to say so.
|
|
2519
|
+
*
|
|
2520
|
+
* Nothing here talks to a device or to the database, so it is testable on its
|
|
2521
|
+
* own — see `test/anpr-revoke.test.mjs`.
|
|
2522
|
+
*/
|
|
2523
|
+
/** One camera's answer to "remove this record". */
|
|
2524
|
+
type AnprRevokeOutcome = {
|
|
2525
|
+
/** True ONLY when the device confirmed the removal, or held nothing to remove. */
|
|
2526
|
+
ok: boolean;
|
|
2527
|
+
/** HTTP status the device answered with, when it answered at all. */
|
|
2528
|
+
statusCode?: number;
|
|
2529
|
+
/** Why it failed, in words an operator can act on. Empty when `ok`. */
|
|
2530
|
+
reason: string;
|
|
2531
|
+
};
|
|
2532
|
+
|
|
2508
2533
|
declare enum CameraType {
|
|
2509
2534
|
IP = "ip",
|
|
2510
2535
|
ANPR = "anpr"
|
|
@@ -2573,7 +2598,7 @@ declare function useDahuaService(): {
|
|
|
2573
2598
|
password: string;
|
|
2574
2599
|
mode: string;
|
|
2575
2600
|
recno: string;
|
|
2576
|
-
}) => Promise<
|
|
2601
|
+
}) => Promise<AnprRevokeOutcome>;
|
|
2577
2602
|
updatePlateNumber: (value: {
|
|
2578
2603
|
host: string;
|
|
2579
2604
|
username: string;
|
|
@@ -2592,6 +2617,18 @@ declare function useDahuaService(): {
|
|
|
2592
2617
|
password: string;
|
|
2593
2618
|
mode: string;
|
|
2594
2619
|
plateNumber: string;
|
|
2620
|
+
/**
|
|
2621
|
+
* Treat a device that did not answer 200 as an error rather than as "the
|
|
2622
|
+
* plate is not on this camera".
|
|
2623
|
+
*
|
|
2624
|
+
* `useDahuaDigest` does not throw on a bad status, so a 401 or a 500 comes
|
|
2625
|
+
* back as an empty body, which `parseDahuaFind` reads as `found=0`. For the
|
|
2626
|
+
* visitor check-in callers that has always meant "not blocklisted" and is
|
|
2627
|
+
* left exactly as it is — a camera fault must not stop a visitor at the
|
|
2628
|
+
* gate. For the vehicle revoke paths it would mean "nothing to remove", and
|
|
2629
|
+
* they must not conclude that from a camera that never answered.
|
|
2630
|
+
*/
|
|
2631
|
+
requireOk?: boolean;
|
|
2595
2632
|
}) => Promise<any>;
|
|
2596
2633
|
bulkInsertPlateNumber: (value: {
|
|
2597
2634
|
host: string;
|
|
@@ -4109,7 +4146,7 @@ declare function useCameraViewService(): {
|
|
|
4109
4146
|
userId?: string;
|
|
4110
4147
|
}) => Promise<{
|
|
4111
4148
|
reason: string;
|
|
4112
|
-
verified: "recorder-video-port"
|
|
4149
|
+
verified: "nothing" | "recorder-video-port";
|
|
4113
4150
|
verifiedDescription: string;
|
|
4114
4151
|
cameraVerified: null;
|
|
4115
4152
|
relayVerified: null;
|
|
@@ -4123,7 +4160,7 @@ declare function useCameraViewService(): {
|
|
|
4123
4160
|
driftSeconds: null;
|
|
4124
4161
|
detailUnavailableReason: string | null;
|
|
4125
4162
|
} | {
|
|
4126
|
-
verified: "recorder-video-port"
|
|
4163
|
+
verified: "nothing" | "recorder-video-port";
|
|
4127
4164
|
verifiedDescription: string;
|
|
4128
4165
|
cameraVerified: null;
|
|
4129
4166
|
relayVerified: null;
|
|
@@ -4166,7 +4203,7 @@ declare function useCameraViewService(): {
|
|
|
4166
4203
|
}) => Promise<{
|
|
4167
4204
|
cameras: ({
|
|
4168
4205
|
reason: string;
|
|
4169
|
-
verified: "recorder-video-port"
|
|
4206
|
+
verified: "nothing" | "recorder-video-port";
|
|
4170
4207
|
verifiedDescription: string;
|
|
4171
4208
|
cameraVerified: null;
|
|
4172
4209
|
relayVerified: null;
|
|
@@ -4180,7 +4217,7 @@ declare function useCameraViewService(): {
|
|
|
4180
4217
|
lastFrameAt: string | null;
|
|
4181
4218
|
unavailableReason: string | null;
|
|
4182
4219
|
} | {
|
|
4183
|
-
verified: "recorder-video-port"
|
|
4220
|
+
verified: "nothing" | "recorder-video-port";
|
|
4184
4221
|
verifiedDescription: string;
|
|
4185
4222
|
cameraVerified: null;
|
|
4186
4223
|
relayVerified: null;
|
|
@@ -5011,7 +5048,10 @@ declare function usePersonRepo(): {
|
|
|
5011
5048
|
plateNumber: string;
|
|
5012
5049
|
recNo: string;
|
|
5013
5050
|
}, session?: ClientSession) => Promise<void>;
|
|
5014
|
-
pullVehicleByRecNo: (recNo: string,
|
|
5051
|
+
pullVehicleByRecNo: (recNo: string, scope: {
|
|
5052
|
+
peopleId?: string | ObjectId;
|
|
5053
|
+
site?: string | ObjectId;
|
|
5054
|
+
}, session?: ClientSession) => Promise<void>;
|
|
5015
5055
|
getByUserId: (userId: string | ObjectId) => Promise<TPerson | null>;
|
|
5016
5056
|
reviewResidentPerson: (_id: ObjectId | string, value: Partial<TPerson>, session?: ClientSession) => Promise<mongodb.UpdateResult<TPerson>>;
|
|
5017
5057
|
addFromVisitor: (value: visitorPersonRepo, session?: ClientSession) => Promise<mongodb.InsertOneResult<TPerson>>;
|