Compare commits
2 Commits
63b1bfd758
...
e058dedb82
| Author | SHA1 | Date |
|---|---|---|
|
|
e058dedb82 | |
|
|
1e532029fa |
|
|
@ -0,0 +1 @@
|
|||
<EFBFBD>覆骒陡<EFBFBD>'<27>噏
|
||||
Binary file not shown.
|
|
@ -0,0 +1,71 @@
|
|||
# DocuSign CLM Template Mapping Snippet
|
||||
|
||||
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
|
||||
|
||||
```json
|
||||
{
|
||||
"templateKey": "APPRAISER_REVIEW_LETTER",
|
||||
"recordId": "a01XXXXXXXXXXXX",
|
||||
"sourceObject": "Appraiser_Case__c",
|
||||
"mergeData": {
|
||||
"appraiserCaseNumber": "AC-000123",
|
||||
"appraiserFieldReviewDate": "2026-04-01",
|
||||
"propertyStreet": "123 Main St",
|
||||
"propertyCity": "Ottawa",
|
||||
"propertyStateProvince": "ON",
|
||||
"propertyPostalCode": "K1A 0A1",
|
||||
"propertyCountry": "Canada",
|
||||
"propertyAddressSingleLine": "123 Main St | Ottawa, ON, K1A 0A1 | Canada",
|
||||
"deficiencies": [
|
||||
{
|
||||
"deficiencyNumber": "1",
|
||||
"description": "Missing comparable sale analysis",
|
||||
"resolution": "Provide updated comparable sales section",
|
||||
"sortOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Suggested Template Tokens
|
||||
|
||||
Use the actual DocuSign CLM token syntax used in your tenant, but the field model should be equivalent to:
|
||||
|
||||
- `{{mergeData.appraiserCaseNumber}}`
|
||||
- `{{mergeData.appraiserFieldReviewDate}}`
|
||||
- `{{mergeData.propertyStreet}}`
|
||||
- `{{mergeData.propertyCity}}`
|
||||
- `{{mergeData.propertyStateProvince}}`
|
||||
- `{{mergeData.propertyPostalCode}}`
|
||||
- `{{mergeData.propertyCountry}}`
|
||||
- `{{mergeData.propertyAddressSingleLine}}`
|
||||
|
||||
### Deficiency Repeat Block
|
||||
|
||||
```handlebars
|
||||
{{#each mergeData.deficiencies}}
|
||||
| {{deficiencyNumber}} | {{description}} | {{resolution}} |
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
## Recommended Letter Sections
|
||||
|
||||
1. Header / logo block
|
||||
2. Review metadata
|
||||
- Case number
|
||||
- Field review date
|
||||
- Property address
|
||||
3. Deficiency table
|
||||
- Deficiency number
|
||||
- Description
|
||||
- Resolution
|
||||
4. Closing / signature block
|
||||
|
||||
## Notes
|
||||
|
||||
- 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,39 +1,11 @@
|
|||
# Deployment & Testing — Appraiser Review Letter
|
||||
|
||||
## Implemented Salesforce Metadata
|
||||
- Parent object: Appraiser_Case__c (label: Appraiser Case)
|
||||
- Name field (auto number): Appraiser Case Number (AC-{00000})
|
||||
- Fields on Appraiser Case:
|
||||
- Appraiser_Field_Review_Date__c (Date)
|
||||
- Property_Address__c (Text 255)
|
||||
- Child object for repeatable deficiencies: Appraiser_Case_Deficiency__c
|
||||
- Fields on Appraiser Case Deficiency:
|
||||
- Appraiser_Case__c (Master-Detail to Appraiser_Case__c)
|
||||
- Deficiency_Number__c (Number)
|
||||
- Description__c (Long Text Area)
|
||||
- Resolution__c (Long Text Area)
|
||||
- Permission set: Appraiser_Case_Access
|
||||
- Apex Classes:
|
||||
- AppraiserCasePayloadBuilder: Transforms Salesforce data to CLM merge payload
|
||||
- AppraiserCasePayloadBuilderTest: Unit tests for payload builder
|
||||
- CLMDocGenCallout: HTTP integration with DocuSign CLM API
|
||||
|
||||
## Deployment Steps
|
||||
1. Deploy custom objects & fields
|
||||
2. Deploy Apex classes (included in manifest)
|
||||
3. Configure Named Credentials for CLM API access
|
||||
4. Configure Remote Site Settings for CLM instance
|
||||
5. Map merge fields to object schema
|
||||
6. Configure CLM template and connect API
|
||||
7. Test with sample Appraiser Case records
|
||||
|
||||
### Suggested CLI Deploy Commands
|
||||
1. Authenticate to your org:
|
||||
- sf org login web --alias appraiser-dev
|
||||
2. Validate source deploy:
|
||||
- sf project deploy validate --target-org appraiser-dev --manifest manifest/package.xml
|
||||
3. Deploy metadata:
|
||||
- sf project deploy start --target-org appraiser-dev --manifest manifest/package.xml
|
||||
2. Copy updated template files into Salesforce docs directory
|
||||
3. Map merge fields to object schema
|
||||
4. Configure CLM template and connect API
|
||||
5. Test with sample Appraisal and Deficiency objects
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -43,39 +15,6 @@
|
|||
- Test table rendering for arrays (DeficiencyList, ReviewerComments)
|
||||
- Document any failed merges or formatting gaps
|
||||
|
||||
## CLM Data Mapping Starter
|
||||
- AppraiserCaseNumber -> Appraiser_Case__c.Name
|
||||
- AppraiserFieldReviewDate -> Appraiser_Case__c.Appraiser_Field_Review_Date__c
|
||||
- PropertyAddress -> Appraiser_Case__c.Property_Address__c
|
||||
- DeficiencyList[] -> Appraiser_Case__c.Deficiencies__r
|
||||
- deficiencyNumber -> Deficiency_Number__c
|
||||
- description -> Description__c
|
||||
- resolution -> Resolution__c
|
||||
|
||||
## Smoke Test Execution
|
||||
After deployment, sample test data was created:
|
||||
- Appraiser Case: AC-00001 (a0wKW000007OIiCYAW)
|
||||
- 3 related deficiency records verified
|
||||
|
||||
### Test Payload Query (run in Apex Execute or Query Editor)
|
||||
```soql
|
||||
SELECT Id, Name, Appraiser_Field_Review_Date__c, Property_Address__c,
|
||||
(SELECT Id, Deficiency_Number__c, Description__c, Resolution__c
|
||||
FROM Deficiencies__r ORDER BY Deficiency_Number__c)
|
||||
FROM Appraiser_Case__c
|
||||
WHERE Id='a0wKW000007OIiCYAW'
|
||||
```
|
||||
|
||||
### Test Payload Generation (Apex)
|
||||
```apex
|
||||
String caseId = 'a0wKW000007OIiCYAW';
|
||||
Map<String, Object> payload = AppraiserCasePayloadBuilder.buildPayload(caseId);
|
||||
System.debug(JSON.serializePretty(payload));
|
||||
```
|
||||
|
||||
## CLM Doc Gen Integration
|
||||
See [CLM_INTEGRATION.md](CLM_INTEGRATION.md) for complete setup and integration patterns.
|
||||
|
||||
---
|
||||
|
||||
_Last updated: 2026-02-26 13:26 PM_
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# Next Steps — DocuSign CLM Launch Path
|
||||
|
||||
I added a placeholder Quick Action metadata file so there is an explicit place in the project for the document generation launch pattern.
|
||||
|
||||
## Reality check
|
||||
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
|
||||
### Option C — Button / URL hack
|
||||
- Usually fast, usually brittle. I don’t recommend it unless your CLM package explicitly documents it.
|
||||
|
||||
## What to confirm in your org
|
||||
|
||||
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.)
|
||||
|
||||
## Good next move
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Salesforce Setup — Appraiser Case + DocuSign CLM
|
||||
|
||||
## What was added
|
||||
|
||||
### Custom object: `Appraiser_Case__c`
|
||||
- Auto-number name field labeled **Appraiser Case Number**
|
||||
- `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_Deficiency__c`
|
||||
- Master-detail to `Appraiser_Case__c`
|
||||
- `Deficiency_Number__c` (Text 50)
|
||||
- `Description__c` (Long Text Area)
|
||||
- `Resolution__c` (Long Text Area)
|
||||
- `Sort_Order__c` (Number)
|
||||
|
||||
### Layouts
|
||||
- Basic page layout for Appraiser Case
|
||||
- Basic page layout for Appraiser Deficiency
|
||||
- Related list on Appraiser Case for deficiencies
|
||||
- Basic list view on Appraiser Case
|
||||
|
||||
### 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`
|
||||
|
||||
## Deploy
|
||||
|
||||
From the project root:
|
||||
|
||||
```bash
|
||||
sf project deploy start --source-dir force-app
|
||||
```
|
||||
|
||||
Or from elsewhere:
|
||||
|
||||
```bash
|
||||
sf project deploy start --source-dir /home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app
|
||||
```
|
||||
|
||||
## Test Apex
|
||||
|
||||
```bash
|
||||
sf apex run test --tests AppraiserCaseDocGenServiceTest --result-format human
|
||||
```
|
||||
|
||||
## Load sample data
|
||||
|
||||
```bash
|
||||
sf apex run --file scripts/apex/createSampleAppraiserCase.apex
|
||||
```
|
||||
|
||||
## Assign permission set
|
||||
|
||||
```bash
|
||||
sf org assign permset --name Appraiser_Case_Admin
|
||||
```
|
||||
|
||||
## Suggested next steps in Salesforce
|
||||
|
||||
1. Deploy metadata.
|
||||
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 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
|
||||
|
||||
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”
|
||||
|
||||
You said “just do default.” In Salesforce terms that means I created the baseline metadata so records can be created and edited in a normal Lightning UI without custom polish yet.
|
||||
|
|
@ -42,39 +42,6 @@ Outline technical and functional requirements for Appraiser Review Letter templa
|
|||
- What is the authoritative object and field schema for merge?
|
||||
- Are custom field mappings needed for relationships (e.g. lookup fields)?
|
||||
|
||||
## Initial Authoritative Salesforce Schema
|
||||
- Appraiser_Case__c (Appraiser Case)
|
||||
- Name (label: Appraiser Case Number, AutoNumber)
|
||||
- Appraiser_Field_Review_Date__c (Date)
|
||||
- Property_Address__c (Text)
|
||||
- Appraiser_Case_Deficiency__c (Appraiser Case Deficiency)
|
||||
- Appraiser_Case__c (Master-Detail -> Appraiser_Case__c)
|
||||
- Deficiency_Number__c (Number)
|
||||
- Description__c (Long Text Area)
|
||||
- Resolution__c (Long Text Area)
|
||||
|
||||
This schema supports CLM array merges by iterating child deficiency records tied to one appraiser case.
|
||||
|
||||
## CLM Integration
|
||||
|
||||
### Payload Structure (from AppraiserCasePayloadBuilder)
|
||||
The Apex class `AppraiserCasePayloadBuilder` transforms Salesforce records into CLM-ready JSON:
|
||||
- AppraiserCaseNumber (string) -> Appraiser_Case__c.Name
|
||||
- AppraiserFieldReviewDate (ISO date) -> Appraiser_Case__c.Appraiser_Field_Review_Date__c
|
||||
- PropertyAddress (string) -> Appraiser_Case__c.Property_Address__c
|
||||
- DeficiencyList (array of objects):
|
||||
- deficiencyNumber (number) -> Appraiser_Case_Deficiency__c.Deficiency_Number__c
|
||||
- description (string) -> Appraiser_Case_Deficiency__c.Description__c
|
||||
- resolution (string) -> Appraiser_Case_Deficiency__c.Resolution__c
|
||||
|
||||
### CLM API Integration (CLMDocGenCallout)
|
||||
- HTTP POST to DocuSign CLM API with merge payload
|
||||
- Named Credentials: Securely store CLM endpoint + OAuth token
|
||||
- Remote Site Settings: Whitelist CLM instance domain
|
||||
- Response includes document URL and ID for tracking
|
||||
|
||||
See [CLM_INTEGRATION.md](CLM_INTEGRATION.md) for setup and usage patterns.
|
||||
|
||||
---
|
||||
_Last updated: 2026-02-26 10:35 AM_
|
||||
_Work in progress: Integration and schema expansion next._
|
||||
---
|
||||
_Last updated: 2026-02-26 10:35 AM_
|
||||
_Work in progress: Integration and schema expansion next._
|
||||
|
|
|
|||
|
|
@ -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,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>63.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -112,20 +112,25 @@ public class CLMDocGenCallout {
|
|||
private static String buildDataXml(Map<String, Object> payload) {
|
||||
String xml = '<TemplateFieldData>';
|
||||
|
||||
// Emit flat fields first
|
||||
for (String key : payload.keySet()) {
|
||||
if (key == 'DeficiencyList') continue;
|
||||
xml += '<' + key + '>' + escapeXml(String.valueOf(payload.get(key))) + '</' + key + '>';
|
||||
}
|
||||
|
||||
// Emit DeficiencyList as a nested list so templates can iterate dynamically
|
||||
List<Object> deficiencies = (List<Object>) payload.get('DeficiencyList');
|
||||
if (deficiencies != null) {
|
||||
if (deficiencies != null && !deficiencies.isEmpty()) {
|
||||
xml += '<DeficiencyList>';
|
||||
for (Integer i = 0; i < deficiencies.size(); i++) {
|
||||
Map<String, Object> d = (Map<String, Object>) deficiencies[i];
|
||||
String p = 'Deficiency_' + (i + 1) + '_';
|
||||
xml += '<' + p + 'Number>' + escapeXml(String.valueOf(d.get('deficiencyNumber'))) + '</' + p + 'Number>';
|
||||
xml += '<' + p + 'Description>' + escapeXml(String.valueOf(d.get('description'))) + '</' + p + 'Description>';
|
||||
xml += '<' + p + 'Resolution>' + escapeXml(String.valueOf(d.get('resolution'))) + '</' + p + 'Resolution>';
|
||||
xml += '<Deficiency>';
|
||||
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>';
|
||||
xml += '<DeficiencyCount>' + deficiencies.size() + '</DeficiencyCount>';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<?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>Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>Name</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Appraiser_Field_Review_Date__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>CreatedDate</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>LastModifiedDate</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Property Address</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Property_Street__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Property_City__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Property_State_Province__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Property_Postal_Code__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Property_Country__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<relatedLists>
|
||||
<fields>NAME</fields>
|
||||
<fields>Deficiency_Number__c</fields>
|
||||
<fields>Description__c</fields>
|
||||
<fields>Resolution__c</fields>
|
||||
<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>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
</Layout>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?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>Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>Name</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>Appraiser_Case__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Deficiency_Number__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Sort_Order__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Details</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Description__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Resolution__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>true</showHighlightsPanel>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
</Layout>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?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>CLMuatDownload</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://apidownloaduatna11.springcm.com</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
<parameterName>ExternalCredential</parameterName>
|
||||
<parameterType>Authentication</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialType>SecuredEndpoint</namedCredentialType>
|
||||
</NamedCredential>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?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>CLMuatDownloadNamedCreds</label>
|
||||
<namedCredentialParameters>
|
||||
<parameterName>Url</parameterName>
|
||||
<parameterType>Url</parameterType>
|
||||
<parameterValue>https://apidownloaduatna11.springcm.com</parameterValue>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialParameters>
|
||||
<externalCredential>DocusignJWT</externalCredential>
|
||||
<parameterName>ExternalCredential</parameterName>
|
||||
<parameterType>Authentication</parameterType>
|
||||
</namedCredentialParameters>
|
||||
<namedCredentialType>SecuredEndpoint</namedCredentialType>
|
||||
</NamedCredential>
|
||||
|
|
@ -1,16 +1,38 @@
|
|||
<?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>
|
||||
<description>Main record for appraiser review letter generation in CLM.</description>
|
||||
<enableActivities>true</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>Private</externalSharingModel>
|
||||
<label>Appraiser Case</label>
|
||||
<nameField>
|
||||
<displayFormat>AC-{00000}</displayFormat>
|
||||
<displayFormat>AC-{000000}</displayFormat>
|
||||
<label>Appraiser Case Number</label>
|
||||
<type>AutoNumber</type>
|
||||
</nameField>
|
||||
<pluralLabel>Appraiser Cases</pluralLabel>
|
||||
<searchLayouts></searchLayouts>
|
||||
<sharingModel>ReadWrite</sharingModel>
|
||||
<visibility>Public</visibility>
|
||||
</CustomObject>
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Appraiser_Field_Review_Date__c</fullName>
|
||||
<description>Date the appraiser field review was completed.</description>
|
||||
<label>Appraiser Field Review Date</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Date</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Property_City__c</fullName>
|
||||
<label>Property City</label>
|
||||
<length>80</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Property_Country__c</fullName>
|
||||
<label>Property Country</label>
|
||||
<length>80</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Property_Postal_Code__c</fullName>
|
||||
<label>Property Postal Code</label>
|
||||
<length>20</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Property_State_Province__c</fullName>
|
||||
<label>Property State/Province</label>
|
||||
<length>80</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Property_Street__c</fullName>
|
||||
<label>Property Street</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Text</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +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>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Description__c</fullName>
|
||||
<label>Description</label>
|
||||
<length>32768</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>LongTextArea</type>
|
||||
<visibleLines>5</visibleLines>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Resolution__c</fullName>
|
||||
<label>Resolution</label>
|
||||
<length>32768</length>
|
||||
<required>false</required>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>LongTextArea</type>
|
||||
<visibleLines>5</visibleLines>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Sort_Order__c</fullName>
|
||||
<label>Sort Order</label>
|
||||
<precision>6</precision>
|
||||
<required>false</required>
|
||||
<scale>0</scale>
|
||||
<trackHistory>true</trackHistory>
|
||||
<type>Number</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<description>Access to Appraiser Case and Appraiser Deficiency objects for setup and early testing.</description>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Appraiser_Field_Review_Date__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Property_Street__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Property_City__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Property_State_Province__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Property_Postal_Code__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Case__c.Property_Country__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Deficiency__c.Appraiser_Case__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Deficiency__c.Deficiency_Number__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Deficiency__c.Description__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Deficiency__c.Resolution__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Appraiser_Deficiency__c.Sort_Order__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<hasActivationRequired>false</hasActivationRequired>
|
||||
<label>Appraiser Case Admin</label>
|
||||
<objectPermissions>
|
||||
<allowCreate>true</allowCreate>
|
||||
<allowDelete>true</allowDelete>
|
||||
<allowEdit>true</allowEdit>
|
||||
<allowRead>true</allowRead>
|
||||
<modifyAllRecords>true</modifyAllRecords>
|
||||
<object>Appraiser_Case__c</object>
|
||||
<viewAllRecords>true</viewAllRecords>
|
||||
</objectPermissions>
|
||||
<objectPermissions>
|
||||
<allowCreate>true</allowCreate>
|
||||
<allowDelete>true</allowDelete>
|
||||
<allowEdit>true</allowEdit>
|
||||
<allowRead>true</allowRead>
|
||||
<modifyAllRecords>true</modifyAllRecords>
|
||||
<object>Appraiser_Deficiency__c</object>
|
||||
<viewAllRecords>true</viewAllRecords>
|
||||
</objectPermissions>
|
||||
<tabSettings>
|
||||
<tab>Appraiser_Case__c</tab>
|
||||
<visibility>Visible</visibility>
|
||||
</tabSettings>
|
||||
<tabSettings>
|
||||
<tab>Appraiser_Deficiency__c</tab>
|
||||
<visibility>Visible</visibility>
|
||||
</tabSettings>
|
||||
</PermissionSet>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<QuickAction xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<description>Placeholder action to launch Appraiser review letter generation flow or component.</description>
|
||||
<height>600</height>
|
||||
<label>Generate Review Letter</label>
|
||||
<optionsCreateFeedItem>false</optionsCreateFeedItem>
|
||||
<standardLabel>New</standardLabel>
|
||||
<targetObject>Appraiser_Case__c</targetObject>
|
||||
<type>Create</type>
|
||||
<width>800</width>
|
||||
</QuickAction>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<customObject>true</customObject>
|
||||
<motif>Custom67: Gears</motif>
|
||||
</CustomTab>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<customObject>true</customObject>
|
||||
<motif>Custom18: Form</motif>
|
||||
</CustomTab>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
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 List<Appraiser_Deficiency__c>{
|
||||
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
|
||||
),
|
||||
new Appraiser_Deficiency__c(
|
||||
Name = 'Deficiency 2',
|
||||
Appraiser_Case__c = appraiserCase.Id,
|
||||
Deficiency_Number__c = '2',
|
||||
Description__c = 'Exterior condition comments are too brief',
|
||||
Resolution__c = 'Expand comments and attach photos',
|
||||
Sort_Order__c = 2
|
||||
)
|
||||
};
|
||||
|
||||
System.debug('Created Appraiser Case Id: ' + appraiserCase.Id);
|
||||
System.debug('Payload JSON: ' + AppraiserCaseDocGenService.buildPayloadJson(appraiserCase.Id));
|
||||
System.debug('Doc Gen JSON: ' + AppraiserCaseDocGenService.buildDocGenRequestJson(appraiserCase.Id, 'APPRAISER_REVIEW_LETTER'));
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
import json, difflib
|
||||
|
||||
postman_json_text = '''{
|
||||
"TemplateDocument": {
|
||||
"Href": "https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/a0cbc0e6-d87d-459e-8d63-66baa47878f3" },
|
||||
"DataXML": "<TemplateFieldData><AppraiserCaseNumber>AC-00001</AppraiserCaseNumber><AppraiserFieldReviewDate>02/04/2026</AppraiserFieldReviewDate><PropertyAddress>123 Main St, Denver, CO 80202</PropertyAddress><DeficiencyList><Deficiency><Number>1</Number><Description>Missing comparable sale adjustment detail.</Description><Resolution>Added adjustment rationale and supporting calculations.</Resolution></Deficiency><Deficiency><Number>2</Number><Description>Neighborhood trend explanation insufficient.</Description><Resolution>Expanded market trend narrative with MLS evidence.</Resolution></Deficiency><Deficiency><Number>3</Number><Description>Photo date stamps were not included.</Description><Resolution>Re-uploaded photos with date metadata and captions.</Resolution></Deficiency></DeficiencyList><DeficiencyCount>3</DeficiencyCount></TemplateFieldData>",
|
||||
"DestinationDocumentName": "Review_AC-00001.docx",
|
||||
"DestinationFolder": {
|
||||
"Href": "https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c"
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
# Parse the Postman JSON
|
||||
postman = json.loads(postman_json_text)
|
||||
|
||||
# Reconstruct the Apex payload (simulate AppraiserCasePayloadBuilder output)
|
||||
payload = {
|
||||
'AppraiserCaseNumber': 'AC-00001',
|
||||
'AppraiserFieldReviewDate': '02/04/2026',
|
||||
'PropertyAddress': '123 Main St, Denver, CO 80202',
|
||||
'DeficiencyList': [
|
||||
{'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.'}
|
||||
]
|
||||
}
|
||||
|
||||
# Function to mimic buildDataXml from Apex implementation
|
||||
def escape_xml(s):
|
||||
if s is None:
|
||||
return ''
|
||||
return s.replace('&','&').replace('<','<').replace('>','>').replace('"','"').replace("'","'")
|
||||
|
||||
def build_data_xml(payload):
|
||||
xml = '<TemplateFieldData>'
|
||||
# Emit flat fields first (Apex iterates payload.keySet() which is not ordered in Python dict, but we'll follow this order)
|
||||
# We'll follow the same ordering as in Postman: AppraiserCaseNumber, AppraiserFieldReviewDate, PropertyAddress
|
||||
for key in ['AppraiserCaseNumber','AppraiserFieldReviewDate','PropertyAddress']:
|
||||
v = payload.get(key)
|
||||
xml += f'<{key}>' + escape_xml(str(v) if v is not None else '') + f'</{key}>'
|
||||
|
||||
# Emit DeficiencyList nested structure
|
||||
deficiencies = payload.get('DeficiencyList')
|
||||
if deficiencies:
|
||||
xml += '<DeficiencyList>'
|
||||
for d in deficiencies:
|
||||
xml += '<Deficiency>'
|
||||
xml += '<Number>' + escape_xml(str(d.get('deficiencyNumber'))) + '</Number>'
|
||||
xml += '<Description>' + escape_xml(str(d.get('description'))) + '</Description>'
|
||||
xml += '<Resolution>' + escape_xml(str(d.get('resolution'))) + '</Resolution>'
|
||||
xml += '</Deficiency>'
|
||||
xml += '</DeficiencyList>'
|
||||
xml += '<DeficiencyCount>' + str(len(deficiencies)) + '</DeficiencyCount>'
|
||||
xml += '</TemplateFieldData>'
|
||||
return xml
|
||||
|
||||
apex_data_xml = build_data_xml(payload)
|
||||
|
||||
apex_request = {
|
||||
'TemplateDocument': {'Href': postman['TemplateDocument']['Href']},
|
||||
'DataXML': apex_data_xml,
|
||||
'DestinationDocumentName': postman['DestinationDocumentName'],
|
||||
'DestinationFolder': {'Href': postman['DestinationFolder']['Href']}
|
||||
}
|
||||
|
||||
# Compare postman vs apex
|
||||
print('Comparing top-level fields:')
|
||||
for key in ['TemplateDocument','DestinationDocumentName','DestinationFolder']:
|
||||
p = postman.get(key)
|
||||
a = apex_request.get(key)
|
||||
equal = (p == a)
|
||||
print(f' - {key}:', 'MATCH' if equal else 'DIFFER')
|
||||
|
||||
# Compare DataXML
|
||||
postman_xml = postman['DataXML']
|
||||
apex_xml = apex_request['DataXML']
|
||||
|
||||
if postman_xml == apex_xml:
|
||||
print('\nDataXML: EXACT MATCH')
|
||||
else:
|
||||
print('\nDataXML: DIFFER')
|
||||
# show diff
|
||||
pd = postman_xml.split('><')
|
||||
ad = apex_xml.split('><')
|
||||
diff = difflib.unified_diff(pd, ad, fromfile='postman_xml', tofile='apex_xml', lineterm='')
|
||||
print('\n'.join(diff))
|
||||
# also show small head/tail
|
||||
print('\n-- Postman head 200 chars --\n', postman_xml[:200])
|
||||
print('\n-- Apex head 200 chars --\n', apex_xml[:200])
|
||||
|
||||
# Additionally print whether other top-level objects match (TemplateDocument Href and Folder Href)
|
||||
print('\nTemplateDocument.Href match:', postman['TemplateDocument']['Href'] == apex_request['TemplateDocument']['Href'])
|
||||
print('DestinationFolder.Href match:', postman['DestinationFolder']['Href'] == apex_request['DestinationFolder']['Href'])
|
||||
|
||||
# Print apex_data_xml for inspection
|
||||
print('\n--- Apex DataXML ---\n')
|
||||
print(apex_data_xml)
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
import os, re, base64, zipfile, xml.etree.ElementTree as ET, sys
|
||||
log_path = '/home/paulh/.vscode-server/data/User/workspaceStorage/79b924110cb5ff6de49811d445e59969-1/GitHub.copilot-chat/chat-session-resources/90e6dae0-2184-412b-af0b-eac258be98c5/call_gY8uUyzuGvFiN46d4ZPVFtjz__vscode-1775271381281/content.txt'
|
||||
out_dir = 'artifacts/doc_inspect'
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
start=False
|
||||
chunks = []
|
||||
with open(log_path, 'r', errors='replace') as f:
|
||||
for line in f:
|
||||
if 'BASE64_BEGIN' in line:
|
||||
start = True
|
||||
continue
|
||||
if 'BASE64_END' in line:
|
||||
break
|
||||
if start:
|
||||
if 'BASE64_CHUNK:' in line:
|
||||
parts = line.split('BASE64_CHUNK:',1)[1].strip()
|
||||
chunks.append(parts)
|
||||
else:
|
||||
# fallback: if line looks like base64 (long and only base64 chars + =), take it
|
||||
s = line.strip()
|
||||
if len(s) > 100 and re.fullmatch(r'[A-Za-z0-9+/=\n\r]+', s):
|
||||
chunks.append(s)
|
||||
|
||||
if not chunks:
|
||||
print('ERROR: no base64 chunks found in log at', log_path)
|
||||
sys.exit(2)
|
||||
|
||||
b64 = ''.join(chunks)
|
||||
# sanitize (remove any DEBUG prefixes that snuck in)
|
||||
b64 = re.sub(r'\s+', '', b64)
|
||||
try:
|
||||
data = base64.b64decode(b64)
|
||||
except Exception as e:
|
||||
print('ERROR decoding base64:', e)
|
||||
sys.exit(3)
|
||||
|
||||
docx_path = os.path.join(out_dir, 'downloaded.docx')
|
||||
with open(docx_path, 'wb') as f:
|
||||
f.write(data)
|
||||
print('WROTE_DOCX:', docx_path, 'size=', os.path.getsize(docx_path))
|
||||
|
||||
# unzip
|
||||
unzip_dir = os.path.join(out_dir, 'unzipped')
|
||||
os.makedirs(unzip_dir, exist_ok=True)
|
||||
try:
|
||||
with zipfile.ZipFile(docx_path, 'r') as z:
|
||||
z.extractall(unzip_dir)
|
||||
except Exception as e:
|
||||
print('ERROR unzipping docx:', e)
|
||||
sys.exit(4)
|
||||
|
||||
doc_xml = os.path.join(unzip_dir, 'word', 'document.xml')
|
||||
if not os.path.exists(doc_xml):
|
||||
print('ERROR: word/document.xml not found in the docx')
|
||||
sys.exit(5)
|
||||
|
||||
# parse XML and extract tables
|
||||
ns = {'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
|
||||
ET.register_namespace('w', ns['w'])
|
||||
try:
|
||||
tree = ET.parse(doc_xml)
|
||||
root = tree.getroot()
|
||||
except Exception as e:
|
||||
print('ERROR parsing document.xml:', e)
|
||||
sys.exit(6)
|
||||
|
||||
tables = root.findall('.//w:tbl', ns)
|
||||
print('TABLE_COUNT:', len(tables))
|
||||
# For each table, collect row texts (limit output to first 5 tables and 20 rows each)
|
||||
found_def_texts = []
|
||||
for ti, tbl in enumerate(tables[:5], start=1):
|
||||
rows = tbl.findall('.//w:tr', ns)
|
||||
print('\n--- TABLE', ti, 'rows=', len(rows), '---')
|
||||
for ri, tr in enumerate(rows[:20], start=1):
|
||||
texts = [t.text for t in tr.findall('.//w:t', ns) if t.text]
|
||||
joined = ' | '.join(texts).strip()
|
||||
if joined:
|
||||
print('ROW %d:'%ri, repr(joined))
|
||||
# heuristic: look for keywords
|
||||
if any(k.lower() in joined.lower() for k in ('deficiency','description','defect','ac-','AC-','DeficiencyList')):
|
||||
found_def_texts.append(joined)
|
||||
else:
|
||||
print('ROW %d: <empty>'%ri)
|
||||
|
||||
# Also search whole document.xml for certain keywords
|
||||
full_xml = open(doc_xml,'r',encoding='utf-8',errors='replace').read()
|
||||
keywords = ['Deficiency','DeficiencyList','Description','<TableRow','AC-']
|
||||
hits = {k: (full_xml.count(k)) for k in keywords}
|
||||
print('\nKEYWORD_COUNTS:')
|
||||
for k,v in hits.items():
|
||||
print(k+':', v)
|
||||
|
||||
print('\nFOUND_DEFICIENCY_TEXTS_COUNT:', len(found_def_texts))
|
||||
for i, txt in enumerate(found_def_texts[:20], start=1):
|
||||
print('FOUND_%d:'%i, txt)
|
||||
|
||||
# Exit with success
|
||||
print('\nSUMMARY: docx_size=%d tables=%d deficiency_text_found=%s' % (os.path.getsize(docx_path), len(tables), bool(found_def_texts)))
|
||||
print('OUTPUT_DIR:', os.path.abspath(out_dir))
|
||||
|
|
@ -8,5 +8,5 @@
|
|||
"name": "salesforce-appraiser-review-letter",
|
||||
"namespace": "",
|
||||
"sfdcLoginUrl": "https://login.salesforce.com",
|
||||
"sourceApiVersion": "62.0"
|
||||
"sourceApiVersion": "63.0"
|
||||
}
|
||||
Loading…
Reference in New Issue