Integrate Distribution Lists with Campaign recipients
This commit is contained in:
@@ -12,6 +12,11 @@ import {
|
||||
xlsxSheetsFromArrayBuffer,
|
||||
type RecipientColumnMapping
|
||||
} from "../src/features/campaigns/utils/bulkImport";
|
||||
import {
|
||||
distributionListDrift,
|
||||
materializeDistributionListExpansion,
|
||||
type DistributionListExpansionSnapshot
|
||||
} from "../src/features/campaigns/utils/distributionListImport";
|
||||
|
||||
function assert(condition: unknown, message = "assertion failed"): void {
|
||||
if (!condition) throw new Error(message);
|
||||
@@ -130,6 +135,108 @@ assert(attachments.length === 2, "valid row patterns become attachment rules");
|
||||
assert(attachments[0].base_path_id === "bp-1" && attachments[0].base_dir === "invoices", "attachment rules use the selected source");
|
||||
assert(attachments[0].file_filter === "${customer_id}.pdf", "field placeholders remain in imported patterns");
|
||||
|
||||
const distributionExpansion: DistributionListExpansionSnapshot = {
|
||||
source: {
|
||||
id: "list-1",
|
||||
tenant_id: "tenant-1",
|
||||
name: "Residents",
|
||||
revision_id: "revision-3",
|
||||
revision: 3,
|
||||
definition_hash: "definition-hash-3",
|
||||
definition_kind: "static",
|
||||
status: "active",
|
||||
entry_count: 2,
|
||||
read_only: false,
|
||||
stale: false,
|
||||
parameters: [],
|
||||
provenance: {},
|
||||
metadata: {}
|
||||
},
|
||||
request: { requested_channels: ["email", "postal"] },
|
||||
recipients: [
|
||||
{
|
||||
recipient_key: "contact:ada",
|
||||
display_name: "Ada Lovelace",
|
||||
status: "usable",
|
||||
channels: [
|
||||
{
|
||||
channel: "email",
|
||||
target: "ada@example.org",
|
||||
target_key: "email:ada@example.org",
|
||||
status: "usable",
|
||||
preferred: true,
|
||||
decision_provenance: { policy: "allow" }
|
||||
},
|
||||
{
|
||||
channel: "postal",
|
||||
target: "Example Street 1",
|
||||
target_key: "postal:ada",
|
||||
status: "usable",
|
||||
preferred: false,
|
||||
decision_provenance: { policy: "fallback" }
|
||||
}
|
||||
],
|
||||
source_entry_ids: ["entry-addresses"],
|
||||
explanations: [],
|
||||
attributes: { district: "north" },
|
||||
provenance: { provider: "addresses" }
|
||||
},
|
||||
{
|
||||
recipient_key: "contact:postal",
|
||||
display_name: "Postal only",
|
||||
status: "usable",
|
||||
channels: [
|
||||
{
|
||||
channel: "postal",
|
||||
target: "Example Street 2",
|
||||
target_key: "postal:only",
|
||||
status: "usable",
|
||||
preferred: true,
|
||||
decision_provenance: { policy: "allow" }
|
||||
}
|
||||
],
|
||||
source_entry_ids: ["entry-postal"],
|
||||
explanations: [],
|
||||
attributes: {},
|
||||
provenance: {}
|
||||
}
|
||||
],
|
||||
excluded: [
|
||||
{
|
||||
recipient_key: "contact:suppressed",
|
||||
display_name: "Suppressed",
|
||||
status: "suppressed",
|
||||
channels: [],
|
||||
source_entry_ids: ["entry-addresses"],
|
||||
explanations: [{ code: "opt_out", message: "Recipient opted out.", severity: "warning", provenance: {} }],
|
||||
attributes: {},
|
||||
provenance: {}
|
||||
}
|
||||
],
|
||||
diagnostics: [],
|
||||
provider_evidence: [],
|
||||
expansion_hash: "expansion-hash-3",
|
||||
generated_at: "2026-08-02T10:00:00Z",
|
||||
snapshot_id: "snapshot-3",
|
||||
stale: false,
|
||||
truncated: false
|
||||
};
|
||||
const distributionDraft = materializeDistributionListExpansion(draft, distributionExpansion, "replace");
|
||||
const distributionEntries = asRecord(distributionDraft.entries);
|
||||
const distributionInline = distributionEntries.inline as Record<string, unknown>[];
|
||||
const firstDistributionSource = asRecord(distributionInline[0].distribution_source);
|
||||
const secondDistributionSource = asRecord(distributionInline[1].distribution_source);
|
||||
const distributionImports = distributionEntries.imports as Record<string, unknown>[];
|
||||
|
||||
assert(distributionInline.length === 2, "all included Distribution List recipients are frozen into Campaign");
|
||||
assert(distributionInline[0].active === true && distributionInline[0].email === "ada@example.org", "one preferred route is selected without duplicating channels");
|
||||
assert(distributionInline[1].active === false, "postal-only recipients are retained but not sent through the mail-only path");
|
||||
assert(firstDistributionSource.snapshot_id === "snapshot-3" && firstDistributionSource.list_revision_id === "revision-3", "recipient rows retain snapshot and list revision evidence");
|
||||
assert((secondDistributionSource.source_entry_ids as string[])[0] === "entry-postal", "recipient rows retain source entry references");
|
||||
assert(distributionImports[0].source_type === "distribution_list", "Campaign stores Distribution List import provenance");
|
||||
assert((asRecord(distributionImports[0].source_provenance).exclusions as unknown[]).length === 1, "excluded recipients remain in immutable import evidence");
|
||||
assert(distributionListDrift(distributionEntries.imports, [{ id: "list-1", revision: 4, revision_id: "revision-4", definition_hash: "definition-hash-4" }]).length === 1, "list revision drift is detected without changing frozen recipients");
|
||||
|
||||
void runXlsxImportAssertions();
|
||||
|
||||
async function runXlsxImportAssertions(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user