Apttus Digital Commerce SDK

The purpose of this guide is to provide technical instructions on the installation and setup of an Apttus Digital Commerce storefront. The intended audience are developers who will be creating and maintaining new storefronts on a Salesforce Apttus installation. If you haven't already, please contact Apttus to get the Digital Commerce package installed in your org prior to installation.

Create your first Storefront

The Apttus Digital Commerce SDK is intended to work with the unified data model of the underlying Apttus Quote-to-cash platform. This means, there are no additional data setup steps in order to create and maintain your catalog within Apttus outside of what you may already do if you use Apttus CPQ. That said, this guide assumes you have already created data for the following within Apttus:

  • Category
  • Product
  • Price List

Community Setup

The Apttus Digital Commerce platform leverages a Salesforce Community to provide authentication and hosting features for guest users. After the Digital Commerce package is deployed, the next step is to create a Salesforce Community. At minimum, you just need the community URL. However, if you intend to support guest users, you will need to enable that within the community settings. After deployment, the angular library will provide a Visualforce page that you can set as the default page for all page settings within the community (i.e home, login, forgot password, change password etc). Being that its a single page application, it is designed to handle all incoming requests.

Storefront Object

Apart from the underlying catalog, the Digital Commerce package comes with a store object and tab to map a storefront to a catalog. If you are using an Apttus MDO org, there may already be a 'store' object installed. This object is deprecated in favor of the 'Storefront' object that comes with the Digital Commerce package.

After your catalog has been setup within Apttus, the next step is to create a 'Storefront' record. The storefront object is very basic and contains only a couple fields to map a storefront to a price list and logo for the guest user. The price list should look up to the price list you want the guest user to access and the logo should be an id or a url of the logo attachment for the store. The storefront record also has a 'banner' related list that can be used to setup banners for the jumbotron component in the reference template. Remember the name of the storefront you created. This will be used in a later step to associate with a storefront codebase.

Apttus Digital Commerce Permission Set

The Digital Commerce package comes with a basic permission set for providing the necessary access to users. The permission set is named 'Apttus Ecommerce' and should be assigned to users access the Digital Commerce storefront. If you would like to make any changes to the permissions, you may clone the permission set and make any changes necessary.

Angular Library Installation

In order to install the apttus Digital Commerce libraries, you must first be granted access. Please reach out to Apttus support for obtaining access to this libraries.

To install all required libraries, run:

npm install --save @apttus/core @apttus/ecommerce @apttus/elements

AppModule.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { ApttusModule } from '@apttus/core';
import { CommerceModule, TranslatorLoaderService } from '@apttus/ecommerce';

import { environment } from '../environments/environment';
import { ComponentModule } from './components/component.module';
import { RouteGuard } from './services/route.guard';
import { AuthGuard } from './services/auth.guard';
import { ConfigureGuard } from './services/configure.guard';
import { ConstraintRuleGuard } from './services/constraint-rule.guard';

// Register locale data
import localeMx from '@angular/common/locales/es-MX';
import localeMxExtra from '@angular/common/locales/extra/es-MX';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import { registerLocaleData } from '@angular/common';
import { ServiceWorkerModule } from '@angular/service-worker';
import { ModalModule } from 'ngx-bootstrap/modal';
import { HttpClientModule } from '@angular/common/http';

import { ProductDrawerModule, ApttusModalModule } from '@apttus/elements';
// If using additional locales, register the locale data here
registerLocaleData(localeMx, 'es-MX', localeMxExtra);
registerLocaleData(localeFr, 'fr-FR', localeFrExtra);

// Translations
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { AboGuard } from './services/aboGuard';
import { OrderDetailsGuard } from '@apttus/ecommerce';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    // Pass environment file to ApttusModule.
    ApttusModule.forRoot(environment),
    // Pass the string name of the APTSMD_Store__c record you want to use to the CommerceModule.
    CommerceModule.forRoot('My Store'),
    ProductDrawerModule,
    ModalModule.forRoot(),
    ApttusModalModule,
    TranslateModule.forRoot({
      loader: { provide: TranslateLoader, useClass: TranslatorLoaderService }
    }),
    HttpClientModule,
    ComponentModule,
    ServiceWorkerModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [RouteGuard, AuthGuard, AboGuard, ConfigureGuard, ConstraintRuleGuard, OrderDetailsGuard],
  bootstrap: [AppComponent]
})
export class AppModule { }

Once your library is imported, you can use its components, directives and pipes in your Angular application:

Defining a model

Proper state management is crucial to a successful Angular PWA. To that end, accessing data within Salesforce requires you to define your model within a Typescript class. Behind the scenes, @apttus/core is managing how your components access and interact with that data to avoid uncessary operations and maintain synchronicity.

Here's an example that defines the account and contact relationship for a component.

import { AObject, ATable, AField } from '@apttus/core';

// We use the decorator ATable to tell @apttus/core this class maps to the 'Account' object.
// In order to access standard sObject fields, our Account class must extend 'AObject' (a subclass of 'SObject).
// The class name can be anything, but the sobjectName and aqlName properties in the ATable decorator must map
// to an object API name within Salesforce or AIC respectively.
@ATable({
  sobjectName : 'Account',
  aqlName: 'crm_Account'
})
export class Account extends AObject {

  // Similar to how the ATable decorator is used on model objects the AField decorator is used for fields within the model.
  // The soql and aql properties must map to a field API name in Salesforce or AIC respectively.
  @AField({
    soql: 'Name',
    aql: 'Name'
  })
  public Name: string = null;

  @AField({
    soql: 'BillingStreet',
    aql: 'BillingStreet'
  })
  BillingStreet: string = null;

  @AField({
    soql: 'My_Custom_Field__c'
  })
  public MyCustomField: number = null;

  //Related Lists
  @AField({
    soql: 'PriceListId__r',
    aql: 'DefaultPriceListId'
  })
  PriceList: PriceList = new PriceList();
}

Defining a Service

After defining your model, you can access the data by creating a service for the models. The AObjectService class contains many of the standard DML and query operations to access the data, however you may add any convenience methods you want to your service. The core service methods are usually very simple.

import { AObjectService } from '@apttus/core';
import { Injectable } from '@angular/core';
import { Account } from './account.model.ts'  // This is a reference to the account model created in the previous section


@Injectable({
    providedIn : 'root'
})
export class AccountService extends AObjectService{
  //Add service methods here
  type = Account;
}

Access the data in your component

import { Component, OnOnit } from '@angular/core';
import { AccountService } from './account.service.ts';
import { Account } from './account.model.ts';

@Component({
  selector : 'app-account',
  template : `

  `,
  styles : [``]
})
export class AccountComponent implements OnInit{

  constructor(private accountService: AccountService){}

  // Always perform service methods in the ngOnInit method
  ngOnInit(){
    this.accountService.where([new ACondition(this.accountService.type, 'Id', 'NotNull', null)], 'AND', null, null, new APageInfo(1, 1)).subscribe(res => {
      /*
        res === [
          {
            Name: 'Account A',
            BillingStreet: '1234 Main Street',
            MyCustomField: 44,
            PriceList: {
              ...
            }
          }
        ]
      */
    });
    this.accountService.describe(Account, 'MyCustomField', false).subscribe(res => {
      // Describe information for My_Custom_Field__c
    });

    this.accountService.search('Main street').subscribe(res => {
      // SOSL Search Results
      // Note : search does not follow the model pattern and will return results specified in the query
    });

    this.accountService.get(['00Fxxxxxxx', '00Fxxxxxxx']).subscribe(res => {
      // Returns an array of account records
    })

    this.accountService.aggregate([new ACondition(this.accountService.type, 'Id', 'NotNull', null)]).subscribe(res => {
      // Returns aggregates for the specified clause. (i.e. total records as well as max/min values for all 
      // fields specified in the model)
    })

    this.accountService.create([new Account()]).subscribe(res => {
      // Returns list of id's created
    })

    this.accountService.update([new Account()]).subscribe(res => {
      // Returns list of id's updated
    })  

    this.accountService.upsert([new Account()]).subscribe(res => {
      // Returns list of objects upserted
    }) 

    this.accountService.delete([new Account()]).subscribe(res => {
      // Returns list of boolean values for accounts that were successfully deleted
    })
  }
}

deploy

To lint all *.ts files:

$ npm run lint

To deploy your code to your salesforce org

$ npm run deploy

# Known Issues

@Angular/cli 6.0.0 Can't Resolve Stream

If you've upgraded to @angular/cli 6, and you're seeing the following error

WARNING in C:/Workspace/ngs-workspace/node_modules/xml2js/node_modules/sax/lib/sax.js
Module not found: Error: Can't resolve 'stream' in 'C:\Workspace\ngs-workspace\node_modules\xml2js\node_modules\sax\lib'

ERROR in C:/Workspace/ngs-workspace/node_modules/csv-parse/lib/index.js
Module not found: Error: Can't resolve 'stream' in 'C:\Workspace\ngs-workspace\node_modules\csv-parse\lib'
ERROR in C:/Workspace/ngs-workspace/node_modules/csv-stringify/lib/index.js
Module not found: Error: Can't resolve 'stream' in 'C:\Workspace\ngs-workspace\node_modules\csv-stringify\lib'
ERROR in C:/Workspace/ngs-workspace/node_modules/xml2js/lib/parser.js
Module not found: Error: Can't resolve 'timers' in 'C:\Workspace\ngs-workspace\node_modules\xml2js\lib'
i 「wdm」: Failed to compile.

Fix: Add path

Add the following to your tsconfig.app.json file under 'compilerOptions':

"paths" : {
  "jsforce" : ["./node_modules/jsforce/build/jsforce.min.js"]
  ...
}

Uncaught Error: No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.

In order to work on a visualforce page, your app needs to be setup to use the hash routing location strategy instead of the default

Fix: Set the useHash flag in your app-routing.module.ts file

In your app-routing.module.ts file, set the useHash flag in the RouterModule.forRoot(...) call

@NgModule({
  imports: [RouterModule.forRoot(appRoutes,  { useHash: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

result-matching ""

    No results matching ""