first commit Riabu
This commit is contained in:
12
.forceignore
Normal file
12
.forceignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
|
||||||
|
# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
|
||||||
|
#
|
||||||
|
|
||||||
|
package.xml
|
||||||
|
|
||||||
|
# LWC configuration files
|
||||||
|
**/jsconfig.json
|
||||||
|
**/.eslintrc.json
|
||||||
|
|
||||||
|
# LWC Jest
|
||||||
|
**/__tests__/**
|
||||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
|
||||||
|
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
|
||||||
|
# For useful gitignore templates see: https://github.com/github/gitignore
|
||||||
|
|
||||||
|
# Salesforce cache
|
||||||
|
.sf/
|
||||||
|
.sfdx/
|
||||||
|
.localdevserver/
|
||||||
|
deploy-options.json
|
||||||
|
|
||||||
|
# LWC VSCode autocomplete
|
||||||
|
**/lwc/jsconfig.json
|
||||||
|
|
||||||
|
# LWC Jest coverage reports
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# ApexPMD cache
|
||||||
|
.pmdCache
|
||||||
|
|
||||||
|
# Eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# MacOS system files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Windows system files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
[Dd]esktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Local environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python Salesforce Functions
|
||||||
|
**/__pycache__/
|
||||||
|
**/.venv/
|
||||||
|
**/venv/
|
||||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npm run precommit
|
||||||
11
.prettierignore
Normal file
11
.prettierignore
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# List files or directories below to ignore them when running prettier
|
||||||
|
# More information: https://prettier.io/docs/en/ignore.html
|
||||||
|
#
|
||||||
|
|
||||||
|
**/staticresources/**
|
||||||
|
.localdevserver
|
||||||
|
.sfdx
|
||||||
|
.sf
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
coverage/
|
||||||
17
.prettierrc
Normal file
17
.prettierrc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"plugins": [
|
||||||
|
"prettier-plugin-apex",
|
||||||
|
"@prettier/plugin-xml"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "**/lwc/**/*.html",
|
||||||
|
"options": { "parser": "lwc" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.{cmp,page,component}",
|
||||||
|
"options": { "parser": "html" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
.vscode/extensions.json
vendored
Normal file
9
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"salesforce.salesforcedx-vscode",
|
||||||
|
"redhat.vscode-xml",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"financialforce.lana"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Apex Replay Debugger",
|
||||||
|
"type": "apex-replay",
|
||||||
|
"request": "launch",
|
||||||
|
"logFile": "${command:AskForLogFileName}",
|
||||||
|
"stopOnEntry": true,
|
||||||
|
"trace": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/.sf": true,
|
||||||
|
"**/.sfdx": true
|
||||||
|
},
|
||||||
|
"xml.preferences.showSchemaDocumentationType": "none"
|
||||||
|
}
|
||||||
13
config/project-scratch-def.json
Normal file
13
config/project-scratch-def.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"orgName": "Demo company",
|
||||||
|
"edition": "Developer",
|
||||||
|
"features": ["EnableSetPasswordInApi"],
|
||||||
|
"settings": {
|
||||||
|
"lightningExperienceSettings": {
|
||||||
|
"enableS1DesktopEnabled": true
|
||||||
|
},
|
||||||
|
"mobileSettings": {
|
||||||
|
"enableS1EncryptedStoragePref2": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
eslint.config.js
Normal file
55
eslint.config.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const { defineConfig } = require('eslint/config');
|
||||||
|
const eslintJs = require('@eslint/js');
|
||||||
|
const jestPlugin = require('eslint-plugin-jest');
|
||||||
|
const auraConfig = require('@salesforce/eslint-plugin-aura');
|
||||||
|
const lwcConfig = require('@salesforce/eslint-config-lwc/recommended');
|
||||||
|
const globals = require('globals');
|
||||||
|
|
||||||
|
module.exports = defineConfig([
|
||||||
|
// Aura configuration
|
||||||
|
{
|
||||||
|
files: ['**/aura/**/*.js'],
|
||||||
|
extends: [
|
||||||
|
...auraConfig.configs.recommended,
|
||||||
|
...auraConfig.configs.locker
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// LWC configuration
|
||||||
|
{
|
||||||
|
files: ['**/lwc/**/*.js'],
|
||||||
|
extends: [lwcConfig]
|
||||||
|
},
|
||||||
|
|
||||||
|
// LWC configuration with override for LWC test files
|
||||||
|
{
|
||||||
|
files: ['**/lwc/**/*.test.js'],
|
||||||
|
extends: [lwcConfig],
|
||||||
|
rules: {
|
||||||
|
'@lwc/lwc/no-unexpected-wire-adapter-usages': 'off'
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Jest mocks configuration
|
||||||
|
{
|
||||||
|
files: ['**/jest-mocks/**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.es2021,
|
||||||
|
...jestPlugin.environments.globals.globals
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
eslintJs
|
||||||
|
},
|
||||||
|
extends: ['eslintJs/recommended']
|
||||||
|
}
|
||||||
|
]);
|
||||||
20
force-app/main/default/classes/AccountTriggerHandler.cls
Normal file
20
force-app/main/default/classes/AccountTriggerHandler.cls
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
public class AccountTriggerHandler {
|
||||||
|
public static boolean firstCreation = True;
|
||||||
|
|
||||||
|
public static void createCustomer(List<Account> accList){
|
||||||
|
if(firstCreation){
|
||||||
|
|
||||||
|
|
||||||
|
for (Account acc : accList ) {
|
||||||
|
if (acc.Id != null) {
|
||||||
|
System.debug('Triggered Account Id: ' + acc.Id);
|
||||||
|
system.enqueueJob(new RiabuAccountToCustomer(acc.Id));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
3
force-app/main/default/classes/ContactTriggerHandler.cls
Normal file
3
force-app/main/default/classes/ContactTriggerHandler.cls
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
public class ContactTriggerHandler {
|
||||||
|
public static Boolean becamePrimary = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
37
force-app/main/default/classes/CustomerCreationService.cls
Normal file
37
force-app/main/default/classes/CustomerCreationService.cls
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
public with sharing class CustomerCreationService {
|
||||||
|
public static void createCustomer(Id accountId) {
|
||||||
|
System.Debug('Triggering Account' +accountId
|
||||||
|
);
|
||||||
|
Account acc = [
|
||||||
|
SELECT Id, Name, Registration_Number__c, CreatedDate, Days_to_Pay__c, Procurement_Portal__c
|
||||||
|
FROM Account WHERE Id = :accountId
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
RIABU_Integration__mdt cfg = RiabuConfigUtil.getConfig();
|
||||||
|
String token = RiabuAuthService.getValidAccessToken();
|
||||||
|
String baseUrl = cfg.Endpoint__c.replace('/oauth/token', ''); // adjust base
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
req.setEndpoint(cfg.Endpoint__c + '/api/v1/wizard/customer');
|
||||||
|
req.setMethod('POST');
|
||||||
|
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
req.setHeader('Authorization', 'Bearer ' + token);
|
||||||
|
|
||||||
|
String body =
|
||||||
|
'company_name=' + EncodingUtil.urlEncode(acc.Name,'UTF-8') +
|
||||||
|
'&company_uen=' + EncodingUtil.urlEncode(acc.Registration_Number__c,'UTF-8') +
|
||||||
|
'&customer_since=' + EncodingUtil.urlEncode(acc.CreatedDate.format('yyyy-MM-dd'),'UTF-8') +
|
||||||
|
'&agreed_credit_terms_day=' + EncodingUtil.urlEncode(String.valueOf(acc.Days_to_Pay__c),'UTF-8') +
|
||||||
|
'&procurement_portal=' + EncodingUtil.urlEncode(acc.Procurement_Portal__c,'UTF-8');
|
||||||
|
|
||||||
|
req.setBody(body);
|
||||||
|
System.Debug('Request Body' + body);
|
||||||
|
System.Debug('Endpoint' + req.getEndpoint());
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
System.debug('Customer Response: ' + res.getBody());
|
||||||
|
System.debug('Response Code: ' + res.getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
7
force-app/main/default/classes/DEF.cls
Normal file
7
force-app/main/default/classes/DEF.cls
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
public with sharing class DEF {
|
||||||
|
|
||||||
|
public DEF() {
|
||||||
|
|
||||||
|
System.System.debug('HELLO WORLD');
|
||||||
|
}
|
||||||
|
}
|
||||||
5
force-app/main/default/classes/DEF.cls-meta.xml
Normal file
5
force-app/main/default/classes/DEF.cls-meta.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
13542
force-app/main/default/classes/MetadataService.cls
Normal file
13542
force-app/main/default/classes/MetadataService.cls
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
15
force-app/main/default/classes/MetadataUtil.cls
Normal file
15
force-app/main/default/classes/MetadataUtil.cls
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
public class MetadataUtil {
|
||||||
|
|
||||||
|
public static MetadataService.MetadataPort createService() {
|
||||||
|
|
||||||
|
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
|
||||||
|
|
||||||
|
service.SessionHeader = new MetadataService.SessionHeader_element();
|
||||||
|
|
||||||
|
service.SessionHeader.sessionId = UserInfo.getSessionId();
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
force-app/main/default/classes/MetadataUtil.cls-meta.xml
Normal file
5
force-app/main/default/classes/MetadataUtil.cls-meta.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
94
force-app/main/default/classes/RiabuAccountSync.cls
Normal file
94
force-app/main/default/classes/RiabuAccountSync.cls
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
public class RiabuAccountSync {
|
||||||
|
|
||||||
|
public static String insertAccounts() {
|
||||||
|
|
||||||
|
RIABU_Integration__mdt config = [
|
||||||
|
SELECT Access_Token__c, Refresh_Token__c, Endpoint__c, RIABU_Fetch_Accounts_Endpoint__c
|
||||||
|
FROM RIABU_Integration__mdt
|
||||||
|
WHERE DeveloperName = 'Riabu_Record'
|
||||||
|
LIMIT 1
|
||||||
|
];
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
req.setEndpoint(config.RIABU_Fetch_Accounts_Endpoint__c);
|
||||||
|
req.setMethod('GET');
|
||||||
|
|
||||||
|
if (config.Access_Token__c == null) {
|
||||||
|
RiabuAuthAccessToken.getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
req.setHeader('Authorization', 'Bearer ' + config.Access_Token__c);
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
|
||||||
|
if (res.getStatusCode() != 200) {
|
||||||
|
return 'Invalid Field Callout';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> root = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
|
||||||
|
List<Object> dataList = (List<Object>) root.get('data');
|
||||||
|
|
||||||
|
if (dataList == null || dataList.isEmpty()) {
|
||||||
|
return 'No Data Returned';
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 1: collect incoming riabu ids
|
||||||
|
Set<String> incomingIds = new Set<String>();
|
||||||
|
for (Object o : dataList) {
|
||||||
|
Map<String, Object> item = (Map<String, Object>) o;
|
||||||
|
incomingIds.add(String.valueOf(item.get('id')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2: fetch existing SF accounts
|
||||||
|
Map<String, Account> existingMap = new Map<String, Account>();
|
||||||
|
|
||||||
|
if (!incomingIds.isEmpty()) {
|
||||||
|
for (Account acc : [
|
||||||
|
SELECT Id, RIABU_Customer_ID__c
|
||||||
|
FROM Account
|
||||||
|
WHERE RIABU_Customer_ID__c IN :incomingIds
|
||||||
|
]) {
|
||||||
|
existingMap.put(acc.RIABU_Customer_ID__c, acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 3: build list of new accounts
|
||||||
|
Set<String> processedIds = new Set<String>();
|
||||||
|
List<Account> toInsert = new List<Account>();
|
||||||
|
|
||||||
|
for (Object o : dataList) {
|
||||||
|
Map<String, Object> item = (Map<String, Object>) o;
|
||||||
|
String riabuId = String.valueOf(item.get('id'));
|
||||||
|
|
||||||
|
if (riabuId == null) continue;
|
||||||
|
|
||||||
|
// skip duplicates in API response
|
||||||
|
if (processedIds.contains(riabuId)) continue;
|
||||||
|
processedIds.add(riabuId);
|
||||||
|
|
||||||
|
// skip existing SF accounts
|
||||||
|
if (existingMap.containsKey(riabuId)) {
|
||||||
|
System.debug('[' + riabuId + '] ALREADY EXISTS IN SALESFORCE');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert new
|
||||||
|
Account a = new Account();
|
||||||
|
a.Name = (String) item.get('name');
|
||||||
|
a.RIABU_Customer_ID__c = riabuId;
|
||||||
|
a.Days_to_Pay__c = (Integer) item.get('agreed_credit_terms_day');
|
||||||
|
toInsert.add(a);
|
||||||
|
|
||||||
|
System.debug('[' + riabuId + '] Record Inserted ***');
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4: insert
|
||||||
|
if (!toInsert.isEmpty()) {
|
||||||
|
insert toInsert;
|
||||||
|
return toInsert.size() + ' new records inserted';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'No new records';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
88
force-app/main/default/classes/RiabuAccountToCustomer.cls
Normal file
88
force-app/main/default/classes/RiabuAccountToCustomer.cls
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
public class RiabuAccountToCustomer implements Queueable, Database.AllowsCallouts {
|
||||||
|
|
||||||
|
Id accId;
|
||||||
|
|
||||||
|
public RiabuAccountToCustomer(Id accountId) {
|
||||||
|
|
||||||
|
this.accId = accountId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(QueueableContext qc) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Account acc = [
|
||||||
|
|
||||||
|
SELECT Id, Name, Registration_Number__c, CreatedDate, Days_to_Pay__c
|
||||||
|
|
||||||
|
FROM Account
|
||||||
|
|
||||||
|
WHERE Id = :accId
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
System.debug('✅ Account Record to Send: ' + acc.Id);
|
||||||
|
|
||||||
|
RIABU_Integration__mdt config = [
|
||||||
|
|
||||||
|
SELECT Access_Token__c, Refresh_Token__c,Endpoint__c
|
||||||
|
|
||||||
|
FROM RIABU_Integration__mdt
|
||||||
|
|
||||||
|
WHERE DeveloperName = 'Riabu_Record'
|
||||||
|
|
||||||
|
LIMIT 1
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
String token = config.Access_Token__c;
|
||||||
|
|
||||||
|
if (String.isBlank(token)) {
|
||||||
|
|
||||||
|
System.debug('❌ No Access Token. Stopping.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
|
||||||
|
req.setEndpoint(config.Endpoint__c + '/api/v1/wizard/customer');
|
||||||
|
|
||||||
|
req.setMethod('POST');
|
||||||
|
|
||||||
|
req.setHeader('Authorization', 'Bearer ' + token);
|
||||||
|
|
||||||
|
req.setHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
Map<String, Object> payload = new Map<String, Object>{
|
||||||
|
|
||||||
|
'company_name' => acc.Name,
|
||||||
|
|
||||||
|
'company_uen' => acc.Registration_Number__c,
|
||||||
|
|
||||||
|
'customer_since' => acc.CreatedDate.format('yyyy-MM-dd'),
|
||||||
|
|
||||||
|
'agreed_credit_terms_day' => (acc.Days_to_Pay__c == null ? 0 : acc.Days_to_Pay__c)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
req.setBody(JSON.serialize(payload));
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
|
||||||
|
System.debug('🌍 Riabu Response Code: ' + res.getStatusCode());
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
System.debug('❌ Error: ' + e.getMessage());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
45
force-app/main/default/classes/RiabuAuthAccessToken.cls
Normal file
45
force-app/main/default/classes/RiabuAuthAccessToken.cls
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
public class RiabuAuthAccessToken {
|
||||||
|
|
||||||
|
public static String getAccessToken() {
|
||||||
|
|
||||||
|
RIABU_Integration__mdt config = RIABU_Integration__mdt.getInstance('Riabu_Record');
|
||||||
|
|
||||||
|
System.debug('🔄 Token expired or unavailable. Requesting new token...');
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
req.setEndpoint(config.Endpoint__c + '/api/v1/oauth/token');
|
||||||
|
req.setMethod('POST');
|
||||||
|
req.setHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
|
||||||
|
Map<String, String> payload = new Map<String, String>();
|
||||||
|
payload.put('username', config.Username__c);
|
||||||
|
payload.put('client_id',config.Client_Id__c);
|
||||||
|
payload.put('client_secret',config.Client_Secret__c);
|
||||||
|
payload.put('password', config.Password__c);
|
||||||
|
payload.put('grant_type', config.Grant_Type__c);
|
||||||
|
req.setBody(JSON.serialize(payload));
|
||||||
|
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
|
||||||
|
System.debug('📥 Token Response: ' + res.getBody());
|
||||||
|
|
||||||
|
if (res.getStatusCode() != 200) {
|
||||||
|
System.debug('❌ Token generation failed');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> tokenMap = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
|
||||||
|
|
||||||
|
String newAccessToken = (String) tokenMap.get('access_token');
|
||||||
|
String newRefreshToken = (String) tokenMap.get('refresh_token');
|
||||||
|
|
||||||
|
System.enqueueJob(new RiabuTokenUpdateJob('Riabu_Record', newAccessToken, newRefreshToken));
|
||||||
|
|
||||||
|
System.debug('✅ Token refreshed and CMDT update queued');
|
||||||
|
|
||||||
|
return newAccessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
95
force-app/main/default/classes/RiabuContactSync.cls
Normal file
95
force-app/main/default/classes/RiabuContactSync.cls
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
public class RiabuContactSync {
|
||||||
|
|
||||||
|
public static String syncContacts(String riabuCustomerId) {
|
||||||
|
|
||||||
|
System.debug(' START — RiabuContactSync for CustomerId: ' + riabuCustomerId);
|
||||||
|
|
||||||
|
// Load Integration Config
|
||||||
|
RIABU_Integration__mdt config = [
|
||||||
|
SELECT Access_Token__c, Refresh_Token__c, RIABU_Fetch_Contacts_Endpoint__c
|
||||||
|
FROM RIABU_Integration__mdt
|
||||||
|
WHERE DeveloperName = 'Riabu_Record'
|
||||||
|
LIMIT 1
|
||||||
|
];
|
||||||
|
|
||||||
|
if (config == null || config.RIABU_Fetch_Contacts_Endpoint__c == null) {
|
||||||
|
return 'Config or endpoint missing from Metadata';
|
||||||
|
}
|
||||||
|
|
||||||
|
String baseEndpoint = config.RIABU_Fetch_Contacts_Endpoint__c;
|
||||||
|
if (!baseEndpoint.endsWith('/')) {
|
||||||
|
baseEndpoint += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpoint = baseEndpoint + riabuCustomerId;
|
||||||
|
System.debug(' Corrected Request Endpoint: ' + endpoint);
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
req.setEndpoint(endpoint);
|
||||||
|
req.setMethod('GET');
|
||||||
|
|
||||||
|
if (String.isBlank(config.Access_Token__c)) {
|
||||||
|
System.debug('Access Token missing — Generating new token');
|
||||||
|
RiabuAuthAccessToken.getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
req.setHeader('Authorization', 'Bearer ' + config.Access_Token__c);
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
|
||||||
|
System.debug('Response Status: ' + res.getStatusCode());
|
||||||
|
System.debug('Body: ' + res.getBody());
|
||||||
|
|
||||||
|
if (res.getStatusCode() != 200) {
|
||||||
|
return 'Riabu Contact Fetch Error: ' + res.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON
|
||||||
|
Map<String, Object> root = (Map<String, Object>)
|
||||||
|
JSON.deserializeUntyped(res.getBody());
|
||||||
|
|
||||||
|
if (root == null || root.get('data') == null) {
|
||||||
|
return 'Invalid data from Riabu API';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> data = (Map<String, Object>) root.get('data');
|
||||||
|
Map<String, Object> model = (Map<String, Object>) data.get('model');
|
||||||
|
|
||||||
|
if (model == null) {
|
||||||
|
return 'No Contact Model Received';
|
||||||
|
}
|
||||||
|
|
||||||
|
System.debug('Riabu Contact: ' + model);
|
||||||
|
|
||||||
|
// Ensure Account exists
|
||||||
|
Account acc;
|
||||||
|
try {
|
||||||
|
acc = [
|
||||||
|
SELECT Id
|
||||||
|
FROM Account
|
||||||
|
WHERE RIABU_Customer_ID__c = :riabuCustomerId
|
||||||
|
LIMIT 1
|
||||||
|
];
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 'No matching Salesforce Account';
|
||||||
|
}
|
||||||
|
|
||||||
|
String riabuContactId = String.valueOf(model.get('id'));
|
||||||
|
|
||||||
|
Contact con = new Contact();
|
||||||
|
con.RIABU_Contact_ID__c = riabuContactId;
|
||||||
|
con.LastName = String.valueOf(model.get('accounts_payable_contact_name'));
|
||||||
|
con.Phone = String.valueOf(model.get('accounts_payable_phone_number'));
|
||||||
|
con.Email = String.valueOf(model.get('accounts_payable_email_address'));
|
||||||
|
con.Invoice_Email__c = String.valueOf(model.get('email_to_send_e_invoices_to'));
|
||||||
|
con.isPrimary__c = true;
|
||||||
|
con.AccountId = acc.Id;
|
||||||
|
|
||||||
|
// Use UPSERT with external Id (safe duplicate handling)
|
||||||
|
ContactTriggerHandler.becamePrimary = false;
|
||||||
|
upsert con RIABU_Contact_ID__c;
|
||||||
|
|
||||||
|
return 'Contact synced successfully! Riabu ID: ' + riabuContactId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
116
force-app/main/default/classes/RiabuOrderSync.cls
Normal file
116
force-app/main/default/classes/RiabuOrderSync.cls
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
public class RiabuOrderSync {
|
||||||
|
|
||||||
|
public static String syncOrders(String riabuCustomerId) {
|
||||||
|
|
||||||
|
System.debug('START — RiabuOrderSync for CustomerId: ' + riabuCustomerId);
|
||||||
|
|
||||||
|
// Load Integration Config
|
||||||
|
RIABU_Integration__mdt config = [
|
||||||
|
SELECT Access_Token__c, RIABU_Fetch_Orders_Endpoint__c
|
||||||
|
FROM RIABU_Integration__mdt
|
||||||
|
WHERE DeveloperName = 'Riabu_Record'
|
||||||
|
LIMIT 1
|
||||||
|
];
|
||||||
|
|
||||||
|
if (config == null || config.RIABU_Fetch_Orders_Endpoint__c == null) {
|
||||||
|
return 'Orders Endpoint missing in Metadata';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure correct format with trailing slash
|
||||||
|
String baseEndpoint = config.RIABU_Fetch_Orders_Endpoint__c;
|
||||||
|
if (!baseEndpoint.endsWith('/')) {
|
||||||
|
baseEndpoint += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpoint = baseEndpoint + riabuCustomerId + '?per-page=99999&page=1';
|
||||||
|
System.debug('Final API Endpoint: ' + endpoint);
|
||||||
|
|
||||||
|
HttpRequest req = new HttpRequest();
|
||||||
|
req.setEndpoint(endpoint);
|
||||||
|
req.setMethod('GET');
|
||||||
|
req.setHeader('Authorization', 'Bearer ' + config.Access_Token__c);
|
||||||
|
|
||||||
|
Http http = new Http();
|
||||||
|
HttpResponse res = http.send(req);
|
||||||
|
|
||||||
|
System.debug('Response Code: ' + res.getStatusCode());
|
||||||
|
System.debug('Response Body: ' + res.getBody());
|
||||||
|
|
||||||
|
if (res.getStatusCode() != 200) {
|
||||||
|
return 'Riabu Order Fetch Error: ' + res.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> root =
|
||||||
|
(Map<String, Object>) JSON.deserializeUntyped(res.getBody());
|
||||||
|
|
||||||
|
if (root == null || root.get('data') == null) {
|
||||||
|
return 'Invalid or Empty Riabu Response';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> data = (Map<String, Object>) root.get('data');
|
||||||
|
List<Object> items = (List<Object>) data.get('items');
|
||||||
|
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return 'No Orders Found in Riabu';
|
||||||
|
}
|
||||||
|
|
||||||
|
System.debug('Total Orders Returned: ' + items.size());
|
||||||
|
|
||||||
|
Account acc;
|
||||||
|
try {
|
||||||
|
acc = [
|
||||||
|
SELECT Id FROM Account
|
||||||
|
WHERE RIABU_Customer_ID__c = :riabuCustomerId
|
||||||
|
LIMIT 1
|
||||||
|
];
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 'No Matching Salesforce Account Found';
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Opportunity> oppsToUpsert = new List<Opportunity>();
|
||||||
|
|
||||||
|
for (Object o : items) {
|
||||||
|
Map<String, Object> order = (Map<String, Object>) o;
|
||||||
|
|
||||||
|
String riabuOrderId = String.valueOf(order.get('id'));
|
||||||
|
|
||||||
|
Opportunity opp = new Opportunity();
|
||||||
|
opp.Riabu_Order_Id__c = riabuOrderId;
|
||||||
|
opp.AccountId = acc.Id;
|
||||||
|
|
||||||
|
// Name
|
||||||
|
opp.Name = String.isNotBlank((String)order.get('opportunity'))
|
||||||
|
? (String) order.get('opportunity')
|
||||||
|
: 'Order ' + riabuOrderId;
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
if (order.get('total_invoice_amount') != null) {
|
||||||
|
opp.Amount = Decimal.valueOf(String.valueOf(order.get('total_invoice_amount')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close Date
|
||||||
|
try {
|
||||||
|
if (order.get('invoice_due_date') != null) {
|
||||||
|
opp.CloseDate = Date.valueOf(
|
||||||
|
String.valueOf(order.get('invoice_due_date'))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
opp.CloseDate = Date.today().addDays(7);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
opp.CloseDate = Date.today().addDays(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
opp.StageName = 'Prospecting';
|
||||||
|
|
||||||
|
oppsToUpsert.add(opp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oppsToUpsert.isEmpty()) {
|
||||||
|
upsert oppsToUpsert Riabu_Order_Id__c;
|
||||||
|
return oppsToUpsert.size() + ' Orders Synced (Upserted)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'No New Orders to Sync';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
62
force-app/main/default/classes/RiabuTokenUpdateJob.cls
Normal file
62
force-app/main/default/classes/RiabuTokenUpdateJob.cls
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
public class RiabuTokenUpdateJob implements Queueable, Database.AllowsCallouts {
|
||||||
|
|
||||||
|
private String recordName;
|
||||||
|
private String newAccessToken;
|
||||||
|
private String newRefreshToken;
|
||||||
|
//private Datetime expiryTime;
|
||||||
|
|
||||||
|
public RiabuTokenUpdateJob(String recordName, String accessToken, String refreshToken) {
|
||||||
|
this.recordName = recordName;
|
||||||
|
this.newAccessToken = accessToken;
|
||||||
|
this.newRefreshToken = refreshToken;
|
||||||
|
//this.expiryTime = expiryTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(QueueableContext qc) {
|
||||||
|
MetadataService.MetadataPort service = MetadataUtil.createService();
|
||||||
|
MetadataService.IReadResult readResult =
|
||||||
|
service.readMetadata('CustomMetadata', new String[] { 'RIABU_Integration__mdt.' + recordName });
|
||||||
|
MetadataService.CustomMetadata cmdt =
|
||||||
|
(MetadataService.CustomMetadata) readResult.getRecords()[0];
|
||||||
|
// Keep existing field values
|
||||||
|
List<MetadataService.CustomMetadataValue> updatedValues = cmdt.values;
|
||||||
|
// Update Access Token
|
||||||
|
upsertValue(updatedValues, 'Access_Token__c', newAccessToken);
|
||||||
|
// Update Refresh Token only if returned
|
||||||
|
if(String.isNotBlank(newRefreshToken)){
|
||||||
|
upsertValue(updatedValues, 'Refresh_Token__c', newRefreshToken);
|
||||||
|
}
|
||||||
|
cmdt.values = updatedValues;
|
||||||
|
MetadataService.SaveResult[] results =
|
||||||
|
service.updateMetadata(new MetadataService.Metadata[] { cmdt });
|
||||||
|
if (results[0].success) {
|
||||||
|
System.debug('✅ CMDT Updated Successfully');
|
||||||
|
} else {
|
||||||
|
System.debug('❌ CMDT Update Failed: ' + results[0].errors[0].message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void upsertValue(List<MetadataService.CustomMetadataValue> listVals, String fieldName, String fieldValue) {
|
||||||
|
Boolean found = false;
|
||||||
|
for(MetadataService.CustomMetadataValue v : listVals) {
|
||||||
|
if(v.field == fieldName){
|
||||||
|
v.value = fieldValue;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found){
|
||||||
|
MetadataService.CustomMetadataValue newVal = new MetadataService.CustomMetadataValue();
|
||||||
|
newVal.field = fieldName;
|
||||||
|
newVal.value = fieldValue;
|
||||||
|
listVals.add(newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MetadataService.CustomMetadataValue createValue(String fieldName, String val) {
|
||||||
|
MetadataService.CustomMetadataValue v = new MetadataService.CustomMetadataValue();
|
||||||
|
v.field = fieldName;
|
||||||
|
v.value = val;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
5
force-app/main/default/classes/Test2.cls
Normal file
5
force-app/main/default/classes/Test2.cls
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
public with sharing class Test2{
|
||||||
|
public Test2(){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
5
force-app/main/default/classes/Test2.cls-meta.xml
Normal file
5
force-app/main/default/classes/Test2.cls-meta.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
@isTest
|
||||||
|
public class TestLinkCOACustomerToLMALicense {
|
||||||
|
@testSetup
|
||||||
|
static void setup() {
|
||||||
|
// Create test data for licenses
|
||||||
|
List<sfLma__License__c> licenses = new List<sfLma__License__c>();
|
||||||
|
for (Integer i = 0; i < 5; i++) {
|
||||||
|
sfLma__License__c license = new sfLma__License__c();
|
||||||
|
license.sfLma__Subscriber_Org_ID__c = '00D' + i + '00000000001';
|
||||||
|
license.sfLma__Seats__c = 4;
|
||||||
|
licenses.add(license);
|
||||||
|
}
|
||||||
|
insert licenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
static void testTriggerAfterInsert() {
|
||||||
|
// Create test customer records
|
||||||
|
List<CHANNEL_ORDERS__Customer__c> customers = new List<CHANNEL_ORDERS__Customer__c>();
|
||||||
|
for (Integer i = 0; i < 5; i++) {
|
||||||
|
CHANNEL_ORDERS__Customer__c customer = new CHANNEL_ORDERS__Customer__c();
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Company_Name__c = 'Test Customer'+ i;
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Country__c = 'US';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_State__c = 'CA';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_City__c = 'Marseille';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Street__c = 'MyStreet';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Zip_Postal_Code__c = '13001';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Org_ID__c = '00D' + i + '00000000001';
|
||||||
|
customers.add(customer);
|
||||||
|
}
|
||||||
|
insert customers;
|
||||||
|
|
||||||
|
// Query licenses to verify they are updated
|
||||||
|
List<sfLma__License__c> updatedLicenses = [SELECT COA_Customer__c
|
||||||
|
FROM sfLma__License__c
|
||||||
|
WHERE COA_Customer__c != null];
|
||||||
|
|
||||||
|
// Assert that the licenses have been correctly updated with customer references
|
||||||
|
System.assertEquals(5, updatedLicenses.size(), 'All licenses should be updated');
|
||||||
|
for (sfLma__License__c license : updatedLicenses) {
|
||||||
|
System.assertNotEquals(null, license.COA_Customer__c, 'License should be linked to a customer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
static void testTriggerAfterUpdate() {
|
||||||
|
// Create a test customer record and insert it
|
||||||
|
CHANNEL_ORDERS__Customer__c customer = new CHANNEL_ORDERS__Customer__c();
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Company_Name__c = 'Test Customer A';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Country__c = 'US';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_State__c = 'CA';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_City__c = 'Marseille';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Street__c = 'MyStreet';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Zip_Postal_Code__c = '13001';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Org_ID__c = '00DA00000000001';
|
||||||
|
insert customer;
|
||||||
|
|
||||||
|
// Update the customer to trigger the update scenario
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Org_ID__c = '00D100000000001';
|
||||||
|
update customer;
|
||||||
|
|
||||||
|
// Query licenses to verify they are updated
|
||||||
|
List<sfLma__License__c> updatedLicenses = [SELECT Id, COA_Customer__c, sfLma__Subscriber_Org_ID__c
|
||||||
|
FROM sfLma__License__c
|
||||||
|
WHERE sfLma__Subscriber_Org_ID__c = :customer.CHANNEL_ORDERS__Customer_Org_ID__c];
|
||||||
|
|
||||||
|
// Assert that the license has been correctly updated with customer references
|
||||||
|
System.assertEquals(1, updatedLicenses.size(), 'One license should be updated');
|
||||||
|
for (sfLma__License__c license : updatedLicenses) {
|
||||||
|
System.assertEquals(customer.Id, license.COA_Customer__c, 'License should be linked to the updated customer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
static void testTriggerNoMatchingLicense() {
|
||||||
|
// Create a customer with an org ID that does not match any licenses
|
||||||
|
CHANNEL_ORDERS__Customer__c customer = new CHANNEL_ORDERS__Customer__c();
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Company_Name__c = 'Test Customer B';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Country__c = 'US';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_State__c = 'CA';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_City__c = 'Marseille';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Street__c = 'MyStreet';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Zip_Postal_Code__c = '13001';
|
||||||
|
customer.CHANNEL_ORDERS__Customer_Org_ID__c = '00D000000000999';
|
||||||
|
insert customer;
|
||||||
|
|
||||||
|
// Ensure no licenses are updated
|
||||||
|
List<sfLma__License__c> licenses = [SELECT Id, COA_Customer__c FROM sfLma__License__c WHERE COA_Customer__c != null];
|
||||||
|
System.assertEquals(0, licenses.size(), 'No licenses should be updated when there is no matching org ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>61.0</apiVersion>
|
||||||
|
<packageVersions>
|
||||||
|
<majorNumber>3</majorNumber>
|
||||||
|
<minorNumber>71</minorNumber>
|
||||||
|
<namespace>CHANNEL_ORDERS</namespace>
|
||||||
|
</packageVersions>
|
||||||
|
<packageVersions>
|
||||||
|
<majorNumber>1</majorNumber>
|
||||||
|
<minorNumber>21</minorNumber>
|
||||||
|
<namespace>sfLma</namespace>
|
||||||
|
</packageVersions>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
5
force-app/main/default/triggers/AccountTrigger.trigger
Normal file
5
force-app/main/default/triggers/AccountTrigger.trigger
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
trigger AccountTrigger on Account (after insert, after update) {
|
||||||
|
AccountTriggerHandler.createCustomer(Trigger.New);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexTrigger>
|
||||||
12
force-app/main/default/triggers/ContactTrigger.trigger
Normal file
12
force-app/main/default/triggers/ContactTrigger.trigger
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
trigger ContactTrigger on Contact (after insert, after update) {
|
||||||
|
|
||||||
|
if (Trigger.isAfter) {
|
||||||
|
if (Trigger.isInsert || Trigger.isUpdate) {
|
||||||
|
// Only run trigger logic if allowed
|
||||||
|
if (ContactTriggerHandler.becamePrimary) {
|
||||||
|
System.debug('Trigger executed - becamePrimary is TRUE');
|
||||||
|
// your logic here if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>65.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexTrigger>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
trigger OpportunityAfterTrigger on Opportunity(after update, after insert){
|
||||||
|
map<Id, List<Decimal>> accIdOppAmtMap = new map<Id, List<Decimal>>();
|
||||||
|
List<Account> accListToUpdate = new List<Account>();
|
||||||
|
for(Opportunity opp : Trigger.new){
|
||||||
|
accIdOppAmtMap.put(opp.accountId, new List<Decimal>());
|
||||||
|
|
||||||
|
}
|
||||||
|
if(accIdOppAmtMap.size() > 0){
|
||||||
|
|
||||||
|
List<Opportunity> oppList = [Select AccountId, Amount From Opportunity Where AccountId IN : accIdOppAmtMap.keySet() Order By Amount DESC] ;
|
||||||
|
|
||||||
|
for(Opportunity opp : oppList){
|
||||||
|
if(accIdOppAmtMap.ContainsKey(opp.AccountId)){
|
||||||
|
accIdOppAmtMap.get(opp.AccountId).add(opp.Amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Id accId : accIdOppAmtMap.KeySet()){
|
||||||
|
Account acc = new Account(Id = accId);
|
||||||
|
if(accIdOppAmtMap.get(accId).size() > 1){
|
||||||
|
acc.AnnualRevenue = accIdOppAmtMap.get(accId).get(1);
|
||||||
|
} else{
|
||||||
|
acc.AnnualRevenue = accIdOppAmtMap.get(accId).get(0);
|
||||||
|
}
|
||||||
|
accListToUpdate.add(acc);
|
||||||
|
}
|
||||||
|
update accListToUpdate;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>63.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexTrigger>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
trigger linkCOACustomerToLMALicense on CHANNEL_ORDERS__Customer__c (after insert, after update) {
|
||||||
|
// map to capture license id + customer id
|
||||||
|
map<string,id> allCustomersWithOrgID = new map<string,id>();
|
||||||
|
// retrieve all customer org id in the trigger
|
||||||
|
string customerOrgId = '';
|
||||||
|
for (CHANNEL_ORDERS__Customer__c c : trigger.new)
|
||||||
|
{
|
||||||
|
customerOrgId=c.CHANNEL_ORDERS__Customer_Org_ID__c;
|
||||||
|
allCustomersWithOrgID.put(customerOrgId.left(15), c.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
list<sfLma__License__c> licenseToUpdate = new list<sfLma__License__c>();
|
||||||
|
// retrieve all licenses
|
||||||
|
list<sfLma__License__c> allLicensesWithCustomer = [select id, sfLma__Account__c, COA_Customer__c, sfLma__Subscriber_Org_ID__c
|
||||||
|
from sfLma__License__c
|
||||||
|
where sfLma__Subscriber_Org_ID__c in :allCustomersWithOrgID.keyset()];
|
||||||
|
|
||||||
|
for (sfLma__License__c mylicense : allLicensesWithCustomer)
|
||||||
|
{
|
||||||
|
// assigne the right customer to the right license via orgid
|
||||||
|
mylicense.COA_Customer__c = allCustomersWithOrgID.get(mylicense.sfLma__Subscriber_Org_ID__c);
|
||||||
|
licenseToUpdate.add(mylicense);
|
||||||
|
}
|
||||||
|
|
||||||
|
update licenseToUpdate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>61.0</apiVersion>
|
||||||
|
<packageVersions>
|
||||||
|
<majorNumber>3</majorNumber>
|
||||||
|
<minorNumber>71</minorNumber>
|
||||||
|
<namespace>CHANNEL_ORDERS</namespace>
|
||||||
|
</packageVersions>
|
||||||
|
<packageVersions>
|
||||||
|
<majorNumber>1</majorNumber>
|
||||||
|
<minorNumber>21</minorNumber>
|
||||||
|
<namespace>sfLma</namespace>
|
||||||
|
</packageVersions>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexTrigger>
|
||||||
6
jest.config.js
Normal file
6
jest.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...jestConfig,
|
||||||
|
modulePathIgnorePatterns: ['<rootDir>/.localdevserver']
|
||||||
|
};
|
||||||
44
package.json
Normal file
44
package.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "salesforce-app",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Salesforce App",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint **/{aura,lwc}/**/*.js",
|
||||||
|
"test": "npm run test:unit",
|
||||||
|
"test:unit": "sfdx-lwc-jest",
|
||||||
|
"test:unit:watch": "sfdx-lwc-jest --watch",
|
||||||
|
"test:unit:debug": "sfdx-lwc-jest --debug",
|
||||||
|
"test:unit:coverage": "sfdx-lwc-jest --coverage",
|
||||||
|
"prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
|
||||||
|
"prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
|
||||||
|
"prepare": "husky || true",
|
||||||
|
"precommit": "lint-staged"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@lwc/eslint-plugin-lwc": "^3.1.0",
|
||||||
|
"@prettier/plugin-xml": "^3.4.1",
|
||||||
|
"@salesforce/eslint-config-lwc": "^4.0.0",
|
||||||
|
"@salesforce/eslint-plugin-aura": "^3.0.0",
|
||||||
|
"@salesforce/eslint-plugin-lightning": "^2.0.0",
|
||||||
|
"@salesforce/sfdx-lwc-jest": "^7.0.2",
|
||||||
|
"eslint": "^9.29.0",
|
||||||
|
"eslint-plugin-import": "^2.31.0",
|
||||||
|
"eslint-plugin-jest": "^28.14.0",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^16.1.2",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-apex": "^2.2.6"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"**/{aura,lwc}/**/*.js": [
|
||||||
|
"eslint"
|
||||||
|
],
|
||||||
|
"**/lwc/**": [
|
||||||
|
"sfdx-lwc-jest -- --bail --findRelatedTests --passWithNoTests"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
10
scripts/apex/hello.apex
Normal file
10
scripts/apex/hello.apex
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Use .apex files to store anonymous Apex.
|
||||||
|
// You can execute anonymous Apex in VS Code by selecting the
|
||||||
|
// apex text and running the command:
|
||||||
|
// SFDX: Execute Anonymous Apex with Currently Selected Text
|
||||||
|
// You can also execute the entire file by running the command:
|
||||||
|
// SFDX: Execute Anonymous Apex with Editor Contents
|
||||||
|
|
||||||
|
string tempvar = 'Enter_your_name_here';
|
||||||
|
System.debug('Hello World!');
|
||||||
|
System.debug('My name is ' + tempvar);
|
||||||
6
scripts/soql/account.soql
Normal file
6
scripts/soql/account.soql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Use .soql files to store SOQL queries.
|
||||||
|
// You can execute queries in VS Code by selecting the
|
||||||
|
// query text and running the command:
|
||||||
|
// SFDX: Execute SOQL Query with Currently Selected Text
|
||||||
|
|
||||||
|
SELECT Id, Name FROM Account
|
||||||
12
sfdx-project.json
Normal file
12
sfdx-project.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"packageDirectories": [
|
||||||
|
{
|
||||||
|
"path": "force-app",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Raibu Integration",
|
||||||
|
"namespace": "",
|
||||||
|
"sfdcLoginUrl": "https://login.salesforce.com",
|
||||||
|
"sourceApiVersion": "65.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user