mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
feat: scope notification to permission actions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { BookingLifecycleNotifierService } from './booking-lifecycle-notifier.service';
|
||||
import type { Booking } from './entities/booking.entity';
|
||||
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
||||
|
||||
/**
|
||||
* Who hears "Operations wants changes" depends on who owns the booking. A
|
||||
@@ -74,3 +75,39 @@ describe('BookingLifecycleNotifierService — operation changes requested', () =
|
||||
expect(inbox.notify.mock.calls[0][0].recipients).toEqual({ companyId: 'co-1' });
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Staff notifications used to go to every employee in every organization. They
|
||||
* now target a desk — and the two desks are disjoint: the GL presets hold no
|
||||
* bookings:view and no intake keys, so intake pings would be noise they cannot
|
||||
* act on. Both branches run through the same `inAppStaff` helper, which is the
|
||||
* easy place to lose the distinction again.
|
||||
*/
|
||||
describe('BookingLifecycleNotifierService — staff desk targeting', () => {
|
||||
const booking = () =>
|
||||
({ id: 'b-1', reference: 'BKG-0001', companyId: 'co-1' }) as Booking;
|
||||
|
||||
let inbox: { notify: jest.Mock };
|
||||
let service: BookingLifecycleNotifierService;
|
||||
|
||||
beforeEach(() => {
|
||||
inbox = { notify: jest.fn().mockResolvedValue(undefined) };
|
||||
service = new BookingLifecycleNotifierService(
|
||||
{ directSend: jest.fn().mockResolvedValue(undefined) } as never,
|
||||
inbox as never,
|
||||
{ query: jest.fn().mockResolvedValue([]) } as never,
|
||||
);
|
||||
});
|
||||
|
||||
it('routes intake items to the booking desk and clearance items to the clearance desk', () => {
|
||||
service.submittedToStaff(booking());
|
||||
service.clearanceDocsUploadedToStaff(booking());
|
||||
|
||||
expect(inbox.notify.mock.calls[0][0].recipients).toEqual({
|
||||
permissionKeys: [FREIGHT_PERMS.bookings.getNotification],
|
||||
});
|
||||
expect(inbox.notify.mock.calls[1][0].recipients).toEqual({
|
||||
permissionKeys: [FREIGHT_PERMS.bookings.clearanceGetNotification],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,16 @@ import { Booking } from './entities/booking.entity';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { NotificationInboxService } from '../notification-inbox/notification-inbox.service';
|
||||
import { resolveCompanyNotifyPhone } from '../notifications/resolve-company-phone.util';
|
||||
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
||||
|
||||
/**
|
||||
* Clearance items are worked by the GL desks, which hold no bookings:view and
|
||||
* no intake keys — so they take their own selector rather than the booking
|
||||
* desk's. Every override using this deep-links to a clearance page.
|
||||
*/
|
||||
const CLEARANCE_DESK = {
|
||||
permissionKeys: [FREIGHT_PERMS.bookings.clearanceGetNotification],
|
||||
};
|
||||
|
||||
/**
|
||||
* Customer + staff notifications for the booking lifecycle: review, clearance
|
||||
@@ -89,7 +99,11 @@ export class BookingLifecycleNotifierService {
|
||||
});
|
||||
}
|
||||
|
||||
/** Persist + push an in-app item to every backoffice staff user. */
|
||||
/**
|
||||
* Persist + push an in-app item to the booking desk — staff holding
|
||||
* `bookings:get_notification`. Callers whose item belongs to a different desk
|
||||
* override `recipients` (see {@link CLEARANCE_DESK}).
|
||||
*/
|
||||
private inAppStaff(
|
||||
b: Booking,
|
||||
title: string,
|
||||
@@ -97,7 +111,7 @@ export class BookingLifecycleNotifierService {
|
||||
overrides: Partial<NotifyInput> = {},
|
||||
): void {
|
||||
void this.inbox.notify({
|
||||
recipients: { allBackoffice: true },
|
||||
recipients: { permissionKeys: [FREIGHT_PERMS.bookings.getNotification] },
|
||||
audience: NotificationAudience.BACKOFFICE,
|
||||
type: NotificationType.REQUEST_SUBMITTED,
|
||||
title,
|
||||
@@ -274,6 +288,7 @@ export class BookingLifecycleNotifierService {
|
||||
`the customs declaration can be filed.${note ? ` Note: "${note}"` : ''}`;
|
||||
this.logger.log(`TRANSIT ASSIGNEE REQUESTED — ${this.ref(b)}`);
|
||||
this.inAppStaff(b, `Transit assignee needed — ${b.reference}`, msg, {
|
||||
recipients: CLEARANCE_DESK,
|
||||
type: NotificationType.CLEARANCE_REVIEW,
|
||||
link: `/dashboard/gl-djibouti/clearance/${b.id}`,
|
||||
});
|
||||
@@ -288,6 +303,7 @@ export class BookingLifecycleNotifierService {
|
||||
`The customs declaration can now be filed.`;
|
||||
this.logger.log(`TRANSIT ASSIGNEE ASSIGNED — ${this.ref(b)}`);
|
||||
this.inAppStaff(b, `Transit assignee set — ${b.reference}`, msg, {
|
||||
recipients: CLEARANCE_DESK,
|
||||
type: NotificationType.CLEARANCE_REVIEW,
|
||||
link: `/dashboard/bookings/${b.id}/clearance`,
|
||||
});
|
||||
@@ -395,6 +411,7 @@ export class BookingLifecycleNotifierService {
|
||||
'Clearance documents uploaded',
|
||||
`Customer uploaded clearance documents for booking ${this.ref(b)} — review them in the clearance queue.`,
|
||||
{
|
||||
recipients: CLEARANCE_DESK,
|
||||
type: NotificationType.CLEARANCE_REVIEW,
|
||||
link: `/dashboard/bookings/${b.id}/clearance`,
|
||||
},
|
||||
@@ -422,6 +439,7 @@ export class BookingLifecycleNotifierService {
|
||||
`The customer requested a change to the draft declaration on booking ${this.ref(b)}: ` +
|
||||
`"${note}". Send a corrected draft from the clearance page.`;
|
||||
this.inAppStaff(b, `Draft declaration change requested — ${this.ref(b)}`, msg, {
|
||||
recipients: CLEARANCE_DESK,
|
||||
type: NotificationType.CLEARANCE_REVIEW,
|
||||
link: `/dashboard/bookings/${b.id}/clearance`,
|
||||
});
|
||||
@@ -440,6 +458,7 @@ export class BookingLifecycleNotifierService {
|
||||
'Payment slip uploaded',
|
||||
`Customer uploaded the ${label} payment slip for booking ${this.ref(b)}.`,
|
||||
{
|
||||
recipients: CLEARANCE_DESK,
|
||||
type: NotificationType.PAYMENT_RECEIVED,
|
||||
link: `/dashboard/bookings/${b.id}/clearance`,
|
||||
},
|
||||
|
||||
@@ -1112,12 +1112,14 @@ export class BookingWagonCancellationService {
|
||||
|
||||
private notifyStaff(booking: Booking, title: string, body: string): void {
|
||||
void this.inbox.notify({
|
||||
recipients: { allBackoffice: true },
|
||||
recipients: { permissionKeys: [FREIGHT_PERMS.bookings.getNotification] },
|
||||
audience: NotificationAudience.BACKOFFICE,
|
||||
type: NotificationType.BOOKING_STATUS,
|
||||
title,
|
||||
body,
|
||||
link: `/bookings/${booking.id}`,
|
||||
// The portal path `/bookings/:id` used to be sent here, which 404s in the
|
||||
// dashboard. The staff view of these lives on the queue page.
|
||||
link: '/dashboard/wagon-cancellations',
|
||||
data: { bookingId: booking.id, reference: booking.reference },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user