@synity/bitrix-skills 1.3.0 → 1.3.2
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/CHANGELOG.md +16 -0
- package/bin/bitrix-skills.js +0 -0
- package/dist/cli.js +80 -31
- package/dist/features/task-sync/index.js +0 -0
- package/package.json +20 -16
- package/src/features/bx/feature.json +5 -3
- package/src/features/bx-calendar/feature.json +7 -3
- package/src/features/bx-crm/assets/SKILL.md +73 -36
- package/src/features/bx-crm/assets/commerce.md +56 -27
- package/src/features/bx-crm/assets/convert.md +70 -0
- package/src/features/bx-crm/assets/document.md +103 -0
- package/src/features/bx-crm/assets/flows.md +144 -0
- package/src/features/bx-crm/assets/onboard.md +91 -73
- package/src/features/bx-crm/assets/report.md +64 -33
- package/src/features/bx-crm/assets/research.md +62 -24
- package/src/features/bx-crm/assets/vn-norms.md +50 -0
- package/src/features/bx-crm/feature.json +7 -3
- package/src/features/bx-task/assets/lib/bx-api.sh +0 -0
- package/src/features/bx-task/assets/lib/bx-resolve-task.sh +0 -0
- package/src/features/bx-task/feature.json +6 -3
- package/src/features/task-sync/assets/githooks/commit-msg +0 -0
- package/src/features/task-sync/assets/githooks/install.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-attach-files.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-lib.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-render-digest.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-session-check.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-session-sync.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-skill-end.sh +0 -0
- package/src/features/task-sync/assets/scripts/bitrix-skill-start.sh +0 -0
- package/src/features/task-sync/assets/skill/SKILL.md +173 -0
- package/src/features/task-sync/feature.json +9 -4
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# bx:crm — Document Template Fill
|
|
2
|
+
|
|
3
|
+
Covers document and PDF generation for contracts, quotes, invoices, and CRM templates.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Trigger Phrases
|
|
8
|
+
|
|
9
|
+
- "render document", "fill contract", "generate PDF"
|
|
10
|
+
- "tạo báo giá PDF", "fill hợp đồng", "xuất PDF", "tạo hợp đồng từ deal"
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Strategy
|
|
15
|
+
|
|
16
|
+
Primary flow: use Bitrix native `crm.documentgenerator.*` REST via `codemode.request`.
|
|
17
|
+
|
|
18
|
+
Fallback flow: user provides a template file/link/text; fetch CRM data, propose a mapping table, and let the user run the final fill in their template tool.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Primary Flow — Bitrix Document Generator
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
// 1. List templates
|
|
26
|
+
codemode.request({
|
|
27
|
+
method: "GET",
|
|
28
|
+
path: "/crm.documentgenerator.template.list/"
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// 2. Pick template by NAME and supported entity type.
|
|
32
|
+
|
|
33
|
+
// 3. Render document
|
|
34
|
+
codemode.request({
|
|
35
|
+
method: "POST",
|
|
36
|
+
path: "/crm.documentgenerator.document.add/",
|
|
37
|
+
body: {
|
|
38
|
+
templateId,
|
|
39
|
+
entityTypeId,
|
|
40
|
+
entityId,
|
|
41
|
+
values: {}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Typical return: `{ document: { id, fileUrl } }`.
|
|
47
|
+
|
|
48
|
+
Use Bitrix `entityTypeId` values discovered from the portal; do not hardcode if uncertain.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Verify Checklist — Primary
|
|
53
|
+
|
|
54
|
+
- [ ] Template supports the selected `entityTypeId`.
|
|
55
|
+
- [ ] `entityId` belongs to the intended customer/deal.
|
|
56
|
+
- [ ] `fileUrl` returned and is accessible to the intended user.
|
|
57
|
+
- [ ] Visual check confirms placeholders are filled.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Fallback Flow — User Paste
|
|
62
|
+
|
|
63
|
+
1. Ask user for template path, URL, or pasted fields.
|
|
64
|
+
2. Fetch CRM data with read helpers only: deal, contact, company, products, estimate/invoice if needed.
|
|
65
|
+
3. Present a mapping table:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
| Placeholder | CRM source | Value preview |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| {{CompanyName}} | company.RQ_COMPANY_FULL_NAME | ... |
|
|
71
|
+
| {{TaxCode}} | company.RQ_VAT_ID | ... |
|
|
72
|
+
| {{DealTotal}} | deal.OPPORTUNITY + CURRENCY_ID | ... |
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
4. Ask user to confirm mapping before any generated document is shared.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Verify Checklist — Fallback
|
|
80
|
+
|
|
81
|
+
- [ ] Mapping table reviewed by user.
|
|
82
|
+
- [ ] No placeholder left unmapped unless user accepts it.
|
|
83
|
+
- [ ] Sensitive data appears only for the intended entity.
|
|
84
|
+
- [ ] Final document is visually checked before sending.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Pitfalls
|
|
89
|
+
|
|
90
|
+
| Pitfall | Risk | Action |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| Hardcoded `templateId` | Wrong portal template | Always list templates first |
|
|
93
|
+
| Entity type mismatch | 4xx or blank fields | Match template to entity type |
|
|
94
|
+
| Wrong `entityId` | PII leak | Verify ownership before render |
|
|
95
|
+
| Synity Docs MCP assumed | Tool unavailable | Use generic fallback flow |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## See Also
|
|
100
|
+
|
|
101
|
+
- Entity preparation: [onboard.md](./onboard.md)
|
|
102
|
+
- Estimate/invoice context: [commerce.md](./commerce.md)
|
|
103
|
+
- Contract PDF combos: [flows.md](./flows.md)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# bx:crm — Cross-Flow Combos
|
|
2
|
+
|
|
3
|
+
Use this file when the user asks for a multi-helper CRM workflow.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Combo Template
|
|
8
|
+
|
|
9
|
+
Each combo includes trigger phrases, sequence, verify checks, and underlying SOP files. On failure mid-chain, stop and report created IDs.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Combo 1 — Deal + Estimate
|
|
14
|
+
|
|
15
|
+
**Trigger:** "tạo deal kèm báo giá", "deal with quote".
|
|
16
|
+
|
|
17
|
+
**Sequence:**
|
|
18
|
+
```js
|
|
19
|
+
const deal = await createDealWithParties({ contact, company, deal })
|
|
20
|
+
await setDealProducts({ dealId: deal.dealId, products })
|
|
21
|
+
const estimate = await createEstimate({ dealId: deal.dealId, title, currency })
|
|
22
|
+
await setEstimateProducts({ estimateId: estimate.estimateId, products })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Verify:** dealId + estimateId returned; products and currency match.
|
|
26
|
+
|
|
27
|
+
**See also:** [onboard.md](./onboard.md), [commerce.md](./commerce.md)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Combo 2 — Deal + Invoice
|
|
32
|
+
|
|
33
|
+
**Trigger:** "tạo deal kèm invoice", "deal with invoice".
|
|
34
|
+
|
|
35
|
+
**Sequence:**
|
|
36
|
+
```js
|
|
37
|
+
const deal = await createDealWithParties({ contact, company, deal })
|
|
38
|
+
await setDealProducts({ dealId: deal.dealId, products })
|
|
39
|
+
// Run commerce.md steps 1-7: estimate -> confirmation -> approve -> invoice.
|
|
40
|
+
// Do not create an invoice directly from a new deal.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Verify:** estimate approved first; invoice status is `N`; payer and products are linked.
|
|
44
|
+
|
|
45
|
+
**See also:** [onboard.md](./onboard.md), [commerce.md](./commerce.md)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Combo 3 — Lead → Deal
|
|
50
|
+
|
|
51
|
+
**Trigger:** "convert lead", "qualify lead", "chuyển lead thành deal".
|
|
52
|
+
|
|
53
|
+
**Sequence:**
|
|
54
|
+
```js
|
|
55
|
+
await setLeadProducts({ leadId, products }) // only if products are provided
|
|
56
|
+
const result = await convertLeadToDeal({
|
|
57
|
+
leadId,
|
|
58
|
+
createDealParams: { stageId, categoryId },
|
|
59
|
+
upsertContact: true,
|
|
60
|
+
upsertCompany: true
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Verify:** lead is converted; deal exists; contact/company linked; warnings reviewed.
|
|
65
|
+
|
|
66
|
+
**See also:** [convert.md](./convert.md), [onboard.md](./onboard.md)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Combo 4 — Báo Giá → Hợp Đồng PDF
|
|
71
|
+
|
|
72
|
+
**Trigger:** "duyệt báo giá rồi tạo hợp đồng PDF", "quote to contract PDF".
|
|
73
|
+
|
|
74
|
+
**Sequence:**
|
|
75
|
+
```js
|
|
76
|
+
await approveEstimate({ estimateId })
|
|
77
|
+
await closeEstimatesForDeal({ dealId })
|
|
78
|
+
// Then run document.md Primary Flow: list templates, verify entity, render.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Verify:** estimate approved; other estimates closed; document URL returned.
|
|
82
|
+
|
|
83
|
+
**See also:** [commerce.md](./commerce.md), [document.md](./document.md)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Combo 5 — Quote-to-Cash Full
|
|
88
|
+
|
|
89
|
+
**Trigger:** "full quote to cash", "đầy đủ báo giá đến hóa đơn".
|
|
90
|
+
|
|
91
|
+
**Sequence:**
|
|
92
|
+
```js
|
|
93
|
+
const deal = await createDealWithParties({ contact, company, deal })
|
|
94
|
+
await setDealProducts({ dealId: deal.dealId, products })
|
|
95
|
+
const estimate = await createEstimate({ dealId: deal.dealId, title, currency })
|
|
96
|
+
await setEstimateProducts({ estimateId: estimate.estimateId, products })
|
|
97
|
+
// STOP. Do not continue until customer/user confirms the estimate.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Post-confirmation sequence:**
|
|
101
|
+
```js
|
|
102
|
+
await approveEstimate({ estimateId: estimate.estimateId })
|
|
103
|
+
await closeEstimatesForDeal({ dealId: deal.dealId })
|
|
104
|
+
const invoice = await createSmartInvoice({ dealId: deal.dealId, contactId: deal.contactId, companyId: deal.companyId })
|
|
105
|
+
await copyDealProductsToInvoice({ dealId: deal.dealId, invoiceId: invoice.invoiceId })
|
|
106
|
+
// Then run document.md Primary Flow for invoice PDF.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Verify:** deal, estimate, invoice, products, payer, and document URL all pass their subfile checklists.
|
|
110
|
+
|
|
111
|
+
**See also:** [onboard.md](./onboard.md), [commerce.md](./commerce.md), [document.md](./document.md)
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Combo 6 — Payment Link Send
|
|
116
|
+
|
|
117
|
+
**Trigger:** "gửi link thanh toán", "send payment link".
|
|
118
|
+
|
|
119
|
+
**Sequence:**
|
|
120
|
+
```js
|
|
121
|
+
// Start from an existing approved invoice or run Combo 5 through invoice creation.
|
|
122
|
+
const paysystems = await codemode.request({ method: "GET", path: "/sale.paysystem.list/" })
|
|
123
|
+
// Only use a portal-approved payment URL generator/API. Do not hand-construct signed URLs.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Verify:** invoice exists; payer linked; URL belongs to invoice and intended customer.
|
|
127
|
+
|
|
128
|
+
**See also:** [commerce.md](./commerce.md)
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Global Cross-Flow Rules
|
|
133
|
+
|
|
134
|
+
- If any write fails mid-chain, stop and report partial state with IDs.
|
|
135
|
+
- Never auto-rollback; helpers do not guarantee rollback support.
|
|
136
|
+
- Do not use `idempotencyKey` until MCP D1 migration is fixed.
|
|
137
|
+
- Keep currency consistent through deal, estimate, invoice, and product rows.
|
|
138
|
+
|
|
139
|
+
## Verify Checklist
|
|
140
|
+
|
|
141
|
+
- [ ] Every write step had user confirmation or an approved prior checkpoint.
|
|
142
|
+
- [ ] Invoice was created only after estimate approval unless user supplied an existing approved invoice.
|
|
143
|
+
- [ ] Document rendering followed [document.md](./document.md) template/entity checks.
|
|
144
|
+
- [ ] Partial failures reported created IDs and next manual action.
|
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
# bx:crm — Onboard (Write SOP)
|
|
2
2
|
|
|
3
|
-
Covers: contact, company (VN), deal, lead creation.
|
|
3
|
+
Covers: contact, company (VN), deal, lead creation, and safe updates.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Phone Normalization (CRITICAL)
|
|
8
|
-
|
|
9
|
-
`upsertContact` auto-normalizes — **pass raw phone, never pre-format**.
|
|
10
|
-
|
|
11
|
-
| Input | Stored as |
|
|
12
|
-
|-------|-----------|
|
|
13
|
-
| `0977680550` | `+84977680550` |
|
|
14
|
-
| `0977 68 0550` | `+84977680550` |
|
|
15
|
-
| `84977680550` | `+84977680550` |
|
|
16
|
-
| `+84977680550` | `+84977680550` (idempotent) |
|
|
17
|
-
| `+6591234567` | `+6591234567` (non-VN: keep) |
|
|
5
|
+
## Phone + VN Normalization
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## HONORIFIC
|
|
7
|
+
See [vn-norms.md](./vn-norms.md) for phone `+84`, HONORIFIC, address, and MST rules.
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|-------|-------|
|
|
25
|
-
| Anh / Mr | `Anh` |
|
|
26
|
-
| Chị / Ms / Mrs | `Chị` |
|
|
27
|
-
| Ông (senior male) | `Ông` |
|
|
28
|
-
| Bà (senior female) | `Bà` |
|
|
29
|
-
| Unknown | omit field |
|
|
9
|
+
`upsertContact` auto-normalizes phone numbers. Pass raw phone, never pre-format.
|
|
30
10
|
|
|
31
11
|
---
|
|
32
12
|
|
|
@@ -35,93 +15,131 @@ Covers: contact, company (VN), deal, lead creation.
|
|
|
35
15
|
```js
|
|
36
16
|
upsertContact({
|
|
37
17
|
name: "Nguyen Van A",
|
|
38
|
-
phone: "0977680550",
|
|
18
|
+
phone: "0977680550",
|
|
39
19
|
email: "a@example.com",
|
|
40
20
|
HONORIFIC: "Anh",
|
|
41
|
-
match: { phone }
|
|
21
|
+
match: { phone }
|
|
42
22
|
})
|
|
43
23
|
```
|
|
44
24
|
|
|
45
|
-
Minimum: `name` + (`phone` OR `email`) + `HONORIFIC`
|
|
46
|
-
|
|
47
|
-
---
|
|
25
|
+
Minimum: `name` + (`phone` OR `email`) + `HONORIFIC` if determinable. See [vn-norms.md](./vn-norms.md) for the HONORIFIC table.
|
|
48
26
|
|
|
49
27
|
## Company — VN Rule (CRITICAL)
|
|
50
28
|
|
|
51
|
-
**
|
|
29
|
+
**Never call `crm.company.add` directly for VN companies with MST.**
|
|
52
30
|
|
|
53
31
|
`upsertCompanyByTaxCode` auto-does:
|
|
54
|
-
1. VietQR lookup → legal name + address from GDT tax registry
|
|
55
|
-
2. Sets `RQ_INN` = `RQ_VAT_ID` = taxCode on requisite
|
|
56
|
-
3. Creates address entity
|
|
32
|
+
1. VietQR lookup → legal name + address from GDT tax registry.
|
|
33
|
+
2. Sets `RQ_INN` = `RQ_VAT_ID` = taxCode on requisite.
|
|
34
|
+
3. Creates address entity for document templates.
|
|
57
35
|
|
|
58
36
|
```js
|
|
59
37
|
upsertCompanyByTaxCode({
|
|
60
|
-
taxCode: "0315427733",
|
|
61
|
-
title: "Pegasus Realty",
|
|
38
|
+
taxCode: "0315427733",
|
|
39
|
+
title: "Pegasus Realty",
|
|
62
40
|
phone: "...",
|
|
63
|
-
email: "..."
|
|
41
|
+
email: "..."
|
|
64
42
|
})
|
|
65
43
|
```
|
|
66
44
|
|
|
67
|
-
**Verify
|
|
68
|
-
- `RQ_INN` populated
|
|
69
|
-
- `
|
|
70
|
-
-
|
|
71
|
-
- Address entity exists (ENTITY_TYPE_ID=8)
|
|
72
|
-
|
|
73
|
-
---
|
|
45
|
+
**Verify company:**
|
|
46
|
+
- `RQ_INN` and `RQ_VAT_ID` populated.
|
|
47
|
+
- `RQ_COMPANY_FULL_NAME` populated from registry.
|
|
48
|
+
- Address entity exists (`ENTITY_TYPE_ID=8`).
|
|
74
49
|
|
|
75
50
|
## Deal — Required Fields
|
|
76
51
|
|
|
77
|
-
Use `createDealWithParties` for atomic contact + company + deal
|
|
52
|
+
Use `createDealWithParties` for atomic contact + company + deal.
|
|
78
53
|
|
|
79
54
|
| Field | How to get | Example |
|
|
80
|
-
|
|
81
|
-
| `STAGE_ID` | `codemode.entityIds()`
|
|
82
|
-
| `CURRENCY_ID` |
|
|
83
|
-
| `OPPORTUNITY` |
|
|
84
|
-
| `SOURCE_ID` |
|
|
85
|
-
|
|
86
|
-
**SOURCE_ID mapping:**
|
|
87
|
-
|
|
88
|
-
| Context | Value |
|
|
89
|
-
|---------|-------|
|
|
90
|
-
| Event / offline meeting | `OTHER` |
|
|
91
|
-
| Website / form | `WEB` |
|
|
92
|
-
| Referral / partner | `PARTNER` |
|
|
93
|
-
| Cold call / outreach | `CALL` |
|
|
94
|
-
| Social / ad | `ADVERTISING` |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `STAGE_ID` | `codemode.entityIds()` | never hardcode |
|
|
57
|
+
| `CURRENCY_ID` | user input | `USD`, `VND` |
|
|
58
|
+
| `OPPORTUNITY` | user input number | `588` |
|
|
59
|
+
| `SOURCE_ID` | mapping below | `OTHER` |
|
|
60
|
+
|
|
61
|
+
**SOURCE_ID mapping:** Event/offline=`OTHER`, website/form=`WEB`, partner=`PARTNER`, call=`CALL`, social/ad=`ADVERTISING`.
|
|
95
62
|
|
|
96
63
|
**COMMENTS — BANT template:**
|
|
64
|
+
```text
|
|
65
|
+
Budget: <amount/range/unknown>
|
|
66
|
+
Authority: <decision maker>
|
|
67
|
+
Need: <problem or goal>
|
|
68
|
+
Timeline: <start date>
|
|
69
|
+
Notes: <context>
|
|
97
70
|
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
71
|
+
|
|
72
|
+
## Lead — Create + Products
|
|
73
|
+
|
|
74
|
+
Use lead when the prospect is unqualified or missing deal-level budget/timeline.
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
createLeadWithParties({
|
|
78
|
+
title: "Nguyen Van A - CRM training",
|
|
79
|
+
name: "Nguyen Van A",
|
|
80
|
+
phone: "0977680550",
|
|
81
|
+
email: "a@example.com",
|
|
82
|
+
companyTitle: "ABC Training",
|
|
83
|
+
sourceId: "WEB",
|
|
84
|
+
comments: "Need: CRM training\nTimeline: Q2"
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
setLeadProducts({
|
|
88
|
+
leadId,
|
|
89
|
+
products: [{ name: "Workshop", price: 5000000, quantity: 1, currency: "VND" }]
|
|
90
|
+
})
|
|
103
91
|
```
|
|
104
92
|
|
|
105
|
-
|
|
93
|
+
For qualification, convert via [convert.md](./convert.md).
|
|
94
|
+
|
|
95
|
+
## Update Existing Entity
|
|
96
|
+
|
|
97
|
+
1. Search before update: `codemode.search({ keywords, entities: ["contact","company","deal","lead"], intent: "read" })`.
|
|
98
|
+
2. Read current entity by ID and validate target ownership.
|
|
99
|
+
3. Discover allowed fields with `crm.{entity}.fields` when field names are uncertain.
|
|
100
|
+
4. Show before/after field diff and confirm every update, even for a single match.
|
|
101
|
+
5. Send only changed fields.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
// MCP gap fallback when no typed update helper exists.
|
|
105
|
+
codemode.request({
|
|
106
|
+
method: "POST",
|
|
107
|
+
path: "/crm.deal.update/",
|
|
108
|
+
body: { id: dealId, fields: { OPPORTUNITY: 9000000, ASSIGNED_BY_ID: 12 } }
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Skip this search rule for `upsertContact` and `upsertCompanyByTaxCode`; they dedup internally.
|
|
113
|
+
|
|
114
|
+
## Custom Fields & Routing
|
|
115
|
+
|
|
116
|
+
- `UF_CRM_*`: discover with `codemode.entityIds()` or `crm.{entity}.fields`.
|
|
117
|
+
- `categoryId`: route deal to the correct pipeline.
|
|
118
|
+
- `assignedById`: responsible user. Resolve user IDs before writing.
|
|
119
|
+
- Do not echo full custom-field PII unless user explicitly asks.
|
|
120
|
+
|
|
121
|
+
## Idempotency
|
|
122
|
+
|
|
123
|
+
Omit `idempotencyKey`. **Verified 2026-05-15:** D1 table `idempotency_keys` missing; passing the key throws `SQLITE_ERROR` before write. Re-verify when MCP version changes. Escalation: MCP team.
|
|
106
124
|
|
|
107
125
|
---
|
|
108
126
|
|
|
109
127
|
## Verify Checklist (mandatory after every write op)
|
|
110
128
|
|
|
111
|
-
- [ ] Contact:
|
|
112
|
-
- [ ] Company: `RQ_VAT_ID` populated, address entity exists
|
|
113
|
-
- [ ] Deal: `STAGE_ID` set, `OPPORTUNITY > 0`, payer linked
|
|
114
|
-
- [ ] Lead: `SOURCE_ID` set, phone/email present
|
|
129
|
+
- [ ] Contact: VN phone stored with `+84`; HONORIFIC matches [vn-norms.md](./vn-norms.md).
|
|
130
|
+
- [ ] Company: `RQ_VAT_ID` populated, address entity exists, MST format valid.
|
|
131
|
+
- [ ] Deal: `STAGE_ID` set, `OPPORTUNITY > 0`, payer linked.
|
|
132
|
+
- [ ] Lead: `SOURCE_ID` set, phone/email present, products set when user requested products.
|
|
133
|
+
- [ ] Update: only intended fields changed.
|
|
115
134
|
|
|
116
135
|
---
|
|
117
136
|
|
|
118
137
|
## Known Pitfalls
|
|
119
138
|
|
|
120
139
|
| Mistake | Symptom | Fix |
|
|
121
|
-
|
|
122
|
-
| Used `bx
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| Used `bx:task` for CRM | Creates project task, not CRM record | Use `/bx:crm` |
|
|
123
142
|
| `crm.contact.add` directly | No dedup, phone not normalized | `upsertContact` |
|
|
124
|
-
| `crm.company.add` directly | Requisite missing
|
|
143
|
+
| `crm.company.add` directly | Requisite missing tax fields + address | `upsertCompanyByTaxCode` |
|
|
125
144
|
| Passed `idempotencyKey` | D1 table error at runtime | Omit the field |
|
|
126
|
-
| Pre-formatted phone `+84977...` | Helper double-normalizes (idempotent, fine) | Pass raw anyway |
|
|
127
145
|
| Skipped verify step | Bugs discovered sessions later | Always verify |
|
|
@@ -4,10 +4,16 @@ Covers: dealForecast, stuckInStage, overdueActivities, arReport, teamReport.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Timezone
|
|
8
|
+
|
|
9
|
+
MCP may return UTC ISO timestamps. Convert display times to Asia/Saigon (UTC+7). Date-only summaries use the Asia/Saigon date.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
7
13
|
## Helper Selection
|
|
8
14
|
|
|
9
15
|
| Report | Helper | Typical Frequency |
|
|
10
|
-
|
|
16
|
+
|---|---|---|
|
|
11
17
|
| Pipeline forecast month/quarter | `dealForecast` | Weekly |
|
|
12
18
|
| Deals stuck too long in stage | `stuckInStage` | Weekly |
|
|
13
19
|
| Overdue activities | `overdueActivities` | Daily |
|
|
@@ -19,61 +25,71 @@ Covers: dealForecast, stuckInStage, overdueActivities, arReport, teamReport.
|
|
|
19
25
|
|
|
20
26
|
## Output Template — dealForecast
|
|
21
27
|
|
|
22
|
-
```
|
|
23
|
-
| Month
|
|
24
|
-
|
|
25
|
-
| T5/26
|
|
28
|
+
```text
|
|
29
|
+
| Month | Predicted | Won | At Risk | # Deals |
|
|
30
|
+
|---|---:|---:|---:|---:|
|
|
31
|
+
| T5/26 | $X | $Y | $Z | N |
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
Red flags: deals in same stage > 30 days with no activity
|
|
34
|
+
Red flags: deals in same stage > 30 days with no activity.
|
|
29
35
|
|
|
30
36
|
---
|
|
31
37
|
|
|
32
38
|
## Output Template — stuckInStage
|
|
33
39
|
|
|
34
|
-
Group by pipeline stage
|
|
40
|
+
Group by pipeline stage. Sort by days stuck DESC within each group.
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
```
|
|
42
|
+
```text
|
|
38
43
|
Stage: [Stage Name]
|
|
39
|
-
|
|
40
|
-
2. ...
|
|
44
|
+
1. [dealId] "[title]" — [owner] — [N] days — last activity: [date]
|
|
41
45
|
```
|
|
42
46
|
|
|
43
47
|
---
|
|
44
48
|
|
|
45
49
|
## Output Template — overdueActivities
|
|
46
50
|
|
|
47
|
-
```
|
|
51
|
+
```text
|
|
48
52
|
| Deal/Contact | Activity | Assigned | Due | Days Overdue |
|
|
49
|
-
|
|
50
|
-
| ...
|
|
53
|
+
|---|---|---|---|---:|
|
|
54
|
+
| ... | ... | ... | ... | N |
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
Sort by days overdue DESC. Group by assignee if
|
|
57
|
+
Sort by days overdue DESC. Group by assignee if more than 10 items.
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
57
|
-
## Output Template — arReport
|
|
61
|
+
## Output Template — arReport
|
|
58
62
|
|
|
63
|
+
```text
|
|
64
|
+
| Bucket | Amount | % of Total | # Invoices |
|
|
65
|
+
|---|---:|---:|---:|
|
|
66
|
+
| Current | $X | X% | N |
|
|
67
|
+
| 1-30d | $X | X% | N |
|
|
68
|
+
| 31-60d | $X | X% | N |
|
|
69
|
+
| 61-90d | $X | X% | N |
|
|
70
|
+
| 90d+ | $X | X% | N |
|
|
59
71
|
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
|
|
73
|
+
For 90d+ bucket: list each debtor below the table.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Output Template — teamReport
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
| Rep | # Deals | Total Value | Avg Response | Overdue |
|
|
81
|
+
|---|---:|---:|---:|---:|
|
|
82
|
+
| ... | N | $X | Nh | N |
|
|
67
83
|
```
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
Sort by Total Value DESC. Flag response time and overdue counts using the thresholds below.
|
|
70
86
|
|
|
71
87
|
---
|
|
72
88
|
|
|
73
89
|
## Interpretation Thresholds
|
|
74
90
|
|
|
75
|
-
| Metric | Yellow
|
|
76
|
-
|
|
91
|
+
| Metric | Yellow | Red | Action |
|
|
92
|
+
|---|---|---|---|
|
|
77
93
|
| stuckInStage | > 14 days | > 30 days | Assign follow-up activity |
|
|
78
94
|
| AR 90d+ bucket | > 10% total AR | > 20% total AR | Escalate to account manager |
|
|
79
95
|
| teamReport response_time | > 12h | > 24h | Flag for coaching |
|
|
@@ -84,15 +100,30 @@ For 90d+ bucket: list each debtor individually below the table.
|
|
|
84
100
|
## Common Report Calls
|
|
85
101
|
|
|
86
102
|
```js
|
|
87
|
-
// Weekly pipeline review
|
|
88
103
|
dealForecast({ period: "month", includeAtRisk: true })
|
|
104
|
+
stuckInStage({ minDays: 14, pipelineId: null })
|
|
105
|
+
overdueActivities({ assignedTo: null })
|
|
106
|
+
arReport({ groupByBucket: true, listDebtors90Plus: true })
|
|
107
|
+
teamReport({ period: "month", includeResponseTime: true })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
89
111
|
|
|
90
|
-
|
|
91
|
-
stuckInStage({ minDays: 14, pipelineId: null }) // null = all pipelines
|
|
112
|
+
## Example Dialog — dealForecast
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
overdueActivities({ assignedTo: null }) // null = all reps
|
|
114
|
+
User: "review pipeline tuần này"
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
LLM behavior: call `dealForecast({ period: "month", includeAtRisk: true })`, convert dates to Asia/Saigon, then summarize risks.
|
|
117
|
+
|
|
118
|
+
Sample output:
|
|
119
|
+
```text
|
|
120
|
+
Pipeline T5/26: predicted 1.2B VND, won 420M, at risk 180M across 4 deals.
|
|
121
|
+
Red flag: 3 deals stuck >30 days with no recent activity.
|
|
122
|
+
Next action: assign follow-up activities today for all red-flag deals.
|
|
98
123
|
```
|
|
124
|
+
|
|
125
|
+
## Verify Checklist
|
|
126
|
+
|
|
127
|
+
- [ ] Date/time displayed in Asia/Saigon.
|
|
128
|
+
- [ ] Totals reconcile with helper output.
|
|
129
|
+
- [ ] Red/yellow flags use the threshold table above.
|