feat(attendance): add notification delivery and NFC security foundation
This commit is contained in:
@@ -6,6 +6,7 @@ import { recordGeofenceIncident } from './attendance-monitoring.js';
|
||||
import { distanceMeters, resolveEffectiveClockInPolicy } from './clock-in-policy.js';
|
||||
import { uploadLocationBatch } from './location-log-storage.js';
|
||||
import { enqueueHubManagerAlert, enqueueUserAlert } from './notification-outbox.js';
|
||||
import { registerPushToken, unregisterPushToken } from './notification-device-tokens.js';
|
||||
import {
|
||||
cancelOrder as cancelOrderCommand,
|
||||
clockIn as clockInCommand,
|
||||
@@ -1614,8 +1615,13 @@ export async function staffClockIn(actor, payload) {
|
||||
accuracyMeters: payload.accuracyMeters,
|
||||
capturedAt: payload.capturedAt,
|
||||
overrideReason: payload.overrideReason || null,
|
||||
proofNonce: payload.proofNonce || null,
|
||||
proofTimestamp: payload.proofTimestamp || null,
|
||||
attestationProvider: payload.attestationProvider || null,
|
||||
attestationToken: payload.attestationToken || null,
|
||||
rawPayload: {
|
||||
notes: payload.notes || null,
|
||||
isMockLocation: payload.isMockLocation ?? null,
|
||||
...(payload.rawPayload || {}),
|
||||
},
|
||||
});
|
||||
@@ -1640,15 +1646,116 @@ export async function staffClockOut(actor, payload) {
|
||||
accuracyMeters: payload.accuracyMeters,
|
||||
capturedAt: payload.capturedAt,
|
||||
overrideReason: payload.overrideReason || null,
|
||||
proofNonce: payload.proofNonce || null,
|
||||
proofTimestamp: payload.proofTimestamp || null,
|
||||
attestationProvider: payload.attestationProvider || null,
|
||||
attestationToken: payload.attestationToken || null,
|
||||
rawPayload: {
|
||||
notes: payload.notes || null,
|
||||
breakMinutes: payload.breakMinutes ?? null,
|
||||
applicationId: payload.applicationId || null,
|
||||
isMockLocation: payload.isMockLocation ?? null,
|
||||
...(payload.rawPayload || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function registerClientPushToken(actor, payload) {
|
||||
const context = await requireClientContext(actor.uid);
|
||||
return withTransaction(async (client) => {
|
||||
await ensureActorUser(client, actor);
|
||||
const token = await registerPushToken(client, {
|
||||
tenantId: context.tenant.tenantId,
|
||||
userId: actor.uid,
|
||||
businessMembershipId: context.business.membershipId,
|
||||
provider: payload.provider,
|
||||
platform: payload.platform,
|
||||
pushToken: payload.pushToken,
|
||||
deviceId: payload.deviceId || null,
|
||||
appVersion: payload.appVersion || null,
|
||||
appBuild: payload.appBuild || null,
|
||||
locale: payload.locale || null,
|
||||
timezone: payload.timezone || null,
|
||||
notificationsEnabled: payload.notificationsEnabled ?? true,
|
||||
metadata: payload.metadata || {},
|
||||
});
|
||||
|
||||
return {
|
||||
tokenId: token.id,
|
||||
provider: token.provider,
|
||||
platform: token.platform,
|
||||
notificationsEnabled: token.notificationsEnabled,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function unregisterClientPushToken(actor, payload) {
|
||||
const context = await requireClientContext(actor.uid);
|
||||
return withTransaction(async (client) => {
|
||||
await ensureActorUser(client, actor);
|
||||
const removed = await unregisterPushToken(client, {
|
||||
tenantId: context.tenant.tenantId,
|
||||
userId: actor.uid,
|
||||
tokenId: payload.tokenId || null,
|
||||
pushToken: payload.pushToken || null,
|
||||
reason: payload.reason || 'CLIENT_SIGN_OUT',
|
||||
});
|
||||
|
||||
return {
|
||||
removedCount: removed.length,
|
||||
removed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function registerStaffPushToken(actor, payload) {
|
||||
const context = await requireStaffContext(actor.uid);
|
||||
return withTransaction(async (client) => {
|
||||
await ensureActorUser(client, actor);
|
||||
const token = await registerPushToken(client, {
|
||||
tenantId: context.tenant.tenantId,
|
||||
userId: actor.uid,
|
||||
staffId: context.staff.staffId,
|
||||
provider: payload.provider,
|
||||
platform: payload.platform,
|
||||
pushToken: payload.pushToken,
|
||||
deviceId: payload.deviceId || null,
|
||||
appVersion: payload.appVersion || null,
|
||||
appBuild: payload.appBuild || null,
|
||||
locale: payload.locale || null,
|
||||
timezone: payload.timezone || null,
|
||||
notificationsEnabled: payload.notificationsEnabled ?? true,
|
||||
metadata: payload.metadata || {},
|
||||
});
|
||||
|
||||
return {
|
||||
tokenId: token.id,
|
||||
provider: token.provider,
|
||||
platform: token.platform,
|
||||
notificationsEnabled: token.notificationsEnabled,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function unregisterStaffPushToken(actor, payload) {
|
||||
const context = await requireStaffContext(actor.uid);
|
||||
return withTransaction(async (client) => {
|
||||
await ensureActorUser(client, actor);
|
||||
const removed = await unregisterPushToken(client, {
|
||||
tenantId: context.tenant.tenantId,
|
||||
userId: actor.uid,
|
||||
tokenId: payload.tokenId || null,
|
||||
pushToken: payload.pushToken || null,
|
||||
reason: payload.reason || 'STAFF_SIGN_OUT',
|
||||
});
|
||||
|
||||
return {
|
||||
removedCount: removed.length,
|
||||
removed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function summarizeLocationPoints(points, assignment) {
|
||||
let outOfGeofenceCount = 0;
|
||||
let missingCoordinateCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user