Spectra
- https://otube.oracle.com/playlist/dedicated/102024782/1_419pe2jh/1_ld5tkapq
- API Gateway
- Entry point of all microservices and provides
- Rate limiting
- Authentication and so
- Helidon
- https://www.youtube.com/watch?v=diUvR6gqHVY
- https://www.youtube.com/watch?v=5cIYnC2fCAs
- https://www.youtube.com/watch?v=BG2IB-1T1dk
- OCI Console
- User Interface to access and manage OCI
- OCI Native services
- OKE
- Streaming Service(equivalent to Kafka)
- To ingest continuous high volume of data
- used in applns where we want event driven implementation
- Object Storage
- High performance storage platform service(replacement for UCM to store files and unstructured data)
- Data Flow
- To run apache spark applications
- This is next gen soln for running import jobs that we run in fa
- Data integration Service
- Helps data engineers and ETL developers.
- Also can be used for Orchestration services
- Virtual Cloud Networks
- Rondy Key Note
- Vision of finance dept
- Touchless processing
- Continuous insights
- Proactive collaboration & actions
- End-end processing
- Entire electronic apis from procurement to automated pay processing with min/no touch
- Centralize and Standardize approaches
- Core common Architecture
- Object modeling approach
- Operational Excellence
- use ci/cd for zero down time
- Overview
- Spectra Platform
- Redwood platform
- Spectra Platform
- enable for lifecycle pieces in building micro services
- BOSS
- Server less platform for building APIs
- Spectra Batch/Data/Messaging
https://otube.oracle.com/playlist/dedicated/102024782/1_419pe2jh/1_5ektiwub
- Helidon
- Collection of Java libraries for writing microservices
- Its available in 2 frameworks
- Helidon SE - Standard edition - microsframework that supports the reactive programming
- Helidon MP - is an Eclipse micro profile runtime that allows the Jakarta EE community to run mircro services in portable way
- FA apis are built using BOSS
- Other popular micro service frameworks - Sprint boot, eclipse vert.x
- Source Control - GIT
- Repository per microservice
- Build and deploy Helidon quick start application locally
- Automated build and deployment of same quick start application in Spectra world
- Use quick start appln developed in Day1
- Add config files required for spectra service deployment
- Source control Helidon quick start into alm git repo
- Register project into nextgen fabs url(nextgen FA build system)
- Modify .java file to add new method with service end point
- git commit, push
- review if new build is triggered
- To build an app with BOSS as backend service and VBCS based UI
- BOSS workspace setup
- BOSS cli
- BOSS Helidon server -> Docker container
- IDE setup
- OCI Services
- Object Storage, Streaming, Dataflow, logging, Orchestration, ADW
- Spectra Common Service Tenancies
- BOSS, Applcore, TM. SPS, etc
- Core ERP Spectra Services(OKE Cluster)
- 1 Spectra Service instance is wired with 1 Fusion Classic Instance. One Fusion Classic instance may be wired with multiple spectra service instances like core erp, cx, hed
Spectra Platform
- Control Plane equivalent for SaaS Spectra Applications.
Provisioning Flow
- CPQ -> TAS -> FASSM -> FALCM -> Fusion Classic POD...
Onboarding of Spectra
- Majority of Fusion REST apis will be implemented using BOSS. Spectra BOSS is a serverless platform focusing on business object REST api development.
- BOSS
- Business Object Spectra Service which runs on Spectra Platform
- Serverless, scalable service.
- Why BOSS
- Common interface regardless of data source(ATP, Elastic, RODS)
- Declarative query executions
- N+1 query problem eliminated
- Allows single source of Update
- Boss views are readonly
- API first model
- Easily extensible by customers
- Elastic Scaling
- out of box diagnosting, tracing, observability & monitoring
- BOSS OVerview
- API developer uploads boss meta data to BOSS server platform. Boss FWK interprets meta data and generates the sql at runtime.
- BOSS Architecture
- BOSS runtime encapsulates the common functionality that every FA product requires to implement their Business Object as a service.
- Meta data
- Service Protocols
- Programming Language
- Data Source
- Customizations
- Module
- Encompasses underlying business objects
- Defines transaction boundary
- analogous to Spectra subservice
- Triggers
- Custom life cycle methods in ADF BC
- Read trigger
- BeforeRead, InitiatilizeParameters, AfterRead
- Writer Trigger
- PrepareData, Cascade, Rollup
- Validation
- Validate
- Enum
- Analogous to Static View Objects used as a LOV in ADF BC
- Business Object
- Fields
- Business logic/Triggers
- View
- Default
- Named
- Security
- relationships
- Datastore mapping
- model/modules
- expenses
- modules.json5
- api
- businessObjects
- views
- scripts
- secutiry.json
- resource/translations
- enums
- Identify logical modules/sub-services
- Identify dependencies(functiona/non-functional)
- Define shapes
- Identify Business Objects
- Define relationships
- Identify appropriate triggers
- erpselfservice
- src/main/resources -> root for Boss Objects
- self/model/modules/expenses/
- api
- businessobjects/<bo name>/bo.json5, views, scripts, security
- enums
- resources
- module.json5
- bo.json5
- dataStoreMapping -> table, where condition
- fields
- identifiedBy -> primary key for the table
- views.json5
- fields
- accessors
- parameters
- collection and filter
- security.json5
- privilege
- grants
- Demo
- boss metadata package //creates zip file in resources directory
- Translation
- self/model/modules/<module name>/resources/translations/<lang>/...MessageBundle-i18n.json
- Testing
- Security
- Spectra Authorization Service(SAS)
- Identity store(similar to LDAP) -> IDCS
- Policy Store(similar to Jazn) -> Boss Security policies
- Authorization engine/Policy decision point(similar to ADFSecurityContext)
- Engine which returns list of conditions applicable for a given privilege on a business object
- Policy endorsement point
- Policy Adminstration point
- Common Components
- ERP Redwood LOV
- Currency Components
- Deployment
- Integrated pipeline
- Build Configuration
- Artifactory
- Deployment pipeline
- Architecture
- Security
https://confluence.oraclecorp.com/confluence/display/fintech/Produce+Business+Object
- Initialize workspace
- git clone ...
https:/.../erp-boss-example.git set proxyModify userconfig.env to update DB detailsset env variables for BOSS_MODEL_HOME & Boss toolsUpdate the super user role mapping for 'boss' service in /scratch/$USER/rwddev/rwd_dev/bin/docker-compose.yaml- Restart the docker containers
- Create Module
- boss module create -m oraErpExpenses ora_erp_expenses
- Create BusinessObject & default view
- boss bo create -bo Expense -t EXM_EXPENSES -m oraErpExpenses ora_erp_expenses
- Modify json file to add person_id condition
- Modify api/v1.json5 to update endpoint definition
- Add additional fields to Expense/views/default.json5
- Building and deploying Metadata
- Create a file called app-package.json5 and place it in the sources directory of the package.
- Create a package deployment file $BOSS_MODEL_HOME/config/application.json
- boss metadata package -a ora_erp_expenses
- Deploy the package to your local APM service.
- curl -vLk --noproxy "*" -u jack:password -X POST 'http://localhost:8000/api/rwdinfra/apm/v1/applications?force=Enabled' -d @$BOSS_MODEL_HOME/config/application.json
- Test the BOSS endpoints
- //Open api
- curl --location --request GET 'http://<host>:<port>/api/boss/data/objects/oraErpExpenses/v1:<deployment_id>/$openapi/expenses' --header 'Authorization: Basic c3VwZXJfdXNlcjp3ZWxjb21lMQ=='
- //Get Expenses
- curl --location --request GET 'http://<host>:<port>/api/boss/data/objects/oraErpExpenses/v1:<deployment_id>/expenses' --header 'Authorization: Basic c3VwZXJfdXNlcjp3ZWxjb21lMQ=='
Define Sort Order On Expense BO
- Update the fields to add property as sortable=true for reimbursableAmount & timeCreated in Expense/bo.json5
- Modify the default view to add the default order by as reimbursableAmount descending and creationDate desc
Update the Deployment
- Update the package configuration file app-package.json5 to increment the version
- Update the package deployment file application.json to increment the version of module
- boss metadata package -a ora_erp_expenses
- Deploy the package
- Test the endpoints
Monitor & Troubleshoot Your Application
- Find the trace id from the previous request on postman. Locate the opc-request-id header.
- http://<host>:16686/search
Introduce Expression Based Attributes & Modify Field Properties
- In Expense/bo.json5 file , add transient attributes with the sql expression as follows; Update sortable to true
- expenseCategoryCodeOrder : {
type : "string",
accessModifier : "public",
dataStoreMapping : {
rdbms : {
column : "CASE EXPENSE_TYPE_CATEGORY_CODE WHEN 'ACCOMMODATIONS' THEN 1 WHEN 'AIRFARE' THEN 2 WHEN 'CAR_RENTAL' THEN 3 WHEN 'MEALS' THEN 4 WHEN 'ENTERTAINMENT' THEN 5 WHEN 'MISC' THEN 6 ELSE 7 END", sortable : true
}
},
readable : "public",
nullable : true,
creatable : "module",
updatable : "never",
fieldSecurityEnabled : false
}, - Update order by clause to add the new transient attribute to default view.json5
- Update the deployment & build the project
- Deploy the package to your local APM service, and record the new deployment ID.
- Test the BOSS endpoints
Define Relationships & Accessors
- Create reference BO
- boss bo create -bo ExpenseType -t EXM_EXPENSE_TYPES -m oraErpExpenses ora_erp_expenses
- Add the fieldList to the default view as follows ExpenseType/views/default.json5
- Update the deployment & build the project
- Deploy and test
- Create a Many to one relationship between expense and expense types
- boss bo addrelation -m oraErpExpenses -bo Expense -tbo ExpenseType -fm expenseTypeId:expenseTypeId -c ManyToOne -acc expenseType -j leftOuterJoin ora_erp_expenses
- Add searchable=true for the attribute name in ExpenseType BO
- Update the expenseType accessor in the Expense default view as follows
accessors: {expenseType: {fields: ["expenseTypeId","name"]},},- Update the deployment & build the project
- Deploy the package to your local APM service, and record the new deployment ID.
- Test the BOSS endpoints
- Filter Based on Accessor Attribute as filter=expenseType.name='Car Rental'
Init Transient Variables In AfterRead Trigger
- Add a transient variable as expenseTypeName in Expense/bo.json5
- Create 2 java files for before & after read triggers as ExpenseBeforeReadTrigger.java & ExpenseAfterReadTrigger.java under triggers directory
- Add the code to add the reference BO as part of the child query in case expenseTypeName field is present in the query list as below
- Add the code init expenseTypeName from child reference expenseType.name as below
Query Over POST & Named Business Views
- Update searchable=true for validationStatusCode, personId, expenseSource, expenseReportId
- Express request as json
{
"fields" : ["expenseId","expenseTypeId","description","location","receiptAmount","receiptCurrencyCode","reimbursableAmount","reimbursementCurrencyCode","receiptDate"],
"accessors": {
"expenseType": {
"fields": ["expenseTypeId","name"]
}
},
"collection": {
"sortBy": [
{ "reimbursableAmount" : "desc" } ,
{ "timeCreated" : "desc" }
],
"filter" : "personId != NULL AND expenseReportId = NULL AND (validationStatusCode=NULL OR validationStatusCode in ('CLEAN')) AND expenseSource='CASH'"
}
}
Named Business Views
- Create a Named View "readyToSubmitExpenses" as follows
boss view create -m oraErpExpenses -bo Expense -v readyToSubmitExpenses ora_erp_expenses - Update accessModifier and readable for expenseDate as public
- Add the view definition of 'readyToSubmitExpenses' with
- field list : expenseType.name, merchantName, description, location, receiptAmount, receiptCurrencyCode, reimbursableAmount, reimbursementCurrencyCode, expenseDate
- filters : personId != NULL AND expenseReportId = NULL AND (validationStatusCode=null OR validationStatusCode in ('CLEAN')) AND expenseSource='CASH'
- accessors: expenseType
Initialise Parameters For NamedViews
- Update the filter condition in the view to filter by the bindParamValue
AND expenseSource=:expSourceParam - Create initialise parameters script java file as ExpenseInitializeParametersTriggers.java
String expenseSource="CASH";
if (context.parameterValue("formOfPaymentsParam").isPresent()) {
expenseSource=("card".equalsIgnoreCase((String)context.parameterValue("formOfPaymentsParam").get()))?
"CORP_CARD":expenseSource;
}
context.initParameterValue("expSourceParam", expenseSource);
Defining LOVs
- Create Dynamic LOV For Expense Type Reference
- Add the dynamic lookup definition for Expense type
expenseType : {
type : "object",
accessModifier : "public",
target : {
module : "oraErpExpenses",
businessObject : "ExpenseType"
},
joinType : "leftOuterJoin",
mapping : {
expenseTypeId : "expenseTypeId"
},
dynamicLookup: {}
},
- Define FND Lookup Based LOVs With Translations
- boss module create -m oraErpApplcore ora_erp_expenses
- boss bo create -bo FndLookupValue -t FND_LOOKUP_VALUES_B -m oraErpApplcore ora_erp_expenses
- boss bo addtranslation -bo FndLookupValue -m oraErpApplcore -trm sparse ora_erp_expenses
- Add the below meaning , description & sourceLang with translatable as true to the FndLookupValue BO
- Update fndlookupvalue→ fndlookupvalues in v1.json; remove the entry for fndlookupvalueTranslation
- Uptake Fndlookup Based References For flightclass, flighttype & formOfPayments LOV
- Create Named views for formOfPayment, flightClass & flightType
- boss view create -m oraErpApplcore -bo FndLookupValue -v formOfPayment ora_erp_expenses
- boss view create -m oraErpApplcore -bo FndLookupValue -v flightClass ora_erp_expenses
- boss view create -m oraErpApplcore -bo FndLookupValue -v flightType ora_erp_expenses
- Update view definitions as needed
- Add fndlookupvalue based relationship mapping to formOfPayment, flightClass & flightType as follows
- boss bo addrelation -m oraErpExpenses -bo Expense -tm oraErpApplcore -tbo FndLookupValue -fm ticketClassCode:lookupCode -c ManyToOne -acc flightClass -j leftOuterJoin ora_erp_expenses
- boss bo addrelation -m oraErpExpenses -bo Expense -tm oraErpApplcore -tbo FndLookupValue -fm travelType:lookupCode -c ManyToOne -acc flightType -j leftOuterJoin ora_erp_expenses
- boss bo addrelation -m oraErpExpenses -bo Expense -tm oraErpApplcore -tbo FndLookupValue -fm expenseSource:lookupCode -c ManyToOne -acc formOfPayment -j leftOuterJoin ora_erp_expenses
- Add relevant view based dynamic lookup references on the accessor definition in Expense BO
- dynamicLookup : {
view : "flightClass"
} - Enum Based Lists
- Create a file named FormOfPayments under <GIT_REPO>/ora_erp_expenses/sources/model/self/modules/oraErpExpenses/enums
- Add the below definition for the enum created
- Uptake the enum as lov src in expense BO as below
formOfPaymentEnum: {
type: "enumeration",
target: {
module: "oraErpExpenses",
enumeration: "FormOfPayments"
},
dataStoreMapping: {
rdbms: {
column: "EXPENSE_SOURCE",
searchable: true,
sortable: true
}
},
accessModifier: "public",
readable : "public",
creatable : "module",
updatable : "never",
nullable : true
}
- Update Field Attributes updatable/creatable As Public
- Add Id Generator Property To Initialize Primary Key
- Add historyType Attribute For WHO Columns
- security.json5 - add update/create privilege for super_user
{
$dt_version : "2310.0.550",
dataSecurityEnabled : true,
dataSecurity : {
rules : [ {
privilege : "read",
grants : [ {
role : "SUPER_USER_ROLE"
} ]
}, {
privilege : "create",
grants : [ {
role : "SUPER_USER_ROLE"
} ]
}, {
privilege : "update",
grants : [ {
role : "SUPER_USER_ROLE"
} ]
} ]
},
allowAnonymousAccess : false,
allowSkipDataSecurityViaAccessor : false,
faStripeName : "fscm"
}
- add below java files under businessObjects/Expense/scripts/expenses/expense/triggers
- ExpensePrepareDataTrigger.java
- ExpenseBeforeReadTrigger.java
- ExpenseInitializeParametersTriggers.java
- ExpenseAfterReadTrigger.java
- ExpenseCascadeTrigger.java
- ExpenseRollupTrigger.java
- Declarative Field Value Defaulting
- Add the defaultValue as 0 for personalReceiptAmount field in BO definition
- Programmatic Field Value Defaulting for ReimbursementAmount, ReimbursableCurrencyCode / ReceiptCurrencyCode, ExpenseLocation, ExpenseSource, ExchangeRate, ExpenseTypeCaetgoryCode
- Create ExpenseFields.java file under businessObjects/Expense/scripts/oraErpExpenses/expense/constants
- Add reimbursableAmount,reimbursementCurrencyCode ,locationId,expenseSource , exchangeRate fields to querycontext as part of preparedata trigger , to ensure the fields are fetched for the expense update use case
- Create enum as ExchangeRates.java to add predefined set of exchange rates from receipt currency code to reimbursementCurrencyCode
- Add the java file under Expense/scripts/oraErpExpenses/expense/constants
- Update the Cascade trigger with the defaulting logic as mentioned in the use case section
- Add defaulting logic for create use cases
- update the expense id to negated value of the rowid value as generated by the mid-tier. As this training data is created against the actual dev dbs, it's better to distinguish the actual transaction data used on day to day basis v/s training app specific data.
- default the reimbursement currency code & receipt currency code to USD .
- Update the Default values for expenseSource, LocationId in case its not added in the payload
- Derive the exchange rate from enum defined above ,if not included in the payload
- Initialise startDate to enddate in case startdate is not specified in the paylaod
- Derive Reimbursement Amount based as (ReceiptAmount-Personal)*ExchangeRate
- Field Value Derivation Based On Adhoc Query -
- Derive the following field values for expense BO for a given personid
- [orgid ,assignmentid]
- The org and assignment information is saved in PER_ALL_ASSIGNMENTS_M table.
- Note : Since the cross family BO delivery mechanism is not clear , for now lets go ahead and create a BO for thr HCM BO in our training repo. Ideally this should be delivered as part of hcm person module related BOSS service artefacts.
boss module create -m oraErpHcm ora_erp_expensesboss bo create -bo PersonAssignment -t PER_ALL_ASSIGNMENTS_M -m oraErpHcm ora_erp_expenses- Update the where clause on the PersonAssignment BO as below
where :"PRIMARY_FLAG = 'Y' AND ASSIGNMENT_STATUS_TYPE ='ACTIVE'" - Update default view definition as needed
- Add PersonInfoComputator class under businessObjects/Expense/scripts/oraErpExpenses/expense/computators/
Add the code to derive the assignmentid,orgid from the new BO using adhocquery builder - Update the defaultOrgAssigmentAndPersonId method in Cascade trigger to invoke the above computator
- Update the searchable attribute to true for in PersonAssignment BO as follows
- Add the adhoc query to fetch the assignmentid & orgid from the PersonAssignment BO
try(var personAssignments = context.newQueryBuilder("oraErpHcm.PersonAssignment").filter(filtercondition).build().read()) {StreamSupport.stream(personAssignments.spliterator(),false).forEach(personAssignment -> {Number orgId=(Number) personAssignment.fieldValueAsNumber("organizationId").orElse(dftOrgId);Number assignmentId=(Number) personAssignment.fieldValueAsNumber("assignmentId").orElse(dftAssignmentId);context.logger().info("In expense.PersonInfoComputator::compute:[[derived orgid - "+ orgId +", derived assignmentId - "+ assignmentId+"]]");expense.setFieldValue(ExpenseFields.ORG_ID,orgId);expense.setFieldValue(ExpenseFields.ASSIGNMENT_ID,assignmentId );});}
Adding Business Specific Validations
- Declarative Validation
- Data type validations - User should not be allowed to set string value on date or number type fields
- ReceiptAmount – add a maximum property to 1000 and minimum to 0
- Description should be at least 3 chars long & within 30 char limit-- Add the maxLength and minLength constraints
- lov field validation on value set
- Required field validators- ExpenseTypeid, endDate, receiptAmount, receiptCurrencyCode
- Data type validations
- Section 1 : Data Type Based Validations
- User should not be allowed to set string value on date or number type fields
endDate : {type :"date",...- Section 2 : Maximum & Minimum Value Based Validations
receiptAmount : {type :"big-decimal",minimum :"0",maximum :"1000"},- Section 3 : Maximum & Minimum Length Based Validations
description : {type :"string",maxLength :30,minLength :3},- Section 4 : Lov Value Validation
- Value exists validation on lov field type via reference BO
- If we have the reference BO added via the relationship , in the POST/PATCH payload if we pass the FK as a part of the reference BO $id , BOSS fwk would implicitly check if the value sent exists
- In the reference BO dataset and report errors in case of invalid data is set
- Section 5 : Required Field Validation
- nullable property in BOSS defines if the field can accept null values or not. By default this property is set to false for all fields
- nullable : false,
- Section 6 : Commit to Create a Checkpoint
- git commit
- Business Rule Validation Using Validate Trigger
- Section 1 : Required Field Validators
- Create a new Java class as ExpenseValidateTrigger.java under /triggers
- public final class ExpenseValidateTrigger {
- @Validate
- public static void validate(final ValidateContext context) {
- context.logger().info("In expense.ExpenseValidateTrigger::validate:START");
- //Add the validation logic here
- context.logger().info("In expense.ExpenseValidateTrigger::validate:END");
- }
- }
- Create a java class as RequiredFieldValidator.java under /validations
- public final class RequiredFieldValidator {
- public static void validate(TransactionBusinessObject expense,final ValidateContext context){
- context.logger().info("In expense.RequiredFieldValidator::validate::START");
- /*
- add required field validation code here
- */
- context.logger().info("In expense.RequiredFieldValidator::validate::END");
- }
- }
- Update the required field validator with the field value check code as below
- private static void requiredFieldValidation(String fieldName, TransactionBusinessObject expense,ValidateContext context) {
- context.logger().info("In expense.RequiredFieldValidator::requiredFieldValidation::START"+expense.fieldValue(fieldName).orElse(null));
- if (expense.changedFields().contains(fieldName)) {
- if (expense.fieldValue(fieldName).isEmpty()) {
- context.logger().info(fieldName + " Value set is null");
- context.raiseError("Enter a value", expense, fieldName);
- }
- } else {
- context.logger().info(fieldName + " field is not defined");
- context.raiseError("Enter a value", expense, fieldName);
- }
- context.logger().info("In expense.RequiredFieldValidator::requiredFieldValidation::END");
- }
- Added requiredFieldValidator checks for expenseSource, expenseTypeId , endDate and locationId as follows
- public static void validate(TransactionBusinessObject expense,final ValidateContext context){
- context.logger().info("In expense.RequiredFieldValidator::validate::START");
- if(hasChanged(expense, ExpenseFields.EXPENSE_SOURCE)) {
- requiredFieldValidation(ExpenseFields.EXPENSE_SOURCE,expense,context);
- }
- if(hasChanged(expense, ExpenseFields.LOCATION_ID)) {
- requiredFieldValidation(ExpenseFields.LOCATION_ID,expense,context);
- }
- if(hasChanged(expense, ExpenseFields.EXPENSE_TYPE_ID)) {
- requiredFieldValidation(ExpenseFields.EXPENSE_TYPE_ID,expense,context);
- }
- if(hasChanged(expense, ExpenseFields.END_DATE)) {
- requiredFieldValidation(ExpenseFields.END_DATE,expense,context);
- }
- context.logger().info("In expense.RequiredFieldValidator::validate::END");
- }
- invoke the RequireFieldValidator in the validate trigger as below
- //Add the validation logic here
- stream(context.businessObjects())
- .forEach(bo -> {
- RequiredFieldValidator.validate(bo,context);
- });
- Section 2 : Conditional hidden/required Field Validations
- Use case & References
- flighttype, class are required for airfare but hidden for others
- start & end date is required for accommodation, end date(rename as expense date in UI) for others
- For hidden fields, Check if the non applicable fields for category are set in payload
- Create a method to report required field errors based on category code for airfare fields and startDate in RequiredFieldValidator.java as follows
if("ACCOMODATIONS".equalsIgnoreCase(expensetypeCategoryCode)){if(hasChanged(expense, ExpenseFields.START_DATE)) {requiredFieldValidation(ExpenseFields.START_DATE, expense, context);}}elseif("AIRFARE".equalsIgnoreCase(expensetypeCategoryCode)){if(hasChanged(expense, ExpenseFields.FLIGHT_TYPE)) {requiredFieldValidation(ExpenseFields.FLIGHT_TYPE, expense, context);}if(hasChanged(expense, ExpenseFields.FLIGHT_CLASS)) {requiredFieldValidation(ExpenseFields.FLIGHT_CLASS, expense, context);}} - Create a method to check if the value is set for non applicable fields for a given category
if(expense.changedFields().contains(fieldName)) {if(!expense.fieldValue(fieldName).isEmpty()) {context.logger().info(fieldName+" Value set is not null : "+expense.fieldValue(fieldName));context.raiseError(fieldName+" is not applicable for category "+expensetypeCategoryCode, expense, fieldName);}}else{context.logger().info(fieldName +" field is not defined");}context.logger().info("In expense.RequiredFieldValidator::hiddenFieldValidation::END"); - Add ExpensetypeCategoryCode in the fields list in preparedata trigger as follows, as we need to fetch the expensetyepcategorycode for validation trigger
if(!fieldNames.contains(ExpenseFields.EXPENSE_TYPE_CATEGORY_CODE)){query.fieldNames(List.of(ExpenseFields.EXPENSE_TYPE_CATEGORY_CODE));}- Section 3 : Conditional Updatable Check Based Validations
- Use case
- Enable or disable field based on form of payment selected
- when expenseSource=CORP_CARD, fields as receiptamount, dates, merchantname, expensetype, location are not updatable, but description can be updated
- Check if the payload has modified values for non modifiable fields
- Ref: Expense Training Application
- Create a file as FormFieldValidator.java under /expense/validations
packageoraErpExpenses.expense.validations;importoracle.boss.script.ValidateContext;importoracle.boss.script.TransactionBusinessObject;publicfinalclassFormFieldValidator {publicstaticvoidvalidate(TransactionBusinessObject expense,finalValidateContext context){/*conditional hidden value set*/}}- Add the business logic to check if the non modifiable fields are updated in the request payload if expense source is CORP_CARD
- Update the validate Trigger ExpenseValidateTrigger.java to invoke the FormFieldValidator as below
- Section 4 : Reference BO Value Exists Validators Using Adhoc Queries
- lov Value validation based on date effectivity and value set
- ex: flight class , type, form of payments
- Note :Adhoc queries are allowed in the validation trigger.
- Create a java file LovValueValidator.java /Expense/scripts/oraErpExpenses/expense/validations
packageoraErpExpenses.expense.validations;importoracle.boss.script.ValidateContext;importoracle.boss.script.TransactionBusinessObject;importoracle.boss.script.TransactionBusinessObject.TransactionState;importoraErpExpenses.expense.constants.ExpenseFields;publicfinalclassLovValueValidator {publicstaticvoidvalidate(TransactionBusinessObject expense,finalValidateContext context){context.logger().info("In expense.LovValueValidator::validate::START");context.logger().info("In expense.LovValueValidator::validate::END");}} - Invoke the LovValueValidator in the ExpenseValidateTrigger.java as follows
stream(context.businessObjects()).forEach(bo -> {RequiredFieldValidator.validate(bo,context);FormFieldValidator.validate(bo,context);LovValueValidator.validate(bo,context);}); - Add adhoc query to fetch flight class/type/form of payments lookup based on the ticketclasscode,traveltype and expensesource set in request payload
booleanvalueExists =false;try(var lookupCodes = context.newQueryBuilder("oraErpApplcore.FndLookupValue").fieldNames(Arrays.asList(newString[]{"lookupCode","meaning"})).orderBy(List.of(orderByField)).filter("lookupType = '"+lookupType+"' AND enabledFlag='Y' AND viewApplicationId in (0, 10016) and lookupCode='"+lookupCode+"'").build().read()) {Iterator<BusinessObject> lookupCodeItreator = lookupCodes.iterator();if(lookupCodeItreator.hasNext()) {valueExists =true;}} - Section 5 : Custom Business Logic Based Validations
- usecase
- Description should be at least 3 chars
- Business Rule check as 0< ReimburseableAmount<1000
- Exchange Rate limit validations
exchange Rate exists for to and from currency Code , tolerance of 3% is allowed else error
else exchange Rate >100 - Add the description validator DescriptionValidator.java as follows
packageoraErpExpenses.expense.validations;importoracle.boss.script.ValidateContext;importoracle.boss.script.TransactionBusinessObject;importoraErpExpenses.expense.constants.ExpenseFields;publicfinalclassDescriptionValidator {publicstaticvoidvalidate(TransactionBusinessObject expense,finalValidateContext context){context.logger().info("In expense.DescriptionValidator::validate::START");if(expense.changedFields().contains(ExpenseFields.DESCRIPTION)) {String description = (String)expense.fieldValue(ExpenseFields.DESCRIPTION).orElse(null);if(description==null|| description.length()<=3){context.logger().info(ExpenseFields.DESCRIPTION+" Value set should be at least 3 chars long : "+description);context.raiseError(ExpenseFields.DESCRIPTION+" should be more than 3 chars", expense, ExpenseFields.DESCRIPTION);}}context.logger().info("In expense.DescriptionValidator::validate::END");}}- Add the amount validator AmountValidator.java as follows
packageoraErpExpenses.expense.validations;importoracle.boss.script.ValidateContext;importoracle.boss.script.TransactionBusinessObject;importoraErpExpenses.expense.constants.ExpenseFields;importjava.math.BigDecimal;publicfinalclassAmountValidator {publicstaticvoidvalidate(TransactionBusinessObject expense,finalValidateContext context){context.logger().info("In expense.AmountValidator::validate::START");if(expense.changedFields().contains(ExpenseFields.RECEIPT_AMOUNT)) {BigDecimal amount = (BigDecimal)expense.fieldValueAsBigDecimal(ExpenseFields.REIMBURSABLE_AMOUNT).orElse(null);if(amount !=null&& amount.doubleValue()<0){context.logger().info(ExpenseFields.REIMBURSABLE_AMOUNT+" should be >0 : "+amount);context.raiseError("Invalid amount entered", expense, ExpenseFields.RECEIPT_AMOUNT);}elseif(amount !=null&& amount.doubleValue()>1000){context.logger().info(ExpenseFields.REIMBURSABLE_AMOUNT+" Value set should be <1000 "+amount);context.raiseError(" Reimbursable amount exceeds 1000$ limit", expense, ExpenseFields.RECEIPT_AMOUNT);}}context.logger().info("In expense.AmountValidator::validate::END");}}- Add the exchange rate validator ExchangeRateValidator.java as follows
packageoraErpExpenses.expense.validations;importoracle.boss.script.ValidateContext;importoracle.boss.script.TransactionBusinessObject;importoraErpExpenses.expense.constants.ExpenseFields;importoraErpExpenses.expense.constants.ExchangeRates;importjava.math.BigDecimal;publicfinalclassExchangeRateValidator {publicstaticvoidvalidate(TransactionBusinessObject expense,finalValidateContext context) {context.logger().info("In expense.ExchangeRateValidator::validate::START");if(expense.changedFields().contains(ExpenseFields.EXCHANGE_RATE)) {String receiptCurrencyCode = (String) expense.fieldValue(ExpenseFields.RECEIPT_CURRENCY_CODE).orElse(null);expense.fieldValueAsBigDecimal(ExpenseFields.EXCHANGE_RATE).ifPresent(v -> {if(v !=null) {BigDecimal systemGeneratedRate = ExchangeRates.getRate(receiptCurrencyCode);BigDecimal exchgRatesetAtBO = (BigDecimal) v;if(systemGeneratedRate.doubleValue() ==1.0&& !"USD".equalsIgnoreCase(receiptCurrencyCode)) {/*default generated rate*/if(exchgRatesetAtBO.doubleValue() >100) {context.logger().info(systemGeneratedRate.doubleValue() +" No rate exists for the receiptcurrency code, exchange rate entered exceeds the approved limit of 100 "+ exchgRatesetAtBO.doubleValue());context.raiseError("Invalid value ,Exchange rate entered exceeds the approved limit of 100", expense, ExpenseFields.EXCHANGE_RATE);}}elseif(systemGeneratedRate.compareTo(exchgRatesetAtBO) <0&& (1.03* systemGeneratedRate.doubleValue() < exchgRatesetAtBO.doubleValue())) {context.logger().info(exchgRatesetAtBO.doubleValue() +" exceeds the system generated exchaneg rate with 3% talerance "+ (1.03* systemGeneratedRate.doubleValue()));context.raiseError("Invalid value ,Exchange rate entered exceeds the approved limit of "+1.03* systemGeneratedRate.doubleValue(), expense, ExpenseFields.EXCHANGE_RATE);}elseif(exchgRatesetAtBO.doubleValue() <=0) {context.logger().info(exchgRatesetAtBO.doubleValue() +" exchange rate is negative ");context.raiseError("Invalid value , Enter a value >0", expense, ExpenseFields.EXCHANGE_RATE);}}});}context.logger().info("In expense.ExchangeRateValidator::validate::END");}}- Invoke the validators from the validate trigger
stream(context.businessObjects()).forEach(bo -> {RequiredFieldValidator.validate(bo,context);FormFieldValidator.validate(bo,context);LovValueValidator.validate(bo,context);DescriptionValidator.validate(bo,context);AmountValidator.validate(bo,context);ExchangeRateValidator.validate(bo,context);});- Section 6 : Commit to Create a Checkpoint
Delete Business Object Using REST API
- Update the security.json5 file with the below entry for super_user
{privilege :"delete",grants : [ {role :"SUPER_USER_ROLE"} ]}
Translation Uptake In BOSS Metadata
- Section 1: Error Message With Token Uptake For Custom Error Message
- Create a Message bundle file as "ExpenseErrorMessageBundle-i18n.json" under /oraErpExpenses/resources/translations
- Update the module.json5 to add the new resource bundle entry as follows
{$dt_version :"2310.0.550",allowAnonymousAccess :false,translations : {self : {path :"oraErpExpensesMBundle-i18n.json"},errorMsgs: {path :"ExpenseErrorMessageBundle-i18n.json"}},dataStore : {rdbms : {name :"ApplicationDBDS"}}}Add the below entry for string definition with tokens{"ValidateExchangeRateLimitMsg":"Invalid value ,Exchange rate entered exceeds the approved limit of {rateLimit}","@ValidateExchangeRateLimitMsg": {"x-StrId":"226016","x-StrType":"Validation","x-SRKey":"Validation.Get.ValidateExchangeRateLimitMsg","placeholders": {"rateLimit": {"type":"Number","required":"true"}}}}- Uptake the translatable string in the Validation Error msg(ExchangeRateValidator.java) thrown as follows
elseif(systemGeneratedRate.compareTo(exchgRatesetAtBO) <0&& (1.03* systemGeneratedRate.doubleValue() < exchgRatesetAtBO.doubleValue())) {context.logger().info(exchgRatesetAtBO.doubleValue() +" exceeds the system generated exchange rate with 3% talerance "+ (1.03* systemGeneratedRate.doubleValue()));try{context.raiseError(context.messageBundle("errorMsgs").builder("ValidateExchangeRateLimitMsg").token("rateLimit",(1.03* systemGeneratedRate.doubleValue())).format(), expense, ExpenseFields.EXCHANGE_RATE);}catch(RequiredParameterException| MissingStringException| InvalidParameterException e){thrownewRuntimeException(e);}}- Translatable BO Field Labels , Description , Hints
- Create a Message bundle file as "ExpenseErrorMessageBundle-i18n.json" under translations/ko folder
- Update the translations for the strings in Korean as follows
- Translatable BO Field Labels , Description , Hints
- Create a Message bundle file as "oraErpExpensesMBundle-i18n.json" under translations/ko folder
- Add the string entries in base resource model
- Uptake the resource string in the BO metadata of Expence
Implementing Security
- Update the user role mapping for 'boss' service in /scratch/$USER/rwddev/rwd_dev/bin/docker-compose.yaml
security.providers.0.http-basic-auth.users.1.login: FINUSER1security.providers.0.http-basic-auth.users.1.password: Welcome1security.providers.0.http-basic-auth.users.1.roles: ORA_EMPLOYEE_ABSTRACTsecurity.providers.0.http-basic-auth.users.2.login: FINUSER2security.providers.0.http-basic-auth.users.2.password: Welcome1security.providers.0.http-basic-auth.users.2.roles: ORA_EMPLOYEE_ABSTRACTsecurity.providers.0.http-basic-auth.users.3.login: FINUSER3security.providers.0.http-basic-auth.users.3.password: Welcome1security.providers.0.http-basic-auth.users.3.roles: ORA_EMPLOYEE_ABSTRACTsecurity.providers.0.http-basic-auth.users.4.login: FINUSER4security.providers.0.http-basic-auth.users.4.password: Welcome1security.providers.0.http-basic-auth.users.4.roles: ORA_EMPLOYEE_ABSTRACT- Restart docker containers
- Update Security Construct With Read Access For Expense Users
- Create a module for oraErpHcm if not already created. The module naming should be in accordance with the BO Specifications and the BOSS Taxonomy
- Create a BO on PER_USERS table and default view with userid parameter. The BO naming should be in accordance with the BO Specifications and the BOSS Taxonomy
- Add a where clause for the Person BO as follows
rdbms : {PER_USERS : {table :"PER_USERS",where :"ACTIVE_FLAG = 'Y'"}}- Update searchable to true for username on persons BO
- Update the accessModifier for Person BO as public
accessModifier :"public"- Create a relationship between expense to person Bo
- boss bo addrelation -m oraErpExpenses -bo Expense -tm oraErpHcm -tbo Person -fm personId:personId -c OneToOne -acc exmOwner -j innerJoin ora_erp_expense
- Add accessor to defaultview of Expense as follows
exmOwner: {fields: ["personId","userId"]} - Update security side car file(i.e Expense/security.json5) with ORA_EMPLOYEE_ABSTRACT role based grant for CRUD access
{$dt_version :"2310.0.550",dataSecurityEnabled :true,dataSecurity : {rules : [ {privilege :"read",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT"} ]}, {privilege :"create",grants : [ {role :"SUPER_USER_ROLE"} ]}, {privilege :"update",grants : [ {role :"SUPER_USER_ROLE"} ]}, {privilege :"delete",grants : [ {role :"SUPER_USER_ROLE"} ]} ]},allowAnonymousAccess :false,allowSkipDataSecurityViaAccessor :false,faStripeName :"fscm"} - Add the Security/policy construct to filter the expenses by logged in userid in security side car file(i.e security.json5) for Expense BO
{$dt_version :"2310.0.550",dataSecurityEnabled :true,dataSecurity : {conditions: [{name:"EXM_LOGGEDINUSER_BASED_ACCESS",condition:"exmOwner.username=@securityContext.username",}],rules : [ {privilege :"read",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT",grantConditions: [{condition:"EXM_LOGGEDINUSER_BASED_ACCESS",description:"Normal employee should be able to view his expenses"}]} ]}, {privilege :"create",grants : [ {role :"SUPER_USER_ROLE"} ]}, {privilege :"update",grants : [ {role :"SUPER_USER_ROLE"} ]}, {privilege :"delete",grants : [ {role :"SUPER_USER_ROLE"} ]} ]},allowAnonymousAccess :false,allowSkipDataSecurityViaAccessor :false,faStripeName :"fscm"}- Update the deployment & build the project
- Update Security Construct With Create, Update And Delete Access For Expense Users
- Add the Security/policy construct to filter the expenses by logged in userid in security side car file(i.e security.json5) for Expense BO for update , delete . Just add the role based grant for create . Since we have no way to enforce personid based filter on the create use case
{$dt_version :"2310.0.550",dataSecurityEnabled :true,dataSecurity : {conditions: [{name:"EXM_LOGGEDINUSER_BASED_ACCESS",condition:"exmOwner.username=@securityContext.username",}],rules : [ {privilege :"read",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT",grantConditions: [{condition:"EXM_LOGGEDINUSER_BASED_ACCESS",description:"Normal employee should be able to view his expenses"}]} ]}, {privilege :"create",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT"} ]}, {privilege :"update",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT",grantConditions: [{condition:"EXM_LOGGEDINUSER_BASED_ACCESS",description:"Normal employee should be able to view his expenses"}]} ]}, {privilege :"delete",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT",grantConditions: [{condition:"EXM_LOGGEDINUSER_BASED_ACCESS",description:"Normal employee should be able to view his expenses"}]} ]} ]},allowAnonymousAccess :false,allowSkipDataSecurityViaAccessor :false,faStripeName :"fscm"}- Update security.json5 for all businessObjects except Expense with read access for 'ORA_EMPLOYEE_ABSTRACT'
{$dt_version :"2310.0.550",dataSecurityEnabled :true,dataSecurity : {rules : [ {privilege :"read",grants : [ {role :"ORA_EMPLOYEE_ABSTRACT"} ]} ]},allowAnonymousAccess :false,allowSkipDataSecurityViaAccessor :false,faStripeName :"fscm"} - In previous section 'Produce Business Object' we had added a filter on Expense/bo.json5 to retrieve expenses of a predefined user. As we have implemented Security/policy construct which retrieves expenses only of the logged in user, we can remove this filter.
where :"person_id = 100010026335799" - Update the deployment & build the project
BOSS Testing
https://confluence.oraclecorp.com/confluence/display/SPECTRA/Spectra+Weekly
https://confluence.oraclecorp.com/confluence/display/FFT/ERPM+Spectra+Training+2023
Comments
Post a Comment