first commit Riabu

This commit is contained in:
2025-11-26 15:57:11 +05:30
parent dd002222ad
commit a5dea75cc3
51 changed files with 14671 additions and 0 deletions

12
.forceignore Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
npm run precommit

11
.prettierignore Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/.sf": true,
"**/.sfdx": true
},
"xml.preferences.showSchemaDocumentationType": "none"
}

View 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
View 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']
}
]);

View 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));
}
}
}
}
}

View 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>

View File

@@ -0,0 +1,3 @@
public class ContactTriggerHandler {
public static Boolean becamePrimary = true;
}

View 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>

View 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());
}
}

View 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>

View File

@@ -0,0 +1,7 @@
public with sharing class DEF {
public DEF() {
System.System.debug('HELLO WORLD');
}
}

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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;
}
}

View 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>

View 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';
}
}

View 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>

View 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());
}
}
}

View 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>

View 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;
}
}

View 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>

View 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;
}
}

View 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>

View 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';
}
}

View 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>

View 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;
}
}

View 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>

View File

@@ -0,0 +1,5 @@
public with sharing class Test2{
public Test2(){
}
}

View 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>

View File

@@ -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');
}
}

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
trigger AccountTrigger on Account (after insert, after update) {
AccountTriggerHandler.createCustomer(Trigger.New);
}

View File

@@ -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>

View 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
}
}
}
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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
View File

@@ -0,0 +1,6 @@
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
...jestConfig,
modulePathIgnorePatterns: ['<rootDir>/.localdevserver']
};

44
package.json Normal file
View 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
View 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);

View 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
View File

@@ -0,0 +1,12 @@
{
"packageDirectories": [
{
"path": "force-app",
"default": true
}
],
"name": "Raibu Integration",
"namespace": "",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "65.0"
}