consumer-goods-promotion-bo-api-deploy
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDeploy the Promotion BO API
部署Promotion BO API
What this does
功能说明
Turns on the Consumer Goods Cloud TPM Promotion Business Object (BO) API in a
Salesforce org, interviews the user for one custom Workflow Step to add to
the promotion pipeline, generates and deploys that Apex class, registers it
against a BO API entity, wires it into the customer's chosen subset of
workflows, then proves the whole path end-to-end via
three REST endpoints:
{create, update, copy}POST /services/apexrest/<prefix>/promotions/initializePOST /services/apexrest/<prefix>/promotions/ingestGET /services/apexrest/<prefix>/promotions/status?importId=...
It is meant for headless delivery: point it at an org, answer the interview,
and the skill installs the step and verifies it. The Promotion BO API ships in
the CGCloud managed package (namespace released,
dev/beta). Every Apex identifier, object/field API name, and REST URL is derived
from the detected package prefix at runtime — this skill never hard-codes it.
cgcloudcgcloud_devderivereferences/- — sales-org partitioning, schema-first contract derivation, and the eight cross-workflow payload rules R1–R8.
references/conventions-and-payload-rules.md - — Phase 5a (generate + deploy the class) and Phase 5b (register + wire the step), with the full
references/generate-and-wire.md.register-step.apex - — Phase 6 (contract derivation, payload materialization, create/update/copy legs + verification).
references/smoke-and-verify.md - — the shipped
references/reference-example-set-comment-value.mdpreset (assets atSetCommentValue).assets/set-comment-value/
在Salesforce组织中启用Consumer Goods Cloud TPM Promotion业务对象(BO)API,询问用户想要添加到推广流程中的一个自定义工作流步骤,生成并部署对应的Apex类,将其注册到BO API实体,集成到客户选择的工作流子集,然后通过三个REST端点完成全流程端到端验证:
{create, update, copy}POST /services/apexrest/<prefix>/promotions/initializePOST /services/apexrest/<prefix>/promotions/ingestGET /services/apexrest/<prefix>/promotions/status?importId=...
本工具支持无界面交付:指定目标组织,回答访谈问题,即可完成步骤安装并验证。Promotion BO API包含在CGCloud托管包中(正式版命名空间为,开发/测试版为)。所有Apex标识符、对象/字段API名称以及REST URL均在运行时根据检测到的包前缀生成——本工具绝不会硬编码前缀。
cgcloudcgcloud_devderivereferences/- ——销售组织分区、基于Schema的契约推导,以及跨工作流的8项负载规则R1–R8。
references/conventions-and-payload-rules.md - ——第5a阶段(生成+部署类)和第5b阶段(注册+集成步骤),包含完整的
references/generate-and-wire.md代码。register-step.apex - ——第6阶段(契约推导、负载实例化、创建/更新/复制流程+验证)。
references/smoke-and-verify.md - ——附带的
references/reference-example-set-comment-value.md预设示例(资源位于SetCommentValue)。assets/set-comment-value/
Inputs to collect first
需预先收集的输入项
Ask before starting; do not guess.
- Org alias — the alias of the target org. Required.
sf - Sales org — required pre-parameter, default . Must be 4 chars, uppercase (mirrors
0001). Every downstream lookup (promotion template, tactic template, anchor account, product filter criteria, SKUs) is scoped to this sales org — seeTPMSetupData.validateSalesOrg("Sales-org partitioning").references/conventions-and-payload-rules.md - Yes to seeding? — Phase 3a fetches the packaged BO API seed and asks for confirmation before inserting ~232 rows across 8 objects. Offer for CI callers.
--yes - Dry run? — runs Phases 1, 2, 3a-preview, the Phase 5 interview, and a validate-only deploy. Writes nothing.
--dry-run
What the customization is gets collected in Phase 5's interview, once the
framework state is known.
开始前需询问用户,请勿猜测。
- 组织别名 ——目标组织的别名。必填项。
sf - 销售组织 ——必填前置参数,默认值为。必须为4位大写字符(与
0001规则一致)。所有下游查询(推广模板、策略模板、锚定客户、产品筛选条件、SKU)均限定在该销售组织范围内——详见TPMSetupData.validateSalesOrg中的“销售组织分区”部分。references/conventions-and-payload-rules.md - 是否允许初始化数据? ——第3a阶段会获取托管包中的BO API初始化数据,并在插入8个对象的约232条记录前征求用户确认。为CI调用者提供选项。
--yes - 试运行? ——参数会运行第1、2、3a预览阶段、第5阶段的访谈,以及仅验证的部署操作,不会写入任何数据。
--dry-run
自定义内容的具体信息会在第5阶段的访谈中收集,需在了解框架状态后进行。
Find the namespace first
先查找命名空间
The prefix depends on the installed build — detect it, do not guess. Released
package → ; dev/beta → ; source-deployed → no prefix.
cgcloudcgcloud_devbash
sf data query --target-org <alias> \
--query "SELECT NamespacePrefix FROM ApexClass WHERE Name = 'TPMSetupData'" --jsonRead and bind three variables used everywhere below:
records[0].NamespacePrefix- — Apex references:
PREFIX_DOT/cgcloud./ empty.cgcloud_dev. - — object/field API names:
PREFIX_UNDER/cgcloud__/ empty.cgcloud_dev__ - — REST paths:
URL_NS//cgcloud/ empty./cgcloud_dev
Every Apex call is ; every SOQL object/field is
; every REST URL is
. Substitute at run time.
${PREFIX_DOT}ClassName.method(...)${PREFIX_UNDER}Object__c/services/apexrest${URL_NS}/promotions/<endpoint>前缀取决于已安装的构建版本——需自动检测,请勿猜测。正式版包→;开发/测试版→;源码部署版→无前缀。
cgcloudcgcloud_devbash
sf data query --target-org <alias> \
--query "SELECT NamespacePrefix FROM ApexClass WHERE Name = 'TPMSetupData'" --json读取并绑定以下三个全局变量:
records[0].NamespacePrefix- ——Apex引用:
PREFIX_DOT/cgcloud./ 空值。cgcloud_dev. - ——对象/字段API名称:
PREFIX_UNDER/cgcloud__/ 空值。cgcloud_dev__ - ——REST路径:
URL_NS//cgcloud/ 空值。/cgcloud_dev
所有Apex调用格式为;所有SOQL对象/字段格式为;所有REST URL格式为。需在运行时动态替换。
${PREFIX_DOT}ClassName.method(...)${PREFIX_UNDER}Object__c/services/apexrest${URL_NS}/promotions/<endpoint>Phase 1 — Preflight
第1阶段——预检查
Run these and stop on the first failure.
- ;
sf --version(only if running shipped scripts).node --version - — confirm reachable, capture
sf org display --target-org <alias> --json+ org id (for display/logging only). Every REST call in later phases goes throughinstanceUrl, which uses the CLI's stored session — this skill never extracts the access token.sf api request rest - Validate the sales org locally: length 4, uppercase, non-blank (mirrors ). Fail fast instead of surfacing an Apex stack trace.
TPMSetupData.validateSalesOrg - TPM entitlement (Permission Set License) — the TPM app is licensed through the Permission Set License ("CGC Retail and Trade Promotion Management"); there is no
CGCRetailAndTPMMgmtPslUserLicense. Check the running user's PSL assignment:TPM AppOn scratch/dev-hub the PSL may be legitimately unassigned; detect env withsqlSELECT Id FROM PermissionSetLicenseAssign WHERE Assignee.Username = '<running-user-name>' AND PermissionSetLicense.DeveloperName = 'CGCRetailAndTPMMgmtPsl':SELECT OrganizationType, IsSandbox FROM Organization- OR
OrganizationType = 'Developer Edition'and empty → warn ("permset gate below is the real check"), continue.IsSandbox = true - Any other org shape and empty → stop ("TPM Permission Set License not assigned to the running user").
CGCRetailAndTPMMgmtPsl
- Running-user permission set (TPM Admin persona) — the setup needs a permission set granting the TPM Master Data Admin persona. The shipped permset is (packaged;
TPM_Master_Data_Adminis the bare name regardless of namespace), but customers frequently clone it under their own name — so treat a miss as "ask", not "fail". TakePermissionSet.Namefrom<running-user-name>:sf org display ... .result.usernameMatch → proceed. No match → do not hard-fail on the name: showsqlSELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment WHERE Assignee.Username = '<running-user-name>' AND (PermissionSet.Name = 'TPM_Master_Data_Admin' OR PermissionSet.Label LIKE '%TPM%Master Data Admin%')as the default option, but also ask the user to confirm whether they already hold an equivalent (possibly cloned/renamed) permission set for the TPM admin persona. Pause, wait for a cloned-permset confirmation or the assignment, then re-query. Proceed only once admin access is confirmed.sf org assign permset --name TPM_Master_Data_Admin --target-org <alias> - Namespace discovery (above).
运行以下步骤,遇到第一个失败项即停止。
- ;
sf --version(仅在运行附带脚本时需要)。node --version - ——确认组织可访问,捕获
sf org display --target-org <alias> --json和组织ID(仅用于显示/日志)。后续阶段的所有REST调用均通过instanceUrl执行,使用CLI存储的会话——本工具绝不会提取访问令牌。sf api request rest - 本地验证销售组织:长度为4、大写、非空(与规则一致)。提前失败,避免出现Apex堆栈跟踪。
TPMSetupData.validateSalesOrg - TPM授权(权限集许可证) ——TPM应用通过**权限集许可证(“CGC Retail and Trade Promotion Management”)授权;不存在
CGCRetailAndTPMMgmtPsl**用户许可证。检查当前用户的PSL分配情况:TPM App在临时沙箱/开发中心组织中,PSL可能未被合法分配;通过sqlSELECT Id FROM PermissionSetLicenseAssign WHERE Assignee.Username = '<running-user-name>' AND PermissionSetLicense.DeveloperName = 'CGCRetailAndTPMMgmtPsl'检测环境:SELECT OrganizationType, IsSandbox FROM Organization- 或
OrganizationType = 'Developer Edition'且查询结果为空 → 警告(“下方的权限集检查才是关键”),继续执行。IsSandbox = true - 其他组织类型且查询结果为空 → 停止(“当前用户未分配TPM权限集许可证”)。
CGCRetailAndTPMMgmtPsl
- 当前用户权限集(TPM管理员角色) ——设置操作需要授予TPM Master Data Admin角色的权限集。默认权限集为(托管包中的权限集;
TPM_Master_Data_Admin为无命名空间的裸名称),但客户经常会克隆该权限集并使用自定义名称——因此查询不到时应询问用户,而非直接失败。从PermissionSet.Name获取sf org display ... .result.username:<running-user-name>匹配成功 → 继续执行。无匹配结果 → 请勿因名称不匹配直接失败:将sqlSELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment WHERE Assignee.Username = '<running-user-name>' AND (PermissionSet.Name = 'TPM_Master_Data_Admin' OR PermissionSet.Label LIKE '%TPM%Master Data Admin%')作为默认选项展示,同时询问用户是否已持有等效的(可能已克隆/重命名)TPM管理员角色权限集。暂停执行,等待用户确认已持有克隆权限集或完成权限集分配,然后重新查询。仅在确认管理员权限后继续执行。sf org assign permset --name TPM_Master_Data_Admin --target-org <alias> - 命名空间检测(见前文)。
Phase 2 — Verify BO API framework state
第2阶段——验证BO API框架状态
Read-only; tells you whether Phase 3 needs to run. All three counts MUST be
scoped to — the framework rows are sales-org-partitioned.
<salesOrg>bash
undefined仅读取操作;用于判断是否需要运行第3阶段。所有三个查询结果必须限定在范围内——框架数据按销售组织分区。
<salesOrg>bash
undefined1. Workflow rows for this sales org (expect 4: create/update/copy/derive)
1. 该销售组织的工作流记录(预期为4条:create/update/copy/derive)
sf data query --target-org <alias> --json --query "
SELECT Name FROM ${PREFIX_UNDER}BO_API_Workflow__c
WHERE ${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
AND ${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
sf data query --target-org <alias> --json --query "
SELECT Name FROM ${PREFIX_UNDER}BO_API_Workflow__c
WHERE ${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
AND ${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
2. Step rows for this sales org
2. 该销售组织的步骤记录
sf data query --target-org <alias> --json --query "
SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Step__c
WHERE ${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
sf data query --target-org <alias> --json --query "
SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Step__c
WHERE ${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
3. Junction rows whose parent workflow belongs to a Promotion BO API for this sales org
3. 父工作流属于该销售组织Promotion BO API的关联记录
sf data query --target-org <alias> --json --query "
SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Workflow_Step__c
WHERE ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
AND ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
- Zero workflow rows → fresh for this sales org, Phase 3 will seed.
- Full state (4 workflow rows `create/update/copy/derive`; >0 step + junction rows) → Phase 3 is a no-op for this sales org (still run for idempotency; 3a shows zero net-new).
- Partial state → **error**; stop, report which rows are missing. A human inspects first.sf data query --target-org <alias> --json --query "
SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Workflow_Step__c
WHERE ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
AND ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
- 工作流记录数为0 → 该销售组织为全新状态,第3阶段将执行初始化。
- 完整状态(4条工作流记录`create/update/copy/derive`;步骤记录+关联记录数>0)→ 该销售组织无需执行第3阶段(仍会运行以保证幂等性;3a阶段会显示无新增记录)。
- 部分状态 → **错误**;停止执行,报告缺失的记录类型。需人工检查后再继续。Phase 3a — Preview the default BO API seed (fetch + confirm)
第3a阶段——预览默认BO API初始化数据(获取+确认)
The workflows/steps/junctions/entities/input-structures ship as CSVs inside the
packaged static resource; reads and
upserts them. Before running it, show the user what will land.
TPMSetupDataTPMSetupData.setupBOApi- Locate the resource:
Zero rows → stop ("TPMSetupData static resource not found — is the CGCloud package installed?"). Multiple rows → prefer the one whosebash
sf data query --target-org <alias> --json --query " SELECT Id, Name, NamespacePrefix, SystemModStamp, BodyLength FROM StaticResource WHERE Name = 'TPMSetupData'"matches Phase 1; if prefixes disagree, stop and ask.NamespacePrefix - Retrieve + expand the resource cross-platform via SFDX — use , which unpacks a zip StaticResource into a folder on every OS (no
sf project retrieve, nocurl—unzipisn't present on Windows by default). Retrieve into a dedicatedunzipsubproject so the read-only managed resource never mixes into the Phase-5a deploy tree (setup-data/, which is what gets deployed back):./.promotion-bo-api-deploy/force-appNon-success → stop, printbashmkdir -p ./.promotion-bo-api-deploy/setup-data/force-app/main/default printf '{ "packageDirectories": [{ "path": "force-app", "default": true }], "sourceApiVersion": "60.0" }' \ > ./.promotion-bo-api-deploy/setup-data/sfdx-project.json ( cd ./.promotion-bo-api-deploy/setup-data && sf project retrieve start \ --metadata "StaticResource:${PREFIX_UNDER}TPMSetupData" --target-org <alias> --json )errors.result - SFDX expands the zip to .
./.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/it. Expected:ls,0_BO_API__c.csv,0_BO_API_Entity__c.csv,0_BO_API_Output_Entity__c.csv,0_BO_API_Workflow__c.csv,0_BO_API_Workflow_Entity__c.csv,0_BO_API_Workflow_Step__c.csv,0_BO_API_Workflow_Workflow_Step__c.csv,0_BO_API_Step_Input_Structure__c.csv. Any missing → stop, print the delta.import.json - Confirm each target sObject exists: (etc.). Any describe failure → stop (package partially installed).
sf sobject describe --sobject '${PREFIX_UNDER}BO_API_Workflow__c' --target-org <alias> > /dev/null - Show a summary (row counts per object; total ~232 rows across 8 objects; "upsert on Unique_Key__c — re-running is idempotent"), plus the first 3 rows of and
0_BO_API_Workflow__c.csvwith0_BO_API_Workflow_Step__c.csv/{{NS}}substituted.{{SALES_ORG}} - Wait for confirmation unless . "no"/blank → abort clean, point at
--yes../.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/ - On → stop here; do not run 3b.
--dry-run
工作流/步骤/关联/实体/输入结构以CSV格式存储在托管包的静态资源中;会读取并执行upsert操作。运行该方法前,需向用户展示即将插入的数据。
TPMSetupDataTPMSetupData.setupBOApi- 定位资源:
无记录 → 停止执行(“未找到TPMSetupData静态资源——是否已安装CGCloud包?”)。多条记录 → 优先选择与第1阶段检测到的命名空间匹配的资源;若命名空间不匹配,停止执行并询问用户。bash
sf data query --target-org <alias> --json --query " SELECT Id, Name, NamespacePrefix, SystemModStamp, BodyLength FROM StaticResource WHERE Name = 'TPMSetupData'" - 通过SFDX跨平台获取并解压资源 ——使用,该命令可在所有操作系统上将压缩的StaticResource解压到文件夹(无需
sf project retrieve或curl——Windows默认未安装unzip)。将资源获取到专用的unzip子项目中,避免只读托管资源混入第5a阶段的部署目录(setup-data/,该目录内容会被部署回组织):./.promotion-bo-api-deploy/force-app执行失败 → 停止执行,打印bashmkdir -p ./.promotion-bo-api-deploy/setup-data/force-app/main/default printf '{ "packageDirectories": [{ "path": "force-app", "default": true }], "sourceApiVersion": "60.0" }' \ > ./.promotion-bo-api-deploy/setup-data/sfdx-project.json ( cd ./.promotion-bo-api-deploy/setup-data && sf project retrieve start \ --metadata "StaticResource:${PREFIX_UNDER}TPMSetupData" --target-org <alias> --json )中的错误信息。result - SFDX会将压缩包解压到目录。执行
./.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/查看该目录。预期包含:ls,0_BO_API__c.csv,0_BO_API_Entity__c.csv,0_BO_API_Output_Entity__c.csv,0_BO_API_Workflow__c.csv,0_BO_API_Workflow_Entity__c.csv,0_BO_API_Workflow_Step__c.csv,0_BO_API_Workflow_Workflow_Step__c.csv,0_BO_API_Step_Input_Structure__c.csv。若有缺失 → 停止执行,打印缺失的文件。import.json - 确认每个目标sObject存在:(以此类推)。若任何describe操作失败 → 停止执行(包未完全安装)。
sf sobject describe --sobject '${PREFIX_UNDER}BO_API_Workflow__c' --target-org <alias> > /dev/null - 展示摘要信息(每个对象的记录数;8个对象共约232条记录;“基于Unique_Key__c执行upsert——重复执行具有幂等性”),并展示和
0_BO_API_Workflow__c.csv的前3行(替换0_BO_API_Workflow_Step__c.csv/{{NS}})。{{SALES_ORG}} - 等待用户确认,除非使用参数。输入“no”或空白 → 干净终止,指向
--yes目录。./.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/ - 若使用参数 → 在此处停止;不执行3b阶段。
--dry-run
Phase 3b — Apply the default seed
第3b阶段——应用默认初始化数据
Only after 3a confirmation.
- .
PHASE3_START=$(date -u +%FT%TZ) - Run metadata-wizard setup via anon Apex: →
${PREFIX_DOT}TPMSetupData.setupMetadataWizard('<salesOrg>');.sf apex run --target-org <alias> --file ./.promotion-bo-api-deploy/setup-metadata-wizard.apex - Wait for the whole batch chain. self-chains via
GenericDemoSetupDataBatch; polling a single job id misses children. Poll by class:finish()Sleep 5s between polls; break when count is zero for two consecutive polls (covers the gap between a parent'sbashsf data query --target-org <alias> --json --query " SELECT COUNT() FROM AsyncApexJob WHERE ApexClass.Name = 'GenericDemoSetupDataBatch' AND CreatedDate >= ${PHASE3_START} AND Status NOT IN ('Completed','Failed','Aborted')"and the child'sfinish()row appearing).AsyncApexJob - Final check — every child succeeded (). Any
SELECT Id, Status, NumberOfErrors, ExtendedStatus FROM AsyncApexJob WHERE ApexClass.Name = 'GenericDemoSetupDataBatch' AND CreatedDate >= ${PHASE3_START}/Failed/Aborted→ stop, printNumberOfErrors > 0.ExtendedStatus - Repeat 2–4 for .
${PREFIX_DOT}TPMSetupData.setupBOApi('<salesOrg>');
仅在3a阶段确认后执行。
- 。
PHASE3_START=$(date -u +%FT%TZ) - 通过匿名Apex运行元数据向导设置:→
${PREFIX_DOT}TPMSetupData.setupMetadataWizard('<salesOrg>');。sf apex run --target-org <alias> --file ./.promotion-bo-api-deploy/setup-metadata-wizard.apex - 等待整个批处理链完成。通过
GenericDemoSetupDataBatch自动触发子批处理;仅轮询单个作业ID会遗漏子批处理。按类轮询:finish()每5秒轮询一次;当连续两次轮询结果均为0时停止(覆盖父批处理bashsf data query --target-org <alias> --json --query " SELECT COUNT() FROM AsyncApexJob WHERE ApexClass.Name = 'GenericDemoSetupDataBatch' AND CreatedDate >= ${PHASE3_START} AND Status NOT IN ('Completed','Failed','Aborted')"与子批处理finish()记录出现之间的间隙)。AsyncApexJob - 最终检查——所有子批处理均成功()。若存在
SELECT Id, Status, NumberOfErrors, ExtendedStatus FROM AsyncApexJob WHERE ApexClass.Name = 'GenericDemoSetupDataBatch' AND CreatedDate >= ${PHASE3_START}/Failed/Aborted的记录 → 停止执行,打印NumberOfErrors > 0。ExtendedStatus - 对重复步骤2–4。
${PREFIX_DOT}TPMSetupData.setupBOApi('<salesOrg>');
Phase 4 — Verify the BO API framework is on
第4阶段——验证BO API框架已启用
Re-run the Phase 2 queries. Expect 4 rows
(///, uniqueness on ), ≥46
rows, ≥54 junction
rows. Anything short → stop, print what's missing. Never claim success while a
component failed.
BO_API_Workflow__ccreateupdatecopyderiveUnique_Key__cBO_API_Workflow_Step__cBO_API_Workflow_Workflow_Step__c重新运行第2阶段的查询。预期得到4条记录(///,基于保证唯一性)、≥46条记录、≥54条关联记录。若数量不足 → 停止执行,打印缺失的记录类型。绝不能在组件安装失败的情况下声称成功。
BO_API_Workflow__ccreateupdatecopyderiveUnique_Key__cBO_API_Workflow_Step__cBO_API_Workflow_Workflow_Step__cPhase 5 — Interview: what does the user want to customize?
第5阶段——访谈:用户需要什么样的自定义?
The framework is now on. The customization is user-supplied — do not skip
the interview; do not invent an answer. Print:
text
The Promotion BO API framework is installed. To add a custom step, tell me:
- Which BO API entity does it target? (e.g. Promotion or Tactic — I'll list the exact entities registered in your org)
- Which workflows should it fire in? (create, update, copy)
- What does it read from the ingest input?
- What does it write on the SObject or elsewhere?
- Any preconditions or side effects I should know about?
If you want a worked example, say "use the SetCommentValue reference" —
it copies the tactic input Comment onto Tactic.Comment__c.框架已启用。自定义内容由用户提供——请勿跳过访谈;请勿自行编造答案。打印以下内容:
text
Promotion BO API框架已安装。如需添加自定义步骤,请告知:
- 目标BO API实体是什么?(例如Promotion或Tactic——我会列出您组织中已注册的具体实体)
- 需要在哪些工作流中触发?(create, update, copy)
- 从导入输入中读取哪些内容?
- 需要写入到SObject或其他位置的内容是什么?
- 是否有需要注意的前置条件或副作用?
如果需要参考示例,请说“use the SetCommentValue reference”——
该示例会将策略输入中的Comment复制到Tactic.Comment__c字段。5.1 — Answers to collect
5.1 ——需收集的答案
| Field | Required | Notes |
|---|---|---|
| yes | PascalCase Apex class name + BO API Workflow Step |
| yes | BO API entity |
| yes | Non-empty subset of |
| yes | Symbolic action string the class receives in |
| yes (≥1) | JSON paths to read from |
| yes | JSON type of the input paths — |
| yes (≥1) | |
| yes | One-sentence step description → |
| optional | Free-text guards before writing. |
| optional | Named packaged step to sort after; default |
| 字段 | 是否必填 | 说明 |
|---|---|---|
| 是 | PascalCase格式的Apex类名 + BO API工作流步骤 |
| 是 | BO API实体 |
| 是 | |
| 是 | 类在 |
| 是(≥1) | 从 |
| 是 | 输入路径的JSON类型—— |
| 是(≥1) | |
| 是 | 步骤的一句话描述 → 写入 |
| 可选 | 写入操作前的自由文本前置条件。 |
| 可选 | 要排在其后的托管包步骤名称;默认值为 |
5.2 — Interview flow
5.2 ——访谈流程
- Preset shortcut. If or the user says "use the SetCommentValue reference", load the answers from
--preset set-comment-value(references/reference-example-set-comment-value.md) and skip the interactive interview.assets/set-comment-value/interview-answers.jsonloads answers from a JSON file mirroring the 5.1 table (wins over--interview-file <path>).--preset - Ask the questions one at a time (or as a block); do not proceed until every required field has a concrete value.
- Show the entity's writeable fields after the user names the entity:
Anybash
sf sobject describe --target-org <alias> --sobject '${PREFIX_UNDER}<Entity>__c' --json \ | jq -r '.fields[] | select(.updateable == true and .createable == true) | .name'field must appear here; otherwise stop and ask for a different one (FLS is caller-scoped).outputWrites - Confirm the plan (recap Class/Entity/Workflows/Reads/Writes/Sort/Description → "Deploy + wire? [y/N]"). /blank → save answers to
nand exit../.promotion-bo-api-deploy/interview.json→ Phase 5a.y
- 预设快捷方式。若使用参数或用户说“use the SetCommentValue reference”,则从
--preset set-comment-value(references/reference-example-set-comment-value.md)加载答案并跳过交互式访谈。assets/set-comment-value/interview-answers.json参数会从镜像5.1表格的JSON文件加载答案(优先级高于--interview-file <path>)。--preset - 逐个提问(或批量提问);在所有必填字段获取具体值前请勿继续。
- 用户指定实体后,展示该实体的可写字段:
所有bash
sf sobject describe --target-org <alias> --sobject '${PREFIX_UNDER}<Entity>__c' --json \ | jq -r '.fields[] | select(.updateable == true and .createable == true) | .name'中的字段必须出现在该列表中;否则停止执行并询问用户更换字段(字段级安全性由调用者决定)。outputWrites - 确认计划(回顾类名/实体/工作流/读取内容/写入内容/排序/描述 → “是否部署并集成?[y/N]”)。输入“n”或空白 → 将答案保存到并退出。输入“y” → 进入第5a阶段。
./.promotion-bo-api-deploy/interview.json
Phases 5a / 5b — Generate + deploy, register + wire
第5a/5b阶段——生成+部署,注册+集成
Runbook in . In short: guard the class name for
idempotency; confirm target-field writeability; generate a namespace-agnostic
from the interview (reads /, derives the
prefix from at runtime); show + confirm; . Then, in one anon-Apex transaction, upsert one
(bare class name in ), one
junction per chosen workflow (never assume all three; dropped), and the
rows (RecordType per ). Both
upserts key on (idempotent).
references/generate-and-wire.mdCallablecurrentInputcurrentOutputgetSObjectType()sf project deploy startBO_API_Workflow_Step__cClassname__cBO_API_Workflow_Workflow_Step__cderiveBO_API_Step_Input_Structure__cinputPathTypeUnique_Key__c运行手册位于。简而言之:保证类名的幂等性;确认目标字段的可写性;根据访谈内容生成与命名空间无关的类(在运行时从推导前缀);展示并确认;执行。然后,在一个匿名Apex事务中,upsert一条记录(字段为裸类名)、为每个选定的工作流upsert一条关联记录(绝不默认选择全部三个;排除),以及记录(按设置RecordType)。所有upsert操作均基于保证幂等性。
references/generate-and-wire.mdCallablegetSObjectType()sf project deploy startBO_API_Workflow_Step__cClassname__cBO_API_Workflow_Workflow_Step__cderiveBO_API_Step_Input_Structure__cinputPathTypeUnique_Key__cPhase 6 — Smoke test through the REST endpoints
第6阶段——通过REST端点进行冒烟测试
Runbook in . Derive the payload contract per
invoked workflow from (schema-first), apply
rules R1–R8 from , resolve every
reference in-sales-org, materialize , then
run for each chosen workflow. Verify via a
direct query (any row → fail) plus
an field assertion against . Update and
copy legs run only if the interview included them.
references/smoke-and-verify.mdBO_API_Step_Input_Structure__creferences/conventions-and-payload-rules.md./out/smoke/{create,update,copy}.jsoninitialize → ingest → poll statusBO_API_Transaction_Log__c!= 'Calculated'outputWrites./out/smoke/expected.json运行手册位于。根据调用的工作流,从推导负载契约(基于Schema),应用中的规则R1–R8,解析所有销售组织内的引用,生成,然后为每个选定的工作流执行流程。通过直接查询验证(任何记录状态 → 失败),并针对断言字段的值。仅当访谈中包含update和copy工作流时,才会执行对应的测试流程。
references/smoke-and-verify.mdBO_API_Step_Input_Structure__creferences/conventions-and-payload-rules.md./out/smoke/{create,update,copy}.jsoninitialize → ingest → 轮询状态BO_API_Transaction_Log__c!= 'Calculated'./out/smoke/expected.jsonoutputWritesPhase 7 — Report
第7阶段——生成报告
Short status: org, sales org, dry-run flag; namespace prefix; BO API seed rows
before/after (Phases 2 & 4); interview (preset name or resolved ,
, , , ); class deploy result +
workflow-step id + junction ids (1–3); smoke import ids per invoked workflow, all
with matching write assertions — or exactly which failed (SOQL,
expected, actual); manual follow-ups ( if clean).
stepNameentityworkflowsinputPathsoutputWritesCalculatednone简短状态报告:组织信息、销售组织、试运行标记;命名空间前缀;BO API初始化数据前后记录数(第2和第4阶段);访谈信息(预设名称或解析后的、、、、);类部署结果+工作流步骤ID+关联记录ID(1–3条);每个调用工作流的冒烟测试导入ID,所有状态均为且写入断言匹配——或明确指出失败项(SOQL、预期值、实际值);人工后续操作(若无问题则为)。
stepNameentityworkflowsinputPathsoutputWritesCalculatednoneRules
规则
- Never claim success while any row for the smoke import ids has
BO_API_Transaction_Log__c.Status__c != 'Calculated'is the terminal success state (R2);Calculateddoes NOT exist in the picklist.Processed - Never claim success while any assertion fails, or while any
outputWritesGenericDemoSetupDataBatchsince the phase start is still running or failed.AsyncApexJob - Stop on the first failed preflight or verify check; report the exact failure.
- Never hard-code the namespace prefix — compose every Apex/SOQL/REST reference from the Phase-1 detection.
- Never invent Apex method signatures — use what the packaged /
TPMSetupDataobjects and the shipping REST endpoints declare.BO_API_* - The seeded BO API metadata is packaged content — preview it and get user confirmation before invoking .
TPMSetupData.setupBOApi - Never invent an interview answer. Absent a user and any /
--preset, stop and print the questions.--interview-file - Never wire a workflow the interview did not include. /
create/updateare individually opt-in;copyis always excluded.derive - Never compose a smoke payload from a hard-coded template — derive accepted paths per workflow from and validate user input against that contract first.
BO_API_Step_Input_Structure__c - Never resolve a reference value without the sales-org filter. A record under a different sales org is not valid for this invocation.
- 若任何冒烟测试导入ID对应的记录状态
BO_API_Transaction_Log__c,绝不能声称成功。Status__c != 'Calculated'是最终成功状态(规则R2);选择列表中不存在Calculated状态。Processed - 若任何断言失败,或自阶段启动以来存在仍在运行或失败的
outputWrites类型GenericDemoSetupDataBatch,绝不能声称成功。AsyncApexJob - 遇到第一个失败的预检查或验证项时立即停止;报告具体失败原因。
- 绝不硬编码命名空间前缀——所有Apex/SOQL/REST引用均从第1阶段的检测结果动态组合。
- 绝不自行编造Apex方法签名——使用托管包/
TPMSetupData对象和官方REST端点声明的签名。BO_API_* - 初始化的BO API元数据是托管包内容——在调用前需预览并获取用户确认。
TPMSetupData.setupBOApi - 绝不自行编造访谈答案。若无用户输入且未使用/
--preset参数,停止执行并打印问题列表。--interview-file - 绝不集成访谈中未包含的工作流。/
create/update为单独可选;copy始终排除在外。derive - 绝不从硬编码模板生成冒烟测试负载——需从推导每个工作流的可接受路径,并先验证用户输入是否符合该契约。
BO_API_Step_Input_Structure__c - 绝不未加销售组织筛选就解析引用值。属于其他销售组织的记录对本次调用无效。