@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.
@@ -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
- <table style="height: 100%; width: 100%">
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: middle; text-align: center">
4
- <div style="padding: 24px; justify-content: center;">
5
- <div style="background-color: #052439; elevation: 2;">
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
- <div style="margin-top: 16px; text-align: center; color: #052439; ">
22
- <p>Please present the QR code sent to you at the pedestrian entrance for access.</p>
23
- </div>
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
- <div style="margin-top: 16px; padding: 5px; background-color: #052439; border-radius: 16px; text-align: center;">
26
- <p style="color: white; font-weight: bold;">Visitor Information</p>
27
- </div>
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
- <div style="margin-top: 16px; max-width:100%; margin-left: auto; margin-right: auto; margin-bottom: 50px">
30
- <table style="color: #052439; font-size: 16px; margin: 0 auto; border-collapse: collapse; width: 100%;">
31
- <tbody>
32
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
33
- <td style="text-align: left; font-weight: bold; padding: 8px;">Name</td>
34
- <td style="text-align: right; padding: 8px;">{{name}}</td>
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
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
38
- <td style="text-align: left; font-weight: bold; padding: 8px;">Tenant/Resident Address</td>
39
- <td style="text-align: right; padding: 8px;">{{address}}</td>
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
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
42
- <td style="text-align: left; font-weight: bold; padding: 8px;">Contact No.</td>
43
- <td style="text-align: right; padding: 8px;">{{contactNumber}}</td>
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 style="border-bottom: 1px solid gray; padding: 20px;">
46
- <td style="text-align: left; font-weight: bold; padding: 8px;">Email</td>
47
- <td style="text-align: right; padding: 8px;">{{email}}</td>
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 />&copy; Seven365 Pte. Ltd.</p>
160
+ </td>
161
+ </tr>
49
162
 
50
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
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
- <table style="height: 100%; width: 100%">
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: middle; text-align: center">
4
- <div style="padding: 24px; justify-content: center;">
5
- <div style="background-color: #052439; elevation: 2;">
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
- <div style="margin-top: 16px; text-align: center; color: #052439; ">
22
- <p>Please present the QR code sent to you at the pedestrian entrance for access.</p>
23
- </div>
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
- <div style="margin-top: 16px; padding: 5px; background-color: #052439; border-radius: 16px; text-align: center;">
26
- <p style="color: white; font-weight: bold;">Visitor Information</p>
27
- </div>
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
- <div style="margin-top: 16px; max-width:100%; margin-left: auto; margin-right: auto; margin-bottom: 50px">
30
- <table style="color: #052439; font-size: 16px; margin: 0 auto; border-collapse: collapse; width: 100%;">
31
- <tbody>
32
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
33
- <td style="text-align: left; font-weight: bold; padding: 8px;">Name</td>
34
- <td style="text-align: right; padding: 8px;">{{name}}</td>
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
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
38
- <td style="text-align: left; font-weight: bold; padding: 8px;">Tenant/Resident Address</td>
39
- <td style="text-align: right; padding: 8px;">{{address}}</td>
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
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
42
- <td style="text-align: left; font-weight: bold; padding: 8px;">Contact No.</td>
43
- <td style="text-align: right; padding: 8px;">{{contactNumber}}</td>
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 style="border-bottom: 1px solid gray; padding: 20px;">
46
- <td style="text-align: left; font-weight: bold; padding: 8px;">Email</td>
47
- <td style="text-align: right; padding: 8px;">{{email}}</td>
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 />&copy; Seven365 Pte. Ltd.</p>
160
+ </td>
161
+ </tr>
49
162
 
50
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
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<urllib.HttpClientResponse<any> | undefined>;
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" | "nothing";
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" | "nothing";
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" | "nothing";
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" | "nothing";
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, session?: ClientSession) => Promise<void>;
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>>;