@aglyn/plugins-crm 1.0.0-beta.232 → 1.0.0-beta.234
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/components/companies-section.js +43 -8
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +3 -1
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +6 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contacts-section.js +37 -8
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-email-check.d.ts +14 -0
- package/src/lib/components/crm-email-check.js +38 -1
- package/src/lib/components/crm-email-check.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.d.ts +5 -4
- package/src/lib/components/crm-next-activity-column.js +5 -4
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +3 -1
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-section.js +50 -6
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +9 -3
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/leads-section.js +61 -11
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/tasks-section.js +61 -9
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/company-filters.js +40 -2
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +10 -1
- package/src/lib/constants/contact-filters.js +62 -2
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.js +78 -0
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
- package/src/lib/hooks/use-crm-column-sort.js +156 -0
- package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
- package/src/lib/hooks/use-crm-tasks.js +10 -8
- package/src/lib/hooks/use-crm-tasks.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +6 -0
- package/src/lib/model/crm-list-query.js +13 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +19 -2
- package/src/lib/model/lead-filters.js +112 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/native-vocabulary.d.ts +41 -0
- package/src/lib/model/native-vocabulary.js +59 -0
- package/src/lib/model/native-vocabulary.js.map +1 -0
- package/src/lib/model/task-views.d.ts +15 -2
- package/src/lib/model/task-views.js +50 -4
- package/src/lib/model/task-views.js.map +1 -1
- package/src/lib/server/email-check.d.ts +4 -1
- package/src/lib/server/email-check.js +18 -6
- package/src/lib/server/email-check.js.map +1 -1
- package/src/lib/transfer/tasks.js +2 -1
- package/src/lib/transfer/tasks.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/task-views.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type CrmTask,\n type CrmTaskKind,\n type CrmTaskPriority,\n type TaskDueState,\n taskDueState,\n} from '@aglyn/aglyn'\nimport type { ListFilterField } from '@aglyn/shared-util-tools/list-query/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQuerySort,\n} from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport { CRM_LIST_SEARCH, crmSelectField } from './crm-list-query'\nimport type { CrmRoutes } from './crm-routes'\n\n/**\n * The tasks section's views and the query each one is (AGL-2599).\n *\n * Pure: what a view MEANS is written here once, as a plan the hook turns into\n * Firestore constraints and a spec can read back without Firestore. \"Overdue\"\n * and \"today\" are not stored on the task — nothing runs at midnight to stamp\n * them — so each view is a window over `dueAtMs` computed at read time from\n * the reader's own clock, and the same day boundaries {@link taskDueState}\n * paints a row with.\n */\n\nexport type CrmTaskView =\n | 'mine'\n | 'overdue'\n | 'today'\n | 'upcoming'\n | 'open'\n | 'done'\n\nexport const CRM_TASK_VIEWS: ReadonlyArray<{ id: CrmTaskView; label: string }> =\n [\n { id: 'mine', label: 'My tasks' },\n { id: 'overdue', label: 'Overdue' },\n { id: 'today', label: 'Today' },\n { id: 'upcoming', label: 'Upcoming' },\n { id: 'open', label: 'All open' },\n { id: 'done', label: 'Done' },\n ]\n\nexport const CRM_TASK_KINDS: readonly CrmTaskKind[] = [\n 'call',\n 'email',\n 'meeting',\n 'todo',\n]\n\nexport const CRM_TASK_KIND_LABELS: Record<CrmTaskKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\nexport const CRM_TASK_PRIORITIES: readonly CrmTaskPriority[] = [\n 'low',\n 'normal',\n 'high',\n]\n\nexport const CRM_TASK_PRIORITY_LABELS: Record<CrmTaskPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n}\n\n\n/** Midnight at the START of the local calendar day `nowMs` falls in. */\nexport function startOfLocalDay(nowMs: number): number {\n const date = new Date(nowMs)\n return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n}\n\n/**\n * Midnight at the start of the NEXT local day — built from the calendar\n * rather than by adding 24 hours, because a day that a clock change falls in\n * is 23 or 25 hours long and a task due at 00:30 on that morning belongs to\n * it either way.\n */\nexport function startOfNextLocalDay(nowMs: number): number {\n const date = new Date(nowMs)\n return new Date(\n date.getFullYear(),\n date.getMonth(),\n date.getDate() + 1,\n ).getTime()\n}\n\n/**\n * One view as constraints, in the vocabulary of the `crmTasks` indexes.\n *\n * `assigneeUid` narrows to the reader and rides the\n * `(visibleTo, assigneeUid, status, dueAtMs)` index; every other view rides\n * `(visibleTo, status, dueAtMs)`. `dueFrom` is inclusive and `dueBefore`\n * exclusive so that two adjacent windows share a boundary and no task falls\n * between \"today\" and \"upcoming\". A task with no due date has `dueAtMs:\n * null`, which a range excludes and a bare order sorts first, so it appears\n * in \"My tasks\" and \"All open\" and in none of the dated views.\n */\nexport interface CrmTaskViewPlan {\n status: 'open' | 'done'\n assigneeUid?: string\n dueFrom?: number\n dueBefore?: number\n direction: 'asc' | 'desc'\n}\n\nexport function crmTaskViewPlan(\n view: CrmTaskView,\n options: { nowMs: number; uid: string | null | undefined },\n): CrmTaskViewPlan {\n const { nowMs, uid } = options\n const today = startOfLocalDay(nowMs)\n const tomorrow = startOfNextLocalDay(nowMs)\n switch (view) {\n case 'mine':\n return { status: 'open', assigneeUid: uid ?? '', direction: 'asc' }\n case 'overdue':\n return { status: 'open', dueBefore: today, direction: 'asc' }\n case 'today':\n return { status: 'open', dueFrom: today, dueBefore: tomorrow, direction: 'asc' }\n case 'upcoming':\n return { status: 'open', dueFrom: tomorrow, direction: 'asc' }\n case 'open':\n return { status: 'open', direction: 'asc' }\n case 'done':\n // Most recently due first: what was finished lately is what a reader\n // scrolls the Done view for, and the oldest completed task is the one\n // nobody comes back to.\n return { status: 'done', direction: 'desc' }\n }\n}\n\n/*==========================================\n * THE TASKS LIST'S QUERY (AGL-3321).\n *\n * The view is the query's base — its status, the reader as assignee for\n * \"My tasks\", and the `dueAtMs` window for Overdue, Today and Upcoming —\n * and Kind, Priority, Assignee and the search word are predicates on the\n * same query, paged by it. Every equality rides one `(field, dueAtMs)`\n * composite per direction: ascending for the working views, descending for\n * Done.\n *=========================================*/\n\n/** The orders the list takes: soonest first, and Done's most recently due first. */\nexport const TASK_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'dueAtMs', direction: 'asc' },\n { path: 'dueAtMs', direction: 'desc' },\n]\n\n/** What the Filters panel may name on a task, besides the view. */\nexport const TASK_LIST_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('kind'),\n crmSelectField('priority'),\n crmSelectField('assigneeUid'),\n]\n\nexport const TASK_LIST_DECLARATION: ListQueryDeclaration = {\n fields: TASK_LIST_QUERY_FIELDS,\n sorts: TASK_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The view's own predicates, which the plan takes as its base beside the scope. */\nexport function crmTaskViewBase(plan: CrmTaskViewPlan): ListQueryFilter[] {\n return [\n { path: 'status', op: '==', value: plan.status },\n ...(plan.assigneeUid ? [{ path: 'assigneeUid', op: '==' as const, value: plan.assigneeUid }] : []),\n ...(plan.dueFrom !== undefined ? [{ path: 'dueAtMs', op: '>=' as const, value: plan.dueFrom }] : []),\n ...(plan.dueBefore !== undefined ? [{ path: 'dueAtMs', op: '<' as const, value: plan.dueBefore }] : []),\n ]\n}\n\n/** The base's shape for `listQueryIndexes`: the view's two equalities. */\nexport const TASK_LIST_VIEW_BASE_INDEX = [{ path: 'status' }, { path: 'assigneeUid' }] as const\n\n/**\n * Rows in the order a view should paint them.\n *\n * Firestore sorts `null` before every number, so an ascending order by\n * `dueAtMs` puts the undated tasks at the TOP of \"All open\" — above the one\n * due in an hour. The query stays as it is (the index decides that); the\n * undated rows are moved to the end here, in a stable pass that leaves the\n * dated order the server chose alone.\n */\nexport function orderTaskRows<T extends Pick<CrmTask, 'dueAtMs'>>(\n rows: readonly T[],\n): T[] {\n const dated: T[] = []\n const undated: T[] = []\n for (const row of rows) {\n ;(typeof row.dueAtMs === 'number' ? dated : undated).push(row)\n }\n return [...dated, ...undated]\n}\n\n/** The MUI palette key a due date is painted with, by its state. */\nexport const TASK_DUE_COLORS: Record<TaskDueState, string> = {\n overdue: 'error.main',\n today: 'warning.main',\n upcoming: 'text.primary',\n none: 'text.secondary',\n done: 'text.secondary',\n}\n\n/**\n * The due date as a person reads it: \"Overdue · Tue, Sep 1, 9:00 AM\",\n * \"Today, 3:30 PM\", \"No due date\". The state word comes first because it is\n * the part a reader scans a column for; the timestamp is what they read once\n * a row has their attention.\n */\nexport function describeTaskDue(\n task: Pick<CrmTask, 'status' | 'dueAtMs'>,\n nowMs: number,\n locale?: string,\n): { state: TaskDueState; label: string } {\n const state = taskDueState(task, nowMs)\n const dueAtMs = task.dueAtMs\n if (typeof dueAtMs !== 'number' || !Number.isFinite(dueAtMs)) {\n return { state, label: 'No due date' }\n }\n const due = new Date(dueAtMs)\n const time = due.toLocaleTimeString(locale, {\n hour: 'numeric',\n minute: '2-digit',\n })\n switch (state) {\n case 'today':\n return { state, label: `Today, ${time}` }\n case 'overdue':\n return {\n state,\n label: `Overdue · ${due.toLocaleDateString(locale, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })}, ${time}`,\n }\n default:\n return {\n state,\n label: `${due.toLocaleDateString(locale, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })}, ${time}`,\n }\n }\n}\n\n/**\n * `datetime-local` ↔ epoch milliseconds, in the reader's own zone.\n *\n * The input's value is a wall-clock string with no zone; `new Date(string)`\n * reads such a string as LOCAL time, which is what a person typing \"9:00\"\n * into a due-date field means. Built by hand in the other direction because\n * `toISOString()` would render the UTC clock, and a task due at nine would\n * reopen in the drawer at whatever nine is in Greenwich.\n */\nexport function dueAtToLocalInput(ms: number | null | undefined): string {\n if (typeof ms !== 'number' || !Number.isFinite(ms)) return ''\n const date = new Date(ms)\n const pad = (value: number) => String(value).padStart(2, '0')\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-` +\n `${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`\n )\n}\n\nexport function localInputToDueAt(value: string): number | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n const ms = new Date(text).getTime()\n return Number.isFinite(ms) ? ms : null\n}\n\n/**\n * The two snoozes a row and the drawer offer by name (AGL-2619), in the\n * order a person reaches for them; \"Pick a date\" is the third and takes a\n * date rather than an option.\n */\nexport type CrmTaskSnoozeOption = 'tomorrow' | 'nextWeek'\n\nexport const CRM_TASK_SNOOZE_OPTIONS: ReadonlyArray<{\n id: CrmTaskSnoozeOption\n label: string\n}> = [\n { id: 'tomorrow', label: 'Tomorrow' },\n { id: 'nextWeek', label: 'Next week' },\n]\n\n/** The hour a snoozed task lands on when it had no time of day of its own. */\nexport const CRM_TASK_SNOOZE_DEFAULT_HOUR = 9\n\n/**\n * Where a snoozed task is due next.\n *\n * Counted from NOW, not from the old due date: a task a week overdue\n * snoozed to \"tomorrow\" is due tomorrow, which is what the word means to the\n * person pressing it, and not due six days ago. The time of day is kept —\n * a nine o'clock call stays a nine o'clock call — and a task that had no\n * time lands at {@link CRM_TASK_SNOOZE_DEFAULT_HOUR}, a working hour rather\n * than midnight. Built from the calendar for the reason\n * {@link startOfNextLocalDay} gives.\n */\nexport function snoozeDueAt(\n option: CrmTaskSnoozeOption,\n dueAtMs: number | null | undefined,\n nowMs: number,\n): number {\n const had = typeof dueAtMs === 'number' && Number.isFinite(dueAtMs) ? new Date(dueAtMs) : null\n const hours = had ? had.getHours() : CRM_TASK_SNOOZE_DEFAULT_HOUR\n const minutes = had ? had.getMinutes() : 0\n const now = new Date(nowMs)\n const days = option === 'tomorrow' ? 1 : 7\n return new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + days,\n hours,\n minutes,\n ).getTime()\n}\n\n/**\n * The record a task hangs off, as a link into the hub — or `null` for a\n * task that is nobody's in particular.\n *\n * One record per task on screen even when the document names more than one:\n * the contact wins, then the deal, then the company, because that is the\n * order of how specific each is about WHO the work is for. All three ids\n * stay on the document for the record pages' own queries.\n */\nexport function taskRecordLink(\n task: Pick<CrmTask, 'contactId' | 'companyId' | 'dealId'>,\n routes: CrmRoutes,\n): { kind: 'contact' | 'deal' | 'company'; id: string; href: string } | null {\n if (task.contactId) {\n return { kind: 'contact', id: task.contactId, href: routes.contact(task.contactId) }\n }\n if (task.dealId) {\n return { kind: 'deal', id: task.dealId, href: routes.deal(task.dealId) }\n }\n if (task.companyId) {\n return { kind: 'company', id: task.companyId, href: routes.company(task.companyId) }\n }\n return null\n}\n"],"names":["taskDueState","CRM_LIST_SEARCH","crmSelectField","CRM_TASK_VIEWS","id","label","CRM_TASK_KINDS","CRM_TASK_KIND_LABELS","call","email","meeting","todo","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","low","normal","high","startOfLocalDay","nowMs","date","Date","getFullYear","getMonth","getDate","getTime","startOfNextLocalDay","crmTaskViewPlan","view","options","uid","today","tomorrow","status","assigneeUid","direction","dueBefore","dueFrom","TASK_LIST_SORTS","path","TASK_LIST_QUERY_FIELDS","TASK_LIST_DECLARATION","fields","sorts","search","crmTaskViewBase","plan","op","value","undefined","TASK_LIST_VIEW_BASE_INDEX","orderTaskRows","rows","dated","undated","row","dueAtMs","push","TASK_DUE_COLORS","overdue","upcoming","none","done","describeTaskDue","task","locale","state","Number","isFinite","due","time","toLocaleTimeString","hour","minute","toLocaleDateString","weekday","month","day","dueAtToLocalInput","ms","pad","String","padStart","getHours","getMinutes","localInputToDueAt","text","trim","CRM_TASK_SNOOZE_OPTIONS","CRM_TASK_SNOOZE_DEFAULT_HOUR","snoozeDueAt","option","had","hours","minutes","now","days","taskRecordLink","routes","contactId","kind","href","contact","dealId","deal","companyId","company"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/task-views.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type CrmTask,\n type CrmTaskKind,\n type CrmTaskPriority,\n type TaskDueState,\n taskDueState,\n} from '@aglyn/aglyn/app-utils/crm'\nimport type { ListFilterField } from '@aglyn/shared-util-tools/list-query/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQuerySort,\n} from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport { CRM_LIST_SEARCH, crmSelectField } from './crm-list-query'\nimport type { CrmRoutes } from './crm-routes'\n\n/**\n * The tasks section's views and the query each one is (AGL-2599).\n *\n * Pure: what a view MEANS is written here once, as a plan the hook turns into\n * Firestore constraints and a spec can read back without Firestore. \"Overdue\"\n * and \"today\" are not stored on the task — nothing runs at midnight to stamp\n * them — so each view is a window over `dueAtMs` computed at read time from\n * the reader's own clock, and the same day boundaries {@link taskDueState}\n * paints a row with.\n */\n\nexport type CrmTaskView =\n | 'mine'\n | 'overdue'\n | 'today'\n | 'upcoming'\n | 'open'\n | 'done'\n\nexport const CRM_TASK_VIEWS: ReadonlyArray<{ id: CrmTaskView; label: string }> =\n [\n { id: 'mine', label: 'My tasks' },\n { id: 'overdue', label: 'Overdue' },\n { id: 'today', label: 'Today' },\n { id: 'upcoming', label: 'Upcoming' },\n { id: 'open', label: 'All open' },\n { id: 'done', label: 'Done' },\n ]\n\nexport const CRM_TASK_KINDS: readonly CrmTaskKind[] = [\n 'call',\n 'email',\n 'meeting',\n 'todo',\n]\n\nexport const CRM_TASK_KIND_LABELS: Record<CrmTaskKind, string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\nexport const CRM_TASK_PRIORITIES: readonly CrmTaskPriority[] = [\n 'low',\n 'normal',\n 'high',\n]\n\nexport const CRM_TASK_PRIORITY_LABELS: Record<CrmTaskPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n}\n\n\n/** Midnight at the START of the local calendar day `nowMs` falls in. */\nexport function startOfLocalDay(nowMs: number): number {\n const date = new Date(nowMs)\n return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n}\n\n/**\n * Midnight at the start of the NEXT local day — built from the calendar\n * rather than by adding 24 hours, because a day that a clock change falls in\n * is 23 or 25 hours long and a task due at 00:30 on that morning belongs to\n * it either way.\n */\nexport function startOfNextLocalDay(nowMs: number): number {\n const date = new Date(nowMs)\n return new Date(\n date.getFullYear(),\n date.getMonth(),\n date.getDate() + 1,\n ).getTime()\n}\n\n/**\n * One view as constraints, in the vocabulary of the `crmTasks` indexes.\n *\n * `assigneeUid` narrows to the reader and rides the\n * `(visibleTo, assigneeUid, status, dueAtMs)` index; every other view rides\n * `(visibleTo, status, dueAtMs)`. `dueFrom` is inclusive and `dueBefore`\n * exclusive so that two adjacent windows share a boundary and no task falls\n * between \"today\" and \"upcoming\". A task with no due date has `dueAtMs:\n * null`, which a range excludes and a bare order sorts first, so it appears\n * in \"My tasks\" and \"All open\" and in none of the dated views.\n */\nexport interface CrmTaskViewPlan {\n status: 'open' | 'done'\n assigneeUid?: string\n dueFrom?: number\n dueBefore?: number\n direction: 'asc' | 'desc'\n}\n\nexport function crmTaskViewPlan(\n view: CrmTaskView,\n options: { nowMs: number; uid: string | null | undefined },\n): CrmTaskViewPlan {\n const { nowMs, uid } = options\n const today = startOfLocalDay(nowMs)\n const tomorrow = startOfNextLocalDay(nowMs)\n switch (view) {\n case 'mine':\n return { status: 'open', assigneeUid: uid ?? '', direction: 'asc' }\n case 'overdue':\n return { status: 'open', dueBefore: today, direction: 'asc' }\n case 'today':\n return { status: 'open', dueFrom: today, dueBefore: tomorrow, direction: 'asc' }\n case 'upcoming':\n return { status: 'open', dueFrom: tomorrow, direction: 'asc' }\n case 'open':\n return { status: 'open', direction: 'asc' }\n case 'done':\n // Most recently due first: what was finished lately is what a reader\n // scrolls the Done view for, and the oldest completed task is the one\n // nobody comes back to.\n return { status: 'done', direction: 'desc' }\n }\n}\n\n/*==========================================\n * THE TASKS LIST'S QUERY (AGL-3321).\n *\n * The view is the query's base — its status, the reader as assignee for\n * \"My tasks\", and the `dueAtMs` window for Overdue, Today and Upcoming —\n * and Kind, Priority, Assignee and the search word are predicates on the\n * same query, paged by it. Every equality rides one `(field, dueAtMs)`\n * composite per direction: ascending for the working views, descending for\n * Done.\n *=========================================*/\n\n/**\n * The orders the list takes: soonest first, and Done's most recently due\n * first — the Due header either way — then each header the query can order\n * by (AGL-3680): the title (`titleLower`) and the priority by its rank\n * (`priorityRank`), each stored on every task. Each\n * is `alone`: served with no filter or search on beyond the view, beside the\n * view's base by one `(status, field)`, `(assigneeUid, field)` and\n * `(visibleTo, field)` composite per direction — and under a dated view\n * (Overdue, Today, Upcoming), whose window orders the list by Due, not at\n * all, which the list says. Type and Status (picklists, in the org's\n * order), Assignee and For sort the page.\n */\nexport const TASK_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'dueAtMs', direction: 'asc', column: 'dueAtMs', label: 'Due' },\n { path: 'dueAtMs', direction: 'desc', column: 'dueAtMs', label: 'Due' },\n { path: 'titleLower', direction: 'asc', column: 'title', label: 'Task', alone: true },\n { path: 'titleLower', direction: 'desc', column: 'title', label: 'Task', alone: true },\n { path: 'priorityRank', direction: 'asc', column: 'priority', label: 'Priority', alone: true },\n { path: 'priorityRank', direction: 'desc', column: 'priority', label: 'Priority', alone: true },\n]\n\n/** The view's own order: the Due header, the way the view reads it. */\nexport function crmTaskViewSort(plan: Pick<CrmTaskViewPlan, 'direction'>): ListQuerySort {\n return TASK_LIST_SORTS.find(\n (sort) => sort.path === 'dueAtMs' && sort.direction === plan.direction,\n ) as ListQuerySort\n}\n\n/** What the Filters panel may name on a task, besides the view. */\nexport const TASK_LIST_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('kind'),\n crmSelectField('priority'),\n crmSelectField('assigneeUid'),\n]\n\nexport const TASK_LIST_DECLARATION: ListQueryDeclaration = {\n fields: TASK_LIST_QUERY_FIELDS,\n sorts: TASK_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The view's own predicates, which the plan takes as its base beside the scope. */\nexport function crmTaskViewBase(plan: CrmTaskViewPlan): ListQueryFilter[] {\n return [\n { path: 'status', op: '==', value: plan.status },\n ...(plan.assigneeUid ? [{ path: 'assigneeUid', op: '==' as const, value: plan.assigneeUid }] : []),\n ...(plan.dueFrom !== undefined ? [{ path: 'dueAtMs', op: '>=' as const, value: plan.dueFrom }] : []),\n ...(plan.dueBefore !== undefined ? [{ path: 'dueAtMs', op: '<' as const, value: plan.dueBefore }] : []),\n ]\n}\n\n/** The base's shape for `listQueryIndexes`: the view's two equalities. */\nexport const TASK_LIST_VIEW_BASE_INDEX = [{ path: 'status' }, { path: 'assigneeUid' }] as const\n\n/**\n * Rows in the order a view should paint them.\n *\n * Firestore sorts `null` before every number, so an ascending order by\n * `dueAtMs` puts the undated tasks at the TOP of \"All open\" — above the one\n * due in an hour. The query stays as it is (the index decides that); the\n * undated rows are moved to the end here, in a stable pass that leaves the\n * dated order the server chose alone.\n */\nexport function orderTaskRows<T extends Pick<CrmTask, 'dueAtMs'>>(\n rows: readonly T[],\n): T[] {\n const dated: T[] = []\n const undated: T[] = []\n for (const row of rows) {\n ;(typeof row.dueAtMs === 'number' ? dated : undated).push(row)\n }\n return [...dated, ...undated]\n}\n\n/** The MUI palette key a due date is painted with, by its state. */\nexport const TASK_DUE_COLORS: Record<TaskDueState, string> = {\n overdue: 'error.main',\n today: 'warning.main',\n upcoming: 'text.primary',\n none: 'text.secondary',\n done: 'text.secondary',\n}\n\n/**\n * The due date as a person reads it: \"Overdue · Tue, Sep 1, 9:00 AM\",\n * \"Today, 3:30 PM\", \"No due date\". The state word comes first because it is\n * the part a reader scans a column for; the timestamp is what they read once\n * a row has their attention.\n */\nexport function describeTaskDue(\n task: Pick<CrmTask, 'status' | 'dueAtMs'>,\n nowMs: number,\n locale?: string,\n): { state: TaskDueState; label: string } {\n const state = taskDueState(task, nowMs)\n const dueAtMs = task.dueAtMs\n if (typeof dueAtMs !== 'number' || !Number.isFinite(dueAtMs)) {\n return { state, label: 'No due date' }\n }\n const due = new Date(dueAtMs)\n const time = due.toLocaleTimeString(locale, {\n hour: 'numeric',\n minute: '2-digit',\n })\n switch (state) {\n case 'today':\n return { state, label: `Today, ${time}` }\n case 'overdue':\n return {\n state,\n label: `Overdue · ${due.toLocaleDateString(locale, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })}, ${time}`,\n }\n default:\n return {\n state,\n label: `${due.toLocaleDateString(locale, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })}, ${time}`,\n }\n }\n}\n\n/**\n * `datetime-local` ↔ epoch milliseconds, in the reader's own zone.\n *\n * The input's value is a wall-clock string with no zone; `new Date(string)`\n * reads such a string as LOCAL time, which is what a person typing \"9:00\"\n * into a due-date field means. Built by hand in the other direction because\n * `toISOString()` would render the UTC clock, and a task due at nine would\n * reopen in the drawer at whatever nine is in Greenwich.\n */\nexport function dueAtToLocalInput(ms: number | null | undefined): string {\n if (typeof ms !== 'number' || !Number.isFinite(ms)) return ''\n const date = new Date(ms)\n const pad = (value: number) => String(value).padStart(2, '0')\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-` +\n `${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`\n )\n}\n\nexport function localInputToDueAt(value: string): number | null {\n const text = String(value ?? '').trim()\n if (!text) return null\n const ms = new Date(text).getTime()\n return Number.isFinite(ms) ? ms : null\n}\n\n/**\n * The two snoozes a row and the drawer offer by name (AGL-2619), in the\n * order a person reaches for them; \"Pick a date\" is the third and takes a\n * date rather than an option.\n */\nexport type CrmTaskSnoozeOption = 'tomorrow' | 'nextWeek'\n\nexport const CRM_TASK_SNOOZE_OPTIONS: ReadonlyArray<{\n id: CrmTaskSnoozeOption\n label: string\n}> = [\n { id: 'tomorrow', label: 'Tomorrow' },\n { id: 'nextWeek', label: 'Next week' },\n]\n\n/** The hour a snoozed task lands on when it had no time of day of its own. */\nexport const CRM_TASK_SNOOZE_DEFAULT_HOUR = 9\n\n/**\n * Where a snoozed task is due next.\n *\n * Counted from NOW, not from the old due date: a task a week overdue\n * snoozed to \"tomorrow\" is due tomorrow, which is what the word means to the\n * person pressing it, and not due six days ago. The time of day is kept —\n * a nine o'clock call stays a nine o'clock call — and a task that had no\n * time lands at {@link CRM_TASK_SNOOZE_DEFAULT_HOUR}, a working hour rather\n * than midnight. Built from the calendar for the reason\n * {@link startOfNextLocalDay} gives.\n */\nexport function snoozeDueAt(\n option: CrmTaskSnoozeOption,\n dueAtMs: number | null | undefined,\n nowMs: number,\n): number {\n const had = typeof dueAtMs === 'number' && Number.isFinite(dueAtMs) ? new Date(dueAtMs) : null\n const hours = had ? had.getHours() : CRM_TASK_SNOOZE_DEFAULT_HOUR\n const minutes = had ? had.getMinutes() : 0\n const now = new Date(nowMs)\n const days = option === 'tomorrow' ? 1 : 7\n return new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + days,\n hours,\n minutes,\n ).getTime()\n}\n\n/**\n * The record a task hangs off, as a link into the hub — or `null` for a\n * task that is nobody's in particular.\n *\n * One record per task on screen even when the document names more than one:\n * the contact wins, then the deal, then the company, because that is the\n * order of how specific each is about WHO the work is for. All three ids\n * stay on the document for the record pages' own queries.\n */\nexport function taskRecordLink(\n task: Pick<CrmTask, 'contactId' | 'companyId' | 'dealId'>,\n routes: CrmRoutes,\n): { kind: 'contact' | 'deal' | 'company'; id: string; href: string } | null {\n if (task.contactId) {\n return { kind: 'contact', id: task.contactId, href: routes.contact(task.contactId) }\n }\n if (task.dealId) {\n return { kind: 'deal', id: task.dealId, href: routes.deal(task.dealId) }\n }\n if (task.companyId) {\n return { kind: 'company', id: task.companyId, href: routes.company(task.companyId) }\n }\n return null\n}\n"],"names":["taskDueState","CRM_LIST_SEARCH","crmSelectField","CRM_TASK_VIEWS","id","label","CRM_TASK_KINDS","CRM_TASK_KIND_LABELS","call","email","meeting","todo","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","low","normal","high","startOfLocalDay","nowMs","date","Date","getFullYear","getMonth","getDate","getTime","startOfNextLocalDay","crmTaskViewPlan","view","options","uid","today","tomorrow","status","assigneeUid","direction","dueBefore","dueFrom","TASK_LIST_SORTS","path","column","alone","crmTaskViewSort","plan","find","sort","TASK_LIST_QUERY_FIELDS","TASK_LIST_DECLARATION","fields","sorts","search","crmTaskViewBase","op","value","undefined","TASK_LIST_VIEW_BASE_INDEX","orderTaskRows","rows","dated","undated","row","dueAtMs","push","TASK_DUE_COLORS","overdue","upcoming","none","done","describeTaskDue","task","locale","state","Number","isFinite","due","time","toLocaleTimeString","hour","minute","toLocaleDateString","weekday","month","day","dueAtToLocalInput","ms","pad","String","padStart","getHours","getMinutes","localInputToDueAt","text","trim","CRM_TASK_SNOOZE_OPTIONS","CRM_TASK_SNOOZE_DEFAULT_HOUR","snoozeDueAt","option","had","hours","minutes","now","days","taskRecordLink","routes","contactId","kind","href","contact","dealId","deal","companyId","company"],"mappings":"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"}
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Body: `{ hostId, email }`, or `{ orgId, hostId?, email }` at the
|
|
22
22
|
* organization level. Answers `{ ok: true, checked, code, message, gateway,
|
|
23
|
-
* chip }` — see `readAddressDeliverability
|
|
23
|
+
* chip, accountLock }` — see `readAddressDeliverability`, and
|
|
24
|
+
* `accountLockStateFor` for `accountLock` (AGL-3686): whether the address
|
|
25
|
+
* belongs to a locked or banned Aglyn account, which only the house
|
|
26
|
+
* workspace's sites can be told.
|
|
24
27
|
*
|
|
25
28
|
* Two surfaces ask it. A record page draws the gateway chip beside the
|
|
26
29
|
* address: which mail gateway stands in front of it, and what that gateway
|
|
@@ -20,7 +20,10 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
20
20
|
*
|
|
21
21
|
* Body: `{ hostId, email }`, or `{ orgId, hostId?, email }` at the
|
|
22
22
|
* organization level. Answers `{ ok: true, checked, code, message, gateway,
|
|
23
|
-
* chip }` — see `readAddressDeliverability
|
|
23
|
+
* chip, accountLock }` — see `readAddressDeliverability`, and
|
|
24
|
+
* `accountLockStateFor` for `accountLock` (AGL-3686): whether the address
|
|
25
|
+
* belongs to a locked or banned Aglyn account, which only the house
|
|
26
|
+
* workspace's sites can be told.
|
|
24
27
|
*
|
|
25
28
|
* Two surfaces ask it. A record page draws the gateway chip beside the
|
|
26
29
|
* address: which mail gateway stands in front of it, and what that gateway
|
|
@@ -47,6 +50,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
47
50
|
import { firebaseAdmin, getOrgForHost, hostSendingIdentity, memberHasOrgPermission, resolveOrgMembership } from "@aglyn/tenant-data-admin";
|
|
48
51
|
// The leaf, so a spec that stands a partial barrel in still reaches it.
|
|
49
52
|
import { readAddressDeliverability } from "@aglyn/tenant-data-admin/server/capture-email-check";
|
|
53
|
+
import { accountLockStateFor } from "@aglyn/tenant-data-admin/server/account-lock-mail";
|
|
50
54
|
import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
|
|
51
55
|
import { authorizeOrgCaller, readCrmRouteScope } from "./org-caller.js";
|
|
52
56
|
import { crmSuiteRefusal } from "./suite-gate.js";
|
|
@@ -174,13 +178,21 @@ export const crmEmailCheckHandler = async (req, res)=>{
|
|
|
174
178
|
res.status(suite.status).json(suite.body);
|
|
175
179
|
return;
|
|
176
180
|
}
|
|
177
|
-
const answer = await
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
const [answer, accountLock] = await Promise.all([
|
|
182
|
+
readAddressDeliverability({
|
|
183
|
+
email,
|
|
184
|
+
from: await sendingAddressOf(sendingHostId)
|
|
185
|
+
}),
|
|
186
|
+
sendingHostId ? accountLockStateFor({
|
|
187
|
+
hostId: sendingHostId,
|
|
188
|
+
email
|
|
189
|
+
}) : null
|
|
190
|
+
]);
|
|
181
191
|
res.status(200).json(_extends({
|
|
182
192
|
ok: true
|
|
183
|
-
}, answer
|
|
193
|
+
}, answer, {
|
|
194
|
+
accountLock
|
|
195
|
+
}));
|
|
184
196
|
} catch (error) {
|
|
185
197
|
console.error('[crm] email-check failed', scope, error);
|
|
186
198
|
res.status(500).json({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/email-check.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/email-check` — what the platform knows about one address\n * before somebody writes to it (AGL-3328).\n *\n * Body: `{ hostId, email }`, or `{ orgId, hostId?, email }` at the\n * organization level. Answers `{ ok: true, checked, code, message, gateway,\n * chip }` — see `readAddressDeliverability`.\n *\n * Two surfaces ask it. A record page draws the gateway chip beside the\n * address: which mail gateway stands in front of it, and what that gateway\n * did with this site's mail this week. The one-to-one composer warns before\n * Send: a domain with no mail server, or a gateway that refused this site's\n * sending domain twice this month and delivered nothing.\n *\n * ## Why a route\n *\n * The answer is DNS and the platform ledger. The browser does neither: the\n * MX cache and the ledger are server-written and closed to members, and a\n * cold domain is looked up here, once, into the cache every organization\n * reads. The ledger is read for the sending domain of the site the mail\n * would leave from — the mounted site, or at the organization level a site\n * of that organization the body names — so a member reads their own\n * sender's standing and never another workspace's.\n *\n * ## Who may ask\n *\n * Whoever may read the site's people: `data.manage`, the key the contacts\n * read rule gates on, reaching the site; at the organization level an\n * org-wide member holding it. Staff pass, as on every CRM route.\n */\n\nimport {\n type AglynOrgMember,\n isOrgWideMember,\n normalizeContactEmail,\n type PluginApiHandler,\n type PluginApiRequest,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\n// The leaf, so a spec that stands a partial barrel in still reaches it.\nimport { readAddressDeliverability } from '@aglyn/tenant-data-admin/server/capture-email-check'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { authorizeOrgCaller, readCrmRouteScope } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\n\n/** The route key, as `registerCrmConsoleApi` registers it. */\nexport const CRM_EMAIL_CHECK_ROUTE = 'crm/email-check'\n\ntype Refusal = { ok: false; status: number; error: string }\n\nconst READ_REFUSAL = 'Checking an address requires the data.manage permission on this site'\n\n/** A site-level reader: a verified session reaching the site with `data.manage`. */\nasync function authorizeSiteReader(\n req: PluginApiRequest,\n hostId: string,\n): Promise<{ ok: true; orgId: string; org: Record<string, unknown> } | Refusal> {\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return { ok: false, status: 401, error: 'Unauthenticated' }\n let decoded: { uid: string; staff?: unknown }\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n if (isRefusedIdToken(error)) return { ok: false, status: 401, error: 'Unauthenticated' }\n console.error('[crm] email-check could not verify the reader', error)\n return { ok: false, status: 500, error: 'The sign-in could not be checked. Try again.' }\n }\n const resolved = await getOrgForHost(hostId).catch(() => null)\n if (!resolved) return { ok: false, status: 404, error: 'Unknown site' }\n if (decoded.staff !== true) {\n const membership = await resolveOrgMembership(decoded.uid, resolved.orgId).catch(() => null)\n const member = (membership?.member ?? null) as Partial<AglynOrgMember> | null\n const reaches = isOrgWideMember(member) || Boolean(member?.hostAccess?.[hostId])\n const allowed =\n member && reaches && (await memberHasOrgPermission(resolved.orgId, member, 'data.manage'))\n if (!allowed) return { ok: false, status: 403, error: READ_REFUSAL }\n }\n return { ok: true, orgId: resolved.orgId, org: (resolved.org ?? {}) as Record<string, unknown> }\n}\n\n/**\n * The address mail from `hostId` leaves on, or `null` when the site has no\n * identity in effect — then no ledger is read, and only the MX is.\n */\nasync function sendingAddressOf(hostId: string): Promise<string | null> {\n if (!hostId) return null\n const identity = await hostSendingIdentity(hostId).catch(() => null)\n return identity && !identity.refusal ? (identity.from ?? null) : null\n}\n\nexport const crmEmailCheckHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const body = (req.body ?? {}) as Record<string, unknown>\n const scope = readCrmRouteScope(body)\n if (!scope) {\n res.status(400).json({ error: 'Missing hostId' })\n return\n }\n const email = normalizeContactEmail(body['email'])\n if (!email) {\n res.status(400).json({ error: 'Name the address to check.' })\n return\n }\n try {\n let sendingHostId = ''\n let org: Record<string, unknown>\n if (scope.level === 'org') {\n const caller = await authorizeOrgCaller(req, scope.orgId, {\n needs: 'data.manage',\n refusal: 'Checking an address at the organization level requires the data.manage permission across the whole workspace',\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n org = caller.org as Record<string, unknown>\n // A site named beside the org is used only when it is the org's own:\n // its sending domain's ledger is that workspace's standing.\n if (scope.hostId) {\n const owner = await getOrgForHost(scope.hostId).catch(() => null)\n if (owner?.orgId === scope.orgId) sendingHostId = scope.hostId\n }\n } else {\n const reader = await authorizeSiteReader(req, scope.hostId)\n if (reader.ok === false) {\n res.status(reader.status).json({ error: reader.error })\n return\n }\n sendingHostId = scope.hostId\n org = reader.org\n }\n // The plan after the reader, as every CRM read asks it (AGL-2851): the\n // record pages and the composer this serves are the suite's.\n const suite = crmSuiteRefusal(org, 'Checking an address')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const answer = await readAddressDeliverability({ email, from: await sendingAddressOf(sendingHostId) })\n res.status(200).json({ ok: true, ...answer })\n } catch (error) {\n console.error('[crm] email-check failed', scope, error)\n res.status(500).json({ error: 'The address could not be checked.' })\n }\n}\n\nexport default crmEmailCheckHandler\n"],"names":["isOrgWideMember","normalizeContactEmail","firebaseAdmin","getOrgForHost","hostSendingIdentity","memberHasOrgPermission","resolveOrgMembership","readAddressDeliverability","isRefusedIdToken","authorizeOrgCaller","readCrmRouteScope","crmSuiteRefusal","CRM_EMAIL_CHECK_ROUTE","READ_REFUSAL","authorizeSiteReader","req","hostId","resolved","authorization","String","headers","idToken","startsWith","slice","length","undefined","ok","status","error","decoded","app","auth","verifyIdToken","console","catch","staff","member","membership","uid","orgId","reaches","Boolean","hostAccess","allowed","org","sendingAddressOf","identity","refusal","from","crmEmailCheckHandler","res","method","setHeader","json","body","scope","email","sendingHostId","level","caller","needs","owner","reader","suite","answer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/email-check.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/email-check` — what the platform knows about one address\n * before somebody writes to it (AGL-3328).\n *\n * Body: `{ hostId, email }`, or `{ orgId, hostId?, email }` at the\n * organization level. Answers `{ ok: true, checked, code, message, gateway,\n * chip, accountLock }` — see `readAddressDeliverability`, and\n * `accountLockStateFor` for `accountLock` (AGL-3686): whether the address\n * belongs to a locked or banned Aglyn account, which only the house\n * workspace's sites can be told.\n *\n * Two surfaces ask it. A record page draws the gateway chip beside the\n * address: which mail gateway stands in front of it, and what that gateway\n * did with this site's mail this week. The one-to-one composer warns before\n * Send: a domain with no mail server, or a gateway that refused this site's\n * sending domain twice this month and delivered nothing.\n *\n * ## Why a route\n *\n * The answer is DNS and the platform ledger. The browser does neither: the\n * MX cache and the ledger are server-written and closed to members, and a\n * cold domain is looked up here, once, into the cache every organization\n * reads. The ledger is read for the sending domain of the site the mail\n * would leave from — the mounted site, or at the organization level a site\n * of that organization the body names — so a member reads their own\n * sender's standing and never another workspace's.\n *\n * ## Who may ask\n *\n * Whoever may read the site's people: `data.manage`, the key the contacts\n * read rule gates on, reaching the site; at the organization level an\n * org-wide member holding it. Staff pass, as on every CRM route.\n */\n\nimport {\n type AglynOrgMember,\n isOrgWideMember,\n normalizeContactEmail,\n type PluginApiHandler,\n type PluginApiRequest,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\n// The leaf, so a spec that stands a partial barrel in still reaches it.\nimport { readAddressDeliverability } from '@aglyn/tenant-data-admin/server/capture-email-check'\nimport { accountLockStateFor } from '@aglyn/tenant-data-admin/server/account-lock-mail'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { authorizeOrgCaller, readCrmRouteScope } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\n\n/** The route key, as `registerCrmConsoleApi` registers it. */\nexport const CRM_EMAIL_CHECK_ROUTE = 'crm/email-check'\n\ntype Refusal = { ok: false; status: number; error: string }\n\nconst READ_REFUSAL = 'Checking an address requires the data.manage permission on this site'\n\n/** A site-level reader: a verified session reaching the site with `data.manage`. */\nasync function authorizeSiteReader(\n req: PluginApiRequest,\n hostId: string,\n): Promise<{ ok: true; orgId: string; org: Record<string, unknown> } | Refusal> {\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return { ok: false, status: 401, error: 'Unauthenticated' }\n let decoded: { uid: string; staff?: unknown }\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n if (isRefusedIdToken(error)) return { ok: false, status: 401, error: 'Unauthenticated' }\n console.error('[crm] email-check could not verify the reader', error)\n return { ok: false, status: 500, error: 'The sign-in could not be checked. Try again.' }\n }\n const resolved = await getOrgForHost(hostId).catch(() => null)\n if (!resolved) return { ok: false, status: 404, error: 'Unknown site' }\n if (decoded.staff !== true) {\n const membership = await resolveOrgMembership(decoded.uid, resolved.orgId).catch(() => null)\n const member = (membership?.member ?? null) as Partial<AglynOrgMember> | null\n const reaches = isOrgWideMember(member) || Boolean(member?.hostAccess?.[hostId])\n const allowed =\n member && reaches && (await memberHasOrgPermission(resolved.orgId, member, 'data.manage'))\n if (!allowed) return { ok: false, status: 403, error: READ_REFUSAL }\n }\n return { ok: true, orgId: resolved.orgId, org: (resolved.org ?? {}) as Record<string, unknown> }\n}\n\n/**\n * The address mail from `hostId` leaves on, or `null` when the site has no\n * identity in effect — then no ledger is read, and only the MX is.\n */\nasync function sendingAddressOf(hostId: string): Promise<string | null> {\n if (!hostId) return null\n const identity = await hostSendingIdentity(hostId).catch(() => null)\n return identity && !identity.refusal ? (identity.from ?? null) : null\n}\n\nexport const crmEmailCheckHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const body = (req.body ?? {}) as Record<string, unknown>\n const scope = readCrmRouteScope(body)\n if (!scope) {\n res.status(400).json({ error: 'Missing hostId' })\n return\n }\n const email = normalizeContactEmail(body['email'])\n if (!email) {\n res.status(400).json({ error: 'Name the address to check.' })\n return\n }\n try {\n let sendingHostId = ''\n let org: Record<string, unknown>\n if (scope.level === 'org') {\n const caller = await authorizeOrgCaller(req, scope.orgId, {\n needs: 'data.manage',\n refusal: 'Checking an address at the organization level requires the data.manage permission across the whole workspace',\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n org = caller.org as Record<string, unknown>\n // A site named beside the org is used only when it is the org's own:\n // its sending domain's ledger is that workspace's standing.\n if (scope.hostId) {\n const owner = await getOrgForHost(scope.hostId).catch(() => null)\n if (owner?.orgId === scope.orgId) sendingHostId = scope.hostId\n }\n } else {\n const reader = await authorizeSiteReader(req, scope.hostId)\n if (reader.ok === false) {\n res.status(reader.status).json({ error: reader.error })\n return\n }\n sendingHostId = scope.hostId\n org = reader.org\n }\n // The plan after the reader, as every CRM read asks it (AGL-2851): the\n // record pages and the composer this serves are the suite's.\n const suite = crmSuiteRefusal(org, 'Checking an address')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const [answer, accountLock] = await Promise.all([\n readAddressDeliverability({ email, from: await sendingAddressOf(sendingHostId) }),\n sendingHostId ? accountLockStateFor({ hostId: sendingHostId, email }) : null,\n ])\n res.status(200).json({ ok: true, ...answer, accountLock })\n } catch (error) {\n console.error('[crm] email-check failed', scope, error)\n res.status(500).json({ error: 'The address could not be checked.' })\n }\n}\n\nexport default crmEmailCheckHandler\n"],"names":["isOrgWideMember","normalizeContactEmail","firebaseAdmin","getOrgForHost","hostSendingIdentity","memberHasOrgPermission","resolveOrgMembership","readAddressDeliverability","accountLockStateFor","isRefusedIdToken","authorizeOrgCaller","readCrmRouteScope","crmSuiteRefusal","CRM_EMAIL_CHECK_ROUTE","READ_REFUSAL","authorizeSiteReader","req","hostId","resolved","authorization","String","headers","idToken","startsWith","slice","length","undefined","ok","status","error","decoded","app","auth","verifyIdToken","console","catch","staff","member","membership","uid","orgId","reaches","Boolean","hostAccess","allowed","org","sendingAddressOf","identity","refusal","from","crmEmailCheckHandler","res","method","setHeader","json","body","scope","email","sendingHostId","level","caller","needs","owner","reader","suite","answer","accountLock","Promise","all"],"mappings":"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"}
|
|
@@ -297,7 +297,7 @@ const asUpdate = (fields)=>Object.fromEntries(Object.entries(fields).map(([key,
|
|
|
297
297
|
written: pickValues(after, changed)
|
|
298
298
|
};
|
|
299
299
|
} else {
|
|
300
|
-
var _fields_kind, _fields_priority, _fields_status, _get1;
|
|
300
|
+
var _fields_kind, _fields_priority, _fields_status, _stored_priority, _get1;
|
|
301
301
|
const fields = await taskFields(run, values, {}, ctx.actorUid);
|
|
302
302
|
const kind = (_fields_kind = fields['kind']) != null ? _fields_kind : 'todo';
|
|
303
303
|
const priority = (_fields_priority = fields['priority']) != null ? _fields_priority : 'normal';
|
|
@@ -322,6 +322,7 @@ const asUpdate = (fields)=>Object.fromEntries(Object.entries(fields).map(([key,
|
|
|
322
322
|
completedByUid: ctx.actorUid
|
|
323
323
|
} : {}, stored, scope, crmTaskListFields({
|
|
324
324
|
title: stored['title'],
|
|
325
|
+
priority: (_stored_priority = stored['priority']) != null ? _stored_priority : priority,
|
|
325
326
|
visibleTo: scope['visibleTo']
|
|
326
327
|
})));
|
|
327
328
|
const after = (_get1 = (await readTasks(env, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/transfer/tasks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * TASKS ON THE TRANSFER FRAMEWORK (AGL-3528) — the server half of\n * `crm.tasks`.\n *\n * A task's subject, its Type, Priority and Status as the org's lists label\n * them, its due date, its assignee, and the contact, company and deal it is\n * for — each a lookup: a contact by email, a company by domain or name, a\n * deal by its external id or its title. A row matches its task by Aglyn ID\n * or by the external id another product gave it, so a file imported twice\n * updates its tasks rather than doubling them.\n *\n * Written the way the tasks import route wrote them: the importing site's\n * scope stamp, the meaning and the label of each list value together, the\n * list's search keys, and a task marked done stamped with when and by whom.\n *=========================================*/\n\nimport {\n CRM_COLLECTIONS,\n CRM_TASK_PICKLIST_IDS,\n type ContactFieldDefinition,\n type CrmTaskPicklists,\n crmTaskLabelsForNew,\n crmTaskListFields,\n crmTaskPicklistLabels,\n resolveCrmSemanticPicklistWrite,\n} from '@aglyn/aglyn/server'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport {\n buildTransferPlan,\n planTransferUndo,\n TRANSFER_ID_FIELD,\n transferLookupNewName,\n type PlannedTransferRow,\n type TransferRowResult,\n type TransferUndoEntry,\n type TransferUndoStep,\n} from '@aglyn/aglyn/data-transfer'\nimport type { TransferRecordsHooks } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { restampCrmListFieldsOf } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { readCrmTaskPicklists } from '../server/read-picklist'\nimport { TASK_ALIASES } from './aliases'\nimport {\n addCrmPicklistValues,\n countCrmExport,\n creationScope,\n crmMemberEmails,\n crmMembersTarget,\n crmPicklistLists,\n crmRecordsRoom,\n crmTransferEnv,\n type CrmTransferEnv,\n eachWithinBudget,\n failureMessage,\n isoOf,\n lookupByField,\n lookupById,\n lookupResult,\n pickValues,\n plannedValues,\n readCrmExportPage,\n requireCrmRecords,\n requireCrmSuite,\n requireSite,\n textOf,\n TransferEngineError,\n visibleIn,\n} from './common'\nimport { companyForName } from './companies'\nimport {\n CRM_MEMBERS_TARGET,\n CRM_TIMESTAMP_FIELDS,\n TASK_TRANSFER_DERIVED,\n TASK_TRANSFER_FIELDS,\n TASK_TRANSFER_GROUPS,\n} from './fields'\n\nconst SUITE_ACT = 'Importing tasks'\nconst NO_CUSTOM: readonly ContactFieldDefinition[] = []\n\nconst tasksOf = (env: CrmTransferEnv) => env.orgRef.collection(CRM_COLLECTIONS.tasks)\n\n/** Names the export writes in place of the ids a task links by. */\ninterface TaskNames {\n assignee?: (uid: string) => string | undefined\n contact?: (id: string) => string | undefined\n company?: (id: string) => string | undefined\n deal?: (id: string) => string | undefined\n}\n\n/** One task as values by field id; links by id unless `names` names them. */\nexport function taskTransferValues(\n id: string,\n task: Record<string, unknown>,\n lists: CrmTaskPicklists,\n names: TaskNames = {},\n): Record<string, unknown> {\n const text = (value: unknown) => textOf(value) || null\n const link = (value: unknown, resolve?: (id: string) => string | undefined) => {\n const linked = text(value)\n return linked ? (resolve?.(linked) ?? linked) : null\n }\n const labels = crmTaskPicklistLabels(task, lists)\n return {\n title: text(task['title']),\n externalId: text(task['externalId']),\n type: labels.type || null,\n priority: labels.priority || null,\n status: labels.status || null,\n dueAt: isoOf(task['dueAtMs']),\n completedAt: isoOf(task['completedAtMs']),\n assignee: link(task['assigneeUid'], names.assignee),\n contact: link(task['contactId'], names.contact),\n company: link(task['companyId'], names.company),\n deal: link(task['dealId'], names.deal),\n notes: text(task['notes']),\n createdAt: isoOf(task['createdAt']),\n updatedAt: isoOf(task['updatedAt']),\n [TRANSFER_ID_FIELD]: id,\n }\n}\n\n/** The tasks these ids name, as values. */\nasync function readTasks(env: CrmTransferEnv, ids: readonly string[], lists: CrmTaskPicklists) {\n const out = new Map<string, Record<string, unknown>>()\n const wanted = ids.filter((id) => id && !id.includes('/'))\n for (let at = 0; at < wanted.length; at += 500) {\n for (const snapshot of await env.firestore.getAll(...wanted.slice(at, at + 500).map((id) => tasksOf(env).doc(id)))) {\n const data = snapshot.data()\n if (snapshot.exists && data && visibleIn(env, data['visibleTo'])) out.set(snapshot.id, taskTransferValues(snapshot.id, data, lists))\n }\n }\n return out\n}\n\n/** Reads `field` of each linked record by id, for an export's names. */\nasync function namesOf(\n env: CrmTransferEnv,\n collection: string,\n ids: Iterable<string>,\n field: (data: Record<string, unknown>) => string,\n): Promise<Map<string, string>> {\n const out = new Map<string, string>()\n const wanted = [...new Set(ids)].filter((id) => id && !id.includes('/'))\n for (let at = 0; at < wanted.length; at += 500) {\n const refs = wanted.slice(at, at + 500).map((id) => env.orgRef.collection(collection).doc(id))\n for (const snapshot of await env.firestore.getAll(...refs)) {\n if (snapshot.exists) out.set(snapshot.id, field(snapshot.data() ?? {}))\n }\n }\n return out\n}\n\n/** Everything one apply call shares. */\ninterface ApplyRun {\n env: CrmTransferEnv\n actorUid: string | null\n lists: CrmTaskPicklists\n companies: Map<string, Promise<{ id: string; name: string } | null>>\n admit: () => Promise<boolean>\n}\n\n/** A linked record this site sees, by id, or the refusal naming it. */\nasync function linked(run: ApplyRun, collection: string, value: unknown, what: string): Promise<string | null> {\n const id = textOf(value)\n if (!id) return null\n const snapshot = await run.env.orgRef.collection(collection).doc(id).get()\n if (!snapshot.exists || !visibleIn(run.env, snapshot.get('visibleTo'))) {\n throw new TransferEngineError('notFound', 404, `The ${what} this row names no longer exists.`)\n }\n return snapshot.id\n}\n\n/** The fields a row's values write, over the task as it stands; `null` clears. */\nasync function taskFields(\n run: ApplyRun,\n values: Readonly<Record<string, unknown>>,\n current: Record<string, unknown>,\n actorUid: string | null,\n): Promise<Record<string, unknown>> {\n const fields: Record<string, unknown> = {}\n if ('title' in values) {\n const title = textOf(values['title']).slice(0, 200)\n if (!title) throw new TransferEngineError('invalid', 422, 'A task needs a subject.')\n fields['title'] = title\n }\n if ('externalId' in values) fields['externalId'] = textOf(values['externalId']).slice(0, 200) || null\n const semantic = [\n ['type', 'kind', 'typeLabel', CRM_TASK_PICKLIST_IDS.type, run.lists.type],\n ['priority', 'priority', 'priorityLabel', CRM_TASK_PICKLIST_IDS.priority, run.lists.priority],\n ['status', 'status', 'statusLabel', CRM_TASK_PICKLIST_IDS.status, run.lists.status],\n ] as const\n for (const [fieldId, meaningField, labelField, picklistId, list] of semantic) {\n if (!(fieldId in values)) continue\n const write = resolveCrmSemanticPicklistWrite(picklistId, list, values[fieldId], current[labelField])\n if (!write) continue\n if (write.ok === false) throw new TransferEngineError('invalid', 422, write.error)\n fields[meaningField] = write.meaning\n fields[labelField] = write.label\n }\n // Done is a moment: a task the file closes is stamped, one it reopens unstamped.\n if (fields['status'] !== undefined && fields['status'] !== current['status']) {\n const done = fields['status'] === 'done'\n fields['completedAtMs'] = done ? Date.now() : null\n fields['completedByUid'] = done ? actorUid : null\n }\n if ('dueAt' in values) {\n const ms = values['dueAt'] ? Date.parse(String(values['dueAt'])) : NaN\n fields['dueAtMs'] = Number.isFinite(ms) ? ms : null\n }\n if ('assignee' in values) fields['assigneeUid'] = textOf(values['assignee']) || null\n if ('contact' in values) fields['contactId'] = await linked(run, 'contacts', values['contact'], 'contact')\n if ('deal' in values) fields['dealId'] = await linked(run, CRM_COLLECTIONS.deals, values['deal'], 'deal')\n if ('company' in values) {\n const create = transferLookupNewName(values['company'])\n if (create) {\n const company = await companyForName(run.env, create, run.actorUid, run.companies, run.admit)\n if (!company) throw new TransferEngineError('invalid', 422, 'The CRM records limit has no room for the company.')\n fields['companyId'] = company.id\n } else {\n fields['companyId'] = await linked(run, CRM_COLLECTIONS.companies, values['company'], 'company')\n }\n }\n if ('notes' in values) fields['notes'] = textOf(values['notes']).slice(0, 4000)\n return fields\n}\n\nconst asUpdate = (fields: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, value === null ? FieldValue.delete() : value]))\n\n/** The `crm.tasks` hooks. */\nexport function tasksTransferResource(): TransferRecordsHooks {\n return {\n fields: () => ({\n standard: TASK_TRANSFER_FIELDS,\n derived: TASK_TRANSFER_DERIVED,\n system: CRM_TIMESTAMP_FIELDS,\n groups: TASK_TRANSFER_GROUPS,\n }),\n matchKeys: [\n { fieldId: TRANSFER_ID_FIELD, normalizer: 'aglynId' },\n { fieldId: 'externalId', normalizer: 'externalId' },\n ],\n aliases: TASK_ALIASES,\n lookupTargets: { [CRM_MEMBERS_TARGET]: crmMembersTarget() },\n\n async count(ctx, options) {\n const env = await crmTransferEnv(ctx)\n requireCrmRecords(env, 'tasks')\n return countCrmExport(env, tasksOf(env), options, (data) => visibleIn(env, data['visibleTo'], options.scopeTokens))\n },\n\n async readPage(ctx, cursor, fieldIds, options) {\n const env = await crmTransferEnv(ctx)\n requireCrmRecords(env, 'tasks')\n const lists = await readCrmTaskPicklists(env.firestore, env.orgId)\n const emails = fieldIds.includes('assignee') ? await crmMemberEmails(env.orgId) : new Map<string, string>()\n return readCrmExportPage(\n env,\n tasksOf(env),\n cursor,\n options,\n (data) => visibleIn(env, data['visibleTo'], options?.scopeTokens),\n async (docs) => {\n const ids = (field: string) => docs.map((doc) => textOf(doc.data[field])).filter(Boolean)\n const [contacts, companies, deals] = await Promise.all([\n fieldIds.includes('contact') ? namesOf(env, 'contacts', ids('contactId'), (data) => textOf(data['email'])) : new Map<string, string>(),\n fieldIds.includes('company') ? namesOf(env, CRM_COLLECTIONS.companies, ids('companyId'), (data) => textOf(data['name'])) : new Map<string, string>(),\n fieldIds.includes('deal')\n ? namesOf(env, CRM_COLLECTIONS.deals, ids('dealId'), (data) => textOf(data['externalId']) || textOf(data['title']))\n : new Map<string, string>(),\n ])\n const names: TaskNames = {\n assignee: (uid) => emails.get(uid),\n contact: (id) => contacts.get(id) || undefined,\n company: (id) => companies.get(id) || undefined,\n deal: (id) => deals.get(id) || undefined,\n }\n return docs.map((doc) => pickValues(taskTransferValues(doc.id, doc.data, lists, names), fieldIds))\n },\n )\n },\n\n async lookup(ctx, requests) {\n const env = await crmTransferEnv(ctx)\n const lists = await readCrmTaskPicklists(env.firestore, env.orgId)\n const visible = (data: Record<string, unknown>) => visibleIn(env, data['visibleTo'])\n const found = { lookup: new Map<string, string[]>(), docs: new Map<string, Record<string, unknown>>() }\n for (const request of requests) {\n if (request.fieldId === TRANSFER_ID_FIELD) await lookupById(env, tasksOf(env), request, visible, found)\n else if (request.fieldId === 'externalId') {\n await lookupByField(tasksOf(env), request, 'externalId', (value) => value.trim(), visible, found)\n }\n }\n return lookupResult(found, (id, data) => taskTransferValues(id, data, lists))\n },\n\n async picklists(ctx, ids) {\n return crmPicklistLists(await crmTransferEnv(ctx), ids, NO_CUSTOM)\n },\n\n async addPicklistValues(ctx, picklistId, values) {\n await addCrmPicklistValues(await crmTransferEnv(ctx), picklistId, values)\n },\n\n lockedRules: () => [],\n\n async plan(ctx, input) {\n const env = await crmTransferEnv(ctx)\n requireCrmSuite(env, SUITE_ACT)\n requireSite(env, 'tasks')\n return buildTransferPlan(input)\n },\n\n async apply(ctx, chunk, writer) {\n const env = await crmTransferEnv(ctx)\n requireCrmSuite(env, SUITE_ACT)\n requireSite(env, 'tasks')\n const run: ApplyRun = {\n env,\n actorUid: ctx.actorUid,\n lists: await readCrmTaskPicklists(env.firestore, env.orgId),\n companies: new Map(),\n admit: crmRecordsRoom(env),\n }\n const results: TransferRowResult[] = []\n const undo: TransferUndoEntry[] = []\n await eachWithinBudget(chunk.rows as PlannedTransferRow[], () => writer.timeLeftMs(), async (row) => {\n const earlier = await writer.alreadyApplied(row.index)\n if (earlier) {\n results.push(earlier)\n return\n }\n const changed = row.diff.map((change) => change.fieldId)\n let result: TransferRowResult\n let entry: TransferUndoEntry | undefined\n try {\n const values = plannedValues(row)\n if (row.verdict === 'update' && row.recordId) {\n const ref = tasksOf(env).doc(row.recordId)\n const current = (await ref.get()).data()\n if (!current || !visibleIn(env, current['visibleTo'])) throw new TransferEngineError('notFound', 404, 'That task no longer exists.')\n const before = taskTransferValues(ref.id, current, run.lists)\n const fields = await taskFields(run, values, current, ctx.actorUid)\n await ref.update({\n ...asUpdate(fields),\n ...crmTaskListFields({ ...current, ...fields }),\n updatedAt: FieldValue.serverTimestamp(),\n })\n const after = (await readTasks(env, [ref.id], run.lists)).get(ref.id) ?? {}\n result = { row: row.index, outcome: 'updated', recordId: ref.id }\n entry = { row: row.index, recordId: ref.id, action: 'updated', previous: pickValues(before, changed), written: pickValues(after, changed) }\n } else {\n const fields = await taskFields(run, values, {}, ctx.actorUid)\n const kind = (fields['kind'] as 'call' | 'email' | 'meeting' | 'todo' | undefined) ?? 'todo'\n const priority = (fields['priority'] as 'low' | 'normal' | 'high' | undefined) ?? 'normal'\n const status = (fields['status'] as 'open' | 'done' | undefined) ?? 'open'\n const labels = crmTaskLabelsForNew(run.lists, { kind, priority, status })\n const scope = creationScope(env, ctx.actorUid)\n const stored = Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== null && value !== undefined))\n const ref = tasksOf(env).doc(createResourceUid())\n await ref.set({\n kind,\n priority,\n status,\n ...labels,\n dueAtMs: null,\n notes: '',\n completedAtMs: status === 'done' ? Date.now() : null,\n ...(status === 'done' && ctx.actorUid ? { completedByUid: ctx.actorUid } : {}),\n ...stored,\n ...scope,\n // What the Tasks list searches by (AGL-3321).\n ...crmTaskListFields({ title: stored['title'], visibleTo: scope['visibleTo'] }),\n })\n const after = (await readTasks(env, [ref.id], run.lists)).get(ref.id) ?? {}\n result = { row: row.index, outcome: 'created', recordId: ref.id }\n entry = { row: row.index, recordId: ref.id, action: 'created', written: pickValues(after, changed) }\n }\n } catch (error) {\n result = { row: row.index, outcome: 'failed', ...(row.recordId ? { recordId: row.recordId } : {}), message: failureMessage(error) }\n entry = undefined\n }\n await writer.markApplied(result, entry)\n results.push(result)\n if (entry) undo.push(entry)\n })\n return { results, undo }\n },\n\n async revert(ctx, snapshot, decisions) {\n const env = await crmTransferEnv(ctx)\n const run: ApplyRun = {\n env,\n actorUid: ctx.actorUid,\n lists: await readCrmTaskPicklists(env.firestore, env.orgId),\n companies: new Map(),\n admit: crmRecordsRoom(env),\n }\n const current = await readTasks(env, snapshot.entries.map((entry) => entry.recordId), run.lists)\n const done: TransferUndoStep[] = []\n const conflicts: TransferUndoStep[] = []\n const touched: FirebaseFirestore.DocumentReference[] = []\n for (const entry of snapshot.entries) {\n const step = planTransferUndo(entry, current.get(entry.recordId) ?? null)\n if (step.action === 'conflict' && decisions?.[entry.recordId] !== 'revert') {\n if (decisions?.[entry.recordId] === 'keep') done.push({ action: 'nothing', recordId: entry.recordId, why: 'alreadyReverted' })\n else conflicts.push(step)\n continue\n }\n if (step.action === 'nothing') {\n done.push(step)\n continue\n }\n const ref = tasksOf(env).doc(entry.recordId)\n if (entry.action === 'created') {\n await ref.delete()\n done.push({ action: 'delete', recordId: entry.recordId })\n continue\n }\n const stored = (await ref.get()).data() ?? {}\n const values = step.action === 'restore' || step.action === 'conflict' ? step.values : {}\n const fields = await taskFields(run, values, stored, ctx.actorUid)\n await ref.update({ ...asUpdate(fields), ...crmTaskListFields({ ...stored, ...fields }), updatedAt: FieldValue.serverTimestamp() })\n touched.push(ref)\n done.push(step)\n }\n if (touched.length) await restampCrmListFieldsOf(touched, 'crmTasks').catch(() => undefined)\n return { done, conflicts }\n },\n }\n}\n\n"],"names":["CRM_COLLECTIONS","CRM_TASK_PICKLIST_IDS","crmTaskLabelsForNew","crmTaskListFields","crmTaskPicklistLabels","resolveCrmSemanticPicklistWrite","createResourceUid","buildTransferPlan","planTransferUndo","TRANSFER_ID_FIELD","transferLookupNewName","restampCrmListFieldsOf","FieldValue","readCrmTaskPicklists","TASK_ALIASES","addCrmPicklistValues","countCrmExport","creationScope","crmMemberEmails","crmMembersTarget","crmPicklistLists","crmRecordsRoom","crmTransferEnv","eachWithinBudget","failureMessage","isoOf","lookupByField","lookupById","lookupResult","pickValues","plannedValues","readCrmExportPage","requireCrmRecords","requireCrmSuite","requireSite","textOf","TransferEngineError","visibleIn","companyForName","CRM_MEMBERS_TARGET","CRM_TIMESTAMP_FIELDS","TASK_TRANSFER_DERIVED","TASK_TRANSFER_FIELDS","TASK_TRANSFER_GROUPS","SUITE_ACT","NO_CUSTOM","tasksOf","env","orgRef","collection","tasks","taskTransferValues","id","task","lists","names","text","value","link","resolve","linked","labels","title","externalId","type","priority","status","dueAt","completedAt","assignee","contact","company","deal","notes","createdAt","updatedAt","readTasks","ids","out","Map","wanted","filter","includes","at","length","snapshot","firestore","getAll","slice","map","doc","data","exists","set","namesOf","field","Set","refs","run","what","get","taskFields","values","current","actorUid","fields","semantic","fieldId","meaningField","labelField","picklistId","list","write","ok","error","meaning","label","undefined","done","Date","now","ms","parse","String","NaN","Number","isFinite","deals","create","companies","admit","asUpdate","Object","fromEntries","entries","key","delete","tasksTransferResource","standard","derived","system","groups","matchKeys","normalizer","aliases","lookupTargets","count","ctx","options","scopeTokens","readPage","cursor","fieldIds","orgId","emails","docs","Boolean","contacts","Promise","all","uid","lookup","requests","visible","found","request","trim","picklists","addPicklistValues","lockedRules","plan","input","apply","chunk","writer","results","undo","rows","timeLeftMs","row","earlier","alreadyApplied","index","push","changed","diff","change","result","entry","verdict","recordId","ref","before","update","serverTimestamp","after","outcome","action","previous","written","kind","scope","stored","dueAtMs","completedAtMs","completedByUid","visibleTo","message","markApplied","revert","decisions","conflicts","touched","step","why","catch"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;2CAc2C,GAE3C,SACEA,eAAe,EACfC,qBAAqB,EAGrBC,mBAAmB,EACnBC,iBAAiB,EACjBC,qBAAqB,EACrBC,+BAA+B,QAC1B,sBAAqB;AAC5B,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SACEC,iBAAiB,EACjBC,gBAAgB,EAChBC,iBAAiB,EACjBC,qBAAqB,QAKhB,6BAA4B;AAEnC,SAASC,sBAAsB,QAAQ,2BAA0B;AACjE,SAASC,UAAU,QAAQ,2BAA0B;AACrD,SAASC,oBAAoB,QAAQ,6BAAyB;AAC9D,SAASC,YAAY,QAAQ,eAAW;AACxC,SACEC,oBAAoB,EACpBC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,EAEdC,gBAAgB,EAChBC,cAAc,EACdC,KAAK,EACLC,aAAa,EACbC,UAAU,EACVC,YAAY,EACZC,UAAU,EACVC,aAAa,EACbC,iBAAiB,EACjBC,iBAAiB,EACjBC,eAAe,EACfC,WAAW,EACXC,MAAM,EACNC,mBAAmB,EACnBC,SAAS,QACJ,cAAU;AACjB,SAASC,cAAc,QAAQ,iBAAa;AAC5C,SACEC,kBAAkB,EAClBC,oBAAoB,EACpBC,qBAAqB,EACrBC,oBAAoB,EACpBC,oBAAoB,QACf,cAAU;AAEjB,MAAMC,YAAY;AAClB,MAAMC,YAA+C,EAAE;AAEvD,MAAMC,UAAU,CAACC,MAAwBA,IAAIC,MAAM,CAACC,UAAU,CAACjD,gBAAgBkD,KAAK;AAUpF,2EAA2E,GAC3E,OAAO,SAASC,mBACdC,EAAU,EACVC,IAA6B,EAC7BC,KAAuB,EACvBC,QAAmB,CAAC,CAAC;IAErB,MAAMC,OAAO,CAACC,QAAmBtB,OAAOsB,UAAU;IAClD,MAAMC,OAAO,CAACD,OAAgBE;;QAC5B,MAAMC,SAASJ,KAAKC;QACpB,OAAOG,iBAAUD,2BAAAA,QAAUC,0BAAWA,SAAU;IAClD;IACA,MAAMC,SAASzD,sBAAsBiD,MAAMC;IAC3C,OAAO;QACLQ,OAAON,KAAKH,IAAI,CAAC,QAAQ;QACzBU,YAAYP,KAAKH,IAAI,CAAC,aAAa;QACnCW,MAAMH,OAAOG,IAAI,IAAI;QACrBC,UAAUJ,OAAOI,QAAQ,IAAI;QAC7BC,QAAQL,OAAOK,MAAM,IAAI;QACzBC,OAAO1C,MAAM4B,IAAI,CAAC,UAAU;QAC5Be,aAAa3C,MAAM4B,IAAI,CAAC,gBAAgB;QACxCgB,UAAUX,KAAKL,IAAI,CAAC,cAAc,EAAEE,MAAMc,QAAQ;QAClDC,SAASZ,KAAKL,IAAI,CAAC,YAAY,EAAEE,MAAMe,OAAO;QAC9CC,SAASb,KAAKL,IAAI,CAAC,YAAY,EAAEE,MAAMgB,OAAO;QAC9CC,MAAMd,KAAKL,IAAI,CAAC,SAAS,EAAEE,MAAMiB,IAAI;QACrCC,OAAOjB,KAAKH,IAAI,CAAC,QAAQ;QACzBqB,WAAWjD,MAAM4B,IAAI,CAAC,YAAY;QAClCsB,WAAWlD,MAAM4B,IAAI,CAAC,YAAY;QAClC,CAAC5C,kBAAkB,EAAE2C;IACvB;AACF;AAEA,yCAAyC,GACzC,eAAewB,UAAU7B,GAAmB,EAAE8B,GAAsB,EAAEvB,KAAuB;IAC3F,MAAMwB,MAAM,IAAIC;IAChB,MAAMC,SAASH,IAAII,MAAM,CAAC,CAAC7B,KAAOA,MAAM,CAACA,GAAG8B,QAAQ,CAAC;IACrD,IAAK,IAAIC,KAAK,GAAGA,KAAKH,OAAOI,MAAM,EAAED,MAAM,IAAK;QAC9C,KAAK,MAAME,YAAY,CAAA,MAAMtC,IAAIuC,SAAS,CAACC,MAAM,IAAIP,OAAOQ,KAAK,CAACL,IAAIA,KAAK,KAAKM,GAAG,CAAC,CAACrC,KAAON,QAAQC,KAAK2C,GAAG,CAACtC,KAAI,EAAG;YAClH,MAAMuC,OAAON,SAASM,IAAI;YAC1B,IAAIN,SAASO,MAAM,IAAID,QAAQtD,UAAUU,KAAK4C,IAAI,CAAC,YAAY,GAAGb,IAAIe,GAAG,CAACR,SAASjC,EAAE,EAAED,mBAAmBkC,SAASjC,EAAE,EAAEuC,MAAMrC;QAC/H;IACF;IACA,OAAOwB;AACT;AAEA,sEAAsE,GACtE,eAAegB,QACb/C,GAAmB,EACnBE,UAAkB,EAClB4B,GAAqB,EACrBkB,KAAgD;IAEhD,MAAMjB,MAAM,IAAIC;IAChB,MAAMC,SAAS;WAAI,IAAIgB,IAAInB;KAAK,CAACI,MAAM,CAAC,CAAC7B,KAAOA,MAAM,CAACA,GAAG8B,QAAQ,CAAC;IACnE,IAAK,IAAIC,KAAK,GAAGA,KAAKH,OAAOI,MAAM,EAAED,MAAM,IAAK;QAC9C,MAAMc,OAAOjB,OAAOQ,KAAK,CAACL,IAAIA,KAAK,KAAKM,GAAG,CAAC,CAACrC,KAAOL,IAAIC,MAAM,CAACC,UAAU,CAACA,YAAYyC,GAAG,CAACtC;QAC1F,KAAK,MAAMiC,YAAY,CAAA,MAAMtC,IAAIuC,SAAS,CAACC,MAAM,IAAIU,KAAI,EAAG;gBACVZ;YAAhD,IAAIA,SAASO,MAAM,EAAEd,IAAIe,GAAG,CAACR,SAASjC,EAAE,EAAE2C,OAAMV,iBAAAA,SAASM,IAAI,cAAbN,iBAAmB,CAAC;QACtE;IACF;IACA,OAAOP;AACT;AAWA,qEAAqE,GACrE,eAAelB,OAAOsC,GAAa,EAAEjD,UAAkB,EAAEQ,KAAc,EAAE0C,IAAY;IACnF,MAAM/C,KAAKjB,OAAOsB;IAClB,IAAI,CAACL,IAAI,OAAO;IAChB,MAAMiC,WAAW,MAAMa,IAAInD,GAAG,CAACC,MAAM,CAACC,UAAU,CAACA,YAAYyC,GAAG,CAACtC,IAAIgD,GAAG;IACxE,IAAI,CAACf,SAASO,MAAM,IAAI,CAACvD,UAAU6D,IAAInD,GAAG,EAAEsC,SAASe,GAAG,CAAC,eAAe;QACtE,MAAM,IAAIhE,oBAAoB,YAAY,KAAK,CAAC,IAAI,EAAE+D,KAAK,iCAAiC,CAAC;IAC/F;IACA,OAAOd,SAASjC,EAAE;AACpB;AAEA,gFAAgF,GAChF,eAAeiD,WACbH,GAAa,EACbI,MAAyC,EACzCC,OAAgC,EAChCC,QAAuB;IAEvB,MAAMC,SAAkC,CAAC;IACzC,IAAI,WAAWH,QAAQ;QACrB,MAAMxC,QAAQ3B,OAAOmE,MAAM,CAAC,QAAQ,EAAEd,KAAK,CAAC,GAAG;QAC/C,IAAI,CAAC1B,OAAO,MAAM,IAAI1B,oBAAoB,WAAW,KAAK;QAC1DqE,MAAM,CAAC,QAAQ,GAAG3C;IACpB;IACA,IAAI,gBAAgBwC,QAAQG,MAAM,CAAC,aAAa,GAAGtE,OAAOmE,MAAM,CAAC,aAAa,EAAEd,KAAK,CAAC,GAAG,QAAQ;IACjG,MAAMkB,WAAW;QACf;YAAC;YAAQ;YAAQ;YAAazG,sBAAsB+D,IAAI;YAAEkC,IAAI5C,KAAK,CAACU,IAAI;SAAC;QACzE;YAAC;YAAY;YAAY;YAAiB/D,sBAAsBgE,QAAQ;YAAEiC,IAAI5C,KAAK,CAACW,QAAQ;SAAC;QAC7F;YAAC;YAAU;YAAU;YAAehE,sBAAsBiE,MAAM;YAAEgC,IAAI5C,KAAK,CAACY,MAAM;SAAC;KACpF;IACD,KAAK,MAAM,CAACyC,SAASC,cAAcC,YAAYC,YAAYC,KAAK,IAAIL,SAAU;QAC5E,IAAI,CAAEC,CAAAA,WAAWL,MAAK,GAAI;QAC1B,MAAMU,QAAQ3G,gCAAgCyG,YAAYC,MAAMT,MAAM,CAACK,QAAQ,EAAEJ,OAAO,CAACM,WAAW;QACpG,IAAI,CAACG,OAAO;QACZ,IAAIA,MAAMC,EAAE,KAAK,OAAO,MAAM,IAAI7E,oBAAoB,WAAW,KAAK4E,MAAME,KAAK;QACjFT,MAAM,CAACG,aAAa,GAAGI,MAAMG,OAAO;QACpCV,MAAM,CAACI,WAAW,GAAGG,MAAMI,KAAK;IAClC;IACA,iFAAiF;IACjF,IAAIX,MAAM,CAAC,SAAS,KAAKY,aAAaZ,MAAM,CAAC,SAAS,KAAKF,OAAO,CAAC,SAAS,EAAE;QAC5E,MAAMe,OAAOb,MAAM,CAAC,SAAS,KAAK;QAClCA,MAAM,CAAC,gBAAgB,GAAGa,OAAOC,KAAKC,GAAG,KAAK;QAC9Cf,MAAM,CAAC,iBAAiB,GAAGa,OAAOd,WAAW;IAC/C;IACA,IAAI,WAAWF,QAAQ;QACrB,MAAMmB,KAAKnB,MAAM,CAAC,QAAQ,GAAGiB,KAAKG,KAAK,CAACC,OAAOrB,MAAM,CAAC,QAAQ,KAAKsB;QACnEnB,MAAM,CAAC,UAAU,GAAGoB,OAAOC,QAAQ,CAACL,MAAMA,KAAK;IACjD;IACA,IAAI,cAAcnB,QAAQG,MAAM,CAAC,cAAc,GAAGtE,OAAOmE,MAAM,CAAC,WAAW,KAAK;IAChF,IAAI,aAAaA,QAAQG,MAAM,CAAC,YAAY,GAAG,MAAM7C,OAAOsC,KAAK,YAAYI,MAAM,CAAC,UAAU,EAAE;IAChG,IAAI,UAAUA,QAAQG,MAAM,CAAC,SAAS,GAAG,MAAM7C,OAAOsC,KAAKlG,gBAAgB+H,KAAK,EAAEzB,MAAM,CAAC,OAAO,EAAE;IAClG,IAAI,aAAaA,QAAQ;QACvB,MAAM0B,SAAStH,sBAAsB4F,MAAM,CAAC,UAAU;QACtD,IAAI0B,QAAQ;YACV,MAAMzD,UAAU,MAAMjC,eAAe4D,IAAInD,GAAG,EAAEiF,QAAQ9B,IAAIM,QAAQ,EAAEN,IAAI+B,SAAS,EAAE/B,IAAIgC,KAAK;YAC5F,IAAI,CAAC3D,SAAS,MAAM,IAAInC,oBAAoB,WAAW,KAAK;YAC5DqE,MAAM,CAAC,YAAY,GAAGlC,QAAQnB,EAAE;QAClC,OAAO;YACLqD,MAAM,CAAC,YAAY,GAAG,MAAM7C,OAAOsC,KAAKlG,gBAAgBiI,SAAS,EAAE3B,MAAM,CAAC,UAAU,EAAE;QACxF;IACF;IACA,IAAI,WAAWA,QAAQG,MAAM,CAAC,QAAQ,GAAGtE,OAAOmE,MAAM,CAAC,QAAQ,EAAEd,KAAK,CAAC,GAAG;IAC1E,OAAOiB;AACT;AAEA,MAAM0B,WAAW,CAAC1B,SAChB2B,OAAOC,WAAW,CAACD,OAAOE,OAAO,CAAC7B,QAAQhB,GAAG,CAAC,CAAC,CAAC8C,KAAK9E,MAAM,GAAK;YAAC8E;YAAK9E,UAAU,OAAO7C,WAAW4H,MAAM,KAAK/E;SAAM;AAErH,2BAA2B,GAC3B,OAAO,SAASgF;IACd,OAAO;QACLhC,QAAQ,IAAO,CAAA;gBACbiC,UAAUhG;gBACViG,SAASlG;gBACTmG,QAAQpG;gBACRqG,QAAQlG;YACV,CAAA;QACAmG,WAAW;YACT;gBAAEnC,SAASlG;gBAAmBsI,YAAY;YAAU;YACpD;gBAAEpC,SAAS;gBAAcoC,YAAY;YAAa;SACnD;QACDC,SAASlI;QACTmI,eAAe;YAAE,CAAC1G,mBAAmB,EAAEpB;QAAmB;QAE1D,MAAM+H,OAAMC,GAAG,EAAEC,OAAO;YACtB,MAAMrG,MAAM,MAAMzB,eAAe6H;YACjCnH,kBAAkBe,KAAK;YACvB,OAAO/B,eAAe+B,KAAKD,QAAQC,MAAMqG,SAAS,CAACzD,OAAStD,UAAUU,KAAK4C,IAAI,CAAC,YAAY,EAAEyD,QAAQC,WAAW;QACnH;QAEA,MAAMC,UAASH,GAAG,EAAEI,MAAM,EAAEC,QAAQ,EAAEJ,OAAO;YAC3C,MAAMrG,MAAM,MAAMzB,eAAe6H;YACjCnH,kBAAkBe,KAAK;YACvB,MAAMO,QAAQ,MAAMzC,qBAAqBkC,IAAIuC,SAAS,EAAEvC,IAAI0G,KAAK;YACjE,MAAMC,SAASF,SAAStE,QAAQ,CAAC,cAAc,MAAMhE,gBAAgB6B,IAAI0G,KAAK,IAAI,IAAI1E;YACtF,OAAOhD,kBACLgB,KACAD,QAAQC,MACRwG,QACAH,SACA,CAACzD,OAAStD,UAAUU,KAAK4C,IAAI,CAAC,YAAY,EAAEyD,2BAAAA,QAASC,WAAW,GAChE,OAAOM;gBACL,MAAM9E,MAAM,CAACkB,QAAkB4D,KAAKlE,GAAG,CAAC,CAACC,MAAQvD,OAAOuD,IAAIC,IAAI,CAACI,MAAM,GAAGd,MAAM,CAAC2E;gBACjF,MAAM,CAACC,UAAU5B,WAAWF,MAAM,GAAG,MAAM+B,QAAQC,GAAG,CAAC;oBACrDP,SAAStE,QAAQ,CAAC,aAAaY,QAAQ/C,KAAK,YAAY8B,IAAI,cAAc,CAACc,OAASxD,OAAOwD,IAAI,CAAC,QAAQ,KAAK,IAAIZ;oBACjHyE,SAAStE,QAAQ,CAAC,aAAaY,QAAQ/C,KAAK/C,gBAAgBiI,SAAS,EAAEpD,IAAI,cAAc,CAACc,OAASxD,OAAOwD,IAAI,CAAC,OAAO,KAAK,IAAIZ;oBAC/HyE,SAAStE,QAAQ,CAAC,UACdY,QAAQ/C,KAAK/C,gBAAgB+H,KAAK,EAAElD,IAAI,WAAW,CAACc,OAASxD,OAAOwD,IAAI,CAAC,aAAa,KAAKxD,OAAOwD,IAAI,CAAC,QAAQ,KAC/G,IAAIZ;iBACT;gBACD,MAAMxB,QAAmB;oBACvBc,UAAU,CAAC2F,MAAQN,OAAOtD,GAAG,CAAC4D;oBAC9B1F,SAAS,CAAClB,KAAOyG,SAASzD,GAAG,CAAChD,OAAOiE;oBACrC9C,SAAS,CAACnB,KAAO6E,UAAU7B,GAAG,CAAChD,OAAOiE;oBACtC7C,MAAM,CAACpB,KAAO2E,MAAM3B,GAAG,CAAChD,OAAOiE;gBACjC;gBACA,OAAOsC,KAAKlE,GAAG,CAAC,CAACC,MAAQ7D,WAAWsB,mBAAmBuC,IAAItC,EAAE,EAAEsC,IAAIC,IAAI,EAAErC,OAAOC,QAAQiG;YAC1F;QAEJ;QAEA,MAAMS,QAAOd,GAAG,EAAEe,QAAQ;YACxB,MAAMnH,MAAM,MAAMzB,eAAe6H;YACjC,MAAM7F,QAAQ,MAAMzC,qBAAqBkC,IAAIuC,SAAS,EAAEvC,IAAI0G,KAAK;YACjE,MAAMU,UAAU,CAACxE,OAAkCtD,UAAUU,KAAK4C,IAAI,CAAC,YAAY;YACnF,MAAMyE,QAAQ;gBAAEH,QAAQ,IAAIlF;gBAAyB4E,MAAM,IAAI5E;YAAuC;YACtG,KAAK,MAAMsF,WAAWH,SAAU;gBAC9B,IAAIG,QAAQ1D,OAAO,KAAKlG,mBAAmB,MAAMkB,WAAWoB,KAAKD,QAAQC,MAAMsH,SAASF,SAASC;qBAC5F,IAAIC,QAAQ1D,OAAO,KAAK,cAAc;oBACzC,MAAMjF,cAAcoB,QAAQC,MAAMsH,SAAS,cAAc,CAAC5G,QAAUA,MAAM6G,IAAI,IAAIH,SAASC;gBAC7F;YACF;YACA,OAAOxI,aAAawI,OAAO,CAAChH,IAAIuC,OAASxC,mBAAmBC,IAAIuC,MAAMrC;QACxE;QAEA,MAAMiH,WAAUpB,GAAG,EAAEtE,GAAG;YACtB,OAAOzD,iBAAiB,MAAME,eAAe6H,MAAMtE,KAAKhC;QAC1D;QAEA,MAAM2H,mBAAkBrB,GAAG,EAAErC,UAAU,EAAER,MAAM;YAC7C,MAAMvF,qBAAqB,MAAMO,eAAe6H,MAAMrC,YAAYR;QACpE;QAEAmE,aAAa,IAAM,EAAE;QAErB,MAAMC,MAAKvB,GAAG,EAAEwB,KAAK;YACnB,MAAM5H,MAAM,MAAMzB,eAAe6H;YACjClH,gBAAgBc,KAAKH;YACrBV,YAAYa,KAAK;YACjB,OAAOxC,kBAAkBoK;QAC3B;QAEA,MAAMC,OAAMzB,GAAG,EAAE0B,KAAK,EAAEC,MAAM;YAC5B,MAAM/H,MAAM,MAAMzB,eAAe6H;YACjClH,gBAAgBc,KAAKH;YACrBV,YAAYa,KAAK;YACjB,MAAMmD,MAAgB;gBACpBnD;gBACAyD,UAAU2C,IAAI3C,QAAQ;gBACtBlD,OAAO,MAAMzC,qBAAqBkC,IAAIuC,SAAS,EAAEvC,IAAI0G,KAAK;gBAC1DxB,WAAW,IAAIlD;gBACfmD,OAAO7G,eAAe0B;YACxB;YACA,MAAMgI,UAA+B,EAAE;YACvC,MAAMC,OAA4B,EAAE;YACpC,MAAMzJ,iBAAiBsJ,MAAMI,IAAI,EAA0B,IAAMH,OAAOI,UAAU,IAAI,OAAOC;gBAC3F,MAAMC,UAAU,MAAMN,OAAOO,cAAc,CAACF,IAAIG,KAAK;gBACrD,IAAIF,SAAS;oBACXL,QAAQQ,IAAI,CAACH;oBACb;gBACF;gBACA,MAAMI,UAAUL,IAAIM,IAAI,CAAChG,GAAG,CAAC,CAACiG,SAAWA,OAAO/E,OAAO;gBACvD,IAAIgF;gBACJ,IAAIC;gBACJ,IAAI;oBACF,MAAMtF,SAASxE,cAAcqJ;oBAC7B,IAAIA,IAAIU,OAAO,KAAK,YAAYV,IAAIW,QAAQ,EAAE;4BAW9B;wBAVd,MAAMC,MAAMjJ,QAAQC,KAAK2C,GAAG,CAACyF,IAAIW,QAAQ;wBACzC,MAAMvF,UAAU,AAAC,CAAA,MAAMwF,IAAI3F,GAAG,EAAC,EAAGT,IAAI;wBACtC,IAAI,CAACY,WAAW,CAAClE,UAAUU,KAAKwD,OAAO,CAAC,YAAY,GAAG,MAAM,IAAInE,oBAAoB,YAAY,KAAK;wBACtG,MAAM4J,SAAS7I,mBAAmB4I,IAAI3I,EAAE,EAAEmD,SAASL,IAAI5C,KAAK;wBAC5D,MAAMmD,SAAS,MAAMJ,WAAWH,KAAKI,QAAQC,SAAS4C,IAAI3C,QAAQ;wBAClE,MAAMuF,IAAIE,MAAM,CAAC,aACZ9D,SAAS1B,SACTtG,kBAAkB,aAAKoG,SAAYE;4BACtC9B,WAAW/D,WAAWsL,eAAe;;wBAEvC,MAAMC,SAAQ,OAAA,AAAC,CAAA,MAAMvH,UAAU7B,KAAK;4BAACgJ,IAAI3I,EAAE;yBAAC,EAAE8C,IAAI5C,KAAK,CAAA,EAAG8C,GAAG,CAAC2F,IAAI3I,EAAE,aAAtD,OAA2D,CAAC;wBAC1EuI,SAAS;4BAAER,KAAKA,IAAIG,KAAK;4BAAEc,SAAS;4BAAWN,UAAUC,IAAI3I,EAAE;wBAAC;wBAChEwI,QAAQ;4BAAET,KAAKA,IAAIG,KAAK;4BAAEQ,UAAUC,IAAI3I,EAAE;4BAAEiJ,QAAQ;4BAAWC,UAAUzK,WAAWmK,QAAQR;4BAAUe,SAAS1K,WAAWsK,OAAOX;wBAAS;oBAC5I,OAAO;4BAES/E,cACIA,kBACFA,gBAmBF;wBAtBd,MAAMA,SAAS,MAAMJ,WAAWH,KAAKI,QAAQ,CAAC,GAAG6C,IAAI3C,QAAQ;wBAC7D,MAAMgG,QAAQ/F,eAAAA,MAAM,CAAC,OAAO,YAAdA,eAAwE;wBACtF,MAAMxC,YAAYwC,mBAAAA,MAAM,CAAC,WAAW,YAAlBA,mBAAgE;wBAClF,MAAMvC,UAAUuC,iBAAAA,MAAM,CAAC,SAAS,YAAhBA,iBAAoD;wBACpE,MAAM5C,SAAS3D,oBAAoBgG,IAAI5C,KAAK,EAAE;4BAAEkJ;4BAAMvI;4BAAUC;wBAAO;wBACvE,MAAMuI,QAAQxL,cAAc8B,KAAKoG,IAAI3C,QAAQ;wBAC7C,MAAMkG,SAAStE,OAAOC,WAAW,CAACD,OAAOE,OAAO,CAAC7B,QAAQxB,MAAM,CAAC,CAAC,GAAGxB,MAAM,GAAKA,UAAU,QAAQA,UAAU4D;wBAC3G,MAAM0E,MAAMjJ,QAAQC,KAAK2C,GAAG,CAACpF;wBAC7B,MAAMyL,IAAIlG,GAAG,CAAC;4BACZ2G;4BACAvI;4BACAC;2BACGL;4BACH8I,SAAS;4BACTlI,OAAO;4BACPmI,eAAe1I,WAAW,SAASqD,KAAKC,GAAG,KAAK;2BAC5CtD,WAAW,UAAUiF,IAAI3C,QAAQ,GAAG;4BAAEqG,gBAAgB1D,IAAI3C,QAAQ;wBAAC,IAAI,CAAC,GACzEkG,QACAD,OAEAtM,kBAAkB;4BAAE2D,OAAO4I,MAAM,CAAC,QAAQ;4BAAEI,WAAWL,KAAK,CAAC,YAAY;wBAAC;wBAE/E,MAAMN,SAAQ,QAAA,AAAC,CAAA,MAAMvH,UAAU7B,KAAK;4BAACgJ,IAAI3I,EAAE;yBAAC,EAAE8C,IAAI5C,KAAK,CAAA,EAAG8C,GAAG,CAAC2F,IAAI3I,EAAE,aAAtD,QAA2D,CAAC;wBAC1EuI,SAAS;4BAAER,KAAKA,IAAIG,KAAK;4BAAEc,SAAS;4BAAWN,UAAUC,IAAI3I,EAAE;wBAAC;wBAChEwI,QAAQ;4BAAET,KAAKA,IAAIG,KAAK;4BAAEQ,UAAUC,IAAI3I,EAAE;4BAAEiJ,QAAQ;4BAAWE,SAAS1K,WAAWsK,OAAOX;wBAAS;oBACrG;gBACF,EAAE,OAAOtE,OAAO;oBACdyE,SAAS;wBAAER,KAAKA,IAAIG,KAAK;wBAAEc,SAAS;uBAAcjB,IAAIW,QAAQ,GAAG;wBAAEA,UAAUX,IAAIW,QAAQ;oBAAC,IAAI,CAAC;wBAAIiB,SAASvL,eAAe0F;;oBAC3H0E,QAAQvE;gBACV;gBACA,MAAMyD,OAAOkC,WAAW,CAACrB,QAAQC;gBACjCb,QAAQQ,IAAI,CAACI;gBACb,IAAIC,OAAOZ,KAAKO,IAAI,CAACK;YACvB;YACA,OAAO;gBAAEb;gBAASC;YAAK;QACzB;QAEA,MAAMiC,QAAO9D,GAAG,EAAE9D,QAAQ,EAAE6H,SAAS;YACnC,MAAMnK,MAAM,MAAMzB,eAAe6H;YACjC,MAAMjD,MAAgB;gBACpBnD;gBACAyD,UAAU2C,IAAI3C,QAAQ;gBACtBlD,OAAO,MAAMzC,qBAAqBkC,IAAIuC,SAAS,EAAEvC,IAAI0G,KAAK;gBAC1DxB,WAAW,IAAIlD;gBACfmD,OAAO7G,eAAe0B;YACxB;YACA,MAAMwD,UAAU,MAAM3B,UAAU7B,KAAKsC,SAASiD,OAAO,CAAC7C,GAAG,CAAC,CAACmG,QAAUA,MAAME,QAAQ,GAAG5F,IAAI5C,KAAK;YAC/F,MAAMgE,OAA2B,EAAE;YACnC,MAAM6F,YAAgC,EAAE;YACxC,MAAMC,UAAiD,EAAE;YACzD,KAAK,MAAMxB,SAASvG,SAASiD,OAAO,CAAE;oBACC/B,cAgBtB;gBAhBf,MAAM8G,OAAO7M,iBAAiBoL,QAAOrF,eAAAA,QAAQH,GAAG,CAACwF,MAAME,QAAQ,aAA1BvF,eAA+B;gBACpE,IAAI8G,KAAKhB,MAAM,KAAK,cAAca,CAAAA,6BAAAA,SAAW,CAACtB,MAAME,QAAQ,CAAC,MAAK,UAAU;oBAC1E,IAAIoB,CAAAA,6BAAAA,SAAW,CAACtB,MAAME,QAAQ,CAAC,MAAK,QAAQxE,KAAKiE,IAAI,CAAC;wBAAEc,QAAQ;wBAAWP,UAAUF,MAAME,QAAQ;wBAAEwB,KAAK;oBAAkB;yBACvHH,UAAU5B,IAAI,CAAC8B;oBACpB;gBACF;gBACA,IAAIA,KAAKhB,MAAM,KAAK,WAAW;oBAC7B/E,KAAKiE,IAAI,CAAC8B;oBACV;gBACF;gBACA,MAAMtB,MAAMjJ,QAAQC,KAAK2C,GAAG,CAACkG,MAAME,QAAQ;gBAC3C,IAAIF,MAAMS,MAAM,KAAK,WAAW;oBAC9B,MAAMN,IAAIvD,MAAM;oBAChBlB,KAAKiE,IAAI,CAAC;wBAAEc,QAAQ;wBAAUP,UAAUF,MAAME,QAAQ;oBAAC;oBACvD;gBACF;gBACA,MAAMY,UAAS,QAAA,AAAC,CAAA,MAAMX,IAAI3F,GAAG,EAAC,EAAGT,IAAI,cAAtB,QAA4B,CAAC;gBAC5C,MAAMW,SAAS+G,KAAKhB,MAAM,KAAK,aAAagB,KAAKhB,MAAM,KAAK,aAAagB,KAAK/G,MAAM,GAAG,CAAC;gBACxF,MAAMG,SAAS,MAAMJ,WAAWH,KAAKI,QAAQoG,QAAQvD,IAAI3C,QAAQ;gBACjE,MAAMuF,IAAIE,MAAM,CAAC,aAAK9D,SAAS1B,SAAYtG,kBAAkB,aAAKuM,QAAWjG;oBAAW9B,WAAW/D,WAAWsL,eAAe;;gBAC7HkB,QAAQ7B,IAAI,CAACQ;gBACbzE,KAAKiE,IAAI,CAAC8B;YACZ;YACA,IAAID,QAAQhI,MAAM,EAAE,MAAMzE,uBAAuByM,SAAS,YAAYG,KAAK,CAAC,IAAMlG;YAClF,OAAO;gBAAEC;gBAAM6F;YAAU;QAC3B;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/transfer/tasks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * TASKS ON THE TRANSFER FRAMEWORK (AGL-3528) — the server half of\n * `crm.tasks`.\n *\n * A task's subject, its Type, Priority and Status as the org's lists label\n * them, its due date, its assignee, and the contact, company and deal it is\n * for — each a lookup: a contact by email, a company by domain or name, a\n * deal by its external id or its title. A row matches its task by Aglyn ID\n * or by the external id another product gave it, so a file imported twice\n * updates its tasks rather than doubling them.\n *\n * Written the way the tasks import route wrote them: the importing site's\n * scope stamp, the meaning and the label of each list value together, the\n * list's search keys, and a task marked done stamped with when and by whom.\n *=========================================*/\n\nimport {\n CRM_COLLECTIONS,\n CRM_TASK_PICKLIST_IDS,\n type ContactFieldDefinition,\n type CrmTaskPicklists,\n crmTaskLabelsForNew,\n crmTaskListFields,\n crmTaskPicklistLabels,\n resolveCrmSemanticPicklistWrite,\n} from '@aglyn/aglyn/server'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport {\n buildTransferPlan,\n planTransferUndo,\n TRANSFER_ID_FIELD,\n transferLookupNewName,\n type PlannedTransferRow,\n type TransferRowResult,\n type TransferUndoEntry,\n type TransferUndoStep,\n} from '@aglyn/aglyn/data-transfer'\nimport type { TransferRecordsHooks } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { restampCrmListFieldsOf } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { readCrmTaskPicklists } from '../server/read-picklist'\nimport { TASK_ALIASES } from './aliases'\nimport {\n addCrmPicklistValues,\n countCrmExport,\n creationScope,\n crmMemberEmails,\n crmMembersTarget,\n crmPicklistLists,\n crmRecordsRoom,\n crmTransferEnv,\n type CrmTransferEnv,\n eachWithinBudget,\n failureMessage,\n isoOf,\n lookupByField,\n lookupById,\n lookupResult,\n pickValues,\n plannedValues,\n readCrmExportPage,\n requireCrmRecords,\n requireCrmSuite,\n requireSite,\n textOf,\n TransferEngineError,\n visibleIn,\n} from './common'\nimport { companyForName } from './companies'\nimport {\n CRM_MEMBERS_TARGET,\n CRM_TIMESTAMP_FIELDS,\n TASK_TRANSFER_DERIVED,\n TASK_TRANSFER_FIELDS,\n TASK_TRANSFER_GROUPS,\n} from './fields'\n\nconst SUITE_ACT = 'Importing tasks'\nconst NO_CUSTOM: readonly ContactFieldDefinition[] = []\n\nconst tasksOf = (env: CrmTransferEnv) => env.orgRef.collection(CRM_COLLECTIONS.tasks)\n\n/** Names the export writes in place of the ids a task links by. */\ninterface TaskNames {\n assignee?: (uid: string) => string | undefined\n contact?: (id: string) => string | undefined\n company?: (id: string) => string | undefined\n deal?: (id: string) => string | undefined\n}\n\n/** One task as values by field id; links by id unless `names` names them. */\nexport function taskTransferValues(\n id: string,\n task: Record<string, unknown>,\n lists: CrmTaskPicklists,\n names: TaskNames = {},\n): Record<string, unknown> {\n const text = (value: unknown) => textOf(value) || null\n const link = (value: unknown, resolve?: (id: string) => string | undefined) => {\n const linked = text(value)\n return linked ? (resolve?.(linked) ?? linked) : null\n }\n const labels = crmTaskPicklistLabels(task, lists)\n return {\n title: text(task['title']),\n externalId: text(task['externalId']),\n type: labels.type || null,\n priority: labels.priority || null,\n status: labels.status || null,\n dueAt: isoOf(task['dueAtMs']),\n completedAt: isoOf(task['completedAtMs']),\n assignee: link(task['assigneeUid'], names.assignee),\n contact: link(task['contactId'], names.contact),\n company: link(task['companyId'], names.company),\n deal: link(task['dealId'], names.deal),\n notes: text(task['notes']),\n createdAt: isoOf(task['createdAt']),\n updatedAt: isoOf(task['updatedAt']),\n [TRANSFER_ID_FIELD]: id,\n }\n}\n\n/** The tasks these ids name, as values. */\nasync function readTasks(env: CrmTransferEnv, ids: readonly string[], lists: CrmTaskPicklists) {\n const out = new Map<string, Record<string, unknown>>()\n const wanted = ids.filter((id) => id && !id.includes('/'))\n for (let at = 0; at < wanted.length; at += 500) {\n for (const snapshot of await env.firestore.getAll(...wanted.slice(at, at + 500).map((id) => tasksOf(env).doc(id)))) {\n const data = snapshot.data()\n if (snapshot.exists && data && visibleIn(env, data['visibleTo'])) out.set(snapshot.id, taskTransferValues(snapshot.id, data, lists))\n }\n }\n return out\n}\n\n/** Reads `field` of each linked record by id, for an export's names. */\nasync function namesOf(\n env: CrmTransferEnv,\n collection: string,\n ids: Iterable<string>,\n field: (data: Record<string, unknown>) => string,\n): Promise<Map<string, string>> {\n const out = new Map<string, string>()\n const wanted = [...new Set(ids)].filter((id) => id && !id.includes('/'))\n for (let at = 0; at < wanted.length; at += 500) {\n const refs = wanted.slice(at, at + 500).map((id) => env.orgRef.collection(collection).doc(id))\n for (const snapshot of await env.firestore.getAll(...refs)) {\n if (snapshot.exists) out.set(snapshot.id, field(snapshot.data() ?? {}))\n }\n }\n return out\n}\n\n/** Everything one apply call shares. */\ninterface ApplyRun {\n env: CrmTransferEnv\n actorUid: string | null\n lists: CrmTaskPicklists\n companies: Map<string, Promise<{ id: string; name: string } | null>>\n admit: () => Promise<boolean>\n}\n\n/** A linked record this site sees, by id, or the refusal naming it. */\nasync function linked(run: ApplyRun, collection: string, value: unknown, what: string): Promise<string | null> {\n const id = textOf(value)\n if (!id) return null\n const snapshot = await run.env.orgRef.collection(collection).doc(id).get()\n if (!snapshot.exists || !visibleIn(run.env, snapshot.get('visibleTo'))) {\n throw new TransferEngineError('notFound', 404, `The ${what} this row names no longer exists.`)\n }\n return snapshot.id\n}\n\n/** The fields a row's values write, over the task as it stands; `null` clears. */\nasync function taskFields(\n run: ApplyRun,\n values: Readonly<Record<string, unknown>>,\n current: Record<string, unknown>,\n actorUid: string | null,\n): Promise<Record<string, unknown>> {\n const fields: Record<string, unknown> = {}\n if ('title' in values) {\n const title = textOf(values['title']).slice(0, 200)\n if (!title) throw new TransferEngineError('invalid', 422, 'A task needs a subject.')\n fields['title'] = title\n }\n if ('externalId' in values) fields['externalId'] = textOf(values['externalId']).slice(0, 200) || null\n const semantic = [\n ['type', 'kind', 'typeLabel', CRM_TASK_PICKLIST_IDS.type, run.lists.type],\n ['priority', 'priority', 'priorityLabel', CRM_TASK_PICKLIST_IDS.priority, run.lists.priority],\n ['status', 'status', 'statusLabel', CRM_TASK_PICKLIST_IDS.status, run.lists.status],\n ] as const\n for (const [fieldId, meaningField, labelField, picklistId, list] of semantic) {\n if (!(fieldId in values)) continue\n const write = resolveCrmSemanticPicklistWrite(picklistId, list, values[fieldId], current[labelField])\n if (!write) continue\n if (write.ok === false) throw new TransferEngineError('invalid', 422, write.error)\n fields[meaningField] = write.meaning\n fields[labelField] = write.label\n }\n // Done is a moment: a task the file closes is stamped, one it reopens unstamped.\n if (fields['status'] !== undefined && fields['status'] !== current['status']) {\n const done = fields['status'] === 'done'\n fields['completedAtMs'] = done ? Date.now() : null\n fields['completedByUid'] = done ? actorUid : null\n }\n if ('dueAt' in values) {\n const ms = values['dueAt'] ? Date.parse(String(values['dueAt'])) : NaN\n fields['dueAtMs'] = Number.isFinite(ms) ? ms : null\n }\n if ('assignee' in values) fields['assigneeUid'] = textOf(values['assignee']) || null\n if ('contact' in values) fields['contactId'] = await linked(run, 'contacts', values['contact'], 'contact')\n if ('deal' in values) fields['dealId'] = await linked(run, CRM_COLLECTIONS.deals, values['deal'], 'deal')\n if ('company' in values) {\n const create = transferLookupNewName(values['company'])\n if (create) {\n const company = await companyForName(run.env, create, run.actorUid, run.companies, run.admit)\n if (!company) throw new TransferEngineError('invalid', 422, 'The CRM records limit has no room for the company.')\n fields['companyId'] = company.id\n } else {\n fields['companyId'] = await linked(run, CRM_COLLECTIONS.companies, values['company'], 'company')\n }\n }\n if ('notes' in values) fields['notes'] = textOf(values['notes']).slice(0, 4000)\n return fields\n}\n\nconst asUpdate = (fields: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, value === null ? FieldValue.delete() : value]))\n\n/** The `crm.tasks` hooks. */\nexport function tasksTransferResource(): TransferRecordsHooks {\n return {\n fields: () => ({\n standard: TASK_TRANSFER_FIELDS,\n derived: TASK_TRANSFER_DERIVED,\n system: CRM_TIMESTAMP_FIELDS,\n groups: TASK_TRANSFER_GROUPS,\n }),\n matchKeys: [\n { fieldId: TRANSFER_ID_FIELD, normalizer: 'aglynId' },\n { fieldId: 'externalId', normalizer: 'externalId' },\n ],\n aliases: TASK_ALIASES,\n lookupTargets: { [CRM_MEMBERS_TARGET]: crmMembersTarget() },\n\n async count(ctx, options) {\n const env = await crmTransferEnv(ctx)\n requireCrmRecords(env, 'tasks')\n return countCrmExport(env, tasksOf(env), options, (data) => visibleIn(env, data['visibleTo'], options.scopeTokens))\n },\n\n async readPage(ctx, cursor, fieldIds, options) {\n const env = await crmTransferEnv(ctx)\n requireCrmRecords(env, 'tasks')\n const lists = await readCrmTaskPicklists(env.firestore, env.orgId)\n const emails = fieldIds.includes('assignee') ? await crmMemberEmails(env.orgId) : new Map<string, string>()\n return readCrmExportPage(\n env,\n tasksOf(env),\n cursor,\n options,\n (data) => visibleIn(env, data['visibleTo'], options?.scopeTokens),\n async (docs) => {\n const ids = (field: string) => docs.map((doc) => textOf(doc.data[field])).filter(Boolean)\n const [contacts, companies, deals] = await Promise.all([\n fieldIds.includes('contact') ? namesOf(env, 'contacts', ids('contactId'), (data) => textOf(data['email'])) : new Map<string, string>(),\n fieldIds.includes('company') ? namesOf(env, CRM_COLLECTIONS.companies, ids('companyId'), (data) => textOf(data['name'])) : new Map<string, string>(),\n fieldIds.includes('deal')\n ? namesOf(env, CRM_COLLECTIONS.deals, ids('dealId'), (data) => textOf(data['externalId']) || textOf(data['title']))\n : new Map<string, string>(),\n ])\n const names: TaskNames = {\n assignee: (uid) => emails.get(uid),\n contact: (id) => contacts.get(id) || undefined,\n company: (id) => companies.get(id) || undefined,\n deal: (id) => deals.get(id) || undefined,\n }\n return docs.map((doc) => pickValues(taskTransferValues(doc.id, doc.data, lists, names), fieldIds))\n },\n )\n },\n\n async lookup(ctx, requests) {\n const env = await crmTransferEnv(ctx)\n const lists = await readCrmTaskPicklists(env.firestore, env.orgId)\n const visible = (data: Record<string, unknown>) => visibleIn(env, data['visibleTo'])\n const found = { lookup: new Map<string, string[]>(), docs: new Map<string, Record<string, unknown>>() }\n for (const request of requests) {\n if (request.fieldId === TRANSFER_ID_FIELD) await lookupById(env, tasksOf(env), request, visible, found)\n else if (request.fieldId === 'externalId') {\n await lookupByField(tasksOf(env), request, 'externalId', (value) => value.trim(), visible, found)\n }\n }\n return lookupResult(found, (id, data) => taskTransferValues(id, data, lists))\n },\n\n async picklists(ctx, ids) {\n return crmPicklistLists(await crmTransferEnv(ctx), ids, NO_CUSTOM)\n },\n\n async addPicklistValues(ctx, picklistId, values) {\n await addCrmPicklistValues(await crmTransferEnv(ctx), picklistId, values)\n },\n\n lockedRules: () => [],\n\n async plan(ctx, input) {\n const env = await crmTransferEnv(ctx)\n requireCrmSuite(env, SUITE_ACT)\n requireSite(env, 'tasks')\n return buildTransferPlan(input)\n },\n\n async apply(ctx, chunk, writer) {\n const env = await crmTransferEnv(ctx)\n requireCrmSuite(env, SUITE_ACT)\n requireSite(env, 'tasks')\n const run: ApplyRun = {\n env,\n actorUid: ctx.actorUid,\n lists: await readCrmTaskPicklists(env.firestore, env.orgId),\n companies: new Map(),\n admit: crmRecordsRoom(env),\n }\n const results: TransferRowResult[] = []\n const undo: TransferUndoEntry[] = []\n await eachWithinBudget(chunk.rows as PlannedTransferRow[], () => writer.timeLeftMs(), async (row) => {\n const earlier = await writer.alreadyApplied(row.index)\n if (earlier) {\n results.push(earlier)\n return\n }\n const changed = row.diff.map((change) => change.fieldId)\n let result: TransferRowResult\n let entry: TransferUndoEntry | undefined\n try {\n const values = plannedValues(row)\n if (row.verdict === 'update' && row.recordId) {\n const ref = tasksOf(env).doc(row.recordId)\n const current = (await ref.get()).data()\n if (!current || !visibleIn(env, current['visibleTo'])) throw new TransferEngineError('notFound', 404, 'That task no longer exists.')\n const before = taskTransferValues(ref.id, current, run.lists)\n const fields = await taskFields(run, values, current, ctx.actorUid)\n await ref.update({\n ...asUpdate(fields),\n ...crmTaskListFields({ ...current, ...fields }),\n updatedAt: FieldValue.serverTimestamp(),\n })\n const after = (await readTasks(env, [ref.id], run.lists)).get(ref.id) ?? {}\n result = { row: row.index, outcome: 'updated', recordId: ref.id }\n entry = { row: row.index, recordId: ref.id, action: 'updated', previous: pickValues(before, changed), written: pickValues(after, changed) }\n } else {\n const fields = await taskFields(run, values, {}, ctx.actorUid)\n const kind = (fields['kind'] as 'call' | 'email' | 'meeting' | 'todo' | undefined) ?? 'todo'\n const priority = (fields['priority'] as 'low' | 'normal' | 'high' | undefined) ?? 'normal'\n const status = (fields['status'] as 'open' | 'done' | undefined) ?? 'open'\n const labels = crmTaskLabelsForNew(run.lists, { kind, priority, status })\n const scope = creationScope(env, ctx.actorUid)\n const stored = Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== null && value !== undefined))\n const ref = tasksOf(env).doc(createResourceUid())\n await ref.set({\n kind,\n priority,\n status,\n ...labels,\n dueAtMs: null,\n notes: '',\n completedAtMs: status === 'done' ? Date.now() : null,\n ...(status === 'done' && ctx.actorUid ? { completedByUid: ctx.actorUid } : {}),\n ...stored,\n ...scope,\n // What the Tasks list searches and sorts by (AGL-3321, AGL-3680).\n ...crmTaskListFields({\n title: stored['title'],\n priority: stored['priority'] ?? priority,\n visibleTo: scope['visibleTo'],\n }),\n })\n const after = (await readTasks(env, [ref.id], run.lists)).get(ref.id) ?? {}\n result = { row: row.index, outcome: 'created', recordId: ref.id }\n entry = { row: row.index, recordId: ref.id, action: 'created', written: pickValues(after, changed) }\n }\n } catch (error) {\n result = { row: row.index, outcome: 'failed', ...(row.recordId ? { recordId: row.recordId } : {}), message: failureMessage(error) }\n entry = undefined\n }\n await writer.markApplied(result, entry)\n results.push(result)\n if (entry) undo.push(entry)\n })\n return { results, undo }\n },\n\n async revert(ctx, snapshot, decisions) {\n const env = await crmTransferEnv(ctx)\n const run: ApplyRun = {\n env,\n actorUid: ctx.actorUid,\n lists: await readCrmTaskPicklists(env.firestore, env.orgId),\n companies: new Map(),\n admit: crmRecordsRoom(env),\n }\n const current = await readTasks(env, snapshot.entries.map((entry) => entry.recordId), run.lists)\n const done: TransferUndoStep[] = []\n const conflicts: TransferUndoStep[] = []\n const touched: FirebaseFirestore.DocumentReference[] = []\n for (const entry of snapshot.entries) {\n const step = planTransferUndo(entry, current.get(entry.recordId) ?? null)\n if (step.action === 'conflict' && decisions?.[entry.recordId] !== 'revert') {\n if (decisions?.[entry.recordId] === 'keep') done.push({ action: 'nothing', recordId: entry.recordId, why: 'alreadyReverted' })\n else conflicts.push(step)\n continue\n }\n if (step.action === 'nothing') {\n done.push(step)\n continue\n }\n const ref = tasksOf(env).doc(entry.recordId)\n if (entry.action === 'created') {\n await ref.delete()\n done.push({ action: 'delete', recordId: entry.recordId })\n continue\n }\n const stored = (await ref.get()).data() ?? {}\n const values = step.action === 'restore' || step.action === 'conflict' ? step.values : {}\n const fields = await taskFields(run, values, stored, ctx.actorUid)\n await ref.update({ ...asUpdate(fields), ...crmTaskListFields({ ...stored, ...fields }), updatedAt: FieldValue.serverTimestamp() })\n touched.push(ref)\n done.push(step)\n }\n if (touched.length) await restampCrmListFieldsOf(touched, 'crmTasks').catch(() => undefined)\n return { done, conflicts }\n },\n }\n}\n\n"],"names":["CRM_COLLECTIONS","CRM_TASK_PICKLIST_IDS","crmTaskLabelsForNew","crmTaskListFields","crmTaskPicklistLabels","resolveCrmSemanticPicklistWrite","createResourceUid","buildTransferPlan","planTransferUndo","TRANSFER_ID_FIELD","transferLookupNewName","restampCrmListFieldsOf","FieldValue","readCrmTaskPicklists","TASK_ALIASES","addCrmPicklistValues","countCrmExport","creationScope","crmMemberEmails","crmMembersTarget","crmPicklistLists","crmRecordsRoom","crmTransferEnv","eachWithinBudget","failureMessage","isoOf","lookupByField","lookupById","lookupResult","pickValues","plannedValues","readCrmExportPage","requireCrmRecords","requireCrmSuite","requireSite","textOf","TransferEngineError","visibleIn","companyForName","CRM_MEMBERS_TARGET","CRM_TIMESTAMP_FIELDS","TASK_TRANSFER_DERIVED","TASK_TRANSFER_FIELDS","TASK_TRANSFER_GROUPS","SUITE_ACT","NO_CUSTOM","tasksOf","env","orgRef","collection","tasks","taskTransferValues","id","task","lists","names","text","value","link","resolve","linked","labels","title","externalId","type","priority","status","dueAt","completedAt","assignee","contact","company","deal","notes","createdAt","updatedAt","readTasks","ids","out","Map","wanted","filter","includes","at","length","snapshot","firestore","getAll","slice","map","doc","data","exists","set","namesOf","field","Set","refs","run","what","get","taskFields","values","current","actorUid","fields","semantic","fieldId","meaningField","labelField","picklistId","list","write","ok","error","meaning","label","undefined","done","Date","now","ms","parse","String","NaN","Number","isFinite","deals","create","companies","admit","asUpdate","Object","fromEntries","entries","key","delete","tasksTransferResource","standard","derived","system","groups","matchKeys","normalizer","aliases","lookupTargets","count","ctx","options","scopeTokens","readPage","cursor","fieldIds","orgId","emails","docs","Boolean","contacts","Promise","all","uid","lookup","requests","visible","found","request","trim","picklists","addPicklistValues","lockedRules","plan","input","apply","chunk","writer","results","undo","rows","timeLeftMs","row","earlier","alreadyApplied","index","push","changed","diff","change","result","entry","verdict","recordId","ref","before","update","serverTimestamp","after","outcome","action","previous","written","stored","kind","scope","dueAtMs","completedAtMs","completedByUid","visibleTo","message","markApplied","revert","decisions","conflicts","touched","step","why","catch"],"mappings":"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"}
|