For Stakeholders
Why this matters
Before this PR, a WhatsApp contact using a standard, opt-in WhatsApp privacy setting could send a clinic a message that Dentolize's backend would fail to store correctly (Argument 'phone' is missing), and even where a conversation somehow existed, a reply to that contact would silently fail to deliver. Both failure modes are invisible to the clinic — nothing in the UI would have surfaced "this message failed" — so the actual impact prior to this fix is not fully knowable from the code alone; it depends on how many real contacts hit this path in production.
Risk profile of the change
- Data model risk: the migration widens
OnlineConversation.phonefrom required to optional and adds a second unique index. It's additive and non-destructive — no existing data is rewritten, no existing row can violate the new constraint (a nullablebsuiddoesn't clash with itself; Postgres unique indexes ignoreNULLs). Low risk. - Behavioral risk in the conflict case: if a contact's phone and BSUID already resolve to two different existing conversation rows (plausible for conversations that predate this migration), the code deliberately does not merge them or update either — it silently prefers the phone-matched row and leaves the BSUID row untouched. This avoids crashing or corrupting data, but it means a data-quality issue (two rows for one contact) is preserved rather than fixed. See Identifying a Contact.
- Incomplete coverage: hidden-number contacts don't get auto-created as leads, don't support the duplicate-contact check, and can't receive online-payment links. None of these are silent failures anymore (each degrades explicitly/gracefully), but none were built out either — this PR fixes the crash and delivery paths, not full feature parity for hidden-number contacts.
- Bundled, unrelated fix:
whatsapp-official's environment-variable load order was also fixed in this branch (a boot-time crash unrelated to the BSUID work). Bundling it here means it ships with this change rather than independently, which is a minor scope-creep note for release tracking, not a risk.
What "done" looks like for this PR
Every code path that used to assume a phone number was present has been updated to handle its absence — inbound webhook parsing, outbound reply addressing (both the standalone whatsapp-official service and the main GraphQL server), patient/lead matching, and every UI surface that displays a conversation's address. The one deliberately deferred piece is merging conflicting historical rows, called out explicitly in the code and in Identifying a Contact as a separate future job.