Long-range forecasting often needs to plan for things that haven't happened yet — for example, revenue for customers a company intends to onboard in the future. Since these customers don't exist in the planning master data yet, users need a way to create them directly, along with control over attributes like their planning status.
This gave rise to two problems I needed to solve:
With the help of SACs inbuilt scripting capabilities — it gives a limited set of business users a simple, governed way to create a new planning-only customer record with an auto-generated ID, and to update attributes on that record later.
I built a two-part Story using SAC's Advanced Mode scripting capabilities:
Both forms use the SAC Planning Model scripting APIs getMembers, getMember, createMembers, and updateMembers against the Customer dimension of the planning model, all scripted directly on the widgets within the Story canvas.
A handful of script variables drive the whole application:
| Variable | Type | Purpose |
allmember | PlanningModelMember | Holds the JSON payload passed to createMembers / updateMembers |
customerId | FilterValue | Reference to the customer being worked on |
NewCustomerNumber | Number | Numeric form of the next available Customer ID, used for the increment logic |
NewCustomerString | String | String form of NewCustomerNumber, since dimension member IDs are strings |
When the page loads, the same block of logic serves both forms:
Application.showBusyIndicator("Initializing new customer...");
var customerDim = PlanningModel_1.getMembers("Customer");For the Update form, the dropdown is rebuilt from scratch and seeded with a placeholder instruction:
Dropdown_1.removeAllItems();
Dropdown_1.addItem("Select a Customer", "Select a Customer");
Dropdown_1.setSelectedKey("Select a Customer");
for (var i = 1; i < customerDim.length; i++) {
Dropdown_1.addItem(customerDim[i].id, customerDim[i].description);
}Starting the loop at index 1 skips the technical "not assigned" default member that most dimensions carry at index 0 — a small but important detail if you don't want that showing up as a selectable customer.
For the Create form, the trick is generating the next Customer ID automatically. Since new customers are always appended with an incrementing numeric ID, the last entry in the dimension is always the highest existing ID:
var LatestEntry = customerDim[customerDim.length - 1].id;
NewCustomerNumber = ConvertUtils.stringToNumber(LatestEntry) + 1;
NewCustomerString = ConvertUtils.numberToString(NewCustomerNumber);
Text_9.applyText(NewCustomerString);The ID needs to round-trip between number and string because incrementing a string doesn't work the way you'd want ("740" + 1 isn't "741" in JavaScript), but the dimension member ID itself has to be written back as a string.
On submit of the create form, the values entered in the input fields and radio button groups are packaged into a single member object and pushed to the model:
var custName = InputField_2.getValue().trim();
var UARNo = InputField_3.getValue().trim();
var CustomerStatus = RadioButtonGroup_1.getSelectedKey();
var UARStatus = RadioButtonGroup_2.getSelectedKey();
allmember = ({
id: NewCustomerString,
description: custName,
properties: { UAR_NO: UARNo, CustomerStatus: CustomerStatus, UAR_Status: UARStatus }
});
PlanningModel_1.createMembers("Customer", allmember);Once the new member is created, the form resets itself and — this is the part I like — increments the counter again and re-populates the next available Customer ID, so the user can keep creating customers back-to-back without ever touching the ID field manually:
Dropdown_1.addItem(NewCustomerString, custName);
NewCustomerNumber = NewCustomerNumber + 1;
NewCustomerString = ConvertUtils.numberToString(NewCustomerNumber);
Text_9.applyText(NewCustomerString);Note the dropdown on the Update form is also updated in the same step, so a customer created in this session is immediately available for editing without a page refresh.
When a user picks a customer from the dropdown on the Update form, the script reads that member's current properties and reflects them on screen — in this case, setting the Active/Inactive radio button to match the stored CustomerStatus:
var selectedId = Dropdown_1.getSelectedKey();
if (selectedId === "Select a Customer") {
RadioButtonGroup_3.setSelectedKey("Active");
return;
}
var customerDim = PlanningModel_1.getMember("Customer", selectedId);
RadioButtonGroup_3.setSelectedKey(customerDim.properties.CustomerStatus);On submit, only the changed attribute is sent back — there's no need to resend the description or other unrelated properties:
var custId = Dropdown_1.getSelectedKey();
var CustomerStatus = RadioButtonGroup_3.getSelectedKey();
if (custId === "Select a Customer") {
Application.showMessage(ApplicationMessageType.Error, "Customer ID is required.");
return;
}
allmember = ({
id: custId,
properties: { CustomerStatus: CustomerStatus }
});
PlanningModel_1.updateMembers("Customer", allmember);This is a nice illustration of updateMembers behavior: you don't have to pass the full member object, only the ID plus the properties you actually want to change.
Planning cycles don't wait for master data to catch up, and this is a simple example of how a bit of scripting inside a Story can close that gap without any dependency on the source system. With just two forms and a handful of script variables, business users get a self-service way to onboard a new customer for planning purposes — with the ID handled automatically — and to keep that customer's status current as plans evolve.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 10 | |
| 5 | |
| 4 | |
| 3 | |
| 2 | |
| 2 | |
| 1 | |
| 1 | |
| 1 | |
| 1 |