A reusable in-game notification primitive for firms, so the various "tell the firm something happened" features share one code path instead of each hand-rolling message.send(getOnlineEmployees(...), ...).
Goal — a FirmNotificationService (Guice singleton) with a small surface:
notifyFirm(int firmId, String messageKey, Object... placeholders) → sends to all currently-online firm members (proprietor + current employees) via the existing FirmStaffService.getOnlineEmployees.notifyFirmExcept(int firmId, UUID actor, ...) → same, but skips the actor (so the person who triggered the event isn't notified of their own action).Why a service, not inline sends — multiple features want this (incoming transfers, employee join/leave, promotion/demotion, proprietorship change, account changes). Centralising gives one place for: recipient resolution, an optional per-player mute/toggle later, and a consistent business.notify.* message namespace.
Technical notes — The recipient primitive already exists: FirmStaffService.getOnlineEmployees(firm) returns online staff + proprietor (used today by disband). Message.send(Collection<Player>, key, …) is already used. Add the service + impl under services/, bind in BusinessModule, add a business.notify.* message block. No Treasury changes — this only covers events Business itself originates; truly external incoming transfers remain blocked on PAR-75 (per-transfer event).
Consumers (built on this):
This release: ship the service + PAR-56 on top of it.
Shipped on
develop(commit08b8cbb).FirmNotificationService(+FirmNotificationServiceImpl, bound inBusinessModule):notifyFirm(firmId, key, …placeholders)— fans out to the firm's online members (proprietor + current employees) viaFirmStaffService.getOnlineEmployees.notifyFirmExcept(firmId, excluded, …)— same, skipping the actor so they aren't pinged for their own action.Delivery is best-effort — any failure (e.g. firm resolution throwing) is logged at debug and swallowed, so a notification can never break the triggering operation. New
business.notify.*message namespace.First consumer is PAR-56 (incoming-transfer notices). Siblings PAR-95 (employee join), PAR-96 (departure), PAR-97 (promote/demote) build on this. Tested: fan-out, actor exclusion, empty-recipient no-op, best-effort swallow. Coverage gate green.