Compare commits
No commits in common. "66be5d83ff51a5e5bfb66b55210e7779384a5b67" and "e058dedb82a0b15b9ce3741a522950b37a9ca0be" have entirely different histories.
66be5d83ff
...
e058dedb82
|
|
@ -15,9 +15,3 @@ __pycache__/
|
|||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Scratch retrieved metadata (org retrieves for reference only)
|
||||
retrieved/
|
||||
|
||||
# Claude Code agent state
|
||||
.codex
|
||||
|
|
|
|||
168
AGENT.md
168
AGENT.md
|
|
@ -1,168 +0,0 @@
|
|||
# Agent Instructions — Salesforce Appraiser Review Letter
|
||||
|
||||
> Read this file at the start of every iteration. It is the canonical operating guide.
|
||||
|
||||
---
|
||||
|
||||
## Role
|
||||
|
||||
You are a senior Salesforce developer working autonomously on the appraiser review letter project.
|
||||
You have full access to the codebase and can read, write, and run shell commands.
|
||||
|
||||
---
|
||||
|
||||
## Core Loop
|
||||
|
||||
### 1. Orient
|
||||
|
||||
- Read `PROJECT-SPEC.md` — requirements and acceptance criteria for Phase 2
|
||||
- Read `IMPLEMENTATION_PLAN.md` — task list and current status
|
||||
- Read `DECISIONS.md` — architecture decisions already in force
|
||||
- Run `git log --oneline -10` — see what has been done
|
||||
- Check for any prior escalations in `IMPLEMENTATION_PLAN.md`
|
||||
|
||||
### 2. Pick ONE Task
|
||||
|
||||
- Find the first unchecked task `- [ ]` in `IMPLEMENTATION_PLAN.md`
|
||||
- If all tasks are checked, output `<promise>DONE</promise>` and exit
|
||||
- Work only on this one task — do not pick up the next one
|
||||
|
||||
### 3. Implement
|
||||
|
||||
- Follow the existing code patterns in the project
|
||||
- Read the relevant existing classes/LWCs before writing new code
|
||||
- Study the CLM tracking pattern as the model for eSignature tracking
|
||||
|
||||
### 4. Verify — BLOCKING before commit
|
||||
|
||||
```bash
|
||||
# Set up the CLI path (system Node v12 is too old — always use nvm Node)
|
||||
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||
|
||||
# Deploy source to org
|
||||
$NODE $SF project deploy start \
|
||||
--source-dir force-app \
|
||||
--target-org appraiser-dev
|
||||
|
||||
# Run Apex tests (synchronous, human-readable output)
|
||||
$NODE $SF apex run test \
|
||||
--target-org appraiser-dev \
|
||||
--class-names DocusignESignatureServiceTest,CLMAdminServiceTest,AppraiserCasePayloadBuilderTest,CLMDocGenCalloutTest \
|
||||
--result-format human \
|
||||
--synchronous
|
||||
```
|
||||
|
||||
**Do not commit if deploy fails or tests fail.**
|
||||
|
||||
For metadata-only tasks (adding custom fields, no Apex changes): deploy and confirm the object deploys cleanly, but you do not need to run all test classes — run a smoke test against the affected test class only.
|
||||
|
||||
### 5. Commit and Mark Done
|
||||
|
||||
Commit with this exact trailer format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
<optional body>
|
||||
|
||||
Agent: claude-sonnet-4-6
|
||||
Tests: N/N passing | N/A (metadata-only)
|
||||
Tests-Added: +N | 0
|
||||
TypeScript: N/A (Apex project)
|
||||
```
|
||||
|
||||
Then mark the task `- [x]` in `IMPLEMENTATION_PLAN.md` and commit that update in the same commit.
|
||||
|
||||
### 6. Exit
|
||||
|
||||
Output a brief summary (what you did, test results, what is next) and exit.
|
||||
The loop will restart you with fresh context for the next task.
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
1. **One task per iteration.** Never work on multiple tasks.
|
||||
2. **Read before writing.** Always read the existing class/LWC before modifying it.
|
||||
3. **Follow existing patterns.** The CLM tracking pattern is the model for eSignature tracking.
|
||||
4. **Tests are mandatory for every Apex change.** Add to the existing test classes — do not create new ones unless the spec says to.
|
||||
5. **Never add fields or methods not required by the spec.** Implement what is asked, nothing more.
|
||||
6. **Never modify production data.** All DML in tests uses `@isTest` and `Test.startTest()/stopTest()`.
|
||||
7. **Mock HTTP callouts in tests.** Use `HttpCalloutMock` — same pattern as existing test classes.
|
||||
8. **Deploy before committing.** Never commit code that does not deploy cleanly.
|
||||
9. **Follow DECISIONS.md.** Do not change approaches listed as Accepted without escalating.
|
||||
10. **If stuck, escalate.** Do not invent missing requirements.
|
||||
|
||||
---
|
||||
|
||||
## Escalation Protocol
|
||||
|
||||
Stop and add this block at the top of `IMPLEMENTATION_PLAN.md`, then output `<promise>STUCK</promise>`:
|
||||
|
||||
```markdown
|
||||
## ESCALATION REQUIRED
|
||||
- **Task:** [current task]
|
||||
- **Issue:** [what is ambiguous/missing/conflicting]
|
||||
- **What I need:** [specific question or decision]
|
||||
- **What I'd do if I had to guess:** [best guess]
|
||||
```
|
||||
|
||||
Escalate when:
|
||||
- The spec has no FR covering a required decision
|
||||
- A constraint in DECISIONS.md conflicts with the spec
|
||||
- A Salesforce platform limit or API behavior is unclear
|
||||
- A named credential or metadata record is missing from the org
|
||||
- Any task requires deleting or overwriting existing custom fields already in use
|
||||
|
||||
---
|
||||
|
||||
## Output Signals
|
||||
|
||||
- `<promise>PLANNED</promise>` — plan created, ready for build
|
||||
- `<promise>DONE</promise>` — all tasks complete
|
||||
- `<promise>STUCK</promise>` — needs human intervention
|
||||
- `<promise>ERROR</promise>` — unrecoverable error
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
force-app/main/default/
|
||||
├── classes/
|
||||
│ ├── CLMAdminService.cls ← orchestration + persistDocGenResult pattern
|
||||
│ ├── CLMAdminServiceTest.cls ← test pattern to follow
|
||||
│ ├── CLMDocGenCallout.cls
|
||||
│ ├── CLMDocGenCalloutTest.cls
|
||||
│ ├── DocusignESignatureService.cls ← add createEnvelope() here
|
||||
│ ├── DocusignESignatureServiceTest.cls
|
||||
│ ├── AppraiserCasePayloadBuilder.cls
|
||||
│ └── AppraiserCasePayloadBuilderTest.cls
|
||||
├── lwc/
|
||||
│ ├── clmDocGenWorkbench/ ← add Send for Signature button here
|
||||
│ └── docusignEsignWorkbench/ ← read-only browsing (do not modify Phase 2)
|
||||
└── objects/
|
||||
└── Appraiser_Case__c/
|
||||
└── fields/ ← add 5 eSignature tracking fields here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SF CLI Invocation (Always Use This Pattern)
|
||||
|
||||
```bash
|
||||
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||
$NODE $SF <subcommand> <args>
|
||||
```
|
||||
|
||||
Target org alias: `appraiser-dev`
|
||||
|
||||
---
|
||||
|
||||
## Context Anchor
|
||||
|
||||
- **Phase 1 is complete** — CLM doc generation works end-to-end. Do not modify it.
|
||||
- **Phase 2 goal** — after a CLM doc is generated and attached, enable sending it for eSignature using a DocuSign template (Option A).
|
||||
- **The authoritative plan** is in `NEXT_STEPS_DOCGEN.md` under Phase 2. `IMPLEMENTATION_PLAN.md` is the execution tracker derived from it.
|
||||
122
DECISIONS.md
122
DECISIONS.md
|
|
@ -1,122 +0,0 @@
|
|||
# Architecture Decision Records
|
||||
|
||||
> Non-obvious decisions that agents must not undo.
|
||||
> Read during Orient phase every iteration.
|
||||
|
||||
---
|
||||
|
||||
### ADR-001: Option A (template-based envelope) for createEnvelope()
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
Two approaches were considered for creating a DocuSign eSignature envelope:
|
||||
- Option A: POST with a `templateId` — DocuSign template defines recipients/tabs, Salesforce only passes merge data at send time
|
||||
- Option B: POST with a raw document (download CLM blob, upload as base64) — Salesforce manages all recipient config
|
||||
|
||||
**Decision:**
|
||||
Use Option A. Simpler implementation, no blob download, recipient config lives in the DocuSign template where it belongs.
|
||||
|
||||
**Consequences:**
|
||||
✅ Fewer lines of Apex (no ContentVersion download, no base64 encoding)
|
||||
✅ Recipient/tab configuration lives in DocuSign where it is maintained
|
||||
❌ Requires a matching eSignature template to exist in the DocuSign account
|
||||
❌ If the template does not exist, the API returns a 400 — the operator must set up the template first
|
||||
|
||||
**Alternatives Considered:**
|
||||
Option B was rejected as unnecessary complexity for this proof-of-concept phase.
|
||||
|
||||
---
|
||||
|
||||
### ADR-002: Named credentials for all DocuSign API calls
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
All CLM and eSignature API calls must go through Salesforce named credentials. No hardcoded tokens, passwords, or base URLs in Apex.
|
||||
|
||||
**Decision:**
|
||||
Named credential API name is always sourced from `CLM_Account_Setting__mdt`:
|
||||
- CLM calls use `CLM_API_Named_Credential__c`
|
||||
- eSignature calls use `ESignature_Rest_Named_Credential__c`
|
||||
|
||||
The `eSignatureAccountId` is also from `CLM_Account_Setting__mdt.ESignature_Account_Id__c`.
|
||||
|
||||
**Consequences:**
|
||||
✅ No secrets in source code
|
||||
✅ Works with Salesforce's credential management (OAuth token refresh, etc.)
|
||||
❌ Named credentials must be manually configured in each org before the code works
|
||||
|
||||
---
|
||||
|
||||
### ADR-003: persistEnvelopeResult follows persistDocGenResult pattern
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
The project already has `CLMAdminService.persistDocGenResult()` which writes CLM task results back to the case. The eSignature result persistence should follow the same pattern — not a new approach.
|
||||
|
||||
**Decision:**
|
||||
`CLMAdminService.persistEnvelopeResult()` is an `@AuraEnabled` static method that takes structured parameters (not a blob or generic map) and writes specific fields to the case via a SOQL update. Same structure as `persistDocGenResult`.
|
||||
|
||||
**Consequences:**
|
||||
✅ Consistent code pattern, agent can learn from existing method
|
||||
✅ Testable with the same approach as `CLMAdminServiceTest`
|
||||
|
||||
---
|
||||
|
||||
### ADR-004: eSignature tracking fields belong on Appraiser_Case__c
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
The case is the central record. CLM tracking fields already live on `Appraiser_Case__c`. The eSignature tracking fields follow the same pattern.
|
||||
|
||||
**Decision:**
|
||||
All five eSignature lifecycle fields go on `Appraiser_Case__c`, not on a child record or a separate object.
|
||||
|
||||
**Consequences:**
|
||||
✅ Single record for operator to view
|
||||
✅ Consistent with existing CLM field placement
|
||||
❌ Only one envelope tracked per case (sufficient for Phase 2)
|
||||
|
||||
---
|
||||
|
||||
### ADR-005: Operator manually enters template ID in Phase 2
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
Option: auto-populate the template ID from `CLM_Letter_Definition__mdt` or present a template browser.
|
||||
Both require additional metadata fields or another API call and are out of scope for Phase 2.
|
||||
|
||||
**Decision:**
|
||||
The operator inputs the eSignature template ID directly into a text field in `clmDocGenWorkbench`. No auto-selection, no browser, no additional metadata field.
|
||||
|
||||
**Consequences:**
|
||||
✅ Simpler Phase 2 scope
|
||||
✅ No new metadata record changes needed
|
||||
❌ Operator must know the template ID — acceptable for a proof-of-concept
|
||||
|
||||
**Review after:**
|
||||
Phase 3 could add a `ESignature_Template_Id__c` field to `CLM_Letter_Definition__mdt` and pre-populate the input.
|
||||
|
||||
---
|
||||
|
||||
### ADR-006: HTTP mocks required for all Apex callout tests
|
||||
**Date:** 2026-04-09
|
||||
**Status:** Accepted
|
||||
|
||||
**Context:**
|
||||
Salesforce requires all HTTP callouts in test classes to use `Test.setMock(HttpCalloutMock.class, ...)`. The existing test classes all follow this pattern.
|
||||
|
||||
**Decision:**
|
||||
All tests for `createEnvelope()` must mock the HTTP response using an inner `MockHttpResponse` class implementing `HttpCalloutMock`. Do not attempt live callouts in test methods.
|
||||
|
||||
**Consequences:**
|
||||
✅ Tests run in any Salesforce org without external connectivity
|
||||
✅ Consistent with existing test class patterns (see `CLMDocGenCalloutTest`)
|
||||
|
||||
---
|
||||
|
||||
_Decisions don't drift if they're written down._
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Implementation Plan — Phase 2: eSignature Envelope Creation
|
||||
|
||||
> This is the live execution tracker. The agent reads and updates this every iteration.
|
||||
> Check off tasks as they are completed (`- [x]`).
|
||||
> Add `ESCALATION REQUIRED` block at the top if stuck.
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] **Task 1 — Custom fields:** Add 5 eSignature tracking fields to `Appraiser_Case__c` object metadata and update `package.xml` if needed. Deploy to verify. (FR-001)
|
||||
|
||||
- [ ] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002)
|
||||
|
||||
- [ ] **Task 3 — Tests for createEnvelope():** Add test coverage in `DocusignESignatureServiceTest` — success path (mock 201), failure path (mock 400), blank email guard. (NFR-001, TC-001, TC-002)
|
||||
|
||||
- [ ] **Task 4 — persistEnvelopeResult() + CaseContext extension:** Add `persistEnvelopeResult()` to `CLMAdminService`. Extend `CaseContext` inner class and `getCaseContext()` SOQL to include the 5 eSignature fields. (FR-003, FR-004)
|
||||
|
||||
- [ ] **Task 5 — Tests for persistEnvelopeResult():** Add test coverage in `CLMAdminServiceTest` — verify fields written correctly, `ESignature_Sent_At__c` is set, `ESignature_Completed_At__c` is NOT written on a "sent" result. (NFR-001, TC-003)
|
||||
|
||||
- [ ] **Task 6 — clmDocGenWorkbench UI:** Add "eSignature Template ID" text input and "Send for Signature" button to `clmDocGenWorkbench`. Button calls `createEnvelope()` then `persistEnvelopeResult()`, shows result. (FR-005)
|
||||
|
||||
- [ ] **Task 7 — Full deploy and test run:** Deploy entire change set, run all four test classes, verify all pass. Confirm no regressions. Update `docs/CURRENT_STATUS.md` to reflect Phase 2 completion.
|
||||
|
||||
---
|
||||
|
||||
## Completion
|
||||
|
||||
When all tasks are checked, output `<promise>DONE</promise>`.
|
||||
|
||||
---
|
||||
|
||||
_Phase 2 plan created: 2026-04-09_
|
||||
327
PROJECT-SPEC.md
327
PROJECT-SPEC.md
|
|
@ -1,327 +0,0 @@
|
|||
# Project Specification — Phase 2: eSignature Envelope Creation
|
||||
|
||||
---
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
### What are we building?
|
||||
|
||||
Connecting the CLM document generation workflow to DocuSign eSignature. After a review letter is generated and attached to an `Appraiser_Case__c` record, the operator can click "Send for Signature" to create a DocuSign envelope (using a pre-built eSignature template), send it, and have the envelope ID, status, and sent timestamp written back to the case.
|
||||
|
||||
### Why does it matter?
|
||||
|
||||
Phase 1 produces the review letter. Phase 2 delivers it for signature. Without this, the operator must manually log into DocuSign to send the envelope — a workflow gap.
|
||||
|
||||
### Success criteria
|
||||
|
||||
- [ ] Five eSignature tracking fields exist on `Appraiser_Case__c` and deploy cleanly
|
||||
- [ ] `DocusignESignatureService.createEnvelope()` calls the eSignature REST API and returns a structured result
|
||||
- [ ] Envelope ID, status, and sent timestamp are persisted to the case after a successful send
|
||||
- [ ] `clmDocGenWorkbench` exposes a "Send for Signature" button that activates after attach succeeds
|
||||
- [ ] All Apex changes are covered by tests in the existing test classes; all tests pass in appraiser-dev
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Foundation
|
||||
|
||||
### Tech stack
|
||||
|
||||
- **Language:** Apex (Salesforce), LWC (JavaScript/HTML)
|
||||
- **Framework:** Salesforce DX / SFDX source format
|
||||
- **Test framework:** Apex Test (deployed and run via sf CLI)
|
||||
- **Package manager:** N/A — Salesforce metadata deployment
|
||||
- **Org alias:** `appraiser-dev`
|
||||
|
||||
### Project structure
|
||||
|
||||
```
|
||||
force-app/main/default/
|
||||
├── classes/
|
||||
│ ├── CLMAdminService.cls
|
||||
│ ├── CLMAdminServiceTest.cls
|
||||
│ ├── DocusignESignatureService.cls ← primary change target
|
||||
│ └── DocusignESignatureServiceTest.cls ← test target
|
||||
├── lwc/
|
||||
│ └── clmDocGenWorkbench/ ← UI change target
|
||||
└── objects/
|
||||
└── Appraiser_Case__c/
|
||||
└── fields/ ← 5 new fields
|
||||
```
|
||||
|
||||
### Build and test commands
|
||||
|
||||
```bash
|
||||
NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
|
||||
SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
|
||||
|
||||
# Deploy
|
||||
$NODE $SF project deploy start --source-dir force-app --target-org appraiser-dev
|
||||
|
||||
# Run tests
|
||||
$NODE $SF apex run test \
|
||||
--target-org appraiser-dev \
|
||||
--class-names DocusignESignatureServiceTest,CLMAdminServiceTest \
|
||||
--result-format human \
|
||||
--synchronous
|
||||
```
|
||||
|
||||
### Coding standards
|
||||
|
||||
- Follow existing Apex class structure — inner DTO classes, `@AuraEnabled` methods, `with sharing`
|
||||
- Follow CLM tracking pattern: service layer method → result object → persist to case in `CLMAdminService`
|
||||
- All HTTP callouts mocked in tests using `HttpCalloutMock` (see `CLMDocGenCalloutTest` for the pattern)
|
||||
- LWC: follow existing `clmDocGenWorkbench` patterns (wire adapters, async/await handlers, tracked properties)
|
||||
- Field API names use `__c` suffix; field-meta.xml follows Salesforce DX format
|
||||
|
||||
---
|
||||
|
||||
## 3. Requirements
|
||||
|
||||
### FR-001: eSignature Tracking Fields on Appraiser_Case__c
|
||||
|
||||
**Description:** Five custom fields to track the envelope lifecycle on the case record.
|
||||
|
||||
**Fields:**
|
||||
- `ESignature_Envelope_Id__c` — Text(255), label "eSignature Envelope ID"
|
||||
- `ESignature_Envelope_Status__c` — Text(50), label "eSignature Envelope Status" (values: created / sent / delivered / completed / voided)
|
||||
- `ESignature_Sent_At__c` — DateTime, label "eSignature Sent At"
|
||||
- `ESignature_Completed_At__c` — DateTime, label "eSignature Completed At"
|
||||
- `ESignature_Envelope_Url__c` — URL(255), label "eSignature Envelope URL"
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] All 5 field metadata XML files exist under `objects/Appraiser_Case__c/fields/`
|
||||
- [ ] Fields deploy cleanly to appraiser-dev with no errors
|
||||
- [ ] `package.xml` is updated to include the new fields (or already covers `CustomField` members)
|
||||
|
||||
---
|
||||
|
||||
### FR-002: createEnvelope() in DocusignESignatureService
|
||||
|
||||
**Description:** An Apex method that creates a DocuSign eSignature envelope using Option A (template-based). The caller provides a case ID, account code, and template ID. The method resolves the named credential from the account setting, calls `POST /v2.1/accounts/{accountId}/envelopes` with the template ID and a signer role populated from the case's appraiser fields, and returns a structured result.
|
||||
|
||||
**API call shape:**
|
||||
```json
|
||||
POST /v2.1/accounts/{accountId}/envelopes
|
||||
{
|
||||
"templateId": "<templateId>",
|
||||
"templateRoles": [
|
||||
{
|
||||
"email": "<Appraiser_Email__c>",
|
||||
"name": "<Appraiser_Name__c or Appraiser_Salutation__c + Appraiser_Last_Name__c>",
|
||||
"roleName": "Signer"
|
||||
}
|
||||
],
|
||||
"status": "sent"
|
||||
}
|
||||
```
|
||||
|
||||
**Result class:** `EnvelopeCreateResult` — inner class on `DocusignESignatureService`:
|
||||
- `Boolean success`
|
||||
- `String envelopeId`
|
||||
- `String status`
|
||||
- `String envelopeUri`
|
||||
- `String errorMessage`
|
||||
|
||||
**Method signature:**
|
||||
```apex
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static EnvelopeCreateResult createEnvelope(
|
||||
Id caseId,
|
||||
String accountCode,
|
||||
String templateId
|
||||
)
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Method is `@AuraEnabled` and callable from LWC
|
||||
- [ ] Resolves `eSignatureAccountId` and `eSignatureRestNamedCredential` from `CLM_Account_Setting__mdt` via `accountCode`
|
||||
- [ ] Queries `Appraiser_Email__c`, `Appraiser_Name__c`, `Appraiser_Last_Name__c`, `Appraiser_Salutation__c` from the case
|
||||
- [ ] POSTs correct JSON body to the eSignature API
|
||||
- [ ] Returns `EnvelopeCreateResult` with `envelopeId` and `status` on success
|
||||
- [ ] Returns `EnvelopeCreateResult` with `success=false` and `errorMessage` on failure (non-2xx or exception)
|
||||
- [ ] Does NOT throw uncaught exceptions — catches and wraps in result
|
||||
|
||||
---
|
||||
|
||||
### FR-003: persistEnvelopeResult() in CLMAdminService
|
||||
|
||||
**Description:** After a successful `createEnvelope()` call, write the result back to `Appraiser_Case__c`. Follows the same pattern as `persistDocGenResult`.
|
||||
|
||||
**Method signature:**
|
||||
```apex
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static void persistEnvelopeResult(
|
||||
Id caseId,
|
||||
String envelopeId,
|
||||
String envelopeStatus,
|
||||
String envelopeUri
|
||||
)
|
||||
```
|
||||
|
||||
**Fields written:**
|
||||
- `ESignature_Envelope_Id__c` = envelopeId
|
||||
- `ESignature_Envelope_Status__c` = envelopeStatus
|
||||
- `ESignature_Sent_At__c` = Datetime.now()
|
||||
- `ESignature_Envelope_Url__c` = envelopeUri (if not blank)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Method updates the correct case record (by `caseId`)
|
||||
- [ ] Sets `ESignature_Sent_At__c` to current datetime
|
||||
- [ ] Does not overwrite `ESignature_Completed_At__c` (only set when status = completed)
|
||||
|
||||
---
|
||||
|
||||
### FR-004: Extend CaseContext with Envelope Fields
|
||||
|
||||
**Description:** The `CaseContext` inner class in `CLMAdminService` (and its `getCaseContext()` query) must include the 5 eSignature tracking fields so the LWC can display current envelope status.
|
||||
|
||||
**Fields to add to CaseContext:**
|
||||
- `String eSignatureEnvelopeId`
|
||||
- `String eSignatureEnvelopeStatus`
|
||||
- `String eSignatureEnvelopeUrl`
|
||||
- `Datetime eSignatureSentAt`
|
||||
- `Datetime eSignatureCompletedAt`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] `getCaseContext()` SOQL query includes all 5 fields
|
||||
- [ ] `CaseContext` DTO maps all 5 fields with `@AuraEnabled`
|
||||
|
||||
---
|
||||
|
||||
### FR-005: "Send for Signature" Button in clmDocGenWorkbench
|
||||
|
||||
**Description:** After the "Attach Generated Document" step succeeds, a "Send for Signature" button becomes enabled. Clicking it calls `createEnvelope()` and then `persistEnvelopeResult()`, and displays the result (envelope ID and status, or error message) in the workbench UI.
|
||||
|
||||
**Behavior:**
|
||||
- Button is disabled until `attachedFileContentDocumentId` is populated (i.e., attach has succeeded)
|
||||
- Button shows a spinner while in progress (same pattern as generate/attach buttons)
|
||||
- On success: display envelope ID and status; update envelope fields displayed from `caseContext`
|
||||
- On error: display error message
|
||||
- Button does NOT automatically select a template — the operator must provide `templateId` via a text input field in the UI
|
||||
|
||||
**New UI elements:**
|
||||
- Text input: "eSignature Template ID" (maps to `templateId` parameter)
|
||||
- Button: "Send for Signature"
|
||||
- Status display: envelope ID, status, sent timestamp (read from refreshed `caseContext`)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Button is disabled when `attachedFileContentDocumentId` is blank
|
||||
- [ ] Button calls `DocusignESignatureService.createEnvelope()` with the case ID, account code, and template ID from the input
|
||||
- [ ] On success, calls `CLMAdminService.persistEnvelopeResult()` and refreshes the case context display
|
||||
- [ ] On failure, displays the error message from `EnvelopeCreateResult.errorMessage`
|
||||
- [ ] Template ID input is cleared after a successful send
|
||||
- [ ] Spinner shown during the callout; button disabled during in-flight request
|
||||
|
||||
---
|
||||
|
||||
### NFR-001: Test Coverage
|
||||
|
||||
- [ ] `DocusignESignatureServiceTest` covers `createEnvelope()` — at minimum: success path (mock HTTP 201), failure path (mock HTTP 400), missing template ID guard
|
||||
- [ ] `CLMAdminServiceTest` covers `persistEnvelopeResult()` — at minimum: fields written correctly, `ESignature_Sent_At__c` is set
|
||||
- [ ] All existing tests continue to pass after each change
|
||||
|
||||
### NFR-002: No Hardcoded Credentials
|
||||
|
||||
- [ ] All DocuSign API calls go through named credentials (never hardcoded tokens or passwords)
|
||||
- [ ] Named credential API name is sourced from `CLM_Account_Setting__mdt`
|
||||
|
||||
---
|
||||
|
||||
## 4. Data Model
|
||||
|
||||
### New fields on Appraiser_Case__c
|
||||
|
||||
| API Name | Type | Length |
|
||||
|----------|------|--------|
|
||||
| ESignature_Envelope_Id__c | Text | 255 |
|
||||
| ESignature_Envelope_Status__c | Text | 50 |
|
||||
| ESignature_Sent_At__c | DateTime | — |
|
||||
| ESignature_Completed_At__c | DateTime | — |
|
||||
| ESignature_Envelope_Url__c | URL | 255 |
|
||||
|
||||
### Existing fields read by createEnvelope()
|
||||
|
||||
| API Name | Used for |
|
||||
|----------|----------|
|
||||
| Appraiser_Email__c | templateRoles[0].email |
|
||||
| Appraiser_Name__c | templateRoles[0].name (prefer this) |
|
||||
| Appraiser_Salutation__c | fallback for name |
|
||||
| Appraiser_Last_Name__c | fallback for name |
|
||||
|
||||
---
|
||||
|
||||
## 5. Architecture Decisions
|
||||
|
||||
### Constraints
|
||||
|
||||
- **MUST:** Use `CLM_Account_Setting__mdt` to resolve named credentials — never hardcode
|
||||
- **MUST:** Follow the `persistDocGenResult` pattern in `CLMAdminService` for `persistEnvelopeResult`
|
||||
- **MUST:** Mock all HTTP callouts in tests using `HttpCalloutMock`
|
||||
- **MUST NOT:** Modify the CLM document generation flow (Phase 1) — leave it untouched
|
||||
- **MUST NOT:** Create new Apex test classes — add to existing ones
|
||||
- **MUST NOT:** Add eSignature template browsing in this phase — the operator inputs the template ID directly
|
||||
- **PREFER:** Option A (template-based envelope) for `createEnvelope()` — simpler, already decided
|
||||
- **ESCALATE:** If the eSignature named credential does not exist in the org, stop and flag it
|
||||
- **ESCALATE:** If `Appraiser_Email__c` is blank on the queried case, stop — do not silently send with a blank email
|
||||
- **ESCALATE:** If the spec does not cover a required decision — do not fill gaps silently
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `CLM_Account_Setting__mdt` — provides `ESignature_Account_Id__c`, `ESignature_Rest_Named_Credential__c`
|
||||
- DocuSign eSignature REST API v2.1 — `/v2.1/accounts/{accountId}/envelopes`
|
||||
- Named credential (AcctDemo_NamedCreds or equivalent) — must exist in appraiser-dev
|
||||
|
||||
### Known Challenges
|
||||
|
||||
- The named credential for eSignature REST may differ from the CLM named credential — check `CLM_Account_Setting__mdt` for `ESignature_Rest_Named_Credential__c`
|
||||
- DocuSign template roles: the `roleName` must match the role name defined in the eSignature template — "Signer" is the default, but the actual template may use a different name. If tests fail with a 400 from DocuSign about role name, escalate.
|
||||
- LWC must use `async/await` for imperative Apex calls (not wire adapters) for the send action — matches the existing attach pattern
|
||||
|
||||
### Rejected Approaches
|
||||
|
||||
| Rejected option | Why rejected |
|
||||
|-----------------|-------------|
|
||||
| Option B (upload CLM document as envelope document) | More complex, requires downloading the blob and managing recipients in code. Option A with a matching template is simpler and sufficient. |
|
||||
| Creating a new Apex test class for eSignature | Not needed — `DocusignESignatureServiceTest` already exists and is the right home |
|
||||
| Automatic template selection via API | Out of scope for Phase 2 — operator inputs template ID directly |
|
||||
|
||||
---
|
||||
|
||||
## 6. Reference Materials
|
||||
|
||||
### Existing code to learn from
|
||||
|
||||
- `CLMAdminService.persistDocGenResult()` — the exact pattern to replicate for `persistEnvelopeResult()`
|
||||
- `CLMAdminService.getCaseContext()` — how case fields are queried and mapped to a DTO
|
||||
- `CLMDocGenCalloutTest` — the HTTP mock pattern to follow in `DocusignESignatureServiceTest`
|
||||
- `clmDocGenWorkbench` — the LWC pattern (async handlers, spinner, button enable/disable) to follow for the send button
|
||||
|
||||
### Anti-patterns
|
||||
|
||||
- Do not use `HttpRequest.setBodyAsBlob()` for the JSON envelope payload — use `setBody()` with `JSON.serialize()`
|
||||
- Do not trust the `status` field in the LWC to determine if all prior steps completed — check the specific field (e.g., `attachedFileContentDocumentId`) that the button should gate on
|
||||
|
||||
---
|
||||
|
||||
## 7. Evaluation Design
|
||||
|
||||
### Test cases
|
||||
|
||||
#### TC-001: createEnvelope() success path
|
||||
**Input:** Valid caseId, valid accountCode, valid templateId; mock HTTP returns 201 with `{"envelopeId":"abc-123","status":"sent","uri":"/envelopes/abc-123"}`
|
||||
**Expected output:** `EnvelopeCreateResult.success = true`, `envelopeId = "abc-123"`, `status = "sent"`
|
||||
**Verification:** `System.assertEquals(true, result.success)` in `DocusignESignatureServiceTest`
|
||||
|
||||
#### TC-002: createEnvelope() failure path
|
||||
**Input:** Valid caseId/accountCode/templateId; mock HTTP returns 400 with error body
|
||||
**Expected output:** `EnvelopeCreateResult.success = false`, `errorMessage` is non-blank
|
||||
**Verification:** `System.assertEquals(false, result.success)` and `System.assertNotEquals(null, result.errorMessage)`
|
||||
|
||||
#### TC-003: persistEnvelopeResult() writes correct fields
|
||||
**Input:** Existing case record, envelopeId = "test-env-id", envelopeStatus = "sent", envelopeUri = "/envelopes/test-env-id"
|
||||
**Expected output:** Re-queried case has `ESignature_Envelope_Id__c = "test-env-id"`, `ESignature_Sent_At__c` is non-null
|
||||
**Verification:** Re-query case after call and assert in `CLMAdminServiceTest`
|
||||
|
||||
---
|
||||
|
||||
_Last updated: 2026-04-09_
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
# CLM Doc Gen Integration Guide
|
||||
|
||||
> Status note (2026-04-07): the repo is now standardized on the XML merge path built on `AppraiserCasePayloadBuilder` and `CLMDocGenCallout`, using `Appraiser_Case_Deficiency__c` as the canonical child object. See `CURRENT_STATUS.md` before using this guide as the sole source of truth.
|
||||
|
||||
## Overview
|
||||
This guide explains how to integrate Salesforce with DocuSign CLM to generate Appraiser Review Letters dynamically from Appraiser Case records.
|
||||
|
||||
|
|
@ -82,57 +80,42 @@ Recipient Email
|
|||
|
||||
## Usage Patterns
|
||||
|
||||
### Pattern 1: Queueable Apex (Automatic/Trigger-Driven)
|
||||
### Pattern 1: Apex Trigger (Automatic)
|
||||
|
||||
**Scenario**: Generate letter automatically from a trigger or process (callouts cannot be made synchronously from triggers — use a queueable)
|
||||
**Scenario**: Generate letter automatically when Appraiser Case status reaches "Ready for Review"
|
||||
|
||||
```apex
|
||||
public class AppraiserCaseDocGenJob implements Queueable, Database.AllowsCallouts {
|
||||
private Id caseId;
|
||||
private String accountCode;
|
||||
private String templateDocHref;
|
||||
private String destinationFolderHref;
|
||||
private String destinationDocName;
|
||||
// In a trigger on Appraiser_Case__c AFTER UPDATE
|
||||
if (oldMap.get(record.Id).Status__c != 'Ready for Review' &&
|
||||
record.Status__c == 'Ready for Review') {
|
||||
|
||||
public AppraiserCaseDocGenJob(Id caseId, String accountCode,
|
||||
String templateDocHref, String destinationFolderHref, String destinationDocName) {
|
||||
this.caseId = caseId;
|
||||
this.accountCode = accountCode;
|
||||
this.templateDocHref = templateDocHref;
|
||||
this.destinationFolderHref = destinationFolderHref;
|
||||
this.destinationDocName = destinationDocName;
|
||||
}
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMDocGenCallout.generateDocument(
|
||||
record.Id,
|
||||
'TEMPLATE_ID_FROM_CLM', // e.g., '123456'
|
||||
record.Reviewer_Email__c
|
||||
);
|
||||
|
||||
public void execute(QueueableContext ctx) {
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMAdminService.generateDocument(
|
||||
caseId, templateDocHref, destinationFolderHref, destinationDocName, accountCode
|
||||
);
|
||||
if (!response.success) {
|
||||
System.debug('CLM Doc Gen failed: ' + response.message);
|
||||
}
|
||||
if (!response.success) {
|
||||
// Log error or send notification
|
||||
System.debug('CLM Doc Gen failed: ' + response.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue from a trigger:
|
||||
System.enqueueJob(new AppraiserCaseDocGenJob(
|
||||
record.Id,
|
||||
'DTC_CLM_Demo',
|
||||
letterSettings.defaultTemplateDocumentHref,
|
||||
letterSettings.destinationRootFolderHref,
|
||||
'Review_' + record.Name + '.docx'
|
||||
));
|
||||
```
|
||||
|
||||
### Pattern 2: LWC Quick Action (UI-Driven) ✅ Implemented
|
||||
### Pattern 2: Flow (UI-Driven)
|
||||
|
||||
The `clmDocGenWorkbench` component, launched from the **Generate Review Letter** quick action, is the current implemented UI path. It calls `CLMAdminService` to:
|
||||
- Browse available accounts and letter types
|
||||
- Browse CLM template and destination folders
|
||||
- Submit a merge task via `generateDocument()`
|
||||
- Poll task status via `getTaskStatus()`
|
||||
- Attach the generated file to the case via `attachGeneratedDocumentToCase()`
|
||||
**Scenario**: User clicks button to generate letter on-demand
|
||||
|
||||
No additional configuration is needed beyond deploying the metadata and setting up Named Credentials.
|
||||
1. Create a Record-Triggered Flow on Appraiser_Case__c
|
||||
2. Add an Action step:
|
||||
- Action Type: "Apex Action"
|
||||
- Apex Class: Choose custom Apex action that wraps `CLMDocGenCallout.generateDocument()`
|
||||
3. Pass:
|
||||
- recordId (the Appraiser Case)
|
||||
- templateId (hardcoded or allow user to select)
|
||||
- recipientEmail (from record or user input)
|
||||
4. On success: Show toast, store document URL on record
|
||||
5. On error: Show error message
|
||||
|
||||
### Pattern 3: REST API (External System)
|
||||
|
||||
|
|
@ -142,16 +125,12 @@ No additional configuration is needed beyond deploying the metadata and setting
|
|||
@RestResource(urlMapping='/appraiser-case-generate-letter')
|
||||
global class AppraiserCaseDocGenRest {
|
||||
@HttpPost
|
||||
global static void generateLetter(
|
||||
String caseId,
|
||||
String accountCode,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String destinationDocName
|
||||
) {
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMAdminService.generateDocument(
|
||||
(Id) caseId, templateDocHref, destinationFolderHref, destinationDocName, accountCode
|
||||
global static void generateLetter(String caseId, String templateId, String recipientEmail) {
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMDocGenCallout.generateDocument(
|
||||
caseId, templateId, recipientEmail
|
||||
);
|
||||
|
||||
// Return response
|
||||
RestContext.response.statusCode = response.success ? 200 : 400;
|
||||
RestContext.response.responseBody = Blob.valueOf(JSON.serialize(response));
|
||||
}
|
||||
|
|
@ -162,72 +141,97 @@ global class AppraiserCaseDocGenRest {
|
|||
|
||||
## Payload Structure
|
||||
|
||||
### AppraiserCasePayloadBuilder output (intermediate JSON)
|
||||
### Input
|
||||
```json
|
||||
{
|
||||
"AppraiserCaseNumber": "AC-000001",
|
||||
"AppraiserFieldReviewDate": "Apr 02, 2026",
|
||||
"LetterSentDate": "Apr 09, 2026",
|
||||
"FHACaseNumber": "123-4567890",
|
||||
"AppraiserName": "Jamie Appraiser",
|
||||
"AppraiserSalutation": "Ms.",
|
||||
"AppraiserLastName": "Appraiser",
|
||||
"AppraiserEmail": "jamie@example.com",
|
||||
"AppraiserAddress": "245 Lexington Ave, New York, NY 10016, USA",
|
||||
"PropertyAddress": "123 Main St, Denver, CO 80202, USA",
|
||||
"AppraiserCaseNumber": "AC-00001",
|
||||
"AppraiserFieldReviewDate": "2026-04-02",
|
||||
"PropertyAddress": "123 Main St, Denver, CO 80202",
|
||||
"DeficiencyList": [
|
||||
{ "deficiencyNumber": 1, "description": "...", "resolution": "...", "reference": "VC-1" }
|
||||
{
|
||||
"deficiencyNumber": 1,
|
||||
"description": "Missing comparable sale adjustment detail.",
|
||||
"resolution": "Added adjustment rationale and supporting calculations."
|
||||
},
|
||||
{
|
||||
"deficiencyNumber": 2,
|
||||
"description": "Neighborhood trend explanation insufficient.",
|
||||
"resolution": "Expanded market trend narrative with MLS evidence."
|
||||
},
|
||||
{
|
||||
"deficiencyNumber": 3,
|
||||
"description": "Photo date stamps were not included.",
|
||||
"resolution": "Re-uploaded photos with date metadata and captions."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### CLM API Request (what CLMDocGenCallout sends to `/documentxmlmergetasks`)
|
||||
### CLM API Request (what CLMDocGenCallout sends)
|
||||
```json
|
||||
{
|
||||
"TemplateDocument": { "Href": "https://.../documents/<template-guid>" },
|
||||
"DataXML": "<TemplateFieldData><AppraiserCaseNumber>AC-000001</AppraiserCaseNumber>...<DeficiencyList><Deficiency><Number>1</Number>...</Deficiency></DeficiencyList></TemplateFieldData>",
|
||||
"DestinationDocumentName": "Review_AC-000001.docx",
|
||||
"DestinationFolder": { "Href": "https://.../folders/<folder-guid>" }
|
||||
"templateId": "TEMPLATE_ID_FROM_CLM",
|
||||
"mergeData": { ...payload above... },
|
||||
"delivery": {
|
||||
"recipientEmail": "reviewer@example.com",
|
||||
"documentName": "AppraiserReviewLetter_1743724800000"
|
||||
},
|
||||
"metadata": {
|
||||
"salesforceRecordId": "a0wKW000007OIiCYAW",
|
||||
"generatedAt": "2026-04-02T05:27:44Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CLM API Response (on success)
|
||||
```json
|
||||
{
|
||||
"Href": "https://apiuatna11.springcm.com/v2/<account-id>/documentxmlmergetasks/<task-guid>",
|
||||
"Status": "Queued",
|
||||
"Result": { "Href": "https://apiuatna11.springcm.com/v2/<account-id>/documents/<doc-guid>" }
|
||||
"success": true,
|
||||
"documentUrl": "https://clm-instance.docusign.com/documents/ABC123XYZ",
|
||||
"documentId": "DOC-001",
|
||||
"message": "Document generated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
The response is persisted to `Appraiser_Case__c` tracking fields by `CLMAdminService.persistDocGenResult()`.
|
||||
|
||||
---
|
||||
|
||||
## CLM Template Design
|
||||
|
||||
The implementation uses the DocuSign CLM **XML merge** (`documentxmlmergetasks`) API, not a Handlebars/template-key approach. Template field tags in the `.docx` file use CLM's Word merge syntax (typically `«FieldName»` merge fields or CLM repeat-block markers). Refer to your CLM tenant documentation for the exact syntax.
|
||||
### Template Merge Tags (Handlebars syntax)
|
||||
|
||||
**Flat field example** (Word merge field in the .docx):
|
||||
```
|
||||
«AppraiserCaseNumber» «AppraiserFieldReviewDate»
|
||||
«PropertyAddress»
|
||||
**Flat fields**:
|
||||
```handlebars
|
||||
<p>Case Number: {{AppraiserCaseNumber}}</p>
|
||||
<p>Review Date: {{AppraiserFieldReviewDate}}</p>
|
||||
<p>Property: {{PropertyAddress}}</p>
|
||||
```
|
||||
|
||||
**Deficiency repeat block** — the XML structure sent is:
|
||||
```xml
|
||||
<DeficiencyList>
|
||||
<Deficiency>
|
||||
<Number>1</Number>
|
||||
<Description>Missing comparable sale adjustment detail.</Description>
|
||||
<Resolution>Added adjustment rationale and supporting calculations.</Resolution>
|
||||
<Reference>VC-1</Reference>
|
||||
</Deficiency>
|
||||
</DeficiencyList>
|
||||
<DeficiencyCount>1</DeficiencyCount>
|
||||
**Deficiency repeat block**:
|
||||
```handlebars
|
||||
<table>
|
||||
<tr>
|
||||
<th>Deficiency #</th>
|
||||
<th>Description</th>
|
||||
<th>Resolution</th>
|
||||
</tr>
|
||||
{{#each DeficiencyList}}
|
||||
<tr>
|
||||
<td>{{deficiencyNumber}}</td>
|
||||
<td>{{description}}</td>
|
||||
<td>{{resolution}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
```
|
||||
|
||||
Configure a repeat region in your CLM template over `DeficiencyList/Deficiency`, binding `Number`, `Description`, `Resolution`, and `Reference` within the loop.
|
||||
**Conditional (if no deficiencies)**:
|
||||
```handlebars
|
||||
{{#if DeficiencyList.length}}
|
||||
<!-- Deficiency table -->
|
||||
{{else}}
|
||||
<p>No deficiencies found.</p>
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# DocuSign CLM Template Mapping Snippet
|
||||
|
||||
This document reflects an older JSON-oriented payload experiment and is retained only as historical reference. The current working implementation uses `Appraiser_Case__c` and `Appraiser_Case_Deficiency__c` through the XML merge path.
|
||||
This project assumes Salesforce is the system of record and DocuSign CLM doc gen receives a payload built from `Appraiser_Case__c` and its related `Appraiser_Deficiency__c` records.
|
||||
|
||||
## Suggested Merge Payload
|
||||
|
||||
|
|
@ -66,5 +66,6 @@ Use the actual DocuSign CLM token syntax used in your tenant, but the field mode
|
|||
|
||||
## Notes
|
||||
|
||||
- Keep deficiency rows as repeatable child data, not a flattened text blob.
|
||||
- The active implementation in this repo now uses `AppraiserCasePayloadBuilder` plus `CLMDocGenCallout` rather than `AppraiserCaseDocGenService`.
|
||||
- Keep `deficiencies` as a repeatable child collection, not a flattened text blob.
|
||||
- If DocuSign CLM requires a REST callout payload, `AppraiserCaseDocGenService.buildDocGenRequestJson()` is a good source payload to hand to your integration layer.
|
||||
- If your CLM tenant uses a different collection token syntax, map the same logical field names there.
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
# Current Status — 2026-04-09
|
||||
|
||||
## Executive summary
|
||||
The project is now a working Salesforce proof of concept for both DocuSign CLM document generation and basic eSignature API exploration. The app is standardized on `Appraiser_Case__c` plus `Appraiser_Case_Deficiency__c`, uses account-based DocuSign configuration through `CLM_Account_Setting__mdt`, and provides two operator-facing LWCs:
|
||||
- `clmDocGenWorkbench` for CLM document generation
|
||||
- `docusignEsignWorkbench` for eSignature API browsing
|
||||
|
||||
## What is implemented
|
||||
- canonical Salesforce parent object:
|
||||
- `Appraiser_Case__c`
|
||||
- canonical Salesforce child object:
|
||||
- `Appraiser_Case_Deficiency__c`
|
||||
- CLM merge payload builder:
|
||||
- `AppraiserCasePayloadBuilder`
|
||||
- CLM callout service:
|
||||
- `CLMDocGenCallout`
|
||||
- CLM UI orchestration:
|
||||
- `CLMAdminService`
|
||||
- CLM quick-action workbench:
|
||||
- `clmDocGenWorkbench`
|
||||
- eSignature service layer:
|
||||
- `DocusignESignatureService`
|
||||
- eSignature record-page workbench:
|
||||
- `docusignEsignWorkbench`
|
||||
- account-based config through:
|
||||
- `CLM_Account_Setting__mdt`
|
||||
- letter-definition config through:
|
||||
- `CLM_Letter_Definition__mdt`
|
||||
- task and generated-document tracking on `Appraiser_Case__c`
|
||||
- generated-document attachment to Salesforce Files
|
||||
- Apex tests for payload generation, CLM callouts/admin service, and eSignature service
|
||||
|
||||
## What is working
|
||||
- generating CLM review letters from a Salesforce record page
|
||||
- selecting letter types through metadata-backed defaults while preserving the current appraiser letter
|
||||
- browsing CLM templates and destination folders by configured account
|
||||
- polling CLM task status
|
||||
- attaching generated CLM documents back to the case
|
||||
- browsing eSignature login info, user info, accounts, templates, and envelopes from Salesforce UI
|
||||
|
||||
## What is still not finished
|
||||
- only the current appraiser review letter is seeded; the additional three letters still need template records and CLM template build-out
|
||||
- no envelope creation/send workflow yet
|
||||
- no template detail or envelope detail UI yet
|
||||
- no long-term productized admin/config UI beyond Salesforce Setup
|
||||
- some historical docs remain useful but are not authoritative
|
||||
|
||||
## Current source of truth
|
||||
- product-level summary:
|
||||
- [PRODUCT_SPEC.md](./PRODUCT_SPEC.md)
|
||||
- setup and environment notes:
|
||||
- [SALESFORCE_SETUP.md](./SALESFORCE_SETUP.md)
|
||||
- implementation:
|
||||
- Apex classes and LWCs in `force-app/main/default`
|
||||
|
||||
## Immediate recommendation
|
||||
Treat the codebase as a solid platform baseline. The next phase should focus on product enhancements rather than architecture cleanup: better eSignature workflows, richer record-page UX, and clearer admin/operational controls.
|
||||
|
|
@ -1,64 +1,19 @@
|
|||
# Features / Change Log — Appraiser Review Letter
|
||||
# Features/Change Log — Appraiser Review Letter
|
||||
|
||||
## Current state (2026-04-09)
|
||||
|
||||
The project is a working Salesforce proof of concept covering both DocuSign CLM
|
||||
document generation and eSignature API exploration. All items below are deployed
|
||||
and functional.
|
||||
|
||||
### CLM document generation
|
||||
- `Appraiser_Case__c` with full appraiser identity, address, FHA case number,
|
||||
letter sent date, and CLM tracking fields
|
||||
- `Appraiser_Case_Deficiency__c` child object with number, description,
|
||||
resolution, reference, and blank-record validation
|
||||
- `AppraiserCasePayloadBuilder` — transforms case + deficiencies into CLM merge
|
||||
payload (JSON and XML)
|
||||
- `CLMDocGenCallout` — XML merge task submission, task status polling, document
|
||||
download via separate download credential
|
||||
- `CLMAdminService` — UI orchestration layer: account/letter settings, generate,
|
||||
poll, attach, folder browse; persists all CLM results back to the case
|
||||
- `clmDocGenWorkbench` LWC quick action — full generate → poll → attach workflow
|
||||
with folder browsing, template selection, and letter type selection
|
||||
- `clmRequestPreview` LWC — developer/debug view of the XML merge payload and
|
||||
request body for a given case
|
||||
|
||||
### Account-based configuration
|
||||
- `CLM_Account_Setting__mdt` — per-account CLM and eSignature configuration
|
||||
(named credentials, folder hrefs, template hrefs, account IDs)
|
||||
- `CLM_Letter_Definition__mdt` — per-account letter type definitions with
|
||||
optional folder/template overrides; fallback chain to account defaults
|
||||
- Three active accounts: DTC_CLM_Demo (UAT), DTC_IAM_Enterprise (S1),
|
||||
DTC_HUD_Demo (UAT)
|
||||
- Four letter types seeded (APPRAISER_REVIEW fully configured; NOD, Education,
|
||||
Intent to Remove defined but CLM templates not yet built)
|
||||
|
||||
### eSignature API exploration
|
||||
- `DocusignESignatureService` — read-only eSignature API calls: login info,
|
||||
user info, account list, templates, envelopes
|
||||
- `docusignEsignWorkbench` LWC — browse accounts, templates, and recent
|
||||
envelopes from the Salesforce record page
|
||||
- `AcctDemo_NamedCreds` + `DocusignJWT` external credential — JWT bearer auth
|
||||
for eSignature account server calls
|
||||
- `Esignature_Demo_NamedCreds` — REST API calls (templates, envelopes)
|
||||
|
||||
### Infrastructure
|
||||
- Named credentials: CLMuatNamedCreds, CLMs1NamedCreds, CLMuatDownload,
|
||||
CLMs1Download, Esignature_Demo_NamedCreds, AcctDemo_NamedCreds
|
||||
- External credential: DocusignJWT (JWT bearer, RS256, account-d.docusign.com)
|
||||
- Permission sets: Appraiser_Case_Admin, Appraiser_Case_Access
|
||||
- Record page, quick action, custom app, tabs, layouts
|
||||
## Progress Summary
|
||||
- CLM_TEMPLATE_GUIDE.md: Initial merge/tag logic and edge cases outlined
|
||||
- requirements.md and design.md: Created and populated with core requirements and structure
|
||||
- README.md: Project intro and architecture brief
|
||||
- DEPLOYMENT_AND_TESTING.md: Deployment steps and testing workflow drafted
|
||||
|
||||
---
|
||||
|
||||
## What is not yet built
|
||||
|
||||
- Envelope creation and send workflow (eSignature)
|
||||
- Envelope/template detail drill-down UI
|
||||
- eSignature activity tracking on `Appraiser_Case__c` (envelope ID, status,
|
||||
sent/completed dates)
|
||||
- CLM template build-out for NOD, Education, and Intent to Remove letter types
|
||||
- Admin UI for configuration (currently managed through Salesforce Setup)
|
||||
## Next Steps
|
||||
- Expand template engine features (nested conditionals, richer formatting)
|
||||
- Clarify integration specifics with Salesforce CLM
|
||||
- Add more actionable questions in doc footers
|
||||
|
||||
---
|
||||
|
||||
_Last updated: 2026-04-09_
|
||||
_Last updated: 2026-02-26 13:28 PM_
|
||||
_Work in progress: Feature/requirement expansion and blockages/questions to be listed here._
|
||||
|
|
|
|||
|
|
@ -1,91 +1,34 @@
|
|||
# Next Steps
|
||||
# Next Steps — DocuSign CLM Launch Path
|
||||
|
||||
## What is done
|
||||
I added a placeholder Quick Action metadata file so there is an explicit place in the project for the document generation launch pattern.
|
||||
|
||||
The CLM document generation workflow is fully implemented:
|
||||
- `clmDocGenWorkbench` quick action — account selection, letter type selection,
|
||||
folder browsing, template selection, generate, poll, attach
|
||||
- `CLMAdminService` + `CLMDocGenCallout` — full Apex service layer
|
||||
- All CLM tracking fields persisted back to `Appraiser_Case__c`
|
||||
## Reality check
|
||||
|
||||
The eSignature browsing layer is implemented (read-only):
|
||||
- `docusignEsignWorkbench` — accounts, templates, envelopes
|
||||
- `DocusignESignatureService` — login info, user info, account list, templates,
|
||||
envelopes
|
||||
DocuSign CLM launch configuration varies by package and org setup. Because of that, the action in this repo is a scaffold/placeholder, not a guaranteed final production action.
|
||||
|
||||
---
|
||||
## Recommended implementation path
|
||||
|
||||
## Phase 2 — eSignature envelope creation
|
||||
### Option A — Screen Flow (recommended first)
|
||||
- Create a Screen Flow or autolaunched Flow that accepts `recordId`
|
||||
- Call an Apex invocable or Apex action that builds the payload
|
||||
- Hand that payload to your DocuSign CLM mechanism
|
||||
- Redirect user to resulting document or status page
|
||||
|
||||
The natural next step is connecting the two halves: after a CLM document is
|
||||
generated and attached to the case, send it for signature.
|
||||
### Option B — LWC / Aura quick action
|
||||
- Use a Lightning Web Component quick action on `Appraiser_Case__c`
|
||||
- Call `AppraiserCaseDocGenService.buildDocGenRequestJson(recordId, templateKey)`
|
||||
- Send the payload to the installed DocuSign CLM endpoint or orchestration layer
|
||||
|
||||
### 1. Add eSignature tracking fields to `Appraiser_Case__c`
|
||||
### Option C — Button / URL hack
|
||||
- Usually fast, usually brittle. I don’t recommend it unless your CLM package explicitly documents it.
|
||||
|
||||
Fields needed on the case to track the envelope lifecycle:
|
||||
- `ESignature_Envelope_Id__c` (Text)
|
||||
- `ESignature_Envelope_Status__c` (Text — Created / Sent / Delivered / Completed / Voided)
|
||||
- `ESignature_Sent_At__c` (DateTime)
|
||||
- `ESignature_Completed_At__c` (DateTime)
|
||||
- `ESignature_Envelope_Url__c` (URL — link to envelope in DocuSign)
|
||||
## What to confirm in your org
|
||||
|
||||
### 2. Add `createEnvelope()` to `DocusignESignatureService`
|
||||
1. Exact DocuSign CLM package/API available in Salesforce
|
||||
2. Whether generation is initiated by package component, Flow action, Apex callout, or named credential call
|
||||
3. Template identifier format (`templateKey`, template Id, or external document key)
|
||||
4. Returned artifact behavior (attach to record, email, save to CLM repository, etc.)
|
||||
|
||||
Two approaches depending on workflow preference:
|
||||
## Good next move
|
||||
|
||||
**Option A — Envelope from template**
|
||||
Use a pre-built eSignature template. Recipients and document are defined in the
|
||||
template; Salesforce passes merge data (tabs) at send time.
|
||||
```apex
|
||||
// POST /v2.1/accounts/{accountId}/envelopes
|
||||
// body: { templateId, templateRoles: [{ email, name, roleName }], status: 'sent' }
|
||||
```
|
||||
|
||||
**Option B — Envelope from uploaded document**
|
||||
Use the CLM-generated document (already attached to the case as a
|
||||
ContentVersion). Download the blob and POST it directly as an envelope document.
|
||||
```apex
|
||||
// POST /v2.1/accounts/{accountId}/envelopes
|
||||
// body: { documents: [{ documentBase64, name, fileExtension, documentId }],
|
||||
// recipients: { signers: [...] }, status: 'sent' }
|
||||
```
|
||||
|
||||
Option A is simpler if a matching eSignature template exists.
|
||||
Option B gives more control but requires managing recipient configuration in code.
|
||||
|
||||
### 3. Persist envelope result to `Appraiser_Case__c`
|
||||
|
||||
On success, write the envelope ID, status, and sent timestamp back to the case
|
||||
(same pattern as `CLMAdminService.persistDocGenResult`).
|
||||
|
||||
### 4. Add send action to `clmDocGenWorkbench`
|
||||
|
||||
After "Attach Generated Document" succeeds, enable a "Send for Signature" button
|
||||
that calls the new `createEnvelope()` method. Show envelope status alongside
|
||||
the existing task status display.
|
||||
|
||||
### 5. Add envelope status polling (optional)
|
||||
|
||||
Mirror the CLM task status pattern: a "Check Signature Status" button that calls
|
||||
`GET /v2.1/accounts/{accountId}/envelopes/{envelopeId}` and updates the case.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Additional letter types
|
||||
|
||||
Three letter types are defined in `CLM_Letter_Definition__mdt` but have no CLM
|
||||
templates yet:
|
||||
- `NOD_LETTER` — Notice of Deficiency
|
||||
- `EDUCATION_LETTER` — Education correspondence
|
||||
- `INTENT_TO_REMOVE_LETTER` — Intent to Remove notification
|
||||
|
||||
For each:
|
||||
1. Build the CLM template `.docx` and upload to the CLM account
|
||||
2. Update the `Default_Template_Document_Href__c` in the corresponding
|
||||
`CLM_Letter_Definition__mdt` records
|
||||
3. Confirm whether deficiency display or field set differs from the appraiser
|
||||
review letter (if so, extend `AppraiserCasePayloadBuilder`)
|
||||
|
||||
---
|
||||
|
||||
_Last updated: 2026-04-09_
|
||||
Once you know the exact DocuSign package artifact available in the org, I can wire the placeholder into a real Flow/LWC/Apex launch path.
|
||||
|
|
|
|||
|
|
@ -1,291 +0,0 @@
|
|||
# Product Spec — Appraiser Review Letter Platform
|
||||
|
||||
## Purpose
|
||||
This project is a Salesforce-based operator tool for generating appraiser review letters in DocuSign CLM from `Appraiser_Case__c` data, while also serving as a proof-of-concept platform for broader DocuSign API integrations across CLM and eSignature.
|
||||
|
||||
## Product goals
|
||||
- Let a user open an `Appraiser Case` in Salesforce and generate a review letter in DocuSign CLM without using Execute Anonymous.
|
||||
- Keep CLM account, folder, template, and destination selection configurable per account.
|
||||
- Persist generation status and generated-document references back to Salesforce.
|
||||
- Allow operators to browse and test eSignature API data from the same Salesforce app.
|
||||
- Provide a clean foundation for future workflows such as template selection, envelope creation, and richer document operations.
|
||||
|
||||
## Primary users
|
||||
- Salesforce admins configuring DocuSign connectivity and defaults
|
||||
- Business users generating review letters from `Appraiser Case` records
|
||||
- Technical users validating CLM and eSignature API behavior in a safe UI
|
||||
|
||||
## Current in-scope workflows
|
||||
|
||||
### 1. CLM document generation
|
||||
- User opens an `Appraiser Case` record.
|
||||
- User launches the `Generate Review Letter` quick action.
|
||||
- User selects a configured CLM account.
|
||||
- User selects a configured letter type.
|
||||
- User browses CLM folders and templates or uses saved defaults.
|
||||
- User generates a document through CLM `documentxmlmergetasks`.
|
||||
- User checks task status.
|
||||
- User attaches the generated CLM document back to Salesforce as a File.
|
||||
|
||||
### 2. eSignature API browsing
|
||||
- User opens an `Appraiser Case` record.
|
||||
- User navigates to the `Docusign eSignature` tab on the record page.
|
||||
- User selects a configured account.
|
||||
- User loads:
|
||||
- login information
|
||||
- OAuth user info
|
||||
- discovered eSignature accounts
|
||||
- account templates
|
||||
- recent envelopes
|
||||
|
||||
## Canonical Salesforce data model
|
||||
|
||||
### Appraiser Case
|
||||
`Appraiser_Case__c` is the primary business record.
|
||||
|
||||
Current functional fields include:
|
||||
- `Name`
|
||||
- `Appraiser_Field_Review_Date__c`
|
||||
- `Letter_Sent_Date__c`
|
||||
- `FHA_Case_Number__c`
|
||||
- `Appraiser_Name__c`
|
||||
- `Appraiser_Last_Name__c`
|
||||
- `Appraiser_Street__c`
|
||||
- `Appraiser_City__c`
|
||||
- `Appraiser_State_Province__c`
|
||||
- `Appraiser_Postal_Code__c`
|
||||
- `Appraiser_Country__c`
|
||||
- `Property_Street__c`
|
||||
- `Property_City__c`
|
||||
- `Property_State_Province__c`
|
||||
- `Property_Postal_Code__c`
|
||||
- `Property_Country__c`
|
||||
|
||||
Current CLM tracking fields include:
|
||||
- `Last_CLM_Account_Code__c`
|
||||
- `Last_DocGen_Status__c`
|
||||
- `Last_DocGen_Message__c`
|
||||
- `Last_DocGen_Task_Id__c`
|
||||
- `Last_DocGen_Task_Url__c`
|
||||
- `Generated_Document_Id__c`
|
||||
- `Generated_Document_Url__c`
|
||||
- `Last_DocGen_Requested_At__c`
|
||||
- `Last_DocGen_Completed_At__c`
|
||||
- `Last_Template_Document_Href__c`
|
||||
- `Last_Destination_Folder_Href__c`
|
||||
- `Attached_File_Content_Document_Id__c`
|
||||
- `Attached_File_Url__c`
|
||||
|
||||
### Appraiser Case Deficiency
|
||||
`Appraiser_Case_Deficiency__c` is the canonical child object.
|
||||
|
||||
Fields:
|
||||
- `Appraiser_Case__c`
|
||||
- `Deficiency_Number__c`
|
||||
- `Description__c`
|
||||
- `Reference__c`
|
||||
- `Resolution__c`
|
||||
|
||||
Validation:
|
||||
- blank deficiency records are blocked by validation
|
||||
|
||||
### CLM account configuration
|
||||
`CLM_Account_Setting__mdt` is the account-level configuration source of truth.
|
||||
|
||||
Fields include:
|
||||
- account identity
|
||||
- `Account_Code__c`
|
||||
- `Account_Display_Name__c`
|
||||
- `Environment_Code__c`
|
||||
- `Active__c`
|
||||
- CLM configuration
|
||||
- `CLM_Account_Id__c`
|
||||
- `CLM_Api_Named_Credential__c`
|
||||
- `CLM_Download_Named_Credential__c`
|
||||
- `Template_Root_Folder_Href__c`
|
||||
- `Destination_Root_Folder_Href__c`
|
||||
- `Default_Template_Document_Href__c`
|
||||
- `Default_Destination_Document_Name_Prefix__c`
|
||||
- eSignature configuration
|
||||
- `ESignature_Auth_Named_Credential__c`
|
||||
- `ESignature_Rest_Named_Credential__c`
|
||||
- `ESignature_Account_Id__c`
|
||||
|
||||
Current active example accounts:
|
||||
- `DTC CLM Demo`
|
||||
- `DTC IAM Enterprise`
|
||||
- `DTC HUD Demo`
|
||||
|
||||
### Letter definition configuration
|
||||
`CLM_Letter_Definition__mdt` is the extensibility layer for letter types.
|
||||
|
||||
Each record can define:
|
||||
- account code
|
||||
- letter code
|
||||
- display name
|
||||
- default flag
|
||||
- sort order
|
||||
- default template href
|
||||
- template root folder href
|
||||
- destination root folder href
|
||||
- default filename prefix
|
||||
|
||||
Current seeded letter definition:
|
||||
- `APPRAISER_REVIEW`
|
||||
|
||||
Design intent:
|
||||
- keep the current appraiser review letter working
|
||||
- allow three additional letters to be added without redesigning the workbench
|
||||
- support more letters later by metadata rather than code branching
|
||||
|
||||
## Canonical CLM architecture
|
||||
|
||||
### Payload generation
|
||||
[AppraiserCasePayloadBuilder.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/AppraiserCasePayloadBuilder.cls) builds the document payload from `Appraiser_Case__c` and `Appraiser_Case_Deficiency__c`.
|
||||
|
||||
Current payload fields:
|
||||
- `AppraiserCaseNumber`
|
||||
- `AppraiserFieldReviewDate`
|
||||
- `LetterSentDate`
|
||||
- `FHACaseNumber`
|
||||
- `AppraiserName`
|
||||
- `AppraiserLastName`
|
||||
- `AppraiserStreet`
|
||||
- `AppraiserCity`
|
||||
- `AppraiserStateProvince`
|
||||
- `AppraiserPostalCode`
|
||||
- `AppraiserCountry`
|
||||
- `AppraiserAddress`
|
||||
- `PropertyStreet`
|
||||
- `PropertyCity`
|
||||
- `PropertyStateProvince`
|
||||
- `PropertyPostalCode`
|
||||
- `PropertyCountry`
|
||||
- `PropertyAddress`
|
||||
- `DeficiencyList[]`
|
||||
|
||||
### CLM callouts
|
||||
[CLMDocGenCallout.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/CLMDocGenCallout.cls) is the canonical CLM integration class.
|
||||
|
||||
Current supported operations:
|
||||
- submit `documentxmlmergetasks`
|
||||
- poll task status
|
||||
- browse folders/documents through generic resource requests
|
||||
- download generated documents
|
||||
|
||||
### CLM orchestration
|
||||
[CLMAdminService.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/CLMAdminService.cls) is the UI-facing orchestration layer.
|
||||
|
||||
Current supported operations:
|
||||
- account selection and defaults
|
||||
- case context retrieval
|
||||
- folder/document browsing
|
||||
- document generation
|
||||
- task polling
|
||||
- generated document attachment to Salesforce Files
|
||||
|
||||
### CLM UI
|
||||
[clmDocGenWorkbench](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/lwc/clmDocGenWorkbench/clmDocGenWorkbench.js) is the current operator UI.
|
||||
|
||||
Current behavior:
|
||||
- launched from the `Generate Review Letter` quick action
|
||||
- uses account-based CLM config
|
||||
- uses account + letter-type selection
|
||||
- supports template browsing
|
||||
- supports destination browsing
|
||||
- supports destination filename selection
|
||||
- shows case deficiencies before generation
|
||||
- shows task details and file attachment status
|
||||
|
||||
## Canonical eSignature architecture
|
||||
|
||||
### eSignature service layer
|
||||
[DocusignESignatureService.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/DocusignESignatureService.cls) is the current eSignature integration layer.
|
||||
|
||||
Current supported operations:
|
||||
- `getAccountConfig`
|
||||
- `getLoginInformation`
|
||||
- `getUserInfo`
|
||||
- `listAccounts`
|
||||
- `getAccountInformation`
|
||||
- `listTemplates`
|
||||
- `listEnvelopes`
|
||||
|
||||
### eSignature UI
|
||||
[docusignEsignWorkbench](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/lwc/docusignEsignWorkbench/docusignEsignWorkbench.js) is the current exploratory UI.
|
||||
|
||||
Current behavior:
|
||||
- placed on its own `Docusign eSignature` record tab
|
||||
- supports account selection
|
||||
- shows raw login info and user info
|
||||
- shows discovered eSignature accounts
|
||||
- shows templates
|
||||
- shows recent envelopes filtered by `from_date`
|
||||
|
||||
## Named credential model
|
||||
|
||||
### CLM
|
||||
- UAT CLM API: `CLMuatNamedCreds`
|
||||
- UAT CLM download: `CLMuatDownload`
|
||||
- S1 CLM API: `CLMs1NamedCreds`
|
||||
- S1 CLM download: `CLMs1Download`
|
||||
|
||||
### eSignature
|
||||
- eSignature REST: `Esignature_Demo_NamedCreds`
|
||||
- OAuth/account server: `AcctDemo_NamedCreds`
|
||||
|
||||
## Current product behavior
|
||||
|
||||
### What is working now
|
||||
- record-based CLM doc generation from Salesforce UI
|
||||
- XML merge with repeating deficiencies
|
||||
- account-based CLM config
|
||||
- metadata-based letter-type selection with backward-compatible default behavior
|
||||
- generated document status persistence on the case
|
||||
- generated document attachment to the case as a Salesforce File
|
||||
- account-based eSignature API exploration
|
||||
- eSignature template listing
|
||||
- eSignature envelope listing
|
||||
|
||||
### Known limitations
|
||||
- no end-user workflow yet for creating or sending envelopes
|
||||
- no template or envelope detail drill-down in the eSignature UI
|
||||
- no persistent eSignature activity tracking on the case
|
||||
- no dedicated admin UI yet for managing account configuration beyond Salesforce Setup
|
||||
- some older docs still contain historical planning content
|
||||
|
||||
## Current template contract
|
||||
|
||||
### CLM XML merge fields
|
||||
- `//AppraiserCaseNumber`
|
||||
- `//AppraiserFieldReviewDate`
|
||||
- `//PropertyAddress`
|
||||
- `//PropertyStreet`
|
||||
- `//PropertyCity`
|
||||
- `//PropertyStateProvince`
|
||||
- `//PropertyPostalCode`
|
||||
- `//PropertyCountry`
|
||||
|
||||
### Deficiency repeating block
|
||||
- row select: `//DeficiencyList/Deficiency`
|
||||
- fields:
|
||||
- `./Number`
|
||||
- `./Description`
|
||||
- `./Reference`
|
||||
- `./Resolution`
|
||||
|
||||
## Near-term roadmap
|
||||
- add eSignature detail calls for template detail and envelope detail
|
||||
- add envelope creation and draft-send flows
|
||||
- add better operator UX for large account lists and debugging output
|
||||
- tighten documentation around setup and account configuration
|
||||
- decide whether to retire `Property_Address__c` completely from metadata
|
||||
|
||||
## Out of scope for the current proof of concept
|
||||
- full production-grade approval workflow
|
||||
- end-user template authoring inside Salesforce
|
||||
- large-scale reporting or analytics
|
||||
- middleware outside Salesforce
|
||||
|
||||
---
|
||||
Last updated: 2026-04-09
|
||||
|
|
@ -1,26 +1,28 @@
|
|||
# Appraiser Review Letter Generator (Salesforce + DocuSign CLM)
|
||||
|
||||
## Overview
|
||||
This repo contains a Salesforce metadata project for generating appraiser review letters from `Appraiser_Case__c` data in DocuSign CLM, while also providing a growing proof-of-concept surface for DocuSign eSignature API workflows.
|
||||
|
||||
## Current status
|
||||
- The repo is standardized on the XML CLM merge path built around `AppraiserCasePayloadBuilder` + `CLMDocGenCallout`.
|
||||
- `Appraiser_Case_Deficiency__c` is the canonical child deficiency object used by both the UI and document generation.
|
||||
- Account-based DocuSign configuration lives in `CLM_Account_Setting__mdt`.
|
||||
- The current Salesforce UI includes:
|
||||
- the `Generate Review Letter` quick action for CLM doc gen
|
||||
- the `Docusign eSignature` record tab for eSignature browsing
|
||||
- CLM generation, task tracking, file attachment, and eSignature template/envelope browsing are all implemented.
|
||||
|
||||
## Read this first
|
||||
- [PRODUCT_SPEC.md](./PRODUCT_SPEC.md): current product definition and architecture
|
||||
- [CURRENT_STATUS.md](./CURRENT_STATUS.md): implementation snapshot and current recommendations
|
||||
- [SALESFORCE_SETUP.md](./SALESFORCE_SETUP.md): metadata and org setup notes
|
||||
- [CLM_INTEGRATION.md](./CLM_INTEGRATION.md): CLM integration details
|
||||
- [requirements.md](./requirements.md) and [design.md](./design.md): condensed companion docs aligned to the product spec
|
||||
|
||||
## Documentation note
|
||||
Older planning docs still exist, but `PRODUCT_SPEC.md`, `CURRENT_STATUS.md`, `SALESFORCE_SETUP.md`, and the source code are now the best reflection of the project.
|
||||
## Welcome!
|
||||
This project contains documentation and guides for building Appraiser Review Letter templates for use in Salesforce CLM (Contract Lifecycle Management).
|
||||
|
||||
---
|
||||
_Last updated: 2026-04-09_
|
||||
|
||||
## Overview
|
||||
This application automates generation of personalized appraiser review letters within Salesforce, merging Q&A responses and related data into a DocuSign CLM-powered letter. Content and layout adjust dynamically to each review scenario.
|
||||
|
||||
## Architecture Overview
|
||||
- Templates are rendered using dynamic data from Salesforce objects
|
||||
- All merge fields and arrays are mapped from Salesforce data model
|
||||
- Modular blocks for easy maintenance and expansion
|
||||
|
||||
## Key Features
|
||||
- Dynamic merge of Appraiser Review answers (tables, paragraphs)
|
||||
- Salesforce-initiated CLM document generation and delivery
|
||||
- Robust requirements, data, and design documentation
|
||||
|
||||
## Onboarding
|
||||
- Clone docs directory into your Salesforce project repo
|
||||
- Review CLM_TEMPLATE_GUIDE.md for template logic and examples
|
||||
- See requirements.md for merge field details
|
||||
|
||||
---
|
||||
_Last updated: 2026-02-26 13:23 PM_
|
||||
_Work in progress: Add quick-start workflow and test recommendations._
|
||||
|
|
|
|||
|
|
@ -1,72 +1,40 @@
|
|||
# Salesforce Setup — Appraiser Case + DocuSign CLM
|
||||
|
||||
> Status note (2026-04-07): the project is now standardized on `Appraiser_Case_Deficiency__c` for both UI display and CLM document generation.
|
||||
|
||||
## What was added
|
||||
|
||||
### Custom object: `Appraiser_Case__c`
|
||||
- Auto-number name field labeled **Appraiser Case Number**
|
||||
- Dates: `Appraiser_Field_Review_Date__c`, `Letter_Sent_Date__c`
|
||||
- Appraiser identity: `Appraiser_Name__c`, `Appraiser_Last_Name__c`, `Appraiser_Salutation__c`, `Appraiser_Email__c`
|
||||
- Appraiser address: `Appraiser_Street__c`, `Appraiser_City__c`, `Appraiser_State_Province__c`, `Appraiser_Postal_Code__c`, `Appraiser_Country__c`
|
||||
- Property address: `Property_Street__c`, `Property_City__c`, `Property_State_Province__c`, `Property_Postal_Code__c`, `Property_Country__c`
|
||||
- Case metadata: `FHA_Case_Number__c`
|
||||
- CLM tracking: `Last_CLM_Account_Code__c`, `Last_DocGen_Status__c`, `Last_DocGen_Message__c`, `Last_DocGen_Task_Id__c`, `Last_DocGen_Task_Url__c`, `Last_Template_Document_Href__c`, `Last_Destination_Folder_Href__c`, `Last_DocGen_Requested_At__c`, `Last_DocGen_Completed_At__c`
|
||||
- Generated document: `Generated_Document_Id__c`, `Generated_Document_Url__c`
|
||||
- Attached file: `Attached_File_Content_Document_Id__c`, `Attached_File_Url__c`
|
||||
- `Appraiser_Field_Review_Date__c` (Date)
|
||||
- `Property_Street__c` (Text 255)
|
||||
- `Property_City__c` (Text 80)
|
||||
- `Property_State_Province__c` (Text 80)
|
||||
- `Property_Postal_Code__c` (Text 20)
|
||||
- `Property_Country__c` (Text 80)
|
||||
|
||||
### Child custom object: `Appraiser_Case_Deficiency__c`
|
||||
### Child custom object: `Appraiser_Deficiency__c`
|
||||
- Master-detail to `Appraiser_Case__c`
|
||||
- `Deficiency_Number__c` (Number)
|
||||
- `Deficiency_Number__c` (Text 50)
|
||||
- `Description__c` (Long Text Area)
|
||||
- `Resolution__c` (Long Text Area)
|
||||
- `Reference__c` (Text)
|
||||
- `Sort_Order__c` (Number)
|
||||
|
||||
### Custom metadata types
|
||||
- `CLM_Account_Setting__mdt` — per-account CLM and eSignature configuration (Named Credentials, folder hrefs, template hrefs)
|
||||
- `CLM_Letter_Definition__mdt` — per-account letter type definitions with optional folder/template overrides
|
||||
- `CLM_Environment_Setting__mdt` — legacy environment defaults (UAT/S1); superseded by account settings
|
||||
|
||||
### Apex classes
|
||||
- `AppraiserCasePayloadBuilder.cls` + test
|
||||
- `CLMDocGenCallout.cls` + test
|
||||
- `CLMAdminService.cls` + test
|
||||
- `DocusignESignatureService.cls` + test
|
||||
|
||||
### Lightning Web Components
|
||||
- `clmDocGenWorkbench` — CLM document generation UI (account selection, folder browsing, merge task submission, status polling, file attachment)
|
||||
- `docusignEsignWorkbench` — eSignature API browsing (accounts, templates, envelopes)
|
||||
- `clmRequestPreview` — merge request preview utility
|
||||
|
||||
### Named credentials
|
||||
- `CLMuatNamedCreds`, `CLMs1NamedCreds` — CLM API calls
|
||||
- `CLMuatDownload`, `CLMs1Download` — CLM document downloads
|
||||
- `Esignature_Demo_NamedCreds` — eSignature REST API
|
||||
- `AcctDemo_NamedCreds` — eSignature OAuth/userinfo (SecuredEndpoint backed by `DocusignJWT` external credential)
|
||||
|
||||
### Layouts and UI
|
||||
- Page layouts for `Appraiser_Case__c` and `Appraiser_Case_Deficiency__c`
|
||||
### Layouts
|
||||
- Basic page layout for Appraiser Case
|
||||
- Basic page layout for Appraiser Deficiency
|
||||
- Related list on Appraiser Case for deficiencies
|
||||
- Record page: `Appraiser_Case_Record_Page`
|
||||
- Quick action: `Generate Review Letter` (launches `clmDocGenWorkbench`)
|
||||
- Custom tabs for both objects
|
||||
- Custom app: `Appraiser_Review`
|
||||
- Basic list view on Appraiser Case
|
||||
|
||||
### Permissions
|
||||
- `Appraiser_Case_Admin` — full access
|
||||
- `Appraiser_Case_Access` — read/create access
|
||||
### Tabs and permissions
|
||||
- Custom tabs for both objects
|
||||
- Permission set: `Appraiser_Case_Admin`
|
||||
|
||||
### Apex
|
||||
- `AppraiserCaseDocGenService.cls`
|
||||
- `AppraiserCaseDocGenServiceTest.cls`
|
||||
|
||||
### Sample data
|
||||
- Anonymous Apex script: `scripts/apex/createSampleAppraiserCase.apex`
|
||||
|
||||
## Manual post-deploy steps
|
||||
|
||||
|
||||
### DocusignJWT external credential
|
||||
`force-app/main/default/externalCredentials/DocusignJWT.externalCredential-meta.xml` contains demo values for `iss` (integration key) and `sub` (user GUID) targeting `account-d.docusign.com`. Replace these with your org's actual integration key and impersonation user GUID before use.
|
||||
|
||||
---
|
||||
|
||||
## Deploy
|
||||
|
||||
From the project root:
|
||||
|
|
@ -84,7 +52,7 @@ sf project deploy start --source-dir /home/paulh/.openclaw/workspace/projects/sa
|
|||
## Test Apex
|
||||
|
||||
```bash
|
||||
sf apex run test --tests AppraiserCasePayloadBuilderTest --tests CLMAdminServiceTest --tests CLMDocGenCalloutTest --tests DocusignESignatureServiceTest --result-format human
|
||||
sf apex run test --tests AppraiserCaseDocGenServiceTest --result-format human
|
||||
```
|
||||
|
||||
## Load sample data
|
||||
|
|
@ -105,12 +73,12 @@ sf org assign permset --name Appraiser_Case_Admin
|
|||
2. Assign permission set.
|
||||
3. Run the sample Apex script.
|
||||
4. Open the `Appraiser Case` tab and verify the record + related deficiencies.
|
||||
5. Validate the XML merge payload path in debug logs or by running the Apex methods directly.
|
||||
6. Open an `Appraiser Case` record and use the `Generate Review Letter` action to browse folders/templates and submit a merge task.
|
||||
5. Validate the JSON payload in debug logs or by running the Apex methods directly.
|
||||
6. Wire the DocuSign CLM launch path based on the exact package capability in your org.
|
||||
|
||||
## About the quick action
|
||||
|
||||
The `Generate Review Letter` quick action is now wired to the `clmDocGenWorkbench` LWC for interactive CLM browsing and merge submission.
|
||||
A placeholder quick action metadata file was added to mark the launch point, but DocuSign CLM launch mechanics vary by package/org. See `docs/NEXT_STEPS_DOCGEN.md` for the practical wiring options.
|
||||
|
||||
## About “page layout” and “default setup”
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,48 +1,45 @@
|
|||
# Design — Appraiser Review Letter Platform
|
||||
# Design — Appraiser Review Letter Generator
|
||||
|
||||
This document now serves as a compact architecture summary. The broader current-state reference is [PRODUCT_SPEC.md](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/docs/PRODUCT_SPEC.md).
|
||||
|
||||
## Current architecture
|
||||
|
||||
### Salesforce records
|
||||
- `Appraiser_Case__c` is the parent business object
|
||||
- `Appraiser_Case_Deficiency__c` is the canonical child object
|
||||
|
||||
### Configuration
|
||||
- `CLM_Account_Setting__mdt` stores account-level CLM and eSignature configuration
|
||||
|
||||
### CLM path
|
||||
- [AppraiserCasePayloadBuilder.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/AppraiserCasePayloadBuilder.cls)
|
||||
- [CLMDocGenCallout.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/CLMDocGenCallout.cls)
|
||||
- [CLMAdminService.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/CLMAdminService.cls)
|
||||
- [clmDocGenWorkbench](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/lwc/clmDocGenWorkbench/clmDocGenWorkbench.js)
|
||||
|
||||
### eSignature path
|
||||
- [DocusignESignatureService.cls](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/classes/DocusignESignatureService.cls)
|
||||
- [docusignEsignWorkbench](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app/main/default/lwc/docusignEsignWorkbench/docusignEsignWorkbench.js)
|
||||
|
||||
## CLM merge design
|
||||
- Salesforce builds XML merge data from the case and related deficiencies.
|
||||
- CLM document generation uses `documentxmlmergetasks`.
|
||||
- Folder and template selection are account-configurable and browsable in the UI.
|
||||
- Task results and generated-document references are persisted back to `Appraiser_Case__c`.
|
||||
- Generated CLM output can be downloaded via Named Credential and attached to the case as a Salesforce File.
|
||||
|
||||
## eSignature design
|
||||
- Separate Named Credentials are used for REST calls and account-server OAuth/userinfo calls.
|
||||
- The service currently supports:
|
||||
- login information
|
||||
- OAuth user info
|
||||
- discovered accounts
|
||||
- template listing
|
||||
- envelope listing
|
||||
- The current eSignature panel is an operator/admin browsing surface, not yet a business workflow.
|
||||
|
||||
## Design principles
|
||||
- prefer account-based config over environment-only config
|
||||
- keep DocuSign callouts in Apex behind UI-facing service methods
|
||||
- persist important CLM results onto the business record
|
||||
- use record-page or action-based LWCs for operator flows instead of Execute Anonymous
|
||||
## Architecture
|
||||
Describe the template structure, merge field handling logic, and integration with Salesforce CLM.
|
||||
|
||||
---
|
||||
Last updated: 2026-04-09
|
||||
|
||||
## Data Model
|
||||
- Appraiser_Review__c: main record
|
||||
- Appraiser_Review_Question__c: child (per Q&A)
|
||||
|
||||
## Merge Data (to CLM)
|
||||
- Flat fields: appraiser, address, etc.
|
||||
- Collection: reviewQuestions[] with Q, A, comments, etc.
|
||||
|
||||
## Template Structure
|
||||
- Modular blocks (Header/Body/Footer)
|
||||
- Dynamic sections for deficiencies, comments, summary
|
||||
- Use of template language (Handlebars/Mustache/etc)
|
||||
|
||||
## Merge Logic
|
||||
- Iterate lists (arrays) for tables
|
||||
- Conditional display for sections/questions
|
||||
- Null/empty handling (default text or suppression)
|
||||
|
||||
## Example Structure
|
||||
```handlebars
|
||||
{{#each DeficiencyList}}
|
||||
<tr><td>{{DeficiencyType}}</td><td>{{DeficiencyDescription}}</td></tr>
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
## CLM Template Guidance
|
||||
- Repeat blocks/tables for reviewQuestions
|
||||
- Conditional/variable blocks for rich, dynamic output
|
||||
|
||||
---
|
||||
|
||||
## Questions/Decisions
|
||||
- Should merge logic be handled in Salesforce or intermediary middleware?
|
||||
- What template engine is ideal for maintainability and troubleshooting?
|
||||
|
||||
---
|
||||
_Last updated: 2026-02-26 13:20 PM_
|
||||
_Work in progress: More complete architecture diagrams and template examples forthcoming._
|
||||
|
|
|
|||
|
|
@ -1,32 +1,47 @@
|
|||
# Requirements — Appraiser Review Letter Platform
|
||||
# Requirements — Appraiser Review Letter Generator
|
||||
|
||||
This document now serves as a short requirements summary. The full current product specification is in [PRODUCT_SPEC.md](/home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/docs/PRODUCT_SPEC.md).
|
||||
|
||||
## Functional requirements
|
||||
- A user can generate an appraiser review letter from an `Appraiser_Case__c` record in Salesforce.
|
||||
- The generated CLM document merges property data and a repeating deficiency list from Salesforce.
|
||||
- The user can choose a configured CLM account instead of hardcoding environment-specific values.
|
||||
- The user can browse CLM templates and destination folders in the UI.
|
||||
- The user can track CLM task status and attach the generated document back to the case.
|
||||
- The system can browse core eSignature account data, templates, and envelopes for configured accounts.
|
||||
|
||||
## Non-functional requirements
|
||||
- Account-specific configuration must be deployable and maintainable through Salesforce metadata.
|
||||
- The solution must support both UAT and S1 CLM/eSignature account setups.
|
||||
- The primary document-generation path must be test-covered in Apex.
|
||||
- The solution should remain usable as a proof-of-concept platform for additional DocuSign API work.
|
||||
|
||||
## Canonical requirements decisions
|
||||
- Canonical parent object: `Appraiser_Case__c`
|
||||
- Canonical child deficiency object: `Appraiser_Case_Deficiency__c`
|
||||
- Canonical CLM generation path: XML merge via `documentxmlmergetasks`
|
||||
- Canonical config source: `CLM_Account_Setting__mdt`
|
||||
- Structured property address fields are the source of truth; `Property_Address__c` is legacy
|
||||
|
||||
## Current open product questions
|
||||
- What should the first production-grade eSignature workflow be: template detail, envelope detail, draft creation, or send?
|
||||
- Should eSignature activity be persisted back onto `Appraiser_Case__c` the way CLM activity is?
|
||||
- Should the eSignature workbench remain a testing/admin surface or evolve into a business-user workflow?
|
||||
## Purpose
|
||||
Outline technical and functional requirements for Appraiser Review Letter templates in Salesforce CLM. Include assumed merge fields, integration points, edge cases, and design goals.
|
||||
|
||||
---
|
||||
Last updated: 2026-04-09
|
||||
|
||||
## Functional
|
||||
- Reviewer completes form Q&A on Appraiser Review
|
||||
- Each response drives tailored content in final letter (questions, comments, tables/blocks)
|
||||
- Salesforce triggers CLM letter, merges data fields and Q&A collection
|
||||
|
||||
## Non-Functional
|
||||
- Configurable (new Qs/fields easy to add)
|
||||
- Audit and status tracking
|
||||
- Support for complex/dynamic tables in output
|
||||
|
||||
## Key Requirements
|
||||
- Support dynamic template merge fields (arrays/lists, booleans, enums)
|
||||
- Render tables, paragraphs, and conditional sections
|
||||
- Handle edge cases: nulls, empty lists, formatting gaps
|
||||
- Provide fallback text for empty sections (e.g., 'No deficiencies found')
|
||||
- Enable integration with Salesforce data model (objects: Appraisal, Deficiency, Reviewer)
|
||||
|
||||
## Example JSON
|
||||
```json
|
||||
{
|
||||
"AppraisalId": "a1b2c3",
|
||||
"DeficiencyList": [
|
||||
{
|
||||
"DeficiencyType": "Missing Docs",
|
||||
"DeficiencyDescription": "Appraisal report lacking required documents."
|
||||
}
|
||||
],
|
||||
"ReviewerComments": ["Well organized report."]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Questions/Decisions
|
||||
- What is the authoritative object and field schema for merge?
|
||||
- Are custom field mappings needed for relationships (e.g. lookup fields)?
|
||||
|
||||
---
|
||||
_Last updated: 2026-02-26 10:35 AM_
|
||||
_Work in progress: Integration and schema expansion next._
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<description>Dedicated Lightning app for Appraiser Case review and DocuSign CLM proof-of-concept testing.</description>
|
||||
<formFactors>Small</formFactors>
|
||||
<formFactors>Large</formFactors>
|
||||
<isNavAutoTempTabsDisabled>false</isNavAutoTempTabsDisabled>
|
||||
<isNavPersonalizationDisabled>false</isNavPersonalizationDisabled>
|
||||
<isNavTabPersistenceDisabled>false</isNavTabPersistenceDisabled>
|
||||
<isOmniPinnedViewEnabled>false</isOmniPinnedViewEnabled>
|
||||
<label>Appraiser Review</label>
|
||||
<navType>Standard</navType>
|
||||
<tabs>standard-home</tabs>
|
||||
<tabs>Appraiser_Case__c</tabs>
|
||||
<tabs>Appraiser_Case_Deficiency__c</tabs>
|
||||
<tabs>standard-report</tabs>
|
||||
<tabs>standard-Dashboard</tabs>
|
||||
<uiType>Lightning</uiType>
|
||||
</CustomApplication>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
public with sharing class AppraiserCaseDocGenService {
|
||||
public class DeficiencyDTO {
|
||||
@AuraEnabled public String deficiencyNumber;
|
||||
@AuraEnabled public String description;
|
||||
@AuraEnabled public String resolution;
|
||||
@AuraEnabled public Decimal sortOrder;
|
||||
}
|
||||
|
||||
public class AppraiserCasePayload {
|
||||
@AuraEnabled public Id caseId;
|
||||
@AuraEnabled public String appraiserCaseNumber;
|
||||
@AuraEnabled public Date appraiserFieldReviewDate;
|
||||
@AuraEnabled public String propertyStreet;
|
||||
@AuraEnabled public String propertyCity;
|
||||
@AuraEnabled public String propertyStateProvince;
|
||||
@AuraEnabled public String propertyPostalCode;
|
||||
@AuraEnabled public String propertyCountry;
|
||||
@AuraEnabled public String propertyAddressSingleLine;
|
||||
@AuraEnabled public List<DeficiencyDTO> deficiencies;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static String buildPayloadJson(Id appraiserCaseId) {
|
||||
return JSON.serialize(buildPayload(appraiserCaseId));
|
||||
}
|
||||
|
||||
public static AppraiserCasePayload buildPayload(Id appraiserCaseId) {
|
||||
Appraiser_Case__c appraiserCase = [
|
||||
SELECT Id,
|
||||
Name,
|
||||
Appraiser_Field_Review_Date__c,
|
||||
Property_Street__c,
|
||||
Property_City__c,
|
||||
Property_State_Province__c,
|
||||
Property_Postal_Code__c,
|
||||
Property_Country__c,
|
||||
(SELECT Id,
|
||||
Name,
|
||||
Deficiency_Number__c,
|
||||
Description__c,
|
||||
Resolution__c,
|
||||
Sort_Order__c
|
||||
FROM Appraiser_Deficiencies__r
|
||||
ORDER BY Sort_Order__c ASC, CreatedDate ASC)
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCaseId
|
||||
LIMIT 1
|
||||
];
|
||||
|
||||
AppraiserCasePayload payload = new AppraiserCasePayload();
|
||||
payload.caseId = appraiserCase.Id;
|
||||
payload.appraiserCaseNumber = appraiserCase.Name;
|
||||
payload.appraiserFieldReviewDate = appraiserCase.Appraiser_Field_Review_Date__c;
|
||||
payload.propertyStreet = appraiserCase.Property_Street__c;
|
||||
payload.propertyCity = appraiserCase.Property_City__c;
|
||||
payload.propertyStateProvince = appraiserCase.Property_State_Province__c;
|
||||
payload.propertyPostalCode = appraiserCase.Property_Postal_Code__c;
|
||||
payload.propertyCountry = appraiserCase.Property_Country__c;
|
||||
payload.propertyAddressSingleLine = buildAddress(appraiserCase);
|
||||
payload.deficiencies = new List<DeficiencyDTO>();
|
||||
|
||||
for (Appraiser_Deficiency__c deficiency : appraiserCase.Appraiser_Deficiencies__r) {
|
||||
DeficiencyDTO dto = new DeficiencyDTO();
|
||||
dto.deficiencyNumber = deficiency.Deficiency_Number__c;
|
||||
dto.description = deficiency.Description__c;
|
||||
dto.resolution = deficiency.Resolution__c;
|
||||
dto.sortOrder = deficiency.Sort_Order__c;
|
||||
payload.deficiencies.add(dto);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private static String buildAddress(Appraiser_Case__c appraiserCase) {
|
||||
List<String> parts = new List<String>();
|
||||
if (String.isNotBlank(appraiserCase.Property_Street__c)) parts.add(appraiserCase.Property_Street__c);
|
||||
|
||||
List<String> cityLine = new List<String>();
|
||||
if (String.isNotBlank(appraiserCase.Property_City__c)) cityLine.add(appraiserCase.Property_City__c);
|
||||
if (String.isNotBlank(appraiserCase.Property_State_Province__c)) cityLine.add(appraiserCase.Property_State_Province__c);
|
||||
if (String.isNotBlank(appraiserCase.Property_Postal_Code__c)) cityLine.add(appraiserCase.Property_Postal_Code__c);
|
||||
if (!cityLine.isEmpty()) parts.add(String.join(cityLine, ', '));
|
||||
|
||||
if (String.isNotBlank(appraiserCase.Property_Country__c)) parts.add(appraiserCase.Property_Country__c);
|
||||
return String.join(parts, ' | ');
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static Map<String, Object> buildDocGenRequest(Id appraiserCaseId, String templateKey) {
|
||||
AppraiserCasePayload payload = buildPayload(appraiserCaseId);
|
||||
Map<String, Object> requestBody = new Map<String, Object>();
|
||||
requestBody.put('templateKey', templateKey);
|
||||
requestBody.put('recordId', appraiserCaseId);
|
||||
requestBody.put('sourceObject', 'Appraiser_Case__c');
|
||||
requestBody.put('mergeData', payload);
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static String buildDocGenRequestJson(Id appraiserCaseId, String templateKey) {
|
||||
return JSON.serialize(buildDocGenRequest(appraiserCaseId, templateKey));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
@IsTest
|
||||
private class AppraiserCaseDocGenServiceTest {
|
||||
@IsTest
|
||||
static void buildsPayloadAndRequestJson() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.newInstance(2026, 4, 1),
|
||||
Property_Street__c = '123 Main St',
|
||||
Property_City__c = 'Ottawa',
|
||||
Property_State_Province__c = 'ON',
|
||||
Property_Postal_Code__c = 'K1A 0A1',
|
||||
Property_Country__c = 'Canada'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Deficiency__c(
|
||||
Name = 'Deficiency 1',
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = '1',
|
||||
Description__c = 'Missing comparable sale analysis',
|
||||
Resolution__c = 'Provide updated comparable sales section',
|
||||
Sort_Order__c = 1
|
||||
);
|
||||
|
||||
Test.startTest();
|
||||
AppraiserCaseDocGenService.AppraiserCasePayload payload = AppraiserCaseDocGenService.buildPayload(appraiserCase.Id);
|
||||
String json = AppraiserCaseDocGenService.buildDocGenRequestJson(appraiserCase.Id, 'APPRAISER_REVIEW_LETTER');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(appraiserCase.Id, payload.caseId);
|
||||
System.assertEquals('Ottawa', payload.propertyCity);
|
||||
System.assertEquals(1, payload.deficiencies.size());
|
||||
System.assertEquals('1', payload.deficiencies[0].deficiencyNumber);
|
||||
System.assert(json.contains('APPRAISER_REVIEW_LETTER'));
|
||||
System.assert(json.contains('Missing comparable sale analysis'));
|
||||
}
|
||||
}
|
||||
|
|
@ -21,30 +21,7 @@ public class AppraiserCasePayloadBuilder {
|
|||
Map<String, Object> payload = new Map<String, Object>();
|
||||
payload.put('AppraiserCaseNumber', appraiserCase.Name);
|
||||
payload.put('AppraiserFieldReviewDate', formatDate(appraiserCase.Appraiser_Field_Review_Date__c));
|
||||
payload.put('LetterSentDate', formatDate(appraiserCase.Letter_Sent_Date__c));
|
||||
payload.put('FHACaseNumber', appraiserCase.FHA_Case_Number__c);
|
||||
payload.put('AppraiserName', appraiserCase.Appraiser_Name__c);
|
||||
payload.put('AppraiserSalutation', appraiserCase.Appraiser_Salutation__c);
|
||||
payload.put('AppraiserLastName', appraiserCase.Appraiser_Last_Name__c);
|
||||
payload.put('AppraiserEmail', appraiserCase.Appraiser_Email__c);
|
||||
payload.put('AppraiserStreet', appraiserCase.Appraiser_Street__c);
|
||||
payload.put('AppraiserCity', appraiserCase.Appraiser_City__c);
|
||||
payload.put('AppraiserStateProvince', appraiserCase.Appraiser_State_Province__c);
|
||||
payload.put('AppraiserPostalCode', appraiserCase.Appraiser_Postal_Code__c);
|
||||
payload.put('AppraiserCountry', appraiserCase.Appraiser_Country__c);
|
||||
payload.put('AppraiserAddress', formatMailingAddress(
|
||||
appraiserCase.Appraiser_Street__c,
|
||||
appraiserCase.Appraiser_City__c,
|
||||
appraiserCase.Appraiser_State_Province__c,
|
||||
appraiserCase.Appraiser_Postal_Code__c,
|
||||
appraiserCase.Appraiser_Country__c
|
||||
));
|
||||
payload.put('PropertyStreet', appraiserCase.Property_Street__c);
|
||||
payload.put('PropertyCity', appraiserCase.Property_City__c);
|
||||
payload.put('PropertyStateProvince', appraiserCase.Property_State_Province__c);
|
||||
payload.put('PropertyPostalCode', appraiserCase.Property_Postal_Code__c);
|
||||
payload.put('PropertyCountry', appraiserCase.Property_Country__c);
|
||||
payload.put('PropertyAddress', formatAddress(appraiserCase));
|
||||
payload.put('PropertyAddress', appraiserCase.Property_Address__c);
|
||||
|
||||
// Transform child deficiencies into DeficiencyList array
|
||||
List<Map<String, Object>> deficiencyList = new List<Map<String, Object>>();
|
||||
|
|
@ -54,7 +31,6 @@ public class AppraiserCasePayloadBuilder {
|
|||
defMap.put('deficiencyNumber', deficiency.Deficiency_Number__c);
|
||||
defMap.put('description', deficiency.Description__c);
|
||||
defMap.put('resolution', deficiency.Resolution__c);
|
||||
defMap.put('reference', deficiency.Reference__c);
|
||||
deficiencyList.add(defMap);
|
||||
}
|
||||
}
|
||||
|
|
@ -84,23 +60,8 @@ public class AppraiserCasePayloadBuilder {
|
|||
Id,
|
||||
Name,
|
||||
Appraiser_Field_Review_Date__c,
|
||||
Letter_Sent_Date__c,
|
||||
FHA_Case_Number__c,
|
||||
Appraiser_Name__c,
|
||||
Appraiser_Salutation__c,
|
||||
Appraiser_Last_Name__c,
|
||||
Appraiser_Email__c,
|
||||
Appraiser_Street__c,
|
||||
Appraiser_City__c,
|
||||
Appraiser_State_Province__c,
|
||||
Appraiser_Postal_Code__c,
|
||||
Appraiser_Country__c,
|
||||
Property_Street__c,
|
||||
Property_City__c,
|
||||
Property_State_Province__c,
|
||||
Property_Postal_Code__c,
|
||||
Property_Country__c,
|
||||
(SELECT Id, Deficiency_Number__c, Description__c, Resolution__c, Reference__c
|
||||
Property_Address__c,
|
||||
(SELECT Id, Deficiency_Number__c, Description__c, Resolution__c
|
||||
FROM Deficiencies__r
|
||||
ORDER BY Deficiency_Number__c ASC)
|
||||
FROM Appraiser_Case__c
|
||||
|
|
@ -112,60 +73,11 @@ public class AppraiserCasePayloadBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description Format date for CLM merge (for example Apr 09, 2026) or null.
|
||||
* @description Format date for CLM merge (YYYY-MM-DD or null).
|
||||
* @param dt Date field value
|
||||
* @return String Formatted date or null
|
||||
*/
|
||||
private static String formatDate(Date dt) {
|
||||
return dt != null
|
||||
? DateTime.newInstance(dt, Time.newInstance(0, 0, 0, 0)).format('MMM dd, yyyy')
|
||||
: null;
|
||||
}
|
||||
|
||||
private static String formatAddress(Appraiser_Case__c appraiserCase) {
|
||||
return formatMailingAddress(
|
||||
appraiserCase.Property_Street__c,
|
||||
appraiserCase.Property_City__c,
|
||||
appraiserCase.Property_State_Province__c,
|
||||
appraiserCase.Property_Postal_Code__c,
|
||||
appraiserCase.Property_Country__c
|
||||
);
|
||||
}
|
||||
|
||||
public static String formatMailingAddress(
|
||||
String street,
|
||||
String city,
|
||||
String stateProvince,
|
||||
String postalCode,
|
||||
String country
|
||||
) {
|
||||
List<String> lines = new List<String>();
|
||||
if (String.isNotBlank(street)) {
|
||||
lines.add(street.trim());
|
||||
}
|
||||
|
||||
List<String> localityParts = new List<String>();
|
||||
if (String.isNotBlank(city)) {
|
||||
localityParts.add(city.trim());
|
||||
}
|
||||
if (String.isNotBlank(stateProvince)) {
|
||||
localityParts.add(stateProvince.trim());
|
||||
}
|
||||
|
||||
String locality = String.join(localityParts, ', ');
|
||||
if (String.isNotBlank(postalCode)) {
|
||||
locality = String.isNotBlank(locality)
|
||||
? locality + ' ' + postalCode.trim()
|
||||
: postalCode.trim();
|
||||
}
|
||||
if (String.isNotBlank(locality)) {
|
||||
lines.add(locality);
|
||||
}
|
||||
|
||||
if (String.isNotBlank(country)) {
|
||||
lines.add(country.trim());
|
||||
}
|
||||
|
||||
return lines.isEmpty() ? null : String.join(lines, ', ');
|
||||
return dt != null ? dt.format() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,8 @@ private class AppraiserCasePayloadBuilderTest {
|
|||
static void setupTestData() {
|
||||
// Create test Appraiser Case
|
||||
Appraiser_Case__c testCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.newInstance(2026, 4, 2),
|
||||
Letter_Sent_Date__c = Date.newInstance(2026, 4, 9),
|
||||
FHA_Case_Number__c = '123-4567890',
|
||||
Appraiser_Name__c = 'Jamie Appraiser',
|
||||
Appraiser_Salutation__c = 'Ms.',
|
||||
Appraiser_Last_Name__c = 'Appraiser',
|
||||
Appraiser_Email__c = 'jamie.appraiser@example.com',
|
||||
Appraiser_Street__c = '245 Lexington Ave',
|
||||
Appraiser_City__c = 'New York',
|
||||
Appraiser_State_Province__c = 'NY',
|
||||
Appraiser_Postal_Code__c = '10016',
|
||||
Appraiser_Country__c = 'USA',
|
||||
Property_Street__c = '123 Main St',
|
||||
Property_City__c = 'Denver',
|
||||
Property_State_Province__c = 'CO',
|
||||
Property_Postal_Code__c = '80202',
|
||||
Property_Country__c = 'USA'
|
||||
Appraiser_Field_Review_Date__c = Date.parse('04/02/2026'),
|
||||
Property_Address__c = '123 Main St, Denver, CO 80202'
|
||||
);
|
||||
insert testCase;
|
||||
|
||||
|
|
@ -31,15 +16,13 @@ private class AppraiserCasePayloadBuilderTest {
|
|||
Appraiser_Case__c = testCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Missing comparable sale adjustment detail.',
|
||||
Resolution__c = 'Added adjustment rationale and supporting calculations.',
|
||||
Reference__c = 'VC-1'
|
||||
Resolution__c = 'Added adjustment rationale and supporting calculations.'
|
||||
));
|
||||
testDefs.add(new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = testCase.Id,
|
||||
Deficiency_Number__c = 2,
|
||||
Description__c = 'Neighborhood trend explanation insufficient.',
|
||||
Resolution__c = 'Expanded market trend narrative with MLS evidence.',
|
||||
Reference__c = 'MC-2'
|
||||
Resolution__c = 'Expanded market trend narrative with MLS evidence.'
|
||||
));
|
||||
insert testDefs;
|
||||
}
|
||||
|
|
@ -53,39 +36,11 @@ private class AppraiserCasePayloadBuilderTest {
|
|||
Assert.isNotNull(payload, 'Payload should not be null');
|
||||
Assert.isTrue(payload.containsKey('AppraiserCaseNumber'), 'Payload should contain AppraiserCaseNumber');
|
||||
Assert.isTrue(payload.containsKey('AppraiserFieldReviewDate'), 'Payload should contain AppraiserFieldReviewDate');
|
||||
Assert.isTrue(payload.containsKey('LetterSentDate'), 'Payload should contain LetterSentDate');
|
||||
Assert.isTrue(payload.containsKey('FHACaseNumber'), 'Payload should contain FHACaseNumber');
|
||||
Assert.isTrue(payload.containsKey('AppraiserName'), 'Payload should contain AppraiserName');
|
||||
Assert.isTrue(payload.containsKey('AppraiserSalutation'), 'Payload should contain AppraiserSalutation');
|
||||
Assert.isTrue(payload.containsKey('AppraiserLastName'), 'Payload should contain AppraiserLastName');
|
||||
Assert.isTrue(payload.containsKey('AppraiserEmail'), 'Payload should contain AppraiserEmail');
|
||||
Assert.isTrue(payload.containsKey('AppraiserAddress'), 'Payload should contain AppraiserAddress');
|
||||
Assert.isTrue(payload.containsKey('PropertyAddress'), 'Payload should contain PropertyAddress');
|
||||
Assert.isTrue(payload.containsKey('PropertyStreet'), 'Payload should contain PropertyStreet');
|
||||
Assert.isTrue(payload.containsKey('PropertyCity'), 'Payload should contain PropertyCity');
|
||||
Assert.isTrue(payload.containsKey('PropertyStateProvince'), 'Payload should contain PropertyStateProvince');
|
||||
Assert.isTrue(payload.containsKey('PropertyPostalCode'), 'Payload should contain PropertyPostalCode');
|
||||
Assert.isTrue(payload.containsKey('PropertyCountry'), 'Payload should contain PropertyCountry');
|
||||
Assert.isTrue(payload.containsKey('DeficiencyList'), 'Payload should contain DeficiencyList');
|
||||
Assert.areEqual('123 Main St', (String) payload.get('PropertyStreet'));
|
||||
Assert.areEqual('Denver', (String) payload.get('PropertyCity'));
|
||||
Assert.areEqual('CO', (String) payload.get('PropertyStateProvince'));
|
||||
Assert.areEqual('80202', (String) payload.get('PropertyPostalCode'));
|
||||
Assert.areEqual('USA', (String) payload.get('PropertyCountry'));
|
||||
Assert.areEqual('123-4567890', (String) payload.get('FHACaseNumber'));
|
||||
Assert.areEqual('Apr 02, 2026', (String) payload.get('AppraiserFieldReviewDate'));
|
||||
Assert.areEqual('Apr 09, 2026', (String) payload.get('LetterSentDate'));
|
||||
Assert.areEqual('Jamie Appraiser', (String) payload.get('AppraiserName'));
|
||||
Assert.areEqual('Ms.', (String) payload.get('AppraiserSalutation'));
|
||||
Assert.areEqual('Appraiser', (String) payload.get('AppraiserLastName'));
|
||||
Assert.areEqual('jamie.appraiser@example.com', (String) payload.get('AppraiserEmail'));
|
||||
Assert.areEqual('245 Lexington Ave, New York, NY 10016, USA', (String) payload.get('AppraiserAddress'));
|
||||
Assert.areEqual('123 Main St, Denver, CO 80202, USA', (String) payload.get('PropertyAddress'));
|
||||
|
||||
List<Object> deficiencyList = (List<Object>) payload.get('DeficiencyList');
|
||||
Assert.areEqual(2, deficiencyList.size(), 'DeficiencyList should contain 2 items');
|
||||
Map<String, Object> firstDeficiency = (Map<String, Object>) deficiencyList[0];
|
||||
Assert.areEqual('VC-1', (String) firstDeficiency.get('reference'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
|
|
@ -103,10 +58,7 @@ private class AppraiserCasePayloadBuilderTest {
|
|||
static void testPayloadWithNullDate() {
|
||||
// Create case without review date
|
||||
Appraiser_Case__c testCase = new Appraiser_Case__c(
|
||||
Property_Street__c = '456 Oak Ave',
|
||||
Property_City__c = 'Boulder',
|
||||
Property_State_Province__c = 'CO',
|
||||
Property_Postal_Code__c = '80301'
|
||||
Property_Address__c = '456 Oak Ave, Boulder, CO 80301'
|
||||
);
|
||||
insert testCase;
|
||||
|
||||
|
|
@ -114,7 +66,6 @@ private class AppraiserCasePayloadBuilderTest {
|
|||
|
||||
Assert.isNotNull(payload, 'Payload should not be null even with null date');
|
||||
Assert.isNull(payload.get('AppraiserFieldReviewDate'), 'Null date should map to null in payload');
|
||||
Assert.areEqual('456 Oak Ave, Boulder, CO 80301', (String) payload.get('PropertyAddress'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
|
|
|
|||
|
|
@ -1,744 +0,0 @@
|
|||
public with sharing class CLMAdminService {
|
||||
public class CaseDeficiencyItem {
|
||||
@AuraEnabled public Id recordId;
|
||||
@AuraEnabled public Decimal deficiencyNumber;
|
||||
@AuraEnabled public String description;
|
||||
@AuraEnabled public String resolution;
|
||||
@AuraEnabled public String reference;
|
||||
}
|
||||
|
||||
public class CaseContext {
|
||||
@AuraEnabled public Id caseId;
|
||||
@AuraEnabled public String caseNumber;
|
||||
@AuraEnabled public String propertyAddress;
|
||||
@AuraEnabled public String lastDocGenStatus;
|
||||
@AuraEnabled public String lastDocGenMessage;
|
||||
@AuraEnabled public String lastClmAccountCode;
|
||||
@AuraEnabled public String lastDocGenTaskId;
|
||||
@AuraEnabled public String lastDocGenTaskUrl;
|
||||
@AuraEnabled public String generatedDocumentUrl;
|
||||
@AuraEnabled public String generatedDocumentId;
|
||||
@AuraEnabled public String attachedFileContentDocumentId;
|
||||
@AuraEnabled public String attachedFileUrl;
|
||||
@AuraEnabled public Datetime lastDocGenRequestedAt;
|
||||
@AuraEnabled public Datetime lastDocGenCompletedAt;
|
||||
@AuraEnabled public List<CaseDeficiencyItem> deficiencies;
|
||||
}
|
||||
|
||||
public class FileAttachmentResult {
|
||||
@AuraEnabled public Boolean success;
|
||||
@AuraEnabled public String message;
|
||||
@AuraEnabled public String contentDocumentId;
|
||||
@AuraEnabled public String fileUrl;
|
||||
@AuraEnabled public String fileTitle;
|
||||
}
|
||||
|
||||
public class AccountSettings {
|
||||
@AuraEnabled public String accountCode;
|
||||
@AuraEnabled public String accountDisplayName;
|
||||
@AuraEnabled public String environment;
|
||||
@AuraEnabled public String clmAccountId;
|
||||
@AuraEnabled public String clmApiNamedCredential;
|
||||
@AuraEnabled public String clmDownloadNamedCredential;
|
||||
@AuraEnabled public String eSignatureRestNamedCredential;
|
||||
@AuraEnabled public String templateRootFolderHref;
|
||||
@AuraEnabled public String destinationRootFolderHref;
|
||||
@AuraEnabled public String defaultTemplateDocumentHref;
|
||||
@AuraEnabled public String defaultDocumentNamePrefix;
|
||||
@AuraEnabled public Boolean active;
|
||||
}
|
||||
|
||||
public class LetterSettings {
|
||||
@AuraEnabled public String accountCode;
|
||||
@AuraEnabled public String letterCode;
|
||||
@AuraEnabled public String letterDisplayName;
|
||||
@AuraEnabled public String description;
|
||||
@AuraEnabled public Boolean isDefault;
|
||||
@AuraEnabled public Decimal sortOrder;
|
||||
@AuraEnabled public String templateRootFolderHref;
|
||||
@AuraEnabled public String destinationRootFolderHref;
|
||||
@AuraEnabled public String defaultTemplateDocumentHref;
|
||||
@AuraEnabled public String defaultDocumentNamePrefix;
|
||||
@AuraEnabled public Boolean active;
|
||||
}
|
||||
|
||||
public class ResourceItem {
|
||||
@AuraEnabled public String name;
|
||||
@AuraEnabled public String href;
|
||||
@AuraEnabled public String type;
|
||||
@AuraEnabled public String parentHref;
|
||||
@AuraEnabled public String rawJson;
|
||||
}
|
||||
|
||||
public class FolderContents {
|
||||
@AuraEnabled public ResourceItem folder;
|
||||
@AuraEnabled public List<ResourceItem> folders;
|
||||
@AuraEnabled public List<ResourceItem> documents;
|
||||
}
|
||||
|
||||
public class DocGenPreview {
|
||||
@AuraEnabled public String accountCode;
|
||||
@AuraEnabled public String accountDisplayName;
|
||||
@AuraEnabled public String letterCode;
|
||||
@AuraEnabled public String letterDisplayName;
|
||||
@AuraEnabled public String mergeTaskEndpointUrl;
|
||||
@AuraEnabled public String templateDocHref;
|
||||
@AuraEnabled public String destinationFolderHref;
|
||||
@AuraEnabled public String destinationDocName;
|
||||
@AuraEnabled public String payloadJson;
|
||||
@AuraEnabled public String dataXml;
|
||||
@AuraEnabled public String requestBodyJson;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=true)
|
||||
public static List<AccountSettings> listAccountSettings() {
|
||||
List<AccountSettings> settings = new List<AccountSettings>();
|
||||
for (CLM_Account_Setting__mdt row : [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
CLM_Account_Id__c,
|
||||
CLM_Api_Named_Credential__c,
|
||||
CLM_Download_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
ORDER BY Account_Display_Name__c ASC, DeveloperName ASC
|
||||
]) {
|
||||
settings.add(toAccountSettings(row));
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=true)
|
||||
public static AccountSettings getAccountSettings(String accountCode) {
|
||||
CLM_Account_Setting__mdt row = resolveAccountSetting(accountCode);
|
||||
return row == null ? null : toAccountSettings(row);
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=true)
|
||||
public static List<LetterSettings> listLetterSettings(String accountCode) {
|
||||
AccountSettings account = getAccountSettings(accountCode);
|
||||
List<LetterSettings> letters = new List<LetterSettings>();
|
||||
if (account == null) {
|
||||
return letters;
|
||||
}
|
||||
|
||||
for (CLM_Letter_Definition__mdt row : [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Letter_Code__c,
|
||||
Letter_Display_Name__c,
|
||||
Description__c,
|
||||
Active__c,
|
||||
Is_Default__c,
|
||||
Sort_Order__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c
|
||||
FROM CLM_Letter_Definition__mdt
|
||||
WHERE Active__c = true
|
||||
AND Account_Code__c = :account.accountCode
|
||||
ORDER BY Is_Default__c DESC, Sort_Order__c ASC, Letter_Display_Name__c ASC, DeveloperName ASC
|
||||
]) {
|
||||
letters.add(toLetterSettings(row, account));
|
||||
}
|
||||
|
||||
if (letters.isEmpty()) {
|
||||
letters.add(buildFallbackLetterSettings(account));
|
||||
}
|
||||
return letters;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=true)
|
||||
public static LetterSettings getLetterSettings(String accountCode, String letterCode) {
|
||||
AccountSettings account = getAccountSettings(accountCode);
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedLetterCode = String.isBlank(letterCode) ? null : letterCode.trim();
|
||||
if (String.isNotBlank(normalizedLetterCode)) {
|
||||
List<CLM_Letter_Definition__mdt> rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Letter_Code__c,
|
||||
Letter_Display_Name__c,
|
||||
Description__c,
|
||||
Active__c,
|
||||
Is_Default__c,
|
||||
Sort_Order__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c
|
||||
FROM CLM_Letter_Definition__mdt
|
||||
WHERE Active__c = true
|
||||
AND Account_Code__c = :account.accountCode
|
||||
AND Letter_Code__c = :normalizedLetterCode
|
||||
LIMIT 1
|
||||
];
|
||||
if (!rows.isEmpty()) {
|
||||
return toLetterSettings(rows[0], account);
|
||||
}
|
||||
}
|
||||
|
||||
List<LetterSettings> letters = listLetterSettings(account.accountCode);
|
||||
for (LetterSettings letter : letters) {
|
||||
if (letter.isDefault) {
|
||||
return letter;
|
||||
}
|
||||
}
|
||||
return letters.isEmpty() ? buildFallbackLetterSettings(account) : letters[0];
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static CLMDocGenCallout.CLMDocGenResponse generateDocument(
|
||||
Id appraiserCaseId,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String destinationDocName,
|
||||
String accountCode
|
||||
) {
|
||||
CLM_Account_Setting__mdt account = requireAccountSetting(accountCode);
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMDocGenCallout.generateDocument(
|
||||
(String) appraiserCaseId,
|
||||
templateDocHref,
|
||||
destinationFolderHref,
|
||||
destinationDocName,
|
||||
account.Environment_Code__c,
|
||||
account.CLM_Account_Id__c,
|
||||
account.CLM_Api_Named_Credential__c
|
||||
);
|
||||
persistDocGenResult(appraiserCaseId, templateDocHref, destinationFolderHref, response, false, accountCode);
|
||||
return response;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static CLMDocGenCallout.CLMDocGenResponse getTaskStatus(Id appraiserCaseId, String taskId, String accountCode) {
|
||||
CLM_Account_Setting__mdt account = requireAccountSetting(accountCode);
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMDocGenCallout.getTaskStatus(
|
||||
taskId,
|
||||
account.Environment_Code__c,
|
||||
account.CLM_Account_Id__c,
|
||||
account.CLM_Api_Named_Credential__c
|
||||
);
|
||||
persistDocGenResult(appraiserCaseId, null, null, response, true, accountCode);
|
||||
return response;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static String probeResource(String resourceOrHref, String accountCode) {
|
||||
CLM_Account_Setting__mdt account = requireAccountSetting(accountCode);
|
||||
return performGet(resourceOrHref, account).getBody();
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static DocGenPreview getDocGenPreview(Id appraiserCaseId, String accountCode, String letterCode) {
|
||||
if (appraiserCaseId == null) {
|
||||
throw new AuraHandledException('appraiserCaseId is required');
|
||||
}
|
||||
|
||||
AccountSettings account = getAccountSettings(accountCode);
|
||||
if (account == null) {
|
||||
throw new AuraHandledException('No active CLM account setting was found for ' + accountCode + '.');
|
||||
}
|
||||
|
||||
LetterSettings letter = getLetterSettings(account.accountCode, letterCode);
|
||||
List<Appraiser_Case__c> previewRows = [
|
||||
SELECT Id, Name
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCaseId
|
||||
LIMIT 1
|
||||
];
|
||||
if (previewRows.isEmpty()) {
|
||||
throw new AuraHandledException('Appraiser Case not found: ' + appraiserCaseId);
|
||||
}
|
||||
Appraiser_Case__c appraiserCase = previewRows[0];
|
||||
|
||||
String prefix = letter != null && String.isNotBlank(letter.defaultDocumentNamePrefix)
|
||||
? letter.defaultDocumentNamePrefix
|
||||
: account.defaultDocumentNamePrefix;
|
||||
|
||||
DocGenPreview preview = new DocGenPreview();
|
||||
preview.accountCode = account.accountCode;
|
||||
preview.accountDisplayName = account.accountDisplayName;
|
||||
preview.letterCode = letter != null ? letter.letterCode : 'APPRAISER_REVIEW';
|
||||
preview.letterDisplayName = letter != null ? letter.letterDisplayName : 'Appraiser Review Letter';
|
||||
preview.templateDocHref = letter != null ? letter.defaultTemplateDocumentHref : account.defaultTemplateDocumentHref;
|
||||
preview.destinationFolderHref = letter != null ? letter.destinationRootFolderHref : account.destinationRootFolderHref;
|
||||
preview.destinationDocName = buildDefaultDocumentName(prefix, appraiserCase.Name);
|
||||
preview.mergeTaskEndpointUrl = CLMDocGenCallout.buildDocumentXmlMergeTasksUrl(
|
||||
preview.templateDocHref,
|
||||
preview.destinationFolderHref,
|
||||
account.environment,
|
||||
account.clmAccountId
|
||||
);
|
||||
preview.payloadJson = JSON.serializePretty(AppraiserCasePayloadBuilder.buildPayload((String) appraiserCaseId));
|
||||
preview.dataXml = CLMDocGenCallout.prettyPrintXml(CLMDocGenCallout.buildDataXmlForCase((String) appraiserCaseId));
|
||||
preview.requestBodyJson = CLMDocGenCallout.buildRequestBodyJson(
|
||||
(String) appraiserCaseId,
|
||||
preview.templateDocHref,
|
||||
preview.destinationFolderHref,
|
||||
preview.destinationDocName
|
||||
);
|
||||
return preview;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static CaseContext getCaseContext(Id appraiserCaseId) {
|
||||
List<Appraiser_Case__c> contextRows = [
|
||||
SELECT Id,
|
||||
Name,
|
||||
Property_Street__c,
|
||||
Property_City__c,
|
||||
Property_State_Province__c,
|
||||
Property_Postal_Code__c,
|
||||
Property_Country__c,
|
||||
Last_DocGen_Status__c,
|
||||
Last_DocGen_Message__c,
|
||||
Last_CLM_Account_Code__c,
|
||||
Last_DocGen_Task_Id__c,
|
||||
Last_DocGen_Task_Url__c,
|
||||
Generated_Document_Url__c,
|
||||
Generated_Document_Id__c,
|
||||
Attached_File_Content_Document_Id__c,
|
||||
Attached_File_Url__c,
|
||||
Last_DocGen_Requested_At__c,
|
||||
Last_DocGen_Completed_At__c,
|
||||
(SELECT Id,
|
||||
Deficiency_Number__c,
|
||||
Description__c,
|
||||
Resolution__c,
|
||||
Reference__c
|
||||
FROM Deficiencies__r
|
||||
ORDER BY Deficiency_Number__c ASC, CreatedDate ASC)
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCaseId
|
||||
LIMIT 1
|
||||
];
|
||||
if (contextRows.isEmpty()) {
|
||||
throw new AuraHandledException('Appraiser Case not found: ' + appraiserCaseId);
|
||||
}
|
||||
Appraiser_Case__c appraiserCase = contextRows[0];
|
||||
|
||||
CaseContext context = new CaseContext();
|
||||
context.caseId = appraiserCase.Id;
|
||||
context.caseNumber = appraiserCase.Name;
|
||||
context.propertyAddress = formatAddress(appraiserCase);
|
||||
context.lastDocGenStatus = appraiserCase.Last_DocGen_Status__c;
|
||||
context.lastDocGenMessage = appraiserCase.Last_DocGen_Message__c;
|
||||
context.lastClmAccountCode = appraiserCase.Last_CLM_Account_Code__c;
|
||||
context.lastDocGenTaskId = appraiserCase.Last_DocGen_Task_Id__c;
|
||||
context.lastDocGenTaskUrl = appraiserCase.Last_DocGen_Task_Url__c;
|
||||
context.generatedDocumentUrl = appraiserCase.Generated_Document_Url__c;
|
||||
context.generatedDocumentId = appraiserCase.Generated_Document_Id__c;
|
||||
context.attachedFileContentDocumentId = appraiserCase.Attached_File_Content_Document_Id__c;
|
||||
context.attachedFileUrl = appraiserCase.Attached_File_Url__c;
|
||||
context.lastDocGenRequestedAt = appraiserCase.Last_DocGen_Requested_At__c;
|
||||
context.lastDocGenCompletedAt = appraiserCase.Last_DocGen_Completed_At__c;
|
||||
context.deficiencies = new List<CaseDeficiencyItem>();
|
||||
|
||||
if (appraiserCase.Deficiencies__r != null) {
|
||||
for (Appraiser_Case_Deficiency__c deficiency : appraiserCase.Deficiencies__r) {
|
||||
CaseDeficiencyItem item = new CaseDeficiencyItem();
|
||||
item.recordId = deficiency.Id;
|
||||
item.deficiencyNumber = deficiency.Deficiency_Number__c;
|
||||
item.description = deficiency.Description__c;
|
||||
item.resolution = deficiency.Resolution__c;
|
||||
item.reference = deficiency.Reference__c;
|
||||
context.deficiencies.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static FileAttachmentResult attachGeneratedDocumentToCase(Id appraiserCaseId, String accountCode) {
|
||||
if (appraiserCaseId == null) {
|
||||
throw new AuraHandledException('appraiserCaseId is required');
|
||||
}
|
||||
CLM_Account_Setting__mdt account = requireAccountSetting(accountCode);
|
||||
|
||||
Appraiser_Case__c appraiserCase = [
|
||||
SELECT Id,
|
||||
Name,
|
||||
Generated_Document_Url__c,
|
||||
Generated_Document_Id__c
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCaseId
|
||||
LIMIT 1
|
||||
];
|
||||
|
||||
if (String.isBlank(appraiserCase.Generated_Document_Url__c)) {
|
||||
throw new AuraHandledException('No generated document is available to attach yet.');
|
||||
}
|
||||
|
||||
CLMDocGenCallout.DownloadedDocument downloaded = CLMDocGenCallout.downloadDocument(
|
||||
appraiserCase.Generated_Document_Url__c,
|
||||
account.Environment_Code__c,
|
||||
account.CLM_Account_Id__c,
|
||||
account.CLM_Download_Named_Credential__c
|
||||
);
|
||||
String fileName = String.isNotBlank(downloaded.fileName)
|
||||
? downloaded.fileName
|
||||
: 'Generated_' + appraiserCase.Name + '.docx';
|
||||
String title = fileName.contains('.')
|
||||
? fileName.substringBeforeLast('.')
|
||||
: fileName;
|
||||
|
||||
ContentVersion version = new ContentVersion(
|
||||
Title = title,
|
||||
PathOnClient = '/' + fileName,
|
||||
VersionData = downloaded.body
|
||||
);
|
||||
insert version;
|
||||
|
||||
version = [
|
||||
SELECT Id, ContentDocumentId
|
||||
FROM ContentVersion
|
||||
WHERE Id = :version.Id
|
||||
LIMIT 1
|
||||
];
|
||||
|
||||
insert new ContentDocumentLink(
|
||||
ContentDocumentId = version.ContentDocumentId,
|
||||
LinkedEntityId = appraiserCase.Id,
|
||||
ShareType = 'V',
|
||||
Visibility = 'AllUsers'
|
||||
);
|
||||
|
||||
String fileUrl = '/lightning/r/ContentDocument/' + version.ContentDocumentId + '/view';
|
||||
update new Appraiser_Case__c(
|
||||
Id = appraiserCase.Id,
|
||||
Last_CLM_Account_Code__c = accountCode,
|
||||
Attached_File_Content_Document_Id__c = version.ContentDocumentId,
|
||||
Attached_File_Url__c = fileUrl
|
||||
);
|
||||
|
||||
FileAttachmentResult result = new FileAttachmentResult();
|
||||
result.success = true;
|
||||
result.message = 'Generated document attached to the case.';
|
||||
result.contentDocumentId = version.ContentDocumentId;
|
||||
result.fileUrl = fileUrl;
|
||||
result.fileTitle = title;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String formatAddress(Appraiser_Case__c appraiserCase) {
|
||||
return AppraiserCasePayloadBuilder.formatMailingAddress(
|
||||
appraiserCase.Property_Street__c,
|
||||
appraiserCase.Property_City__c,
|
||||
appraiserCase.Property_State_Province__c,
|
||||
appraiserCase.Property_Postal_Code__c,
|
||||
appraiserCase.Property_Country__c
|
||||
);
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static FolderContents getFolderContents(String folderHref, String accountCode) {
|
||||
if (String.isBlank(folderHref)) {
|
||||
throw new IllegalArgumentException('folderHref is required');
|
||||
}
|
||||
CLM_Account_Setting__mdt account = requireAccountSetting(accountCode);
|
||||
|
||||
FolderContents contents = new FolderContents();
|
||||
contents.folder = parseSingleResource(performGet(folderHref, account).getBody(), 'Folder');
|
||||
contents.folders = parseResourceList(performGet(folderHref + '/folders', account).getBody(), 'Folder', folderHref);
|
||||
contents.documents = parseResourceList(performGet(folderHref + '/documents', account).getBody(), 'Document', folderHref);
|
||||
return contents;
|
||||
}
|
||||
|
||||
private static HttpResponse performGet(String resourceOrHref, CLM_Account_Setting__mdt account) {
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(CLMDocGenCallout.buildEndpointForResource(
|
||||
resourceOrHref,
|
||||
account.CLM_Account_Id__c,
|
||||
account.CLM_Api_Named_Credential__c
|
||||
));
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(CLMDocGenCallout.HTTP_TIMEOUT);
|
||||
|
||||
HttpResponse res = new Http().send(req);
|
||||
Integer statusCode = res.getStatusCode();
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new AuraHandledException('CLM API Error (HTTP ' + statusCode + '): ' + res.getBody());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static ResourceItem parseSingleResource(String body, String defaultType) {
|
||||
Object root = JSON.deserializeUntyped(body);
|
||||
if (!(root instanceof Map<String, Object>)) {
|
||||
return null;
|
||||
}
|
||||
return parseResource((Map<String, Object>) root, defaultType, null);
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static List<ResourceItem> parseResourceList(String body, String defaultType, String parentHref) {
|
||||
Object root = JSON.deserializeUntyped(body);
|
||||
List<Object> records = unwrapList(root);
|
||||
List<ResourceItem> items = new List<ResourceItem>();
|
||||
|
||||
for (Object record : records) {
|
||||
if (record instanceof Map<String, Object>) {
|
||||
items.add(parseResource((Map<String, Object>) record, defaultType, parentHref));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static List<Object> unwrapList(Object root) {
|
||||
if (root instanceof List<Object>) {
|
||||
return (List<Object>) root;
|
||||
}
|
||||
|
||||
if (root instanceof Map<String, Object>) {
|
||||
Map<String, Object> payload = (Map<String, Object>) root;
|
||||
for (String key : new List<String>{ 'Results', 'Items', 'Documents', 'Folders' }) {
|
||||
Object value = payload.get(key);
|
||||
if (value instanceof List<Object>) {
|
||||
return (List<Object>) value;
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.size() == 1) {
|
||||
for (Object value : payload.values()) {
|
||||
if (value instanceof List<Object>) {
|
||||
return (List<Object>) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Object>();
|
||||
}
|
||||
|
||||
private static ResourceItem parseResource(Map<String, Object> source, String defaultType, String parentHref) {
|
||||
ResourceItem item = new ResourceItem();
|
||||
item.name = firstString(source, new List<String>{ 'Name', 'DisplayName', 'Title', 'Label' });
|
||||
item.href = firstString(source, new List<String>{ 'Href', 'Uri', 'Location' });
|
||||
item.type = firstString(source, new List<String>{ 'Type', 'ObjectType', 'ItemType' });
|
||||
item.parentHref = extractParentHref(source, parentHref);
|
||||
item.rawJson = JSON.serialize(source);
|
||||
|
||||
if (String.isBlank(item.type)) {
|
||||
item.type = defaultType;
|
||||
}
|
||||
|
||||
if (String.isBlank(item.name) && String.isNotBlank(item.href)) {
|
||||
item.name = item.href.substringAfterLast('/');
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static String extractParentHref(Map<String, Object> source, String fallbackValue) {
|
||||
Object parentValue = source.get('Parent');
|
||||
if (parentValue instanceof Map<String, Object>) {
|
||||
String href = firstString((Map<String, Object>) parentValue, new List<String>{ 'Href', 'Uri', 'Location' });
|
||||
if (String.isNotBlank(href)) {
|
||||
return href;
|
||||
}
|
||||
}
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
private static String firstString(Map<String, Object> source, List<String> keys) {
|
||||
for (String key : keys) {
|
||||
Object value = source.get(key);
|
||||
if (value != null) {
|
||||
String textValue = String.valueOf(value);
|
||||
if (String.isNotBlank(textValue)) {
|
||||
return textValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void persistDocGenResult(
|
||||
Id appraiserCaseId,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
CLMDocGenCallout.CLMDocGenResponse response,
|
||||
Boolean isStatusRefresh,
|
||||
String accountCode
|
||||
) {
|
||||
if (appraiserCaseId == null || response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Appraiser_Case__c updateCase = new Appraiser_Case__c(Id = appraiserCaseId);
|
||||
updateCase.Last_CLM_Account_Code__c = accountCode;
|
||||
updateCase.Last_DocGen_Status__c = String.isNotBlank(response.taskStatus) ? response.taskStatus : (response.success ? 'Submitted' : 'Failed');
|
||||
updateCase.Last_DocGen_Message__c = response.message;
|
||||
updateCase.Last_DocGen_Task_Id__c = response.documentId;
|
||||
updateCase.Last_DocGen_Task_Url__c = response.documentUrl;
|
||||
|
||||
if (!isStatusRefresh) {
|
||||
updateCase.Last_DocGen_Requested_At__c = System.now();
|
||||
updateCase.Last_Template_Document_Href__c = templateDocHref;
|
||||
updateCase.Last_Destination_Folder_Href__c = destinationFolderHref;
|
||||
}
|
||||
|
||||
if (String.isNotBlank(response.generatedDocumentUrl)) {
|
||||
updateCase.Generated_Document_Url__c = response.generatedDocumentUrl;
|
||||
updateCase.Generated_Document_Id__c = response.generatedDocumentId;
|
||||
}
|
||||
|
||||
if (response.success && String.valueOf(response.taskStatus).toLowerCase() == 'completed') {
|
||||
updateCase.Last_DocGen_Completed_At__c = System.now();
|
||||
}
|
||||
|
||||
update updateCase;
|
||||
}
|
||||
|
||||
private static CLM_Account_Setting__mdt requireAccountSetting(String accountCode) {
|
||||
CLM_Account_Setting__mdt row = resolveAccountSetting(accountCode);
|
||||
if (row == null) {
|
||||
throw new AuraHandledException('No active CLM account setting was found for ' + accountCode + '.');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private static CLM_Account_Setting__mdt resolveAccountSetting(String accountCode) {
|
||||
String normalizedCode = String.isBlank(accountCode) ? null : accountCode.trim();
|
||||
List<CLM_Account_Setting__mdt> rows;
|
||||
if (String.isNotBlank(normalizedCode)) {
|
||||
rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
CLM_Account_Id__c,
|
||||
CLM_Api_Named_Credential__c,
|
||||
CLM_Download_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
AND DeveloperName = :normalizedCode
|
||||
LIMIT 1
|
||||
];
|
||||
if (rows.isEmpty()) {
|
||||
rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
CLM_Account_Id__c,
|
||||
CLM_Api_Named_Credential__c,
|
||||
CLM_Download_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
AND Account_Code__c = :normalizedCode
|
||||
LIMIT 1
|
||||
];
|
||||
}
|
||||
} else {
|
||||
rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
CLM_Account_Id__c,
|
||||
CLM_Api_Named_Credential__c,
|
||||
CLM_Download_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
Template_Root_Folder_Href__c,
|
||||
Destination_Root_Folder_Href__c,
|
||||
Default_Template_Document_Href__c,
|
||||
Default_Destination_Document_Name_Prefix__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
ORDER BY Account_Display_Name__c ASC, DeveloperName ASC
|
||||
LIMIT 1
|
||||
];
|
||||
}
|
||||
return rows.isEmpty() ? null : rows[0];
|
||||
}
|
||||
|
||||
private static AccountSettings toAccountSettings(CLM_Account_Setting__mdt row) {
|
||||
AccountSettings settings = new AccountSettings();
|
||||
settings.accountCode = String.isNotBlank(row.Account_Code__c) ? row.Account_Code__c : row.DeveloperName;
|
||||
settings.accountDisplayName = String.isNotBlank(row.Account_Display_Name__c) ? row.Account_Display_Name__c : row.DeveloperName;
|
||||
settings.environment = row.Environment_Code__c;
|
||||
settings.clmAccountId = row.CLM_Account_Id__c;
|
||||
settings.clmApiNamedCredential = row.CLM_Api_Named_Credential__c;
|
||||
settings.clmDownloadNamedCredential = row.CLM_Download_Named_Credential__c;
|
||||
settings.eSignatureRestNamedCredential = row.ESignature_Rest_Named_Credential__c;
|
||||
settings.templateRootFolderHref = row.Template_Root_Folder_Href__c;
|
||||
settings.destinationRootFolderHref = row.Destination_Root_Folder_Href__c;
|
||||
settings.defaultTemplateDocumentHref = row.Default_Template_Document_Href__c;
|
||||
settings.defaultDocumentNamePrefix = row.Default_Destination_Document_Name_Prefix__c;
|
||||
settings.active = row.Active__c;
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static LetterSettings toLetterSettings(CLM_Letter_Definition__mdt row, AccountSettings account) {
|
||||
LetterSettings settings = new LetterSettings();
|
||||
settings.accountCode = String.isNotBlank(row.Account_Code__c) ? row.Account_Code__c : account.accountCode;
|
||||
settings.letterCode = String.isNotBlank(row.Letter_Code__c) ? row.Letter_Code__c : 'APPRAISER_REVIEW';
|
||||
settings.letterDisplayName = String.isNotBlank(row.Letter_Display_Name__c) ? row.Letter_Display_Name__c : row.DeveloperName;
|
||||
settings.description = row.Description__c;
|
||||
settings.isDefault = row.Is_Default__c;
|
||||
settings.sortOrder = row.Sort_Order__c;
|
||||
settings.templateRootFolderHref = firstNonBlankValue(row.Template_Root_Folder_Href__c, account.templateRootFolderHref);
|
||||
settings.destinationRootFolderHref = firstNonBlankValue(row.Destination_Root_Folder_Href__c, account.destinationRootFolderHref);
|
||||
settings.defaultTemplateDocumentHref = firstNonBlankValue(row.Default_Template_Document_Href__c, account.defaultTemplateDocumentHref);
|
||||
settings.defaultDocumentNamePrefix = firstNonBlankValue(row.Default_Destination_Document_Name_Prefix__c, account.defaultDocumentNamePrefix);
|
||||
settings.active = row.Active__c;
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static LetterSettings buildFallbackLetterSettings(AccountSettings account) {
|
||||
LetterSettings settings = new LetterSettings();
|
||||
settings.accountCode = account.accountCode;
|
||||
settings.letterCode = 'APPRAISER_REVIEW';
|
||||
settings.letterDisplayName = 'Appraiser Review Letter';
|
||||
settings.description = 'Fallback current appraiser letter flow.';
|
||||
settings.isDefault = true;
|
||||
settings.sortOrder = 10;
|
||||
settings.templateRootFolderHref = account.templateRootFolderHref;
|
||||
settings.destinationRootFolderHref = account.destinationRootFolderHref;
|
||||
settings.defaultTemplateDocumentHref = account.defaultTemplateDocumentHref;
|
||||
settings.defaultDocumentNamePrefix = account.defaultDocumentNamePrefix;
|
||||
settings.active = true;
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static String firstNonBlankValue(String preferredValue, String fallbackValue) {
|
||||
return String.isNotBlank(preferredValue) ? preferredValue : fallbackValue;
|
||||
}
|
||||
|
||||
private static String buildDefaultDocumentName(String prefix, String caseNumber) {
|
||||
String normalizedPrefix = String.isNotBlank(prefix) ? prefix : 'Review';
|
||||
return String.isNotBlank(caseNumber)
|
||||
? normalizedPrefix + '_' + caseNumber + '.docx'
|
||||
: normalizedPrefix + '.docx';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
@IsTest
|
||||
private class CLMAdminServiceTest {
|
||||
private class FolderBrowseMock implements HttpCalloutMock {
|
||||
public HttpResponse respond(HttpRequest req) {
|
||||
HttpResponse res = new HttpResponse();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.getMethod() == 'POST' && req.getEndpoint().contains('/documentxmlmergetasks')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Href":"https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documentxmlmergetasks/TASK-555","Status":"Queued","Result":{"Href":"https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/generated-555"}}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getEndpoint().endsWith('/folders/root-folder')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Name":"Templates Root","Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/root-folder","Type":"Folder","Parent":{"Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/root-parent"}}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getEndpoint().endsWith('/folders/root-folder/folders')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Results":[{"Name":"Residential","Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/residential","Parent":{"Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/root-folder"}}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getEndpoint().endsWith('/folders/root-folder/documents')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Results":[{"Name":"Appraiser Review Letter Template","Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/documents/template-1"}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/documentxmlmergetasks')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Href":"https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documentxmlmergetasks/TASK-555","Status":"Completed","Result":{"Href":"https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/generated-555"}}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/documents/generated-555')) {
|
||||
res.setStatusCode(200);
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="Review_AC-000001.docx"');
|
||||
res.setBodyAsBlob(Blob.valueOf('docx-binary'));
|
||||
return res;
|
||||
}
|
||||
|
||||
res.setStatusCode(404);
|
||||
res.setBody('{"Message":"Not Found"}');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void parsesFolderContents() {
|
||||
Test.setMock(HttpCalloutMock.class, new FolderBrowseMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMAdminService.FolderContents contents = CLMAdminService.getFolderContents(
|
||||
'https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/root-folder',
|
||||
'DTC_CLM_Demo'
|
||||
);
|
||||
String body = CLMAdminService.probeResource('/documentxmlmergetasks/TASK-555', 'DTC_IAM_Enterprise');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals('Templates Root', contents.folder.name);
|
||||
System.assertEquals('https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/root-parent', contents.folder.parentHref);
|
||||
System.assertEquals(1, contents.folders.size());
|
||||
System.assertEquals('Residential', contents.folders[0].name);
|
||||
System.assertEquals(1, contents.documents.size());
|
||||
System.assertEquals('Appraiser Review Letter Template', contents.documents[0].name);
|
||||
System.assert(body.contains('Completed'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void returnsAccountSettingsFromMetadata() {
|
||||
Test.startTest();
|
||||
List<CLMAdminService.AccountSettings> accounts = CLMAdminService.listAccountSettings();
|
||||
CLMAdminService.AccountSettings settings = CLMAdminService.getAccountSettings('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
Set<String> accountCodes = new Set<String>();
|
||||
for (CLMAdminService.AccountSettings account : accounts) {
|
||||
accountCodes.add(account.accountCode);
|
||||
}
|
||||
System.assert(accountCodes.contains('DTC_CLM_Demo'));
|
||||
System.assert(accountCodes.contains('DTC_IAM_Enterprise'));
|
||||
System.assert(accountCodes.contains('DTC_HUD_Demo'));
|
||||
System.assertNotEquals(null, settings);
|
||||
System.assertEquals('DTC_CLM_Demo', settings.accountCode);
|
||||
System.assertEquals('DTC CLM Demo', settings.accountDisplayName);
|
||||
System.assertEquals('UAT', settings.environment);
|
||||
System.assert(settings.destinationRootFolderHref.contains('/folders/'));
|
||||
System.assert(settings.defaultTemplateDocumentHref.contains('/documents/'));
|
||||
System.assertEquals('Review', settings.defaultDocumentNamePrefix);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void returnsLetterSettingsFromMetadata() {
|
||||
Test.startTest();
|
||||
List<CLMAdminService.LetterSettings> letters = CLMAdminService.listLetterSettings('DTC_CLM_Demo');
|
||||
CLMAdminService.LetterSettings settings = CLMAdminService.getLetterSettings('DTC_CLM_Demo', 'APPRAISER_REVIEW');
|
||||
Test.stopTest();
|
||||
|
||||
System.assert(!letters.isEmpty());
|
||||
System.assertEquals('APPRAISER_REVIEW', settings.letterCode);
|
||||
System.assertEquals('Appraiser Review Letter', settings.letterDisplayName);
|
||||
System.assertEquals(true, settings.isDefault);
|
||||
System.assertEquals('Review', settings.defaultDocumentNamePrefix);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void buildsDocGenPreviewForSelectedLetter() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Letter_Sent_Date__c = Date.newInstance(2026, 4, 9),
|
||||
FHA_Case_Number__c = '123-4567890',
|
||||
Appraiser_Name__c = 'Jamie Carter',
|
||||
Appraiser_Last_Name__c = 'Carter',
|
||||
Appraiser_Street__c = '12 Park Ave',
|
||||
Appraiser_City__c = 'New York',
|
||||
Appraiser_State_Province__c = 'NY',
|
||||
Appraiser_Postal_Code__c = '10016',
|
||||
Appraiser_Country__c = 'USA',
|
||||
Property_Street__c = '245 Lexington Ave',
|
||||
Property_City__c = 'New York',
|
||||
Property_State_Province__c = 'NY',
|
||||
Property_Postal_Code__c = '10016',
|
||||
Property_Country__c = 'USA'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Test deficiency',
|
||||
Resolution__c = 'Test resolution',
|
||||
Reference__c = 'VC-1'
|
||||
);
|
||||
|
||||
Test.startTest();
|
||||
CLMAdminService.DocGenPreview preview = CLMAdminService.getDocGenPreview(
|
||||
appraiserCase.Id,
|
||||
'DTC_CLM_Demo',
|
||||
'NOD_LETTER'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals('DTC_CLM_Demo', preview.accountCode);
|
||||
System.assertEquals('NOD_LETTER', preview.letterCode);
|
||||
System.assertEquals('NOD Letter', preview.letterDisplayName);
|
||||
Appraiser_Case__c refreshedCase = [
|
||||
SELECT Name
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCase.Id
|
||||
LIMIT 1
|
||||
];
|
||||
System.assertEquals('NOD_' + refreshedCase.Name + '.docx', preview.destinationDocName);
|
||||
System.assert(preview.mergeTaskEndpointUrl.endsWith('/documentxmlmergetasks'));
|
||||
System.assert(preview.payloadJson.contains('FHACaseNumber'));
|
||||
System.assert(preview.dataXml.contains('\n <'));
|
||||
System.assert(preview.dataXml.contains('<Reference>VC-1</Reference>'));
|
||||
System.assert(preview.requestBodyJson.contains('"DestinationDocumentName"'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void persistsCaseTrackingOnGenerate() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Property_Street__c = '123 Main St',
|
||||
Property_City__c = 'Denver',
|
||||
Property_State_Province__c = 'CO',
|
||||
Property_Postal_Code__c = '80202'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Test deficiency',
|
||||
Resolution__c = 'Test resolution',
|
||||
Reference__c = 'VC-1'
|
||||
);
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new FolderBrowseMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMDocGenCallout.CLMDocGenResponse generateResponse = CLMAdminService.generateDocument(
|
||||
appraiserCase.Id,
|
||||
'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/template-1',
|
||||
'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/root-folder',
|
||||
'Review_AC-000001.docx',
|
||||
'DTC_CLM_Demo'
|
||||
);
|
||||
CLMAdminService.CaseContext context = CLMAdminService.getCaseContext(appraiserCase.Id);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, generateResponse.success);
|
||||
System.assertEquals('Queued', context.lastDocGenStatus);
|
||||
System.assertEquals('DTC_CLM_Demo', context.lastClmAccountCode);
|
||||
System.assertEquals('TASK-555', context.lastDocGenTaskId);
|
||||
System.assert(context.lastDocGenTaskUrl.contains('/documentxmlmergetasks/TASK-555'));
|
||||
System.assert(context.generatedDocumentUrl.contains('/documents/generated-555'));
|
||||
System.assertNotEquals(null, context.lastDocGenRequestedAt);
|
||||
System.assertEquals(null, context.lastDocGenCompletedAt);
|
||||
System.assertEquals(1, context.deficiencies.size());
|
||||
System.assertEquals('123 Main St, Denver, CO 80202', context.propertyAddress);
|
||||
System.assertEquals('VC-1', context.deficiencies[0].reference);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void persistsCaseTrackingOnStatusRefresh() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Property_Street__c = '123 Main St',
|
||||
Property_City__c = 'Denver',
|
||||
Property_State_Province__c = 'CO',
|
||||
Property_Postal_Code__c = '80202',
|
||||
Last_DocGen_Task_Id__c = 'TASK-555'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Test deficiency',
|
||||
Resolution__c = 'Test resolution',
|
||||
Reference__c = 'VC-1'
|
||||
);
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new FolderBrowseMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMDocGenCallout.CLMDocGenResponse statusResponse = CLMAdminService.getTaskStatus(appraiserCase.Id, 'TASK-555', 'DTC_CLM_Demo');
|
||||
CLMAdminService.CaseContext context = CLMAdminService.getCaseContext(appraiserCase.Id);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, statusResponse.success);
|
||||
System.assertEquals('Completed', context.lastDocGenStatus);
|
||||
System.assertEquals('DTC_CLM_Demo', context.lastClmAccountCode);
|
||||
System.assertEquals('TASK-555', context.lastDocGenTaskId);
|
||||
System.assert(context.lastDocGenTaskUrl.contains('/documentxmlmergetasks/TASK-555'));
|
||||
System.assert(context.generatedDocumentUrl.contains('/documents/generated-555'));
|
||||
System.assertNotEquals(null, context.lastDocGenCompletedAt);
|
||||
System.assertEquals(1, context.deficiencies.size());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void attachesGeneratedDocumentToCaseAsSalesforceFile() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Property_Street__c = '245 Lexington Ave',
|
||||
Property_City__c = 'New York',
|
||||
Property_State_Province__c = 'NY',
|
||||
Property_Postal_Code__c = '10016',
|
||||
Generated_Document_Url__c = 'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/generated-555',
|
||||
Generated_Document_Id__c = 'generated-555'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new FolderBrowseMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMAdminService.FileAttachmentResult result = CLMAdminService.attachGeneratedDocumentToCase(appraiserCase.Id, 'DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
Appraiser_Case__c refreshedCase = [
|
||||
SELECT Last_CLM_Account_Code__c, Attached_File_Content_Document_Id__c, Attached_File_Url__c
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id = :appraiserCase.Id
|
||||
LIMIT 1
|
||||
];
|
||||
List<ContentDocumentLink> links = [
|
||||
SELECT ContentDocumentId, LinkedEntityId
|
||||
FROM ContentDocumentLink
|
||||
WHERE LinkedEntityId = :appraiserCase.Id
|
||||
];
|
||||
|
||||
System.assertEquals(true, result.success);
|
||||
System.assertNotEquals(null, result.contentDocumentId);
|
||||
System.assert(result.fileUrl.contains('/lightning/r/ContentDocument/'));
|
||||
System.assertEquals('DTC_CLM_Demo', refreshedCase.Last_CLM_Account_Code__c);
|
||||
System.assertEquals(result.contentDocumentId, refreshedCase.Attached_File_Content_Document_Id__c);
|
||||
System.assertEquals(result.fileUrl, refreshedCase.Attached_File_Url__c);
|
||||
System.assertEquals(1, links.size());
|
||||
System.assertEquals(result.contentDocumentId, links[0].ContentDocumentId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +1,18 @@
|
|||
public class CLMDocGenCallout {
|
||||
|
||||
// S1 demo environment
|
||||
private static final String CLM_ACCOUNT_ID_S1 = '2371cf36-eb8a-43fe-9f28-b5bbe7644397';
|
||||
private static final String CLM_NAMED_CRED_S1 = 'callout:CLMs1NamedCreds';
|
||||
private static final String CLM_DOWNLOAD_NAMED_CRED_S1 = 'callout:CLMs1Download';
|
||||
private static final String CLM_ACCOUNT_ID_S1 = '2371cf36-eb8a-43fe-9f28-b5bbe7644397';
|
||||
private static final String CLM_BASE_S1 = 'callout:CLMNamedCred/v2/' + CLM_ACCOUNT_ID_S1;
|
||||
|
||||
// UAT demo environment
|
||||
private static final String CLM_ACCOUNT_ID_UAT = 'bccae332-c7db-4892-ab85-257df0f70fea';
|
||||
private static final String CLM_NAMED_CRED_UAT = 'callout:CLMuatNamedCreds';
|
||||
private static final String CLM_DOWNLOAD_NAMED_CRED_UAT = 'callout:CLMuatDownload';
|
||||
private static final String CLM_BASE_UAT = 'callout:CLMuatNamedCreds/v2/' + CLM_ACCOUNT_ID_UAT;
|
||||
|
||||
public static final Integer HTTP_TIMEOUT = 30000;
|
||||
private static final Integer HTTP_TIMEOUT = 30000;
|
||||
|
||||
/** Resolve the correct CLM Named Credential base. env: 'UAT' or 'S1'. */
|
||||
private static String clmNamedCredential(String env) {
|
||||
return env == 'S1' ? CLM_NAMED_CRED_S1 : CLM_NAMED_CRED_UAT;
|
||||
}
|
||||
|
||||
private static String clmDownloadNamedCredential(String env) {
|
||||
return env == 'S1' ? CLM_DOWNLOAD_NAMED_CRED_S1 : CLM_DOWNLOAD_NAMED_CRED_UAT;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
static String defaultAccountId(String env) {
|
||||
return env == 'S1' ? CLM_ACCOUNT_ID_S1 : CLM_ACCOUNT_ID_UAT;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
static String extractAccountId(String resourceOrHref) {
|
||||
if (String.isBlank(resourceOrHref)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer v2Index = resourceOrHref.indexOf('/v2/');
|
||||
if (v2Index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String afterV2 = resourceOrHref.substring(v2Index + 4);
|
||||
List<String> parts = afterV2.split('/');
|
||||
return parts.isEmpty() ? null : parts[0];
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
static String normalizeResourcePath(String resourceOrHref, String env) {
|
||||
return normalizeResourcePathWithAccountId(resourceOrHref, defaultAccountId(env));
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
static String normalizeResourcePathWithAccountId(String resourceOrHref, String fallbackAccountId) {
|
||||
if (String.isBlank(resourceOrHref)) {
|
||||
throw new IllegalArgumentException('resourceOrHref is required');
|
||||
}
|
||||
|
||||
if (resourceOrHref.startsWith('http://') || resourceOrHref.startsWith('https://')) {
|
||||
Integer pathStart = resourceOrHref.indexOf('/', resourceOrHref.indexOf('//') + 2);
|
||||
if (pathStart < 0) {
|
||||
throw new IllegalArgumentException('Unable to determine resource path from href: ' + resourceOrHref);
|
||||
}
|
||||
return resourceOrHref.substring(pathStart);
|
||||
}
|
||||
|
||||
if (resourceOrHref.startsWith('/v2/')) {
|
||||
return resourceOrHref;
|
||||
}
|
||||
|
||||
if (resourceOrHref.startsWith('v2/')) {
|
||||
return '/' + resourceOrHref;
|
||||
}
|
||||
|
||||
if (resourceOrHref.startsWith('/')) {
|
||||
return '/v2/' + fallbackAccountId + resourceOrHref;
|
||||
}
|
||||
|
||||
return '/v2/' + fallbackAccountId + '/' + resourceOrHref;
|
||||
}
|
||||
|
||||
public static String buildEndpointForResource(String resourceOrHref, String env) {
|
||||
return clmNamedCredential(env) + normalizeResourcePath(resourceOrHref, env);
|
||||
}
|
||||
|
||||
public static String buildEndpointForResource(String resourceOrHref, String accountId, String apiNamedCredential) {
|
||||
return 'callout:' + apiNamedCredential + normalizeResourcePathWithAccountId(resourceOrHref, accountId);
|
||||
}
|
||||
|
||||
public static String buildDownloadEndpointForResource(String resourceOrHref, String env) {
|
||||
return clmDownloadNamedCredential(env) + normalizeResourcePath(resourceOrHref, env);
|
||||
}
|
||||
|
||||
public static String buildDownloadEndpointForResource(String resourceOrHref, String accountId, String downloadNamedCredential) {
|
||||
return 'callout:' + downloadNamedCredential + normalizeResourcePathWithAccountId(resourceOrHref, accountId);
|
||||
/** Resolve the correct CLM base URL. env: 'UAT' or 'S1'. */
|
||||
private static String clmBase(String env) {
|
||||
return env == 'S1' ? CLM_BASE_S1 : CLM_BASE_UAT;
|
||||
}
|
||||
|
||||
/** Defaults to UAT environment. */
|
||||
|
|
@ -116,43 +39,22 @@ public class CLMDocGenCallout {
|
|||
String destinationFolderHref,
|
||||
String destinationDocName,
|
||||
String env
|
||||
) {
|
||||
return generateDocument(
|
||||
caseId,
|
||||
templateDocHref,
|
||||
destinationFolderHref,
|
||||
destinationDocName,
|
||||
env,
|
||||
defaultAccountId(env),
|
||||
clmNamedCredential(env).substringAfter('callout:')
|
||||
);
|
||||
}
|
||||
|
||||
public static CLMDocGenResponse generateDocument(
|
||||
String caseId,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String destinationDocName,
|
||||
String env,
|
||||
String configuredAccountId,
|
||||
String apiNamedCredential
|
||||
) {
|
||||
Map<String, Object> casePayload = AppraiserCasePayloadBuilder.buildPayload(caseId);
|
||||
String accountId = firstNonBlank(
|
||||
extractAccountId(templateDocHref),
|
||||
extractAccountId(destinationFolderHref),
|
||||
configuredAccountId,
|
||||
defaultAccountId(env)
|
||||
);
|
||||
Map<String, Object> requestBody = buildRequestBodyMap(
|
||||
casePayload,
|
||||
templateDocHref,
|
||||
destinationFolderHref,
|
||||
destinationDocName
|
||||
);
|
||||
|
||||
Map<String, Object> requestBody = new Map<String, Object>{
|
||||
'TemplateDocument' => new Map<String, Object>{
|
||||
'Href' => templateDocHref
|
||||
},
|
||||
'DataXML' => buildDataXml(casePayload),
|
||||
'DestinationDocumentName' => destinationDocName,
|
||||
'DestinationFolder' => new Map<String, Object>{
|
||||
'Href' => destinationFolderHref
|
||||
}
|
||||
};
|
||||
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint('callout:' + apiNamedCredential + '/v2/' + accountId + '/documentxmlmergetasks');
|
||||
req.setEndpoint(clmBase(env) + '/documentxmlmergetasks');
|
||||
req.setMethod('POST');
|
||||
req.setHeader('Content-Type', 'application/json');
|
||||
req.setTimeout(HTTP_TIMEOUT);
|
||||
|
|
@ -167,59 +69,15 @@ public class CLMDocGenCallout {
|
|||
}
|
||||
}
|
||||
|
||||
public static String buildDataXmlForCase(String caseId) {
|
||||
return buildDataXml(AppraiserCasePayloadBuilder.buildPayload(caseId));
|
||||
}
|
||||
|
||||
public static String buildRequestBodyJson(
|
||||
String caseId,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String destinationDocName
|
||||
) {
|
||||
return JSON.serializePretty(
|
||||
buildRequestBodyMap(
|
||||
AppraiserCasePayloadBuilder.buildPayload(caseId),
|
||||
templateDocHref,
|
||||
destinationFolderHref,
|
||||
destinationDocName
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static String buildDocumentXmlMergeTasksUrl(
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String env,
|
||||
String configuredAccountId
|
||||
) {
|
||||
String accountId = firstNonBlank(
|
||||
extractAccountId(templateDocHref),
|
||||
extractAccountId(destinationFolderHref),
|
||||
configuredAccountId,
|
||||
defaultAccountId(env)
|
||||
);
|
||||
String baseUrl = firstNonBlank(
|
||||
extractBaseUrl(templateDocHref),
|
||||
extractBaseUrl(destinationFolderHref),
|
||||
null,
|
||||
defaultBaseUrl(env)
|
||||
);
|
||||
return baseUrl + '/v2/' + accountId + '/documentxmlmergetasks';
|
||||
}
|
||||
|
||||
/** Poll the status of a submitted merge task by its GUID. */
|
||||
/** Poll the status of a submitted merge task by its GUID (defaults to UAT). */
|
||||
public static CLMDocGenResponse getTaskStatus(String taskId) {
|
||||
return getTaskStatus(taskId, 'UAT');
|
||||
}
|
||||
|
||||
public static CLMDocGenResponse getTaskStatus(String taskId, String env) {
|
||||
return getTaskStatus(taskId, env, defaultAccountId(env), clmNamedCredential(env).substringAfter('callout:'));
|
||||
}
|
||||
|
||||
public static CLMDocGenResponse getTaskStatus(String taskId, String env, String configuredAccountId, String apiNamedCredential) {
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(buildEndpointForResource('/documentxmlmergetasks/' + taskId, configuredAccountId, apiNamedCredential));
|
||||
req.setEndpoint(clmBase(env) + '/documentxmlmergetasks/' + taskId);
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(HTTP_TIMEOUT);
|
||||
try {
|
||||
|
|
@ -238,41 +96,13 @@ public class CLMDocGenCallout {
|
|||
|
||||
public static String probe(String resource, String env) {
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(buildEndpointForResource(resource, env));
|
||||
req.setEndpoint(clmBase(env) + '/' + resource);
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(HTTP_TIMEOUT);
|
||||
HttpResponse res = new Http().send(req);
|
||||
return 'HTTP ' + res.getStatusCode() + ': ' + res.getBody();
|
||||
}
|
||||
|
||||
public static DownloadedDocument downloadDocument(String resourceOrHref, String env) {
|
||||
return downloadDocument(
|
||||
resourceOrHref,
|
||||
env,
|
||||
defaultAccountId(env),
|
||||
clmDownloadNamedCredential(env).substringAfter('callout:')
|
||||
);
|
||||
}
|
||||
|
||||
public static DownloadedDocument downloadDocument(String resourceOrHref, String env, String configuredAccountId, String downloadNamedCredential) {
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(buildDownloadEndpointForResource(resourceOrHref, configuredAccountId, downloadNamedCredential));
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(HTTP_TIMEOUT);
|
||||
|
||||
HttpResponse res = new Http().send(req);
|
||||
Integer statusCode = res.getStatusCode();
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new CalloutException('CLM download error (HTTP ' + statusCode + '): ' + res.getBody());
|
||||
}
|
||||
|
||||
DownloadedDocument document = new DownloadedDocument();
|
||||
document.body = res.getBodyAsBlob();
|
||||
document.contentType = res.getHeader('Content-Type');
|
||||
document.fileName = extractFileName(res, resourceOrHref, document.contentType);
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the DataXML string from the case payload.
|
||||
* Flat fields become direct child elements of <TemplateFieldData>.
|
||||
|
|
@ -285,7 +115,7 @@ public class CLMDocGenCallout {
|
|||
// Emit flat fields first
|
||||
for (String key : payload.keySet()) {
|
||||
if (key == 'DeficiencyList') continue;
|
||||
xml += '<' + key + '>' + escapeXml(safeValue(payload.get(key))) + '</' + key + '>';
|
||||
xml += '<' + key + '>' + escapeXml(String.valueOf(payload.get(key))) + '</' + key + '>';
|
||||
}
|
||||
|
||||
// Emit DeficiencyList as a nested list so templates can iterate dynamically
|
||||
|
|
@ -295,10 +125,9 @@ public class CLMDocGenCallout {
|
|||
for (Integer i = 0; i < deficiencies.size(); i++) {
|
||||
Map<String, Object> d = (Map<String, Object>) deficiencies[i];
|
||||
xml += '<Deficiency>';
|
||||
xml += '<Number>' + escapeXml(safeValue(d.get('deficiencyNumber'))) + '</Number>';
|
||||
xml += '<Description>' + escapeXml(safeValue(d.get('description'))) + '</Description>';
|
||||
xml += '<Resolution>' + escapeXml(safeValue(d.get('resolution'))) + '</Resolution>';
|
||||
xml += '<Reference>' + escapeXml(safeValue(d.get('reference'))) + '</Reference>';
|
||||
xml += '<Number>' + escapeXml(String.valueOf(d.get('deficiencyNumber'))) + '</Number>';
|
||||
xml += '<Description>' + escapeXml(String.valueOf(d.get('description'))) + '</Description>';
|
||||
xml += '<Resolution>' + escapeXml(String.valueOf(d.get('resolution'))) + '</Resolution>';
|
||||
xml += '</Deficiency>';
|
||||
}
|
||||
xml += '</DeficiencyList>';
|
||||
|
|
@ -309,93 +138,6 @@ public class CLMDocGenCallout {
|
|||
return xml;
|
||||
}
|
||||
|
||||
public static String prettyPrintXml(String xml) {
|
||||
if (String.isBlank(xml)) {
|
||||
return xml;
|
||||
}
|
||||
|
||||
String normalized = xml
|
||||
.replace('><', '>\n<')
|
||||
.replace('\r\n', '\n')
|
||||
.replace('\r', '\n');
|
||||
|
||||
List<String> lines = normalized.split('\n');
|
||||
List<String> formatted = new List<String>();
|
||||
Integer indent = 0;
|
||||
|
||||
for (String rawLine : lines) {
|
||||
String line = rawLine == null ? '' : rawLine.trim();
|
||||
if (line == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Boolean isClosing = line.startsWith('</');
|
||||
Boolean isDeclaration = line.startsWith('<?') || line.startsWith('<!');
|
||||
Boolean isSelfClosing = line.endsWith('/>') || (line.contains('</') && line.indexOf('</') > 0);
|
||||
|
||||
if (isClosing && indent > 0) {
|
||||
indent--;
|
||||
}
|
||||
|
||||
formatted.add(repeatIndent(indent) + line);
|
||||
|
||||
if (!isClosing && !isSelfClosing && !isDeclaration) {
|
||||
indent++;
|
||||
}
|
||||
}
|
||||
|
||||
return String.join(formatted, '\n');
|
||||
}
|
||||
|
||||
private static Map<String, Object> buildRequestBodyMap(
|
||||
Map<String, Object> casePayload,
|
||||
String templateDocHref,
|
||||
String destinationFolderHref,
|
||||
String destinationDocName
|
||||
) {
|
||||
return new Map<String, Object>{
|
||||
'TemplateDocument' => new Map<String, Object>{
|
||||
'Href' => templateDocHref
|
||||
},
|
||||
'DataXML' => buildDataXml(casePayload),
|
||||
'DestinationDocumentName' => destinationDocName,
|
||||
'DestinationFolder' => new Map<String, Object>{
|
||||
'Href' => destinationFolderHref
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String extractBaseUrl(String resourceOrHref) {
|
||||
if (String.isBlank(resourceOrHref)) {
|
||||
return null;
|
||||
}
|
||||
if (!(resourceOrHref.startsWith('http://') || resourceOrHref.startsWith('https://'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer schemeIndex = resourceOrHref.indexOf('//');
|
||||
Integer pathStart = resourceOrHref.indexOf('/', schemeIndex + 2);
|
||||
return pathStart > 0 ? resourceOrHref.substring(0, pathStart) : resourceOrHref;
|
||||
}
|
||||
|
||||
private static String defaultBaseUrl(String env) {
|
||||
return env == 'S1'
|
||||
? 'https://api.s1.us.clm.demo.docusign.net'
|
||||
: 'https://apiuatna11.springcm.com';
|
||||
}
|
||||
|
||||
private static String repeatIndent(Integer indent) {
|
||||
String value = '';
|
||||
for (Integer i = 0; i < indent; i++) {
|
||||
value += ' ';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String safeValue(Object val) {
|
||||
return val != null ? String.valueOf(val) : '';
|
||||
}
|
||||
|
||||
private static String escapeXml(String s) {
|
||||
if (s == null) return '';
|
||||
return s.replace('&', '&')
|
||||
|
|
@ -410,160 +152,26 @@ public class CLMDocGenCallout {
|
|||
String body = res.getBody();
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
Map<String, Object> m = (Map<String, Object>) JSON.deserializeUntyped(body);
|
||||
String href = firstString(m, new List<String>{ 'Href', 'Uri', 'Location' });
|
||||
String status = firstString(m, new List<String>{ 'Status', 'State' });
|
||||
String href = (String) m.get('Href');
|
||||
String status = (String) m.get('Status');
|
||||
String taskId = href != null ? href.substringAfterLast('/') : null;
|
||||
String generatedDocumentUrl = findFirstDocumentHref(m);
|
||||
String generatedDocumentId = generatedDocumentUrl != null ? generatedDocumentUrl.substringAfterLast('/') : null;
|
||||
String message = 'Task status: ' + (String.isNotBlank(status) ? status : 'Unknown');
|
||||
return new CLMDocGenResponse(true, message, href, taskId, status, generatedDocumentUrl, generatedDocumentId, body);
|
||||
return new CLMDocGenResponse(true, 'Task status: ' + status, href, taskId);
|
||||
} else {
|
||||
return new CLMDocGenResponse(false, 'CLM API Error (HTTP ' + statusCode + '): ' + body, null, null, null, null, null, body);
|
||||
return new CLMDocGenResponse(false, 'CLM API Error (HTTP ' + statusCode + '): ' + body, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String firstValue, String secondValue, String thirdValue, String fallbackValue) {
|
||||
if (String.isNotBlank(firstValue)) {
|
||||
return firstValue;
|
||||
}
|
||||
if (String.isNotBlank(secondValue)) {
|
||||
return secondValue;
|
||||
}
|
||||
if (String.isNotBlank(thirdValue)) {
|
||||
return thirdValue;
|
||||
}
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
private static String firstString(Map<String, Object> source, List<String> keys) {
|
||||
for (String key : keys) {
|
||||
Object value = source.get(key);
|
||||
if (value != null) {
|
||||
String asString = String.valueOf(value);
|
||||
if (String.isNotBlank(asString)) {
|
||||
return asString;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String findFirstDocumentHref(Object node) {
|
||||
if (node instanceof Map<String, Object>) {
|
||||
Map<String, Object> mapNode = (Map<String, Object>) node;
|
||||
String href = firstString(mapNode, new List<String>{ 'Href', 'Uri', 'Location' });
|
||||
if (String.isNotBlank(href) && href.contains('/documents/') && !href.contains('/documentxmlmergetasks/')) {
|
||||
return href;
|
||||
}
|
||||
|
||||
for (Object value : mapNode.values()) {
|
||||
String nestedHref = findFirstDocumentHref(value);
|
||||
if (String.isNotBlank(nestedHref)) {
|
||||
return nestedHref;
|
||||
}
|
||||
}
|
||||
} else if (node instanceof List<Object>) {
|
||||
for (Object item : (List<Object>) node) {
|
||||
String nestedHref = findFirstDocumentHref(item);
|
||||
if (String.isNotBlank(nestedHref)) {
|
||||
return nestedHref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String extractFileName(HttpResponse res, String resourceOrHref, String contentType) {
|
||||
String disposition = res.getHeader('Content-Disposition');
|
||||
if (String.isNotBlank(disposition)) {
|
||||
Integer marker = disposition.toLowerCase().indexOf('filename=');
|
||||
if (marker >= 0) {
|
||||
String candidate = disposition.substring(marker + 9).trim();
|
||||
if (candidate.startsWith('"') && candidate.endsWith('"') && candidate.length() >= 2) {
|
||||
candidate = candidate.substring(1, candidate.length() - 1);
|
||||
}
|
||||
if (String.isNotBlank(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String baseName = String.isNotBlank(resourceOrHref) ? resourceOrHref.substringAfterLast('/') : 'generated-document';
|
||||
String extension = inferExtension(contentType);
|
||||
if (String.isNotBlank(extension) && !baseName.toLowerCase().endsWith(extension)) {
|
||||
return baseName + extension;
|
||||
}
|
||||
return baseName;
|
||||
}
|
||||
|
||||
private static String inferExtension(String contentType) {
|
||||
if (String.isBlank(contentType)) {
|
||||
return '.docx';
|
||||
}
|
||||
|
||||
String normalizedType = contentType.toLowerCase();
|
||||
if (normalizedType.contains('pdf')) {
|
||||
return '.pdf';
|
||||
}
|
||||
if (normalizedType.contains('wordprocessingml') || normalizedType.contains('officedocument')) {
|
||||
return '.docx';
|
||||
}
|
||||
if (normalizedType.contains('msword')) {
|
||||
return '.doc';
|
||||
}
|
||||
if (normalizedType.contains('json')) {
|
||||
return '.json';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public class CLMDocGenResponse {
|
||||
@AuraEnabled
|
||||
public Boolean success;
|
||||
@AuraEnabled
|
||||
public String message;
|
||||
@AuraEnabled
|
||||
public String documentUrl;
|
||||
@AuraEnabled
|
||||
public String documentId;
|
||||
@AuraEnabled
|
||||
public String taskStatus;
|
||||
@AuraEnabled
|
||||
public String generatedDocumentUrl;
|
||||
@AuraEnabled
|
||||
public String generatedDocumentId;
|
||||
@AuraEnabled
|
||||
public String taskDetailsJson;
|
||||
|
||||
public CLMDocGenResponse(Boolean success, String message, String documentUrl, String documentId) {
|
||||
this(success, message, documentUrl, documentId, null, null, null, null);
|
||||
}
|
||||
|
||||
public CLMDocGenResponse(
|
||||
Boolean success,
|
||||
String message,
|
||||
String documentUrl,
|
||||
String documentId,
|
||||
String taskStatus,
|
||||
String generatedDocumentUrl,
|
||||
String generatedDocumentId,
|
||||
String taskDetailsJson
|
||||
) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.documentUrl = documentUrl;
|
||||
this.documentId = documentId;
|
||||
this.taskStatus = taskStatus;
|
||||
this.generatedDocumentUrl = generatedDocumentUrl;
|
||||
this.generatedDocumentId = generatedDocumentId;
|
||||
this.taskDetailsJson = taskDetailsJson;
|
||||
this.documentId = documentId;
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadedDocument {
|
||||
public Blob body;
|
||||
public String fileName;
|
||||
public String contentType;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
@IsTest
|
||||
private class CLMDocGenCalloutTest {
|
||||
private class CLMCalloutMock implements HttpCalloutMock {
|
||||
public HttpResponse respond(HttpRequest req) {
|
||||
HttpResponse res = new HttpResponse();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.getMethod() == 'POST' && req.getEndpoint().contains('/documentxmlmergetasks')) {
|
||||
System.assert(req.getBody().contains('TemplateDocument'));
|
||||
System.assert(req.getBody().contains('DestinationFolder'));
|
||||
System.assert(req.getBody().contains('DeficiencyList'));
|
||||
System.assert(req.getBody().contains('&'));
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/documentxmlmergetasks/TASK-123","Status":"Queued"}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/documentxmlmergetasks/TASK-123')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"Href":"https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/documentxmlmergetasks/TASK-123","Status":"Completed"}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/documents/template-guid')) {
|
||||
res.setStatusCode(200);
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="GeneratedReview.pdf"');
|
||||
res.setBodyAsBlob(Blob.valueOf('pdf-bytes'));
|
||||
return res;
|
||||
}
|
||||
|
||||
res.setStatusCode(404);
|
||||
res.setBody('{"Message":"Not Found"}');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void generatesDocumentAndPollsTaskStatus() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.newInstance(2026, 4, 2),
|
||||
Letter_Sent_Date__c = Date.newInstance(2026, 4, 9),
|
||||
FHA_Case_Number__c = '123-4567890',
|
||||
Appraiser_Name__c = 'Jamie Appraiser',
|
||||
Appraiser_Last_Name__c = 'Appraiser',
|
||||
Property_Street__c = '123 Main & Main <Suite 5>',
|
||||
Property_City__c = 'Denver',
|
||||
Property_State_Province__c = 'CO',
|
||||
Property_Postal_Code__c = '80202'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Missing comparable sale adjustment detail.',
|
||||
Resolution__c = 'Added supporting calculations & notes.',
|
||||
Reference__c = 'VC-1'
|
||||
);
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new CLMCalloutMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMDocGenCallout.CLMDocGenResponse response = CLMDocGenCallout.generateDocument(
|
||||
appraiserCase.Id,
|
||||
'https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/documents/template-guid',
|
||||
'https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/folder-guid',
|
||||
'Review_AC-00001.docx',
|
||||
'UAT'
|
||||
);
|
||||
CLMDocGenCallout.CLMDocGenResponse taskStatus = CLMDocGenCallout.getTaskStatus('TASK-123', 'S1');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, response.success);
|
||||
System.assertEquals('TASK-123', response.documentId);
|
||||
System.assert(response.documentUrl.contains('/documentxmlmergetasks/TASK-123'));
|
||||
System.assertEquals(true, taskStatus.success);
|
||||
System.assertEquals('Task status: Completed', taskStatus.message);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void normalizesResourcePathsAndExtractsAccountIds() {
|
||||
System.assertEquals(
|
||||
'2371cf36-eb8a-43fe-9f28-b5bbe7644397',
|
||||
CLMDocGenCallout.extractAccountId('https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/folder-guid')
|
||||
);
|
||||
System.assertEquals(
|
||||
'/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/folder-guid',
|
||||
CLMDocGenCallout.normalizeResourcePath(
|
||||
'https://uatna11.springcm.com/v2/2371cf36-eb8a-43fe-9f28-b5bbe7644397/folders/folder-guid',
|
||||
'UAT'
|
||||
)
|
||||
);
|
||||
System.assertEquals(
|
||||
'/v2/' + CLMDocGenCallout.defaultAccountId('UAT') + '/folders/folder-guid',
|
||||
CLMDocGenCallout.normalizeResourcePath('/folders/folder-guid', 'UAT')
|
||||
);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void downloadsGeneratedDocumentThroughDownloadCredential() {
|
||||
Test.setMock(HttpCalloutMock.class, new CLMCalloutMock());
|
||||
|
||||
Test.startTest();
|
||||
CLMDocGenCallout.DownloadedDocument downloaded = CLMDocGenCallout.downloadDocument(
|
||||
'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/template-guid',
|
||||
'UAT'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals('GeneratedReview.pdf', downloaded.fileName);
|
||||
System.assertEquals('application/pdf', downloaded.contentType);
|
||||
System.assertNotEquals(null, downloaded.body);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void buildsPreviewXmlAndRequestBody() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.newInstance(2026, 4, 2),
|
||||
Letter_Sent_Date__c = Date.newInstance(2026, 4, 9),
|
||||
FHA_Case_Number__c = '123-4567890',
|
||||
Appraiser_Name__c = 'Jamie Appraiser',
|
||||
Appraiser_Last_Name__c = 'Appraiser',
|
||||
Property_Street__c = '245 Lexington Ave',
|
||||
Property_City__c = 'New York',
|
||||
Property_State_Province__c = 'NY',
|
||||
Property_Postal_Code__c = '10016',
|
||||
Property_Country__c = 'USA'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
insert new Appraiser_Case_Deficiency__c(
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = 1,
|
||||
Description__c = 'Missing comparable sale adjustment detail.',
|
||||
Resolution__c = 'Added supporting calculations.',
|
||||
Reference__c = 'VC-1'
|
||||
);
|
||||
|
||||
String dataXml = CLMDocGenCallout.buildDataXmlForCase(appraiserCase.Id);
|
||||
String requestBodyJson = CLMDocGenCallout.buildRequestBodyJson(
|
||||
appraiserCase.Id,
|
||||
'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/template-guid',
|
||||
'https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/folder-guid',
|
||||
'Review_AC-000002.docx'
|
||||
);
|
||||
|
||||
System.assert(dataXml.contains('<LetterSentDate>'));
|
||||
System.assert(dataXml.contains('<FHACaseNumber>123-4567890</FHACaseNumber>'));
|
||||
System.assert(dataXml.contains('<Reference>VC-1</Reference>'));
|
||||
System.assert(requestBodyJson.contains('"DataXML"'));
|
||||
System.assert(requestBodyJson.contains('Review_AC-000002.docx'));
|
||||
System.assert(requestBodyJson.contains('template-guid'));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -1,339 +0,0 @@
|
|||
public with sharing class DocusignESignatureService {
|
||||
public class ESignatureAccountConfig {
|
||||
@AuraEnabled public String accountCode;
|
||||
@AuraEnabled public String accountDisplayName;
|
||||
@AuraEnabled public String environment;
|
||||
@AuraEnabled public String eSignatureAuthNamedCredential;
|
||||
@AuraEnabled public String eSignatureRestNamedCredential;
|
||||
@AuraEnabled public String eSignatureAccountId;
|
||||
}
|
||||
|
||||
public class ApiResponse {
|
||||
@AuraEnabled public Boolean success;
|
||||
@AuraEnabled public Integer statusCode;
|
||||
@AuraEnabled public String message;
|
||||
@AuraEnabled public String requestPath;
|
||||
@AuraEnabled public String responseBody;
|
||||
}
|
||||
|
||||
public class ESignatureAccountSummary {
|
||||
@AuraEnabled public String accountId;
|
||||
@AuraEnabled public String accountName;
|
||||
@AuraEnabled public String baseUri;
|
||||
@AuraEnabled public Boolean isDefault;
|
||||
@AuraEnabled public String rawJson;
|
||||
}
|
||||
|
||||
public class TemplateSummary {
|
||||
@AuraEnabled public String templateId;
|
||||
@AuraEnabled public String name;
|
||||
@AuraEnabled public String description;
|
||||
@AuraEnabled public String shared;
|
||||
@AuraEnabled public String lastModified;
|
||||
@AuraEnabled public String rawJson;
|
||||
}
|
||||
|
||||
public class EnvelopeSummary {
|
||||
@AuraEnabled public String envelopeId;
|
||||
@AuraEnabled public String emailSubject;
|
||||
@AuraEnabled public String status;
|
||||
@AuraEnabled public String createdDateTime;
|
||||
@AuraEnabled public String sentDateTime;
|
||||
@AuraEnabled public String completedDateTime;
|
||||
@AuraEnabled public String rawJson;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=true)
|
||||
public static ESignatureAccountConfig getAccountConfig(String accountCode) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
ESignatureAccountConfig config = new ESignatureAccountConfig();
|
||||
config.accountCode = String.isNotBlank(row.Account_Code__c) ? row.Account_Code__c : row.DeveloperName;
|
||||
config.accountDisplayName = String.isNotBlank(row.Account_Display_Name__c) ? row.Account_Display_Name__c : row.DeveloperName;
|
||||
config.environment = row.Environment_Code__c;
|
||||
config.eSignatureAuthNamedCredential = row.ESignature_Auth_Named_Credential__c;
|
||||
config.eSignatureRestNamedCredential = row.ESignature_Rest_Named_Credential__c;
|
||||
config.eSignatureAccountId = row.ESignature_Account_Id__c;
|
||||
return config;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static ApiResponse probe(String accountCode, String relativePath) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
String normalizedPath = normalizePath(relativePath);
|
||||
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(buildEndpoint(normalizedPath, row.ESignature_Rest_Named_Credential__c));
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(30000);
|
||||
|
||||
HttpResponse res = new Http().send(req);
|
||||
ApiResponse response = new ApiResponse();
|
||||
response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
|
||||
response.statusCode = res.getStatusCode();
|
||||
response.message = response.success ? 'eSignature request succeeded.' : 'eSignature request failed.';
|
||||
response.requestPath = normalizedPath;
|
||||
response.responseBody = res.getBody();
|
||||
return response;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static List<ESignatureAccountSummary> listAccounts(String accountCode) {
|
||||
ApiResponse response = getLoginInformation(accountCode);
|
||||
if (!response.success) {
|
||||
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
|
||||
}
|
||||
return parseAccountList(response.responseBody);
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static ApiResponse getLoginInformation(String accountCode) {
|
||||
return probe(accountCode, '/v2.1/login_information');
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static ApiResponse getUserInfo(String accountCode) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
|
||||
HttpRequest req = new HttpRequest();
|
||||
req.setEndpoint(buildEndpoint('/oauth/userinfo', authNamedCredential(row)));
|
||||
req.setMethod('GET');
|
||||
req.setTimeout(30000);
|
||||
|
||||
HttpResponse res = new Http().send(req);
|
||||
ApiResponse response = new ApiResponse();
|
||||
response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
|
||||
response.statusCode = res.getStatusCode();
|
||||
response.message = response.success ? 'eSignature user info request succeeded.' : 'eSignature user info request failed.';
|
||||
response.requestPath = '/oauth/userinfo';
|
||||
response.responseBody = res.getBody();
|
||||
return response;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static ApiResponse getAccountInformation(String accountCode, String eSignatureAccountId) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
String targetAccountId = requireESignatureAccountId(row, accountCode, eSignatureAccountId);
|
||||
return probe(accountCode, '/v2.1/accounts/' + targetAccountId);
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static List<TemplateSummary> listTemplates(String accountCode) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
String targetAccountId = requireESignatureAccountId(row, accountCode, null);
|
||||
ApiResponse response = probe(accountCode, '/v2.1/accounts/' + targetAccountId + '/templates');
|
||||
if (!response.success) {
|
||||
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
|
||||
}
|
||||
return parseTemplateList(response.responseBody);
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static List<EnvelopeSummary> listEnvelopes(String accountCode, String fromDate) {
|
||||
CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
|
||||
String targetAccountId = requireESignatureAccountId(row, accountCode, null);
|
||||
String normalizedFromDate = String.isBlank(fromDate)
|
||||
? DateTime.newInstanceGMT(Date.today().addDays(-30), Time.newInstance(0, 0, 0, 0)).formatGMT('yyyy-MM-dd')
|
||||
: fromDate.trim();
|
||||
ApiResponse response = probe(
|
||||
accountCode,
|
||||
'/v2.1/accounts/' + targetAccountId + '/envelopes?from_date=' + EncodingUtil.urlEncode(normalizedFromDate, 'UTF-8')
|
||||
);
|
||||
if (!response.success) {
|
||||
throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
|
||||
}
|
||||
return parseEnvelopeList(response.responseBody);
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static List<ESignatureAccountSummary> parseAccountList(String body) {
|
||||
Object root = JSON.deserializeUntyped(body);
|
||||
List<Object> records = new List<Object>();
|
||||
if (root instanceof List<Object>) {
|
||||
records = (List<Object>) root;
|
||||
} else if (root instanceof Map<String, Object>) {
|
||||
Object loginAccounts = ((Map<String, Object>) root).get('loginAccounts');
|
||||
if (loginAccounts instanceof List<Object>) {
|
||||
records = (List<Object>) loginAccounts;
|
||||
}
|
||||
Object accounts = ((Map<String, Object>) root).get('accounts');
|
||||
if (records.isEmpty() && accounts instanceof List<Object>) {
|
||||
records = (List<Object>) accounts;
|
||||
}
|
||||
}
|
||||
|
||||
List<ESignatureAccountSummary> summaries = new List<ESignatureAccountSummary>();
|
||||
for (Object record : records) {
|
||||
if (!(record instanceof Map<String, Object>)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> row = (Map<String, Object>) record;
|
||||
ESignatureAccountSummary summary = new ESignatureAccountSummary();
|
||||
summary.accountId = firstString(row, new List<String>{ 'accountId', 'account_id' });
|
||||
summary.accountName = firstString(row, new List<String>{ 'accountName', 'account_name', 'name' });
|
||||
summary.baseUri = firstString(row, new List<String>{ 'baseUri', 'base_uri', 'baseUrl', 'base_url' });
|
||||
summary.isDefault = parseBoolean(row.get('isDefault'));
|
||||
summary.rawJson = JSON.serialize(row);
|
||||
summaries.add(summary);
|
||||
}
|
||||
return summaries;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static List<TemplateSummary> parseTemplateList(String body) {
|
||||
List<Object> records = extractCollection(body, new List<String>{ 'envelopeTemplates', 'templates' });
|
||||
List<TemplateSummary> summaries = new List<TemplateSummary>();
|
||||
for (Object record : records) {
|
||||
if (!(record instanceof Map<String, Object>)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> row = (Map<String, Object>) record;
|
||||
TemplateSummary summary = new TemplateSummary();
|
||||
summary.templateId = firstString(row, new List<String>{ 'templateId', 'template_id' });
|
||||
summary.name = firstString(row, new List<String>{ 'name' });
|
||||
summary.description = firstString(row, new List<String>{ 'description' });
|
||||
summary.shared = firstString(row, new List<String>{ 'shared' });
|
||||
summary.lastModified = firstString(row, new List<String>{ 'lastModified', 'last_modified', 'lastModifiedDateTime' });
|
||||
summary.rawJson = JSON.serialize(row);
|
||||
summaries.add(summary);
|
||||
}
|
||||
return summaries;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static List<EnvelopeSummary> parseEnvelopeList(String body) {
|
||||
List<Object> records = extractCollection(body, new List<String>{ 'envelopes' });
|
||||
List<EnvelopeSummary> summaries = new List<EnvelopeSummary>();
|
||||
for (Object record : records) {
|
||||
if (!(record instanceof Map<String, Object>)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> row = (Map<String, Object>) record;
|
||||
EnvelopeSummary summary = new EnvelopeSummary();
|
||||
summary.envelopeId = firstString(row, new List<String>{ 'envelopeId', 'envelope_id' });
|
||||
summary.emailSubject = firstString(row, new List<String>{ 'emailSubject', 'email_subject' });
|
||||
summary.status = firstString(row, new List<String>{ 'status' });
|
||||
summary.createdDateTime = firstString(row, new List<String>{ 'createdDateTime', 'created_datetime' });
|
||||
summary.sentDateTime = firstString(row, new List<String>{ 'sentDateTime', 'sent_datetime' });
|
||||
summary.completedDateTime = firstString(row, new List<String>{ 'completedDateTime', 'completed_datetime' });
|
||||
summary.rawJson = JSON.serialize(row);
|
||||
summaries.add(summary);
|
||||
}
|
||||
return summaries;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static String buildEndpoint(String relativePath, String namedCredential) {
|
||||
if (String.isBlank(namedCredential)) {
|
||||
throw new AuraHandledException('No eSignature named credential is configured for this account.');
|
||||
}
|
||||
return 'callout:' + namedCredential + normalizePath(relativePath);
|
||||
}
|
||||
|
||||
private static String normalizePath(String relativePath) {
|
||||
if (String.isBlank(relativePath)) {
|
||||
throw new AuraHandledException('A relative path is required.');
|
||||
}
|
||||
return relativePath.startsWith('/') ? relativePath : '/' + relativePath;
|
||||
}
|
||||
|
||||
private static CLM_Account_Setting__mdt requireAccountSetting(String accountCode) {
|
||||
String normalizedCode = String.isBlank(accountCode) ? null : accountCode.trim();
|
||||
List<CLM_Account_Setting__mdt> rows = new List<CLM_Account_Setting__mdt>();
|
||||
if (String.isNotBlank(normalizedCode)) {
|
||||
rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
ESignature_Auth_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
ESignature_Account_Id__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
AND DeveloperName = :normalizedCode
|
||||
LIMIT 1
|
||||
];
|
||||
if (rows.isEmpty()) {
|
||||
rows = [
|
||||
SELECT DeveloperName,
|
||||
Account_Code__c,
|
||||
Account_Display_Name__c,
|
||||
Environment_Code__c,
|
||||
ESignature_Auth_Named_Credential__c,
|
||||
ESignature_Rest_Named_Credential__c,
|
||||
ESignature_Account_Id__c,
|
||||
Active__c
|
||||
FROM CLM_Account_Setting__mdt
|
||||
WHERE Active__c = true
|
||||
AND Account_Code__c = :normalizedCode
|
||||
LIMIT 1
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (rows.isEmpty()) {
|
||||
throw new AuraHandledException('No active CLM account setting was found for ' + accountCode + '.');
|
||||
}
|
||||
|
||||
CLM_Account_Setting__mdt row = rows[0];
|
||||
if (String.isBlank(row.ESignature_Rest_Named_Credential__c)) {
|
||||
throw new AuraHandledException('No eSignature named credential is configured for ' + accountCode + '.');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private static String authNamedCredential(CLM_Account_Setting__mdt row) {
|
||||
return String.isNotBlank(row.ESignature_Auth_Named_Credential__c)
|
||||
? row.ESignature_Auth_Named_Credential__c
|
||||
: row.ESignature_Rest_Named_Credential__c;
|
||||
}
|
||||
|
||||
private static String requireESignatureAccountId(CLM_Account_Setting__mdt row, String accountCode, String eSignatureAccountId) {
|
||||
String targetAccountId = String.isNotBlank(eSignatureAccountId) ? eSignatureAccountId : row.ESignature_Account_Id__c;
|
||||
if (String.isBlank(targetAccountId)) {
|
||||
throw new AuraHandledException('No eSignature account id is configured for ' + accountCode + '.');
|
||||
}
|
||||
return targetAccountId;
|
||||
}
|
||||
|
||||
private static List<Object> extractCollection(String body, List<String> keys) {
|
||||
Object root = JSON.deserializeUntyped(body);
|
||||
if (root instanceof List<Object>) {
|
||||
return (List<Object>) root;
|
||||
}
|
||||
if (root instanceof Map<String, Object>) {
|
||||
Map<String, Object> source = (Map<String, Object>) root;
|
||||
for (String key : keys) {
|
||||
Object records = source.get(key);
|
||||
if (records instanceof List<Object>) {
|
||||
return (List<Object>) records;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new List<Object>();
|
||||
}
|
||||
|
||||
private static String firstString(Map<String, Object> source, List<String> keys) {
|
||||
for (String key : keys) {
|
||||
Object value = source.get(key);
|
||||
if (value != null) {
|
||||
String text = String.valueOf(value);
|
||||
if (String.isNotBlank(text)) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Boolean parseBoolean(Object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
return String.valueOf(value).toLowerCase() == 'true';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
@IsTest
|
||||
private class DocusignESignatureServiceTest {
|
||||
private class ESignatureMock implements HttpCalloutMock {
|
||||
public HttpResponse respond(HttpRequest req) {
|
||||
HttpResponse res = new HttpResponse();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/oauth/userinfo')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"sub":"d9aab149-ff54-408c-a748-baa4b56e2fcd","accounts":[{"account_id":"12345678","account_name":"Demo eSignature Account","base_uri":"https://demo.docusign.net","is_default":true}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/templates')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"envelopeTemplates":[{"templateId":"tmpl-001","name":"Review Letter","description":"Appraiser review letter template","shared":"true","lastModified":"2026-04-08T12:00:00Z"},{"templateId":"tmpl-002","name":"Alternate Review Letter","description":"Alternate version","shared":"false","lastModified":"2026-04-07T12:00:00Z"}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/envelopes?from_date=')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"envelopes":[{"envelopeId":"env-001","emailSubject":"Appraiser Review","status":"completed","createdDateTime":"2026-04-01T10:00:00Z","sentDateTime":"2026-04-01T10:05:00Z","completedDateTime":"2026-04-01T10:15:00Z"},{"envelopeId":"env-002","emailSubject":"Second Review","status":"sent","createdDateTime":"2026-04-02T09:00:00Z","sentDateTime":"2026-04-02T09:05:00Z"}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/v2.1/accounts/12345678')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"accountId":"12345678","accountName":"Demo eSignature Account","status":"active"}');
|
||||
return res;
|
||||
}
|
||||
|
||||
if (req.getMethod() == 'GET' && req.getEndpoint().contains('/v2.1/login_information')) {
|
||||
res.setStatusCode(200);
|
||||
res.setBody('{"loginAccounts":[{"accountId":"12345678","name":"Demo eSignature Account","baseUrl":"https://demo.docusign.net/restapi/v2.1/accounts/12345678","isDefault":"true"},{"accountId":"87654321","name":"Secondary Demo Account","baseUrl":"https://demo.docusign.net/restapi/v2.1/accounts/87654321","isDefault":"false"}]}');
|
||||
return res;
|
||||
}
|
||||
|
||||
res.setStatusCode(404);
|
||||
res.setBody('{"message":"Not Found"}');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void returnsConfiguredAccountInfo() {
|
||||
Test.startTest();
|
||||
DocusignESignatureService.ESignatureAccountConfig config = DocusignESignatureService.getAccountConfig('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals('DTC_CLM_Demo', config.accountCode);
|
||||
System.assertEquals('DTC CLM Demo', config.accountDisplayName);
|
||||
System.assertEquals('AcctDemo_NamedCreds', config.eSignatureAuthNamedCredential);
|
||||
System.assertEquals('Esignature_Demo_NamedCreds', config.eSignatureRestNamedCredential);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void listsAccountsFromEsignatureApi() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
List<DocusignESignatureService.ESignatureAccountSummary> accounts = DocusignESignatureService.listAccounts('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(2, accounts.size());
|
||||
System.assertEquals('12345678', accounts[0].accountId);
|
||||
System.assertEquals('Demo eSignature Account', accounts[0].accountName);
|
||||
System.assertEquals('https://demo.docusign.net/restapi/v2.1/accounts/12345678', accounts[0].baseUri);
|
||||
System.assertEquals(true, accounts[0].isDefault);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void getsLoginInformationUsingWorkingEndpoint() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.ApiResponse response = DocusignESignatureService.getLoginInformation('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, response.success);
|
||||
System.assertEquals(200, response.statusCode);
|
||||
System.assert(response.responseBody.contains('loginAccounts'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void getsUserInfoUsingAuthNamedCredential() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.ApiResponse response = DocusignESignatureService.getUserInfo('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, response.success);
|
||||
System.assertEquals(200, response.statusCode);
|
||||
System.assertEquals('/oauth/userinfo', response.requestPath);
|
||||
System.assert(response.responseBody.contains('"accounts"'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void getsAccountInformationUsingConfiguredAccountId() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.ApiResponse response = DocusignESignatureService.getAccountInformation(
|
||||
'DTC_CLM_Demo',
|
||||
'12345678'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, response.success);
|
||||
System.assertEquals(200, response.statusCode);
|
||||
System.assert(response.responseBody.contains('Demo eSignature Account'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void listsTemplatesUsingConfiguredAccountId() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
List<DocusignESignatureService.TemplateSummary> templates = DocusignESignatureService.listTemplates('DTC_CLM_Demo');
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(2, templates.size());
|
||||
System.assertEquals('tmpl-001', templates[0].templateId);
|
||||
System.assertEquals('Review Letter', templates[0].name);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void listsRecentEnvelopesUsingConfiguredAccountId() {
|
||||
Test.setMock(HttpCalloutMock.class, new ESignatureMock());
|
||||
|
||||
Test.startTest();
|
||||
List<DocusignESignatureService.EnvelopeSummary> envelopes = DocusignESignatureService.listEnvelopes(
|
||||
'DTC_CLM_Demo',
|
||||
'2026-04-01'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(2, envelopes.size());
|
||||
System.assertEquals('env-001', envelopes[0].envelopeId);
|
||||
System.assertEquals('completed', envelopes[0].status);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void buildsEndpointWithNamedCredential() {
|
||||
System.assertEquals(
|
||||
'callout:Esignature_Demo_NamedCreds/v2.1/accounts',
|
||||
DocusignESignatureService.buildEndpoint('/v2.1/accounts', 'Esignature_Demo_NamedCreds')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>DTC CLM Demo</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_CLM_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Account_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC CLM Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Environment_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">UAT</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Account_Id__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">bccae332-c7db-4892-ab85-257df0f70fea</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Api_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMuatNamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Download_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMuatDownload</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Auth_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">AcctDemo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Rest_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Esignature_Demo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Template_Root_Folder_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Destination_Root_Folder_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Template_Document_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/a0cbc0e6-d87d-459e-8d63-66baa47878f3</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>DTC HUD Demo</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_HUD_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Account_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC HUD Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Environment_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">S1</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Account_Id__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2371cf36-eb8a-43fe-9f28-b5bbe7644397</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Api_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMs1NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Download_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMs1Download</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Auth_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">AcctDemo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Rest_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Esignature_Demo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>DTC IAM Enterprise</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_IAM_Enterprise</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Account_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC IAM Enterprise</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Environment_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">S1</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Account_Id__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2371cf36-eb8a-43fe-9f28-b5bbe7644397</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Api_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMs1NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>CLM_Download_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">CLMs1Download</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Auth_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">AcctDemo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>ESignature_Rest_Named_Credential__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Esignature_Demo_NamedCreds</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>S1</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Environment_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">S1</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>UAT</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Environment_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">UAT</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Destination_Root_Folder_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Template_Root_Folder_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Template_Document_Href__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/a0cbc0e6-d87d-459e-8d63-66baa47878f3</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Appraiser Review Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_CLM_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">APPRAISER_REVIEW</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Appraiser Review Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Current appraiser letter template flow.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">10</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Education Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_CLM_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">EDUCATION_LETTER</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education / guidance letter.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">40</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Intent to Remove Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_CLM_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">INTENT_TO_REMOVE_LETTER</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove letter.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">30</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent_Remove</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>NOD Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_CLM_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD_LETTER</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Notice of Deficiency letter.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">20</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Appraiser Review Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_HUD_Demo</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">APPRAISER_REVIEW</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Appraiser Review Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Current appraiser letter template flow.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">10</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Education Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_HUD_Demo</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">EDUCATION_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education / guidance letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">40</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Intent to Remove Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_HUD_Demo</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">INTENT_TO_REMOVE_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">30</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent_Remove</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>NOD Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_HUD_Demo</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Notice of Deficiency letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">20</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Intent to Remove Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_IAM_Enterprise</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">INTENT_TO_REMOVE_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent to Remove letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">30</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Intent_Remove</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Appraiser Review Letter</label>
|
||||
<protected>false</protected>
|
||||
<values>
|
||||
<field>Account_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_IAM_Enterprise</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Code__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">APPRAISER_REVIEW</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Appraiser Review Letter</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Description__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Current appraiser letter template flow.</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Active__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Is_Default__c</field>
|
||||
<value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Sort_Order__c</field>
|
||||
<value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">10</value>
|
||||
</values>
|
||||
<values>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
<value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Review</value>
|
||||
</values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Education Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_IAM_Enterprise</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">EDUCATION_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education / guidance letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">40</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Education</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>NOD Letter</label>
|
||||
<protected>false</protected>
|
||||
<values><field>Account_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">DTC_IAM_Enterprise</value></values>
|
||||
<values><field>Letter_Code__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD_LETTER</value></values>
|
||||
<values><field>Letter_Display_Name__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD Letter</value></values>
|
||||
<values><field>Description__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Notice of Deficiency letter.</value></values>
|
||||
<values><field>Active__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">true</value></values>
|
||||
<values><field>Is_Default__c</field><value xsi:type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">false</value></values>
|
||||
<values><field>Sort_Order__c</field><value xsi:type="xsd:double" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">20</value></values>
|
||||
<values><field>Default_Destination_Document_Name_Prefix__c</field><value xsi:type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">NOD</value></values>
|
||||
</CustomMetadata>
|
||||
|
|
@ -1,386 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FlexiPage xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>collapsed</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>enableActionsConfiguration</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>enableActionsInNative</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>hideChatterActions</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>numVisibleActions</name>
|
||||
<value>3</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>force:highlightsPanel</componentName>
|
||||
<identifier>force_highlightsPanel</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>header</name>
|
||||
<type>Region</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>relatedListComponentOverride</name>
|
||||
<value>NONE</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>rowsToDisplay</name>
|
||||
<value>10</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>showActionBar</name>
|
||||
<value>true</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>force:relatedListContainer</componentName>
|
||||
<identifier>force_relatedListContainer</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>relatedTabContent</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.OwnerId</fieldItem>
|
||||
<identifier>RecordOwnerIdField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Property_Street__c</fieldItem>
|
||||
<identifier>RecordProperty_Street_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Property_City__c</fieldItem>
|
||||
<identifier>RecordProperty_City_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Property_State_Province__c</fieldItem>
|
||||
<identifier>RecordProperty_State_Province_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Property_Postal_Code__c</fieldItem>
|
||||
<identifier>RecordProperty_Postal_Code_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Property_Country__c</fieldItem>
|
||||
<identifier>RecordProperty_Country_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<name>Facet-f500e2fb-11c3-416b-9dc6-9d41da18f8b6</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Name</fieldItem>
|
||||
<identifier>RecordNameField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.Appraiser_Field_Review_Date__c</fieldItem>
|
||||
<identifier>RecordAppraiser_Field_Review_Date_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.CreatedById</fieldItem>
|
||||
<identifier>RecordCreatedByIdField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value>
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.LastModifiedById</fieldItem>
|
||||
<identifier>RecordLastModifiedByIdField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
<name>Facet-dc9d7e8f-5478-43ef-a95e-3f61960274fa</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>Facet-f500e2fb-11c3-416b-9dc6-9d41da18f8b6</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:column</componentName>
|
||||
<identifier>flexipage_column</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>Facet-dc9d7e8f-5478-43ef-a95e-3f61960274fa</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:column</componentName>
|
||||
<identifier>flexipage_column2</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<name>Facet-70cfd25b-1515-494b-91d2-98730c66f733</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentName>force:detailPanel</componentName>
|
||||
<identifier>force_detailPanel</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>columns</name>
|
||||
<value>Facet-70cfd25b-1515-494b-91d2-98730c66f733</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>horizontalAlignment</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>label</name>
|
||||
<value>Section</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:fieldSection</componentName>
|
||||
<identifier>flexipage_fieldSection</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>detailTabContent</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>previewTabContent</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>title</name>
|
||||
<value>CLM Preview</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tab</componentName>
|
||||
<identifier>clmPreviewTab</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>esignTabContent</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>title</name>
|
||||
<value>Docusign eSignature</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tab</componentName>
|
||||
<identifier>esignWorkbenchTab</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>relatedTabContent</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>title</name>
|
||||
<value>Standard.Tab.relatedLists</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tab</componentName>
|
||||
<identifier>relatedListsTab</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>active</name>
|
||||
<value>true</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>detailTabContent</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>title</name>
|
||||
<value>Standard.Tab.detail</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tab</componentName>
|
||||
<identifier>detailTab</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>maintabs</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentName>c:docusignEsignWorkbench</componentName>
|
||||
<identifier>c_docusignEsignWorkbench</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<name>esignTabContent</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentName>c:clmRequestPreview</componentName>
|
||||
<identifier>c_clmRequestPreview</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<name>previewTabContent</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>showLegacyActivityComposer</name>
|
||||
<value>false</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>runtime_sales_activities:activityPanel</componentName>
|
||||
<identifier>runtime_sales_activities_activityPanel</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>activityTabContent</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>active</name>
|
||||
<value>true</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>body</name>
|
||||
<value>activityTabContent</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>title</name>
|
||||
<value>Standard.Tab.activity</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tab</componentName>
|
||||
<identifier>activityTab</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>sidebartabs</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>label</name>
|
||||
<value>Tabs</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>tabs</name>
|
||||
<value>maintabs</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tabset</componentName>
|
||||
<identifier>flexipage_tabset</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>main</name>
|
||||
<type>Region</type>
|
||||
</flexiPageRegions>
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>label</name>
|
||||
<value>Tabs</value>
|
||||
</componentInstanceProperties>
|
||||
<componentInstanceProperties>
|
||||
<name>tabs</name>
|
||||
<value>sidebartabs</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:tabset</componentName>
|
||||
<identifier>flexipage_tabset2</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<mode>Replace</mode>
|
||||
<name>sidebar</name>
|
||||
<type>Region</type>
|
||||
</flexiPageRegions>
|
||||
<masterLabel>Appraiser Case Record Page</masterLabel>
|
||||
<parentFlexiPage>flexipage__default_rec_L</parentFlexiPage>
|
||||
<sobjectType>Appraiser_Case__c</sobjectType>
|
||||
<template>
|
||||
<name>flexipage:recordHomeTemplateDesktop</name>
|
||||
</template>
|
||||
<type>RecordPage</type>
|
||||
</FlexiPage>
|
||||
|
|
@ -7,79 +7,22 @@
|
|||
<label>Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<behavior>Required</behavior>
|
||||
<field>Name</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Field_Review_Date__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns/>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Letter Header</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Name__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Salutation__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Last_Name__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Letter_Sent_Date__c</field>
|
||||
<field>CreatedDate</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>FHA_Case_Number__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Email__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Appraiser Address</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Street__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_City__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_State_Province__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Postal_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Country__c</field>
|
||||
<field>LastModifiedDate</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
|
|
@ -115,94 +58,19 @@
|
|||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Doc Gen Tracking</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Status__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_CLM_Account_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Task_Id__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Generated_Document_Id__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Attached_File_Content_Document_Id__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Requested_At__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Completed_At__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Task_Url__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Generated_Document_Url__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Attached_File_Url__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_Template_Document_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_Destination_Folder_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>Last_DocGen_Message__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<platformActionList>
|
||||
<actionListContext>Record</actionListContext>
|
||||
<platformActionListItems>
|
||||
<actionName>Appraiser_Case__c.Generate_Review_Letter</actionName>
|
||||
<actionType>QuickAction</actionType>
|
||||
<sortOrder>0</sortOrder>
|
||||
</platformActionListItems>
|
||||
<platformActionListItems>
|
||||
<actionName>Edit</actionName>
|
||||
<actionType>StandardButton</actionType>
|
||||
<sortOrder>1</sortOrder>
|
||||
</platformActionListItems>
|
||||
<platformActionListItems>
|
||||
<actionName>Delete</actionName>
|
||||
<actionType>StandardButton</actionType>
|
||||
<sortOrder>2</sortOrder>
|
||||
</platformActionListItems>
|
||||
</platformActionList>
|
||||
<relatedLists>
|
||||
<fields>NAME</fields>
|
||||
<fields>Deficiency_Number__c</fields>
|
||||
<fields>Description__c</fields>
|
||||
<fields>Reference__c</fields>
|
||||
<fields>Resolution__c</fields>
|
||||
<relatedList>Appraiser_Case_Deficiency__c.Appraiser_Case__c</relatedList>
|
||||
<fields>Sort_Order__c</fields>
|
||||
<relatedList>Appraiser_Deficiencies__r</relatedList>
|
||||
</relatedLists>
|
||||
<relatedLists>
|
||||
<fields>SUBJECT</fields>
|
||||
<fields>STATUS</fields>
|
||||
<fields>DUE_DATE</fields>
|
||||
<relatedList>OpenActivities</relatedList>
|
||||
</relatedLists>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>true</showHighlightsPanel>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
<label>Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<behavior>Required</behavior>
|
||||
<field>Name</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<behavior>Required</behavior>
|
||||
<field>Appraiser_Case__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
|
|
@ -19,7 +19,12 @@
|
|||
<field>Deficiency_Number__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns/>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Sort_Order__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
|
|
@ -42,7 +47,7 @@
|
|||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>false</showHighlightsPanel>
|
||||
<showHighlightsPanel>true</showHighlightsPanel>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Account Details</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>MasterLabel</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>DeveloperName</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Account_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Account_Display_Name__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Active__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Environment_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>NamespacePrefix</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>IsProtected</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>CLM_Account_Id__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>CLM API Configuration</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>CLM_Api_Named_Credential__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>CLM_Download_Named_Credential__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Template_Root_Folder_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Destination_Root_Folder_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Default_Template_Document_Href__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>eSignature API Configuration</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>ESignature_Auth_Named_Credential__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>ESignature_Rest_Named_Credential__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>ESignature_Account_Id__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns/>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>false</showHighlightsPanel>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
</Layout>
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Letter Definition</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>MasterLabel</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>DeveloperName</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Account_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Letter_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Letter_Display_Name__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Description__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Active__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Is_Default__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Sort_Order__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>NamespacePrefix</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>IsProtected</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>CLM Defaults</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Template_Root_Folder_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Destination_Root_Folder_Href__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Default_Template_Document_Href__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Default_Destination_Document_Name_Prefix__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>false</showHighlightsPanel>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
</Layout>
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
.panel {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid #d8dde6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #3e3e3c;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.result-success {
|
||||
background: #e8f5e9;
|
||||
color: #1b5e20;
|
||||
}
|
||||
|
||||
.result-error {
|
||||
background: #fdecea;
|
||||
color: #8a1f11;
|
||||
}
|
||||
|
||||
.result-info {
|
||||
background: #eef4ff;
|
||||
color: #16325c;
|
||||
}
|
||||
|
||||
.deficiency-list {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.deficiency-item {
|
||||
border-top: 1px solid #d8dde6;
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.json-block {
|
||||
margin-top: 0.75rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.json-title {
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.json-block pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
<template>
|
||||
<lightning-card title="CLM Doc Gen Workbench" icon-name="standard:document_reference">
|
||||
<div class="panel">
|
||||
<lightning-combobox
|
||||
label="CLM Account"
|
||||
value={accountCode}
|
||||
options={accountOptions}
|
||||
onchange={handleAccountChange}
|
||||
></lightning-combobox>
|
||||
<lightning-combobox
|
||||
label="Letter Type"
|
||||
value={letterCode}
|
||||
options={letterOptions}
|
||||
onchange={handleLetterChange}
|
||||
></lightning-combobox>
|
||||
<template if:true={selectedAccountEnvironment}>
|
||||
<p class="hint">Environment: {selectedAccountEnvironment}</p>
|
||||
</template>
|
||||
<template if:true={selectedLetterDescription}>
|
||||
<p class="hint">{selectedLetterDescription}</p>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
label="Reset To Defaults"
|
||||
onclick={resetSelectionsToDefaults}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
</div>
|
||||
|
||||
<template if:true={caseContext}>
|
||||
<div class="section">
|
||||
<h3 class="section-title">Case Summary</h3>
|
||||
<p class="hint">Case: {caseContext.caseNumber}</p>
|
||||
<template if:true={caseContext.propertyAddress}>
|
||||
<p class="hint">Property: {caseContext.propertyAddress}</p>
|
||||
</template>
|
||||
<template if:true={hasDeficiencies}>
|
||||
<div class="deficiency-list">
|
||||
<template for:each={caseContext.deficiencies} for:item="deficiency">
|
||||
<div key={deficiency.recordId} class="deficiency-item">
|
||||
<p><strong>#{deficiency.deficiencyNumber}</strong> {deficiency.description}</p>
|
||||
<p class="hint">{deficiency.resolution}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Template Browser</h3>
|
||||
<lightning-input
|
||||
label="Template Folder Href"
|
||||
value={templateFolderHref}
|
||||
onchange={handleTemplateFolderHrefChange}
|
||||
></lightning-input>
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
label="Load Templates"
|
||||
onclick={loadTemplateFolder}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Open Selected Template Folder"
|
||||
onclick={openSelectedTemplateFolder}
|
||||
disabled={disableTemplateFolderOpen}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Up One Level"
|
||||
onclick={openTemplateParentFolder}
|
||||
disabled={disableTemplateFolderUp}
|
||||
></lightning-button>
|
||||
</div>
|
||||
<template if:true={templateFolderName}>
|
||||
<p class="hint">Current folder: {templateFolderName}</p>
|
||||
</template>
|
||||
<lightning-combobox
|
||||
label="Template Subfolders"
|
||||
value={selectedTemplateSubfolderHref}
|
||||
options={templateSubfolderOptions}
|
||||
onchange={handleTemplateSubfolderChange}
|
||||
></lightning-combobox>
|
||||
<lightning-combobox
|
||||
label="Template Documents"
|
||||
value={templateDocHref}
|
||||
options={templateDocumentOptions}
|
||||
onchange={handleTemplateDocHrefChange}
|
||||
></lightning-combobox>
|
||||
<lightning-input
|
||||
label="Template Document Href"
|
||||
value={templateDocHref}
|
||||
onchange={handleTemplateDocHrefChange}
|
||||
></lightning-input>
|
||||
<template if:true={templateDocHref}>
|
||||
<p class="hint">Selected template: {selectedTemplateSummary}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Destination Browser</h3>
|
||||
<lightning-input
|
||||
label="Destination Folder Href"
|
||||
value={destinationFolderHref}
|
||||
onchange={handleDestinationFolderHrefChange}
|
||||
></lightning-input>
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
label="Load Destination Folder"
|
||||
onclick={loadDestinationFolder}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Open Selected Destination Folder"
|
||||
onclick={openSelectedDestinationFolder}
|
||||
disabled={disableDestinationFolderOpen}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Up One Level"
|
||||
onclick={openDestinationParentFolder}
|
||||
disabled={disableDestinationFolderUp}
|
||||
></lightning-button>
|
||||
</div>
|
||||
<template if:true={destinationFolderName}>
|
||||
<p class="hint">Current folder: {destinationFolderName}</p>
|
||||
</template>
|
||||
<lightning-combobox
|
||||
label="Destination Folder Documents"
|
||||
value={destinationDocName}
|
||||
options={destinationDocumentOptions}
|
||||
onchange={handleDestinationDocumentSelection}
|
||||
></lightning-combobox>
|
||||
<lightning-combobox
|
||||
label="Destination Subfolders"
|
||||
value={selectedDestinationSubfolderHref}
|
||||
options={destinationSubfolderOptions}
|
||||
onchange={handleDestinationSubfolderChange}
|
||||
></lightning-combobox>
|
||||
<lightning-input
|
||||
label="Destination Filename"
|
||||
value={destinationDocName}
|
||||
onchange={handleDestinationNameChange}
|
||||
></lightning-input>
|
||||
<lightning-input
|
||||
label="Selected Destination Folder Href"
|
||||
value={destinationFolderHref}
|
||||
onchange={handleDestinationFolderHrefChange}
|
||||
></lightning-input>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
variant="brand"
|
||||
label="Generate Document"
|
||||
onclick={generateDocument}
|
||||
disabled={disableGenerate}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Check Task Status"
|
||||
onclick={checkTaskStatus}
|
||||
disabled={disableStatusCheck}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Attach Generated Document"
|
||||
onclick={attachGeneratedDocument}
|
||||
disabled={disableAttachGeneratedDocument}
|
||||
></lightning-button>
|
||||
<template if:true={showCloseButton}>
|
||||
<lightning-button
|
||||
label="Close"
|
||||
onclick={closeAction}
|
||||
></lightning-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template if:true={resultMessage}>
|
||||
<div class={resultClass}>
|
||||
<p>{resultMessage}</p>
|
||||
<template if:true={taskStatus}>
|
||||
<p>Status: {taskStatus}</p>
|
||||
</template>
|
||||
<template if:true={taskId}>
|
||||
<p>Task ID: {taskId}</p>
|
||||
</template>
|
||||
<template if:true={generatedDocumentId}>
|
||||
<p>Generated Document ID: {generatedDocumentId}</p>
|
||||
</template>
|
||||
<template if:true={hasAttachedSalesforceFile}>
|
||||
<p><lightning-formatted-url value={attachedSalesforceFileUrl} label="Open attached Salesforce file"></lightning-formatted-url></p>
|
||||
</template>
|
||||
<template if:true={hasTaskDetails}>
|
||||
<div class="json-block">
|
||||
<p class="json-title">Task Details</p>
|
||||
<pre>{taskDetailsJson}</pre>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</lightning-card>
|
||||
</template>
|
||||
|
|
@ -1,503 +0,0 @@
|
|||
import { LightningElement, api, wire } from 'lwc';
|
||||
import { getRecord } from 'lightning/uiRecordApi';
|
||||
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
|
||||
import { CloseActionScreenEvent } from 'lightning/actions';
|
||||
import getCaseContext from '@salesforce/apex/CLMAdminService.getCaseContext';
|
||||
import attachGeneratedDocumentToCase from '@salesforce/apex/CLMAdminService.attachGeneratedDocumentToCase';
|
||||
import generateDocument from '@salesforce/apex/CLMAdminService.generateDocument';
|
||||
import getAccountSettings from '@salesforce/apex/CLMAdminService.getAccountSettings';
|
||||
import getLetterSettings from '@salesforce/apex/CLMAdminService.getLetterSettings';
|
||||
import listAccountSettings from '@salesforce/apex/CLMAdminService.listAccountSettings';
|
||||
import listLetterSettings from '@salesforce/apex/CLMAdminService.listLetterSettings';
|
||||
import getFolderContents from '@salesforce/apex/CLMAdminService.getFolderContents';
|
||||
import getTaskStatus from '@salesforce/apex/CLMAdminService.getTaskStatus';
|
||||
|
||||
const CASE_FIELDS = ['Appraiser_Case__c.Name'];
|
||||
|
||||
export default class ClmDocGenWorkbench extends LightningElement {
|
||||
@api recordId;
|
||||
@api objectApiName;
|
||||
|
||||
accountCode = '';
|
||||
destinationDocName = '';
|
||||
templateFolderHref = '';
|
||||
templateDocHref = '';
|
||||
destinationFolderHref = '';
|
||||
|
||||
templateFolderName = '';
|
||||
destinationFolderName = '';
|
||||
templateParentFolderHref = '';
|
||||
destinationParentFolderHref = '';
|
||||
templateSubfolderOptions = [];
|
||||
templateDocumentOptions = [];
|
||||
destinationSubfolderOptions = [];
|
||||
destinationDocumentOptions = [];
|
||||
|
||||
selectedTemplateSubfolderHref = '';
|
||||
selectedDestinationSubfolderHref = '';
|
||||
|
||||
resultMessage = '';
|
||||
resultVariant = 'info';
|
||||
taskId = '';
|
||||
taskStatus = '';
|
||||
taskDetailsJson = '';
|
||||
attachedFileUrl = '';
|
||||
attachedFileTitle = '';
|
||||
isBusy = false;
|
||||
hasLoadedDefaults = false;
|
||||
caseNumber = '';
|
||||
caseContext;
|
||||
accountOptions = [];
|
||||
selectedAccountSettings;
|
||||
letterCode = '';
|
||||
letterOptions = [];
|
||||
selectedLetterSettings;
|
||||
|
||||
@wire(getRecord, { recordId: '$recordId', fields: CASE_FIELDS })
|
||||
wiredCase({ data }) {
|
||||
if (data) {
|
||||
this.caseNumber = data.fields.Name.value;
|
||||
}
|
||||
|
||||
if (data && !this.destinationDocName) {
|
||||
this.destinationDocName = this.buildDefaultDocumentName('Review');
|
||||
}
|
||||
|
||||
if (data && !this.hasLoadedDefaults) {
|
||||
this.initializeDefaults();
|
||||
}
|
||||
|
||||
if (data && !this.caseContext) {
|
||||
this.loadCaseContext();
|
||||
}
|
||||
}
|
||||
|
||||
get disableGenerate() {
|
||||
return this.isBusy || !this.recordId || !this.accountCode || !this.templateDocHref || !this.destinationFolderHref || !this.destinationDocName;
|
||||
}
|
||||
|
||||
get disableStatusCheck() {
|
||||
return this.isBusy || !this.taskId;
|
||||
}
|
||||
|
||||
get disableTemplateFolderOpen() {
|
||||
return this.isBusy || !this.selectedTemplateSubfolderHref;
|
||||
}
|
||||
|
||||
get disableTemplateFolderUp() {
|
||||
return this.isBusy || !this.templateParentFolderHref;
|
||||
}
|
||||
|
||||
get disableDestinationFolderOpen() {
|
||||
return this.isBusy || !this.selectedDestinationSubfolderHref;
|
||||
}
|
||||
|
||||
get disableDestinationFolderUp() {
|
||||
return this.isBusy || !this.destinationParentFolderHref;
|
||||
}
|
||||
|
||||
get resultClass() {
|
||||
return `result result-${this.resultVariant}`;
|
||||
}
|
||||
|
||||
get hasDeficiencies() {
|
||||
return this.caseContext && this.caseContext.deficiencies && this.caseContext.deficiencies.length > 0;
|
||||
}
|
||||
|
||||
get selectedTemplateSummary() {
|
||||
const selected = this.templateDocumentOptions.find((item) => item.value === this.templateDocHref);
|
||||
return selected ? selected.label : this.templateDocHref;
|
||||
}
|
||||
|
||||
get lastTaskUrl() {
|
||||
return this.caseContext ? this.caseContext.lastDocGenTaskUrl : null;
|
||||
}
|
||||
|
||||
get generatedDocumentUrl() {
|
||||
return this.caseContext ? this.caseContext.generatedDocumentUrl : null;
|
||||
}
|
||||
|
||||
get generatedDocumentId() {
|
||||
return this.caseContext ? this.caseContext.generatedDocumentId : null;
|
||||
}
|
||||
|
||||
get attachedSalesforceFileUrl() {
|
||||
return this.attachedFileUrl || (this.caseContext ? this.caseContext.attachedFileUrl : null);
|
||||
}
|
||||
|
||||
get hasAttachedSalesforceFile() {
|
||||
return Boolean(this.attachedSalesforceFileUrl);
|
||||
}
|
||||
|
||||
get disableAttachGeneratedDocument() {
|
||||
return this.isBusy || !this.generatedDocumentId;
|
||||
}
|
||||
|
||||
get hasTaskDetails() {
|
||||
return Boolean(this.taskDetailsJson);
|
||||
}
|
||||
|
||||
get showCloseButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
get selectedAccountEnvironment() {
|
||||
return this.selectedAccountSettings ? this.selectedAccountSettings.environment : '';
|
||||
}
|
||||
|
||||
get selectedLetterDescription() {
|
||||
return this.selectedLetterSettings ? this.selectedLetterSettings.description : '';
|
||||
}
|
||||
|
||||
async handleAccountChange(event) {
|
||||
this.accountCode = event.detail.value;
|
||||
await this.initializeDefaults(true);
|
||||
}
|
||||
|
||||
async handleLetterChange(event) {
|
||||
this.letterCode = event.detail.value;
|
||||
await this.initializeDefaults(true);
|
||||
}
|
||||
|
||||
handleDestinationNameChange(event) {
|
||||
this.destinationDocName = event.target.value;
|
||||
}
|
||||
|
||||
handleTemplateFolderHrefChange(event) {
|
||||
this.templateFolderHref = event.target.value;
|
||||
}
|
||||
|
||||
handleTemplateSubfolderChange(event) {
|
||||
this.selectedTemplateSubfolderHref = event.detail.value;
|
||||
}
|
||||
|
||||
handleTemplateDocHrefChange(event) {
|
||||
this.templateDocHref = event.detail.value || event.target.value;
|
||||
}
|
||||
|
||||
handleDestinationFolderHrefChange(event) {
|
||||
this.destinationFolderHref = event.target.value;
|
||||
}
|
||||
|
||||
handleDestinationSubfolderChange(event) {
|
||||
this.selectedDestinationSubfolderHref = event.detail.value;
|
||||
}
|
||||
|
||||
handleDestinationDocumentSelection(event) {
|
||||
this.destinationDocName = event.detail.value;
|
||||
}
|
||||
|
||||
async loadTemplateFolder() {
|
||||
await this.loadFolder('template', this.templateFolderHref);
|
||||
}
|
||||
|
||||
async openSelectedTemplateFolder() {
|
||||
this.templateFolderHref = this.selectedTemplateSubfolderHref;
|
||||
await this.loadTemplateFolder();
|
||||
}
|
||||
|
||||
async openTemplateParentFolder() {
|
||||
this.templateFolderHref = this.templateParentFolderHref;
|
||||
await this.loadTemplateFolder();
|
||||
}
|
||||
|
||||
async loadDestinationFolder() {
|
||||
await this.loadFolder('destination', this.destinationFolderHref);
|
||||
}
|
||||
|
||||
async openSelectedDestinationFolder() {
|
||||
this.destinationFolderHref = this.selectedDestinationSubfolderHref;
|
||||
await this.loadDestinationFolder();
|
||||
}
|
||||
|
||||
async openDestinationParentFolder() {
|
||||
this.destinationFolderHref = this.destinationParentFolderHref;
|
||||
await this.loadDestinationFolder();
|
||||
}
|
||||
|
||||
async initializeDefaults(forceReload = false) {
|
||||
if (this.hasLoadedDefaults && !forceReload) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this.clearResult();
|
||||
|
||||
try {
|
||||
await this.loadAccountOptions();
|
||||
const settings = await getAccountSettings({ accountCode: this.accountCode });
|
||||
await this.loadLetterOptions(forceReload);
|
||||
const letterSettings = await getLetterSettings({ accountCode: this.accountCode, letterCode: this.letterCode });
|
||||
await this.applySettings(settings, letterSettings, forceReload);
|
||||
this.hasLoadedDefaults = true;
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to load CLM account defaults');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async applySettings(settings, letterSettings, forceReload) {
|
||||
this.selectedAccountSettings = settings;
|
||||
this.selectedLetterSettings = letterSettings;
|
||||
const prefix = letterSettings && letterSettings.defaultDocumentNamePrefix
|
||||
? letterSettings.defaultDocumentNamePrefix
|
||||
: settings && settings.defaultDocumentNamePrefix
|
||||
? settings.defaultDocumentNamePrefix
|
||||
: 'Review';
|
||||
if (forceReload || !this.destinationDocName) {
|
||||
this.destinationDocName = this.buildDefaultDocumentName(prefix);
|
||||
}
|
||||
|
||||
if (settings || letterSettings) {
|
||||
if (letterSettings && letterSettings.templateRootFolderHref) {
|
||||
this.templateFolderHref = letterSettings.templateRootFolderHref;
|
||||
} else if (settings && settings.templateRootFolderHref) {
|
||||
this.templateFolderHref = settings.templateRootFolderHref;
|
||||
}
|
||||
if ((forceReload || !this.templateDocHref) && letterSettings && letterSettings.defaultTemplateDocumentHref) {
|
||||
this.templateDocHref = letterSettings.defaultTemplateDocumentHref;
|
||||
} else if ((forceReload || !this.templateDocHref) && settings && settings.defaultTemplateDocumentHref) {
|
||||
this.templateDocHref = settings.defaultTemplateDocumentHref;
|
||||
}
|
||||
if (letterSettings && letterSettings.destinationRootFolderHref) {
|
||||
this.destinationFolderHref = letterSettings.destinationRootFolderHref;
|
||||
} else if (settings && settings.destinationRootFolderHref) {
|
||||
this.destinationFolderHref = settings.destinationRootFolderHref;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.templateFolderHref) {
|
||||
await this.loadFolder('template', this.templateFolderHref, true);
|
||||
}
|
||||
if (this.destinationFolderHref) {
|
||||
await this.loadFolder('destination', this.destinationFolderHref, true);
|
||||
}
|
||||
}
|
||||
|
||||
async loadAccountOptions() {
|
||||
const accounts = await listAccountSettings();
|
||||
this.accountOptions = (accounts || []).map((account) => ({
|
||||
label: account.accountDisplayName,
|
||||
value: account.accountCode
|
||||
}));
|
||||
|
||||
if (!this.accountCode) {
|
||||
if (this.caseContext && this.caseContext.lastClmAccountCode) {
|
||||
this.accountCode = this.caseContext.lastClmAccountCode;
|
||||
} else if (this.accountOptions.length > 0) {
|
||||
this.accountCode = this.accountOptions[0].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadLetterOptions(forceReload) {
|
||||
const letters = await listLetterSettings({ accountCode: this.accountCode });
|
||||
this.letterOptions = (letters || []).map((letter) => ({
|
||||
label: letter.letterDisplayName,
|
||||
value: letter.letterCode
|
||||
}));
|
||||
|
||||
const hasExistingSelection = this.letterOptions.some((letter) => letter.value === this.letterCode);
|
||||
if (!hasExistingSelection || forceReload || !this.letterCode) {
|
||||
const defaultLetter = (letters || []).find((letter) => letter.isDefault);
|
||||
this.letterCode = defaultLetter
|
||||
? defaultLetter.letterCode
|
||||
: this.letterOptions.length > 0
|
||||
? this.letterOptions[0].value
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
buildDefaultDocumentName(prefix) {
|
||||
const normalizedPrefix = prefix || 'Review';
|
||||
if (this.caseNumber) {
|
||||
return `${normalizedPrefix}_${this.caseNumber}.docx`;
|
||||
}
|
||||
return `${normalizedPrefix}.docx`;
|
||||
}
|
||||
|
||||
async resetSelectionsToDefaults() {
|
||||
this.hasLoadedDefaults = false;
|
||||
this.templateFolderName = '';
|
||||
this.destinationFolderName = '';
|
||||
this.templateParentFolderHref = '';
|
||||
this.destinationParentFolderHref = '';
|
||||
this.templateSubfolderOptions = [];
|
||||
this.templateDocumentOptions = [];
|
||||
this.destinationSubfolderOptions = [];
|
||||
this.destinationDocumentOptions = [];
|
||||
this.selectedTemplateSubfolderHref = '';
|
||||
this.selectedDestinationSubfolderHref = '';
|
||||
await this.initializeDefaults(true);
|
||||
}
|
||||
|
||||
async loadCaseContext() {
|
||||
if (!this.recordId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.caseContext = await getCaseContext({ appraiserCaseId: this.recordId });
|
||||
if (this.caseContext && this.caseContext.caseNumber) {
|
||||
this.caseNumber = this.caseContext.caseNumber;
|
||||
}
|
||||
if (this.caseContext && this.caseContext.lastDocGenTaskId) {
|
||||
this.taskId = this.caseContext.lastDocGenTaskId;
|
||||
}
|
||||
if (this.caseContext && this.caseContext.lastClmAccountCode && this.accountCode !== this.caseContext.lastClmAccountCode) {
|
||||
this.accountCode = this.caseContext.lastClmAccountCode;
|
||||
if (this.hasLoadedDefaults) {
|
||||
await this.initializeDefaults(true);
|
||||
}
|
||||
}
|
||||
if (this.caseContext && this.caseContext.lastDocGenStatus && !this.taskStatus) {
|
||||
this.taskStatus = this.caseContext.lastDocGenStatus;
|
||||
}
|
||||
if (this.caseContext && this.caseContext.attachedFileUrl && !this.attachedFileUrl) {
|
||||
this.attachedFileUrl = this.caseContext.attachedFileUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to load case context');
|
||||
}
|
||||
}
|
||||
|
||||
async loadFolder(kind, folderHref, preserveResult = false) {
|
||||
if (!folderHref) {
|
||||
this.showToast('Missing folder href', 'Enter a folder href before loading.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
if (!preserveResult) {
|
||||
this.clearResult();
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = await getFolderContents({ folderHref, accountCode: this.accountCode });
|
||||
const subfolders = (contents.folders || []).map((item) => ({ label: item.name, value: item.href }));
|
||||
|
||||
if (kind === 'template') {
|
||||
this.templateFolderHref = contents.folder ? contents.folder.href : folderHref;
|
||||
this.templateFolderName = contents.folder ? contents.folder.name : '';
|
||||
this.templateParentFolderHref = contents.folder ? contents.folder.parentHref : '';
|
||||
this.templateSubfolderOptions = subfolders;
|
||||
this.templateDocumentOptions = (contents.documents || []).map((item) => ({ label: item.name, value: item.href }));
|
||||
this.selectedTemplateSubfolderHref = '';
|
||||
} else {
|
||||
this.destinationFolderHref = contents.folder ? contents.folder.href : folderHref;
|
||||
this.destinationFolderName = contents.folder ? contents.folder.name : '';
|
||||
this.destinationParentFolderHref = contents.folder ? contents.folder.parentHref : '';
|
||||
this.destinationSubfolderOptions = subfolders;
|
||||
this.destinationDocumentOptions = (contents.documents || []).map((item) => ({ label: item.name, value: item.name }));
|
||||
this.selectedDestinationSubfolderHref = '';
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to load folder contents');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async generateDocument() {
|
||||
this.isBusy = true;
|
||||
this.clearResult();
|
||||
|
||||
try {
|
||||
const response = await generateDocument({
|
||||
appraiserCaseId: this.recordId,
|
||||
templateDocHref: this.templateDocHref,
|
||||
destinationFolderHref: this.destinationFolderHref,
|
||||
destinationDocName: this.destinationDocName,
|
||||
accountCode: this.accountCode
|
||||
});
|
||||
|
||||
this.taskId = response.documentId;
|
||||
this.taskStatus = response.taskStatus || '';
|
||||
this.taskDetailsJson = this.formatJsonString(response.taskDetailsJson);
|
||||
this.resultVariant = response.success ? 'success' : 'error';
|
||||
this.resultMessage = response.message;
|
||||
await this.loadCaseContext();
|
||||
this.showToast(response.success ? 'Document submitted' : 'Submission failed', response.message, response.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Document generation failed');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkTaskStatus() {
|
||||
this.isBusy = true;
|
||||
this.clearResult();
|
||||
|
||||
try {
|
||||
const response = await getTaskStatus({ appraiserCaseId: this.recordId, taskId: this.taskId, accountCode: this.accountCode });
|
||||
this.taskStatus = response.taskStatus || '';
|
||||
this.taskDetailsJson = this.formatJsonString(response.taskDetailsJson);
|
||||
this.resultVariant = response.success ? 'success' : 'error';
|
||||
this.resultMessage = response.message;
|
||||
await this.loadCaseContext();
|
||||
this.showToast('Task status updated', response.message, response.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to fetch task status');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async attachGeneratedDocument() {
|
||||
this.isBusy = true;
|
||||
|
||||
try {
|
||||
const result = await attachGeneratedDocumentToCase({
|
||||
appraiserCaseId: this.recordId,
|
||||
accountCode: this.accountCode
|
||||
});
|
||||
this.resultVariant = result.success ? 'success' : 'error';
|
||||
this.resultMessage = result.message;
|
||||
this.attachedFileUrl = result.fileUrl || '';
|
||||
this.attachedFileTitle = result.fileTitle || '';
|
||||
await this.loadCaseContext();
|
||||
this.showToast('Document attached', result.message, result.success ? 'success' : 'error');
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to attach generated document');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
closeAction() {
|
||||
this.dispatchEvent(new CloseActionScreenEvent());
|
||||
}
|
||||
|
||||
clearResult() {
|
||||
this.resultMessage = '';
|
||||
this.resultVariant = 'info';
|
||||
this.taskDetailsJson = '';
|
||||
}
|
||||
|
||||
handleError(error, title) {
|
||||
const bodyMessage = error && error.body ? error.body.message : null;
|
||||
const directMessage = error ? error.message : null;
|
||||
const message = bodyMessage || directMessage || 'Unknown error';
|
||||
this.resultVariant = 'error';
|
||||
this.resultMessage = message;
|
||||
this.showToast(title, message, 'error');
|
||||
}
|
||||
|
||||
showToast(title, message, variant) {
|
||||
this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
|
||||
}
|
||||
|
||||
formatJsonString(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value), null, 2);
|
||||
} catch (e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<isExposed>true</isExposed>
|
||||
<masterLabel>CLM Doc Gen Workbench</masterLabel>
|
||||
<targets>
|
||||
<target>lightning__RecordAction</target>
|
||||
<target>lightning__RecordPage</target>
|
||||
</targets>
|
||||
<targetConfigs>
|
||||
<targetConfig targets="lightning__RecordAction">
|
||||
<actionType>ScreenAction</actionType>
|
||||
<objects>
|
||||
<object>Appraiser_Case__c</object>
|
||||
</objects>
|
||||
</targetConfig>
|
||||
<targetConfig targets="lightning__RecordPage">
|
||||
<objects>
|
||||
<object>Appraiser_Case__c</object>
|
||||
</objects>
|
||||
</targetConfig>
|
||||
</targetConfigs>
|
||||
</LightningComponentBundle>
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
.panel {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid #d8dde6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #3e3e3c;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.result {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.result-success {
|
||||
background: #e8f5e9;
|
||||
color: #1b5e20;
|
||||
}
|
||||
|
||||
.result-error {
|
||||
background: #fdecea;
|
||||
color: #8a1f11;
|
||||
}
|
||||
|
||||
.result-info {
|
||||
background: #eef4ff;
|
||||
color: #16325c;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<lightning-card title="CLM Request Preview" icon-name="standard:snippet">
|
||||
<div class="panel">
|
||||
<div class="controls">
|
||||
<lightning-combobox
|
||||
label="CLM Account"
|
||||
value={accountCode}
|
||||
options={accountOptions}
|
||||
onchange={handleAccountChange}
|
||||
disabled={isBusy}
|
||||
></lightning-combobox>
|
||||
<lightning-combobox
|
||||
label="Letter Type"
|
||||
value={letterCode}
|
||||
options={letterOptions}
|
||||
onchange={handleLetterChange}
|
||||
disabled={isBusy}
|
||||
></lightning-combobox>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
label="Refresh Preview"
|
||||
onclick={refreshAll}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
</div>
|
||||
|
||||
<template if:true={message}>
|
||||
<div class={resultClass}>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template if:true={hasPreview}>
|
||||
<div class="section">
|
||||
<h3 class="section-title">Effective Settings</h3>
|
||||
<p class="hint">Account: {preview.accountDisplayName}</p>
|
||||
<p class="hint">Letter: {preview.letterDisplayName}</p>
|
||||
<p class="hint">Template Href: {preview.templateDocHref}</p>
|
||||
<p class="hint">Destination Folder Href: {preview.destinationFolderHref}</p>
|
||||
<p class="hint">Destination Filename: {preview.destinationDocName}</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Payload JSON</h3>
|
||||
<pre class="code-block">{preview.payloadJson}</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Data XML</h3>
|
||||
<pre class="code-block">{preview.dataXml}</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">CLM API Endpoint</h3>
|
||||
<pre class="code-block">POST {preview.mergeTaskEndpointUrl}</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">CLM Request Body</h3>
|
||||
<pre class="code-block">{preview.requestBodyJson}</pre>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</lightning-card>
|
||||
</template>
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import { LightningElement, api } from 'lwc';
|
||||
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
|
||||
import getDocGenPreview from '@salesforce/apex/CLMAdminService.getDocGenPreview';
|
||||
import listAccountSettings from '@salesforce/apex/CLMAdminService.listAccountSettings';
|
||||
import listLetterSettings from '@salesforce/apex/CLMAdminService.listLetterSettings';
|
||||
|
||||
export default class ClmRequestPreview extends LightningElement {
|
||||
@api recordId;
|
||||
@api objectApiName;
|
||||
|
||||
accountCode = '';
|
||||
letterCode = '';
|
||||
accountOptions = [];
|
||||
letterOptions = [];
|
||||
preview;
|
||||
isBusy = false;
|
||||
message = '';
|
||||
messageVariant = 'info';
|
||||
|
||||
connectedCallback() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
get hasPreview() {
|
||||
return Boolean(this.preview);
|
||||
}
|
||||
|
||||
get resultClass() {
|
||||
return `result result-${this.messageVariant}`;
|
||||
}
|
||||
|
||||
async initialize(forceReload = false) {
|
||||
if (!this.recordId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this.clearMessage();
|
||||
|
||||
try {
|
||||
const accounts = await listAccountSettings();
|
||||
this.accountOptions = (accounts || []).map((account) => ({
|
||||
label: account.accountDisplayName,
|
||||
value: account.accountCode
|
||||
}));
|
||||
|
||||
if ((!this.accountCode || forceReload) && this.accountOptions.length > 0) {
|
||||
this.accountCode = this.accountOptions[0].value;
|
||||
}
|
||||
|
||||
await this.loadLetterOptions(forceReload);
|
||||
await this.loadPreview();
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to initialize CLM preview');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async loadLetterOptions(forceReload) {
|
||||
const letters = await listLetterSettings({ accountCode: this.accountCode });
|
||||
this.letterOptions = (letters || []).map((letter) => ({
|
||||
label: letter.letterDisplayName,
|
||||
value: letter.letterCode
|
||||
}));
|
||||
|
||||
const hasExistingSelection = this.letterOptions.some((letter) => letter.value === this.letterCode);
|
||||
if (!hasExistingSelection || forceReload || !this.letterCode) {
|
||||
const defaultLetter = (letters || []).find((letter) => letter.isDefault);
|
||||
this.letterCode = defaultLetter
|
||||
? defaultLetter.letterCode
|
||||
: this.letterOptions.length > 0
|
||||
? this.letterOptions[0].value
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
async loadPreview() {
|
||||
if (!this.recordId) {
|
||||
this.showMessage('No record context — place this component on a record page.', 'warning');
|
||||
return;
|
||||
}
|
||||
this.preview = await getDocGenPreview({
|
||||
appraiserCaseId: this.recordId,
|
||||
accountCode: this.accountCode,
|
||||
letterCode: this.letterCode
|
||||
});
|
||||
this.showMessage('Preview loaded.', 'success');
|
||||
}
|
||||
|
||||
async handleAccountChange(event) {
|
||||
this.accountCode = event.detail.value;
|
||||
await this.refreshAll(true);
|
||||
}
|
||||
|
||||
async handleLetterChange(event) {
|
||||
this.letterCode = event.detail.value;
|
||||
await this.refreshAll(false);
|
||||
}
|
||||
|
||||
async refreshAll(forceReloadLetters) {
|
||||
this.isBusy = true;
|
||||
this.clearMessage();
|
||||
|
||||
try {
|
||||
if (forceReloadLetters) {
|
||||
await this.loadLetterOptions(true);
|
||||
}
|
||||
await this.loadPreview();
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to refresh CLM preview');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
this.messageVariant = 'info';
|
||||
}
|
||||
|
||||
showMessage(message, variant) {
|
||||
this.message = message;
|
||||
this.messageVariant = variant;
|
||||
}
|
||||
|
||||
handleError(error, title) {
|
||||
const bodyMessage = error && error.body ? error.body.message : null;
|
||||
const directMessage = error ? error.message : null;
|
||||
const message = bodyMessage || directMessage || 'Unknown error';
|
||||
|
||||
this.showMessage(message, 'error');
|
||||
this.dispatchEvent(
|
||||
new ShowToastEvent({
|
||||
title,
|
||||
message,
|
||||
variant: 'error'
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<isExposed>true</isExposed>
|
||||
<masterLabel>CLM Request Preview</masterLabel>
|
||||
<targets>
|
||||
<target>lightning__RecordPage</target>
|
||||
</targets>
|
||||
<targetConfigs>
|
||||
<targetConfig targets="lightning__RecordPage">
|
||||
<objects>
|
||||
<object>Appraiser_Case__c</object>
|
||||
</objects>
|
||||
</targetConfig>
|
||||
</targetConfigs>
|
||||
</LightningComponentBundle>
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
.panel {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid #d8dde6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #3e3e3c;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.result-success {
|
||||
background: #e8f5e9;
|
||||
color: #1b5e20;
|
||||
}
|
||||
|
||||
.result-error {
|
||||
background: #fdecea;
|
||||
color: #8a1f11;
|
||||
}
|
||||
|
||||
.result-info {
|
||||
background: #eef4ff;
|
||||
color: #16325c;
|
||||
}
|
||||
|
||||
.json-block {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
<template>
|
||||
<lightning-card title="Docusign eSignature Workbench" icon-name="standard:contract">
|
||||
<div class="panel">
|
||||
<div class="controls">
|
||||
<lightning-combobox
|
||||
label="CLM / eSignature Account"
|
||||
value={accountCode}
|
||||
options={accountOptions}
|
||||
onchange={handleAccountChange}
|
||||
disabled={isBusy}
|
||||
></lightning-combobox>
|
||||
<lightning-input
|
||||
type="date"
|
||||
label="Envelope From Date"
|
||||
value={fromDate}
|
||||
onchange={handleFromDateChange}
|
||||
disabled={isBusy}
|
||||
></lightning-input>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<lightning-button
|
||||
label="Refresh All"
|
||||
onclick={refreshData}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
<lightning-button
|
||||
label="Refresh Envelopes"
|
||||
onclick={refreshEnvelopes}
|
||||
disabled={isBusy}
|
||||
></lightning-button>
|
||||
</div>
|
||||
|
||||
<template if:true={hasAccountConfig}>
|
||||
<div class="section">
|
||||
<h3 class="section-title">Account Summary</h3>
|
||||
<p class="hint">Environment: {selectedEnvironment}</p>
|
||||
<p class="hint">Configured eSignature Account Id: {selectedAccountId}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template if:true={message}>
|
||||
<div class={resultClass}>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Discovered Accounts</h3>
|
||||
<template if:true={hasAccounts}>
|
||||
<lightning-datatable
|
||||
key-field="accountId"
|
||||
data={accountSummaries}
|
||||
columns={accountColumns}
|
||||
hide-checkbox-column
|
||||
></lightning-datatable>
|
||||
</template>
|
||||
<template if:false={hasAccounts}>
|
||||
<p class="hint">No account discovery data loaded yet.</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Templates</h3>
|
||||
<template if:true={hasTemplates}>
|
||||
<lightning-datatable
|
||||
key-field="templateId"
|
||||
data={templates}
|
||||
columns={templateColumns}
|
||||
hide-checkbox-column
|
||||
></lightning-datatable>
|
||||
</template>
|
||||
<template if:false={hasTemplates}>
|
||||
<p class="hint">No templates returned for this account.</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Recent Envelopes</h3>
|
||||
<template if:true={hasEnvelopes}>
|
||||
<lightning-datatable
|
||||
key-field="envelopeId"
|
||||
data={envelopes}
|
||||
columns={envelopeColumns}
|
||||
hide-checkbox-column
|
||||
></lightning-datatable>
|
||||
</template>
|
||||
<template if:false={hasEnvelopes}>
|
||||
<p class="hint">No envelopes returned for this date range.</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">Login Information</h3>
|
||||
<template if:true={hasLoginInfo}>
|
||||
<pre class="json-block">{loginInfoJson}</pre>
|
||||
</template>
|
||||
<template if:false={hasLoginInfo}>
|
||||
<p class="hint">No login information loaded yet.</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-title">OAuth User Info</h3>
|
||||
<template if:true={hasUserInfo}>
|
||||
<pre class="json-block">{userInfoJson}</pre>
|
||||
</template>
|
||||
<template if:false={hasUserInfo}>
|
||||
<p class="hint">No user info loaded yet.</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</lightning-card>
|
||||
</template>
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
import { LightningElement, api } from 'lwc';
|
||||
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
|
||||
import getAccountConfig from '@salesforce/apex/DocusignESignatureService.getAccountConfig';
|
||||
import getLoginInformation from '@salesforce/apex/DocusignESignatureService.getLoginInformation';
|
||||
import getUserInfo from '@salesforce/apex/DocusignESignatureService.getUserInfo';
|
||||
import listAccounts from '@salesforce/apex/DocusignESignatureService.listAccounts';
|
||||
import listTemplates from '@salesforce/apex/DocusignESignatureService.listTemplates';
|
||||
import listEnvelopes from '@salesforce/apex/DocusignESignatureService.listEnvelopes';
|
||||
import listAccountSettings from '@salesforce/apex/CLMAdminService.listAccountSettings';
|
||||
|
||||
const TEMPLATE_COLUMNS = [
|
||||
{ label: 'Template Name', fieldName: 'name', type: 'text' },
|
||||
{ label: 'Template Id', fieldName: 'templateId', type: 'text' },
|
||||
{ label: 'Shared', fieldName: 'shared', type: 'text' },
|
||||
{ label: 'Last Modified', fieldName: 'lastModified', type: 'text' }
|
||||
];
|
||||
|
||||
const ENVELOPE_COLUMNS = [
|
||||
{ label: 'Subject', fieldName: 'emailSubject', type: 'text' },
|
||||
{ label: 'Envelope Id', fieldName: 'envelopeId', type: 'text' },
|
||||
{ label: 'Status', fieldName: 'status', type: 'text' },
|
||||
{ label: 'Created', fieldName: 'createdDateTime', type: 'text' },
|
||||
{ label: 'Completed', fieldName: 'completedDateTime', type: 'text' }
|
||||
];
|
||||
|
||||
const ACCOUNT_COLUMNS = [
|
||||
{ label: 'Account Name', fieldName: 'accountName', type: 'text' },
|
||||
{ label: 'Account Id', fieldName: 'accountId', type: 'text' },
|
||||
{ label: 'Base Url', fieldName: 'baseUri', type: 'text' },
|
||||
{ label: 'Default', fieldName: 'isDefault', type: 'boolean' }
|
||||
];
|
||||
|
||||
export default class DocusignEsignWorkbench extends LightningElement {
|
||||
@api recordId;
|
||||
@api objectApiName;
|
||||
|
||||
accountOptions = [];
|
||||
accountCode = '';
|
||||
accountConfig;
|
||||
accountSummaries = [];
|
||||
templates = [];
|
||||
envelopes = [];
|
||||
loginInfoJson = '';
|
||||
userInfoJson = '';
|
||||
isBusy = false;
|
||||
message = '';
|
||||
messageVariant = 'info';
|
||||
fromDate = this.defaultFromDate();
|
||||
|
||||
templateColumns = TEMPLATE_COLUMNS;
|
||||
envelopeColumns = ENVELOPE_COLUMNS;
|
||||
accountColumns = ACCOUNT_COLUMNS;
|
||||
|
||||
connectedCallback() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
get hasAccountConfig() {
|
||||
return Boolean(this.accountConfig);
|
||||
}
|
||||
|
||||
get selectedEnvironment() {
|
||||
return this.accountConfig ? this.accountConfig.environment : '';
|
||||
}
|
||||
|
||||
get selectedAccountId() {
|
||||
return this.accountConfig ? this.accountConfig.eSignatureAccountId : '';
|
||||
}
|
||||
|
||||
get hasAccounts() {
|
||||
return this.accountSummaries.length > 0;
|
||||
}
|
||||
|
||||
get hasTemplates() {
|
||||
return this.templates.length > 0;
|
||||
}
|
||||
|
||||
get hasEnvelopes() {
|
||||
return this.envelopes.length > 0;
|
||||
}
|
||||
|
||||
get hasLoginInfo() {
|
||||
return Boolean(this.loginInfoJson);
|
||||
}
|
||||
|
||||
get hasUserInfo() {
|
||||
return Boolean(this.userInfoJson);
|
||||
}
|
||||
|
||||
get resultClass() {
|
||||
return `result result-${this.messageVariant}`;
|
||||
}
|
||||
|
||||
async initialize(forceReload = false) {
|
||||
if (this.isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this.clearMessage();
|
||||
|
||||
try {
|
||||
const accounts = await listAccountSettings();
|
||||
this.accountOptions = (accounts || []).map((account) => ({
|
||||
label: account.accountDisplayName,
|
||||
value: account.accountCode
|
||||
}));
|
||||
|
||||
if ((!this.accountCode || forceReload) && this.accountOptions.length > 0) {
|
||||
this.accountCode = this.accountOptions[0].value;
|
||||
}
|
||||
|
||||
if (!this.accountCode) {
|
||||
this.showMessage('No active CLM/eSignature accounts are configured.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadAll();
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to load eSignature workbench');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async loadAll() {
|
||||
const [config, accounts, loginInfo, userInfo, templates, envelopes] = await Promise.all([
|
||||
getAccountConfig({ accountCode: this.accountCode }),
|
||||
listAccounts({ accountCode: this.accountCode }),
|
||||
getLoginInformation({ accountCode: this.accountCode }),
|
||||
getUserInfo({ accountCode: this.accountCode }),
|
||||
listTemplates({ accountCode: this.accountCode }),
|
||||
listEnvelopes({ accountCode: this.accountCode, fromDate: this.fromDate })
|
||||
]);
|
||||
|
||||
this.accountConfig = config;
|
||||
this.accountSummaries = accounts || [];
|
||||
this.loginInfoJson = this.prettyJson(loginInfo ? loginInfo.responseBody : null);
|
||||
this.userInfoJson = this.prettyJson(userInfo ? userInfo.responseBody : null);
|
||||
this.templates = templates || [];
|
||||
this.envelopes = envelopes || [];
|
||||
this.showMessage('eSignature data loaded successfully.', 'success');
|
||||
}
|
||||
|
||||
async handleAccountChange(event) {
|
||||
this.accountCode = event.detail.value;
|
||||
await this.refreshData();
|
||||
}
|
||||
|
||||
handleFromDateChange(event) {
|
||||
this.fromDate = event.target.value;
|
||||
}
|
||||
|
||||
async refreshData() {
|
||||
if (!this.accountCode || this.isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this.clearMessage();
|
||||
|
||||
try {
|
||||
await this.loadAll();
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to refresh eSignature data');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshEnvelopes() {
|
||||
if (!this.accountCode || this.isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this.clearMessage();
|
||||
|
||||
try {
|
||||
this.envelopes = await listEnvelopes({ accountCode: this.accountCode, fromDate: this.fromDate });
|
||||
this.showMessage('Envelope list refreshed.', 'success');
|
||||
} catch (error) {
|
||||
this.handleError(error, 'Unable to refresh envelopes');
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
defaultFromDate() {
|
||||
const current = new Date();
|
||||
current.setDate(current.getDate() - 30);
|
||||
return current.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
prettyJson(raw) {
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(raw), null, 2);
|
||||
} catch (error) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
this.messageVariant = 'info';
|
||||
}
|
||||
|
||||
showMessage(message, variant) {
|
||||
this.message = message;
|
||||
this.messageVariant = variant;
|
||||
}
|
||||
|
||||
handleError(error, fallbackMessage) {
|
||||
const message = error && error.body && error.body.message
|
||||
? error.body.message
|
||||
: error && error.message
|
||||
? error.message
|
||||
: fallbackMessage;
|
||||
|
||||
this.showMessage(message, 'error');
|
||||
this.dispatchEvent(
|
||||
new ShowToastEvent({
|
||||
title: 'eSignature Workbench',
|
||||
message,
|
||||
variant: 'error'
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<isExposed>true</isExposed>
|
||||
<masterLabel>Docusign eSignature Workbench</masterLabel>
|
||||
<targets>
|
||||
<target>lightning__RecordPage</target>
|
||||
<target>lightning__AppPage</target>
|
||||
<target>lightning__HomePage</target>
|
||||
</targets>
|
||||
<targetConfigs>
|
||||
<targetConfig targets="lightning__RecordPage">
|
||||
<objects>
|
||||
<object>Appraiser_Case__c</object>
|
||||
</objects>
|
||||
</targetConfig>
|
||||
</targetConfigs>
|
||||
</LightningComponentBundle>
|
||||
|
|
@ -4,16 +4,21 @@
|
|||
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
|
||||
<calloutStatus>Enabled</calloutStatus>
|
||||
<generateAuthorizationHeader>true</generateAuthorizationHeader>
|
||||
<label>AcctDemo_NamedCreds</label>
|
||||
<label>CLMNamedCred</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://account-d.docusign.com</parameterValue>
|
||||
<parameterValue>https://api.s1.us.clm.demo.docusign.net</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
<parameterName>ExternalCredential</parameterName>
|
||||
<parameterType>Authentication</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<certificate>DocusignJWT</certificate>
|
||||
<parameterName>ClientCertificate</parameterName>
|
||||
<parameterType>ClientCertificate</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialType>SecuredEndpoint</namedCredentialType>
|
||||
</NamedCredential>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
|
||||
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
|
||||
<calloutStatus>Enabled</calloutStatus>
|
||||
<generateAuthorizationHeader>true</generateAuthorizationHeader>
|
||||
<label>CLMs1Download</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://api.s1.us.clm.demo.docusign.net/content</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
<parameterName>ExternalCredential</parameterName>
|
||||
<parameterType>Authentication</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialType>SecuredEndpoint</namedCredentialType>
|
||||
</NamedCredential>
|
||||
|
|
@ -4,11 +4,11 @@
|
|||
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
|
||||
<calloutStatus>Enabled</calloutStatus>
|
||||
<generateAuthorizationHeader>true</generateAuthorizationHeader>
|
||||
<label>CLMs1NamedCreds</label>
|
||||
<label>CLMuatDownloadNamedCreds</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://api.s1.us.clm.demo.docusign.net</parameterValue>
|
||||
<parameterValue>https://apidownloaduatna11.springcm.com</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
|
||||
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
|
||||
<calloutStatus>Enabled</calloutStatus>
|
||||
<generateAuthorizationHeader>true</generateAuthorizationHeader>
|
||||
<label>Esignature_Demo_NamedCreds</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://demo.docusign.net/restapi</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
<parameterName>ExternalCredential</parameterName>
|
||||
<parameterType>Authentication</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialType>SecuredEndpoint</namedCredentialType>
|
||||
</NamedCredential>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Reference__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Reference</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<active>true</active>
|
||||
<description>Prevents empty or incomplete deficiency rows from being saved.</description>
|
||||
<fullName>Required_Deficiency_Data</fullName>
|
||||
<errorConditionFormula>OR(
|
||||
ISBLANK(TEXT(Deficiency_Number__c)),
|
||||
ISBLANK(Description__c),
|
||||
ISBLANK(Resolution__c)
|
||||
)</errorConditionFormula>
|
||||
<errorDisplayField>Deficiency_Number__c</errorDisplayField>
|
||||
<errorMessage>Deficiency Number, Description, and Resolution are required.</errorMessage>
|
||||
</ValidationRule>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_City__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser City</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Country__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Country</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Email__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Email</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Email</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Last_Name__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Last Name</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Name__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Name</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Postal_Code__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Postal Code</label>
|
||||
<length>40</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Salutation__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Salutation</label>
|
||||
<length>10</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_State_Province__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser State/Province</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Street__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Appraiser Street</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Attached_File_Content_Document_Id__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Attached File Content Document Id</label>
|
||||
<length>18</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Attached_File_Url__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Attached File Url</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Url</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>FHA_Case_Number__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>FHA Case Number</label>
|
||||
<length>40</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Generated_Document_Id__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Generated Document Id</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Generated_Document_Url__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Generated Document Url</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Url</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_CLM_Account_Code__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last CLM Account Code</label>
|
||||
<length>80</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_Destination_Folder_Href__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last Destination Folder Href</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Completed_At__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Completed At</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>DateTime</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Requested_At__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Requested At</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>DateTime</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Status__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Status</label>
|
||||
<length>100</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Task_Id__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Task Id</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Task_Url__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Task Url</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Url</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_Template_Document_Href__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last Template Document Href</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Letter_Sent_Date__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Letter Sent Date</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Date</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ListView xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>All</fullName>
|
||||
<columns>Name</columns>
|
||||
<columns>Appraiser_Field_Review_Date__c</columns>
|
||||
<columns>Property_Street__c</columns>
|
||||
<columns>Property_City__c</columns>
|
||||
<columns>Property_State_Province__c</columns>
|
||||
<columns>LastModifiedDate</columns>
|
||||
<filterScope>Everything</filterScope>
|
||||
<label>All</label>
|
||||
</ListView>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<actionOverrides>
|
||||
<actionName>New</actionName>
|
||||
<type>Default</type>
|
||||
</actionOverrides>
|
||||
<actionOverrides>
|
||||
<actionName>Edit</actionName>
|
||||
<type>Default</type>
|
||||
</actionOverrides>
|
||||
<actionOverrides>
|
||||
<actionName>View</actionName>
|
||||
<type>Default</type>
|
||||
</actionOverrides>
|
||||
<allowInChatterGroups>false</allowInChatterGroups>
|
||||
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
|
||||
<deploymentStatus>Deployed</deploymentStatus>
|
||||
<enableActivities>false</enableActivities>
|
||||
<enableBulkApi>true</enableBulkApi>
|
||||
<enableFeeds>false</enableFeeds>
|
||||
<enableHistory>true</enableHistory>
|
||||
<enableLicensing>false</enableLicensing>
|
||||
<enableReports>true</enableReports>
|
||||
<enableSearch>true</enableSearch>
|
||||
<enableSharing>true</enableSharing>
|
||||
<enableStreamingApi>true</enableStreamingApi>
|
||||
<externalSharingModel>ControlledByParent</externalSharingModel>
|
||||
<label>Appraiser Deficiency</label>
|
||||
<nameField>
|
||||
<label>Appraiser Deficiency Name</label>
|
||||
<type>Text</type>
|
||||
</nameField>
|
||||
<pluralLabel>Appraiser Deficiencies</pluralLabel>
|
||||
<searchLayouts></searchLayouts>
|
||||
<sharingModel>ControlledByParent</sharingModel>
|
||||
<visibility>Public</visibility>
|
||||
</CustomObject>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Case__c</fullName>
|
||||
<label>Appraiser Case</label>
|
||||
<referenceTo>Appraiser_Case__c</referenceTo>
|
||||
<relationshipLabel>Appraiser Deficiencies</relationshipLabel>
|
||||
<relationshipName>Appraiser_Deficiencies</relationshipName>
|
||||
<reparentableMasterDetail>false</reparentableMasterDetail>
|
||||
<required>true</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>MasterDetail</type>
|
||||
<writeRequiresMasterRead>false</writeRequiresMasterRead>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Deficiency_Number__c</fullName>
|
||||
<label>Deficiency Number</label>
|
||||
<length>50</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Last_DocGen_Message__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>Last DocGen Message</label>
|
||||
<fullName>Description__c</fullName>
|
||||
<label>Description</label>
|
||||
<length>32768</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>LongTextArea</type>
|
||||
<visibleLines>3</visibleLines>
|
||||
<visibleLines>5</visibleLines>
|
||||
</CustomField>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue