Pages

Showing posts with label university. Show all posts
Showing posts with label university. Show all posts

Thursday, October 18, 2018

Salesforce Custom Metadata vs. Custom Objects: Where Should I Put My Configuration and Validation Settings?

(Version française en dessous)

Yesterday, a colleague asked me the difference between "custom metadata" and "custom objects" for storing "configuration and validation" information in Salesforce.

My answer is:

  • Custom metadata if you can. Salesforce says so.
    And it survives sandbox refreshes!
  • Data tables (custom objects) if you truly need it to be part of your data
    (e.g. people will want to include it, hyperlinked to other data, in reports).

When I wrote an Apex trigger that determined which User should "own" each "Admissions Application" in our org, I ended up splitting the configuration data in two.

Here's why:

Custom Metadata

Used for a table that helps Apex code ask, "Who does this?"

  • key: the code for a graduate degree we offer
  • value: a username responsible for recruiting people to that graduate degree

Data (Custom Objects)

Used for a list of U.S. states, their full spellings, and fields with "lookup" links to the User table indicating who does what work for that state.

  • There was strong demand to generate native Salesforce reports per User showing all the states they're responsible for and the high schools in those states. It made sense to ensure that the "high school" table could have a "lookup" field to this "states" table.
  • Custom metadata can have "master-detail" and "lookup" relational links to other custom metadata, but it can't link to ordinary data.
    • This meant we needed to store the the "states" as data (custom objects), even though we would also be using it as configuration information for Apex triggers.

UI & Editability Considerations

I'll let you in on a dirty little secret about another reason I used data tables ("custom objects") for most of the Undergraduate Admissions counselor assignment configuration data.

Undergraduate Admissions tweaks their "recruiter assignment" business rules several times a year. The real-world configuration data for their business is a lot more complex than a simple "list of U.S. states."

I'll be honest: Salesforce's user interfaces for hand-editing, viewing, and bulk-editing data are a lot more end-user-friendly than their user interfaces for the same operations on custom metadata, and setting granular "edit" permissions is a lot more sysadmin-friendly for data. I wanted to make sure end users, not sysadmins, were the ones whose time was spent tweaking the configuration several times a year!

I was thoroughly scolded at Dreamforce. Actually, I stand by my decision to use "data" tables, because there truly is a business need to report on the configuration data alongside normal data. But ... building your own user interfaces (typically using Lightning Components) to help end users edit custom metadata was a big theme. You have been warned:

  1. Dan Appleman's "Build Awesome Configuration Pages with Lightning Components & Custom Metadata"
  2. Gustavo Melendez & Krystian Charubin's "Crafting Flexible APIs in Apex Using Custom Metadata"
  3. Beth Breisness & Randi Wilson's "Create Guided User Experiences for Managing ISV Custom Metadata"


🇫🇷 - en français

Une collègue, peu familière avec Salesforce, m'a demandĂ©e quelle Ă©tait la diffĂ©rence entre « mĂ©tadonnĂ©es personnalisĂ©es » et « objets personnalisĂ©s » pour stocker des donnĂ©es de configuration et de validation dans une organisation Salesforce.

J'ai répondu:

  • Selon Salesforce, s'il est possible, utilisez des mĂ©tadonnĂ©es personnalisĂ©es.
    (Elles survivent l'actualisation d'un environnement sandbox.)

  • Stockez vos informations aux tables de bases de donnĂ©es (objets personnalisĂ©s) s'il faut crĂ©er des relations aux autres objets de votre organisation.
    (Par exemple, s'il y a des utilisateurs qui vont générer des rapports sur ces données.)

Mon approche

Lorsque j'ai Ă©crit un dĂ©clencheur Apex pour attribuer les enregistrements de notre objet « demande d'admission » aux utilisateurs corrects, j'ai fini par utiliser les deux approches:

Métadonnées personnalisées

J'ai choisi une type de mĂ©tadonnĂ©es personnalisĂ©es pour stocker des donnĂ©es simples concernant « qui gère chaque spĂ©cialisation ? »

  • clĂ©: le code qui indique un diplĂ´me d'Ă©tudes supĂ©rieures que l'universitĂ© offre
  • valeur: un nom d'utilisateur chargĂ© de recruter des Ă©tudiants au diplĂ´me

Objets personnalisés

J'ai choisi un objet personalisé pour stocker une liste d'états américains (abréviation et nom), avec quelques champs de référence indiquant les utilisateurs qui y gèrent divers aspects du recrutement d'étudiants en license.

  • Nos utilisateurs ont besoin de gĂ©nĂ©rer des rapports, regroupĂ©s par utilisateur, avec les Ă©tats qu'ils gèrent et les lycĂ©es qui y sont situĂ©s. Donc il fallait pouvoir crĂ©er une relation de l'objet « lycĂ©e » Ă  l'objet « Ă©tat ».
  • On peut crĂ©er des relations entre mĂ©tadonnĂ©es personnalisĂ©es, mais pas entre les types de mĂ©tadonnĂ©es personnalisĂ©es et les objets personalisĂ©s.
    • Donc j'ai choisi un modèle « objet personnalisĂ© » pour stocker les informations Ă  propos des Ă©tats, mĂŞme si ces informations servent aussi comme donnĂ©es de configuration pour un dĂ©clencheur Apex.

Considérations au sujet de l'interface utilisateur et au contrôle d'accès

Je vais vous rĂ©vĂ©ler un secret : ce n'est pas qu‘Ă  propos des besoins relationnels que j'ai choisi le modèle « objet personnalisĂ© » pour les informations Ă  propos de la gestion du recrutement en licence.

Le service admission en license changent les règles de « qui gère quoi » plusieurs fois par an. Et les donnĂ©es de configuration sont, en rĂ©alitĂ©, beaucoup plus complexes qu'une seule liste d'Ă©tats.

Actuellement, les interfaces utilisateur de Salesforce pour voir et enregistrer des donnĂ©es aux objets personnalisĂ©s sont supĂ©rieures Ă  celles pour les mĂ©tadonnĂ©es personnalisĂ©es – surtout si l'on est utilisateur ordinaire. Le contrĂ´le d'accès, aussi, est plus facile Ă  gĂ©rer pour les administrateurs. Je voulais m'assurer que ça soit des utilisateurs ordinaires qui font plusieurs fois par an cette saisie de donnĂ©es, et non pas les administrateurs !

On m'a fait honte de cette position Ă  Dreamforce. Ben, je maintiens ma choix d'objet personnalisĂ© pour raisons de relations entre objets. Mais … on a beaucoup rĂ©pĂ©tĂ© qu'il est de bonne pratique de construire ses propres interfaces utilisateur pour autoriser des utilisateurs ordinaires Ă  modifier les mĂ©tadonnĂ©es personnalisĂ©es en toute sĂ©curitĂ©. Je vous aurai prĂ©venus !

Vidéos pertinentes de Dreamforce

  1. « Build Awesome Configuration Pages with Lightning Components & Custom Metadata » de Dan Appleman
  2. « Crafting Flexible APIs in Apex Using Custom Metadata » de Gustavo Melendez & Krystian Charubin
  3. « Create Guided User Experiences for Managing ISV Custom Metadata » de Beth Breisness & Randi Wilson

Wednesday, June 13, 2018

Trigger/Process/Workflow or Scheduled Script/Tool?

I often get asked to write triggers to detect the existence of certain types of data in Salesforce and, when such data exists, make a modification to some other data.

The only problem is, writing "a trigger" isn't always easy to do in a well-normalized database (that is, a database that leverages master-detail and lookup relationships to avoid redundant data entry).

Take, for example, an architecture where "App Document" records have a master-detail relationship to a parent "Admissions Application" and the "Admissions Application," in turn, has a master-detail relationship to a parent "Contact" record.

The other day, I was asked to automatically flip the "Status" to "Waived" on any "App Document" records that meet the following criteria:

  • The record is of type "English Proficiency" and is in blank/"Required" status
  • The record's parent "Application" has an "application citizenship category" of "International" and a "level" of "Undergraduate"
  • The record's grandparent "Contact" has a "foreign citizenship country" of "United Kingdom," "Ireland," "Canada," "Australia," "New Zealand," (etc.)

This is ill-suited to a Trigger/Process/Workflow because I would actually need THREE "insert"/"update" automations, each with relatively redundant code:

  1. one for all Contacts
    (in case the citizenship country changes ... then go looking for appropriate "child" apps and "grandchild" documents)
  2. one for all Applications
    (in case the "application citizenship category" or "level" changes ... then double-check the parent Contact's citizenship and go looking for "child" documents)
  3. and one for all App Documents
    (check the parent & grandparent details and change self if appropriate)

Yikes! That's a lot of redundant trigger code, and some of those operations aren't very efficient against Salesforce governor limits.

Especially since just one SOQL query can easily fetch the ID of all "App Document" records whose "Status" needs to be set to "Waived":

SELECT Id
FROM AdmDocument__c
WHERE Type__c='English Proficiency'
AND (Status__c = NULL OR Status__c = 'Required')
AND Application__r.Level__r.Name='Undergraduate'
AND Application__r.Citizenship_Category__c='International'
AND Application.Contact__r.Foreign_Citizenship_Country__c IN 
   ('Australia','Canada','Ireland','New Zealand','United Kingdom')

When someone asks you to "write a trigger" or "write a process builder" or "write a workflow" to automate data migration inside of Salesforce, be sure to ask yourself, "How many tables' values changing could cause a scenario to arise that would make this data need to be modified as requested?"

If the answer is "2 or more," and especially if it's "3 or more," think hard about whether "every 15 minutes" / "daily" is frequent enough for your end users to see "automatic fixes," and whether a SOQL query could extract the IDs of the records that need to be changed.

If you can come up with a SOQL query that represents the "problem records," you should be able to use a schedulable ETL tool or scheduled Apex to "extract and load back" with much lower overhead against governor limits than triggers/processes/workflows would incur. Your future self will also thank you when someone (inevitably) asks for a change to your script.

Friday, June 3, 2016

Database Normalization And Salesforce - Ponderings

One of the biggest challenges in moving from a highly structured Oracle environment into Salesforce is how denormalized Salesforce's querying / reporting / data-viewing limitations encourage one's data model to be.

This particularly seems to be an issue in higher education. Like banking or healthcare, most of its data is rigid, with the typical business user's number one concern being easily expressed as, "Please don't mess up my data." Overall, higher education's business needs beg to have a university's databases insanely normalized.

Except on the fringes, that is. On the fringes, a university's interactions with the outside world stop being about individual human beings who care about "their" data "not being messed up." Prime "fringe" examples include:

  1. Email-address-based marketing, which is free and great, but hard to pin down to an actual person because people share
  2. Continuing education and seminars, where it's not really a big deal to swap attendees - if it's corporate training, who at the sending company cares who comes as long as someone gets trained?

These "fringe of the university's business" use cases, plus staff's desire to see the two sets of data married, seem to tempt universities to look at databases beyond their 20-year-old ERPs. (Of course such "data marriages" also desire the tall order of doing so without polluting vital information like the admission-graduation pipeline, employment history and paychecks, donation history!)

  • Unfortunately, this marriage means you're back to squeezing "fuzzy" data into a normalized structure. Which, in theory, you could do in your ERP if it gave you an easy way to add tables (and user interfaces to those tables).
  • Then there's the route of struggling to squeeze highly-normalized "vital" data into a "flat" structure like Salesforce encourages. Unfortunately, this can mean you're always fighting inaccuracy, lag, duplication...

Integrating Salesforce and our ERP (plus a few new data stores) is a much longer road than I was expecting, and it all keeps coming down to issues with matching a data model to the real world.

Banner is an amazing set of highly normalized tables for "don't lose my data" core university functions.
(If you get a look at the EnrollmentRx plugin for Salesforce as a Banner user, you'll be amazed - it's basically SPAIDEN/SRARECR/SAAADMS/SOATEST built into a newer database and restricted to "1 Contact/SPAIDEN record per email address." Everyone re-invents the wheel for higher ed because, like banking, some things just don't change.)

I think Sungard/Ellucian really missed the boat here.

They could have raked in the big bucks if they'd "taken care of the fringes" and:

  1. Given Banner just a few tweaks for easily adding/removing tables (and corresponding user interfaces)
  2. dded a good way to upsert the database 1-record-at-a-time
    (e.g. web forms - as FormAssembly's Salesforce connector proves, you really just need to have a user interface that lets a form-handler-configuration-managing user build complex SQL queries sorted by LastModifiedDate descending - not everything has to be "common-matched" - similar idea for fast deduplication ... DemandTools, which is to PL/SQL what Cognos is to SQL, exists because a good CRUD API to the Salesforce database exists)
  3. Engineered a great "over"-layer for email tracking and sending - especially by admissions departments.

For example, what if there were some sort of API that let you quickly create/update/delete SPACMNT records from what's otherwise basically a Cognos report? Like, a little pencil-shaped "edit" button near the list of comments, but otherwise, nothing is editable - it's just the read-only report (doing nice things like hiding details of inactive records and only showing you the most-recent SRARECR/SAAADMS/SGASTDN data available, as determined by the report-author)? Or a little "edit" button next to "status" that lets you pick from a picklist, and then when you commit it, the "interactive report" author has programmed logic to update SRARECR/SAAADMS/SGASTDN accordingly? That's basically what one of our main departments wants. A "collapsed, current, relevant" view of dispersed back-end data, with a few "edit" buttons interspersed so they don't have to surf through the normalized tables themselves to make updates.

The other major reason we tried Salesforce was because it had plugins for the web/email era and Banner didn't. (And a final reason was to accommodate those "fuzzy"-work departments that were using spreadsheets, Access databases, Rolodexes, etc.)

I often wonder where we'd be if we'd been able to do all this development straight into our highly-normalized ERP instead of half-rebuilding Banner inside of Salesforce.

What would we have done if we could have web'd/emailed/custom-tabled/easy-writeback'ed our existing database + data model instead of replicating our tried-and-true data model inside of Salesforce? What could we have done if Ellucian had tacked on the user-friendly concepts that Salesforce made popular instead of leaving their customers in the 1990s?

Salesforce's good luck and Ellucian's loss, I suppose. But I wish I often wish I could have my cake and eat it too - full normalization support and modern-database flexibility.

Since I can't, I still believe that the ideal for universities is to roll out Salesforce "from Rolodex to ERP," not "from ERP to Rolodex."

Thursday, August 13, 2015

Universities: To Break Down Silos, Implement SalesForce Starting With Your Smallest, Simplest Use Case - Not Your Highest-Revenue Use Case

If there's one thing I'm passionate about after 9 months in SalesForce, it's the idea that when implementing SalesForce at a university or in a university department, it's best to start small and "natively like SalesForce."

Don't start with your "sales" departments that already have highly established databases and processes, like the Admissions department. Despite SalesForce's name having "sales" in it.


1st, start with a dean + dean's secretary who want to digitize their Rolodex. Observe their struggles and refine your user training manuals to take them into account. Write all the Reports they need.

2nd, move on to an associate dean who wants to digitize their Rolodex. Train them in with your training manuals. Write all the Reports they need.

Observe the conflicts that come up from suddenly being able to "step on each other's toes."

Refine the data model (and report organization model) if you need to, so that people can tell what data is "theirs." Think about whether that data model will be scalable to 20 or 40 different "clumps" of users who want to mark records as "records they are working with.

Make some governance ("don't edit this field unless...") manuals and pass them around to all new users. Update your training manuals (including adding sections on "how to tell whether you are working with someone," "how to tell who else is working with someone," etc. and pass them around to all new users; make sure they read the new parts.

3rd, add another user with a Rolodex.

Again, observe and improve your reports, data model, governance manuals, & training manuals - and re-train all existing users.


Now, 4th, you're ready to try taking a group of 1-4 staff with a very simple "events" model and adding them to SalesForce (perhaps using the "Campaign" object). Teach them not only what you taught the other groups, but also to set up an event (new Campaign), say that a Contact is coming to an event (add a Campaign Membership), and update whether they came to the event (update a CampaignMembership's status field). Write all the Reports they need.

Again, observe and improve your data model, governance manuals, & training manuals.

Now try letting this 4th group - the one with events - send out a mass email about to everyone that "they are working with" (or some subset thereof). Note how you show them to make sure they ONLY send it to people that THEY are working with. WRITE THIS DOWN! This is the foundation of your "how to send mass mailings with SalesForce" training manual that you're going to write.

Use the "mass mailing" training manual you just wrote to train someone else from the 4th group if there is one. See if the training manual teaches them everything you wanted them to know. Refine and publish & make sure everyone from this group is using it (plus the governance & other training manuals). And write any Reports they need in light of now starting to send mass mailings.

5th, add another group of 1-4 staff with a very simple "events" model (as simple as the 4th group).

Observe the conflicts that come up from suddenly being able to "step on each other's toes" with respect to events management.

Refine the data model if you need to, so that people can tell what data is "theirs." Think about whether that data model will be scalable to 20 or 40 different "clumps" of users who want to mark records as "records they are working with.

Again, update your reports & training & governance manuals until all 5 groups (including 2 groups with "events") are working seamlessly in the same SalesForce instance ("org").

Repeat one more time w/ a 6th group - again, a small group with a very simple "events" model.


If you've gotten this far, and you've developed a successful way to document and project-manage these 6 rollouts, updates of training end users, etc. CONGRATULATIONS! You are NOW on track to start thinking about how to move past "Rolodex & simple events" departments.


But you're not all the way to a big, organized department like Admissions yet!

7th, try doing a department that already had its "Rolodex" in a relational database with "Person," "Company," and "Event" tables that are structured exactly the way SalesForce's "Contact," "Account," and "Campaign" tables are structured and doesn't have any additional tables beyond that. (That is, 1 person can work at 1 company at any given time. A company can have lots of people working at it. People, not companies, attend events. A person can attend lots of events over time. An event can have lots of people attending it.) It's okay if they have more fields on their "Person" & "Company" tables than your average business card would have previously made you add to Contact/Account/Campaign. This is going to be Round 1 of seeing what it's like to move people with an established database into SalesForce - people who are used to seeing and filtering by a higher level of detail!

Get them up and running with the appropriate modifications to data model, training/governance manuals, Reports, etc. and back-updating all other users of the system. Migrate their data from their old database and have them cut over to using SalesForce instead. Keep working with them until they're happy and there isn't anything they used to be able to do but now can't. DO NOT MOVE ON until they're 100% as productive as they were in their old database! And remember that they, in particular, are not used to having to "share" edit-permissions on their People/Companies with other sub-departments, so expect a LOT of governance updates with all existing users. It's going to be a while until they're happy. Convince your superiors to be okay with that - that they will waste less time doing "measuring twice and cutting once" than moving on until all 7 departments (especially this 7th) are using the same database in perfect harmony.

8th - do Step 7 again. Only now you have two "semi-mature" subdepartments in the same database. How do you need to change your data model to tell whose "extra fields" on Contact/Account are whose? Change all your reports/governance/training manuals and fill everybody in again.

Don't move on until every single current user (including this "8th" sub-department) is happy, no matter how slow your progress feels.

Bringing something like your Admissions department into SalesForce - especially if you need to keep your data synced to a central student/employee database - is going to be like building a house (or an addition to a house). You are working your way up from "building a birdhouse" through "building a shed" through "building a stand-alone garage" to "building an addition onto a house." The project management habits, documentation, etc. that you are gaining along the way are going to be vital when it comes time to build that home-addition.

Yes, you are spending a lot of money on SalesForce for very little revenue increase, if any, but a failed implementation for a high-revenue department isn't going to be any more profitable, either.

If the reason your university wants SalesForce is to get everybody having "intelligence" on everybody else's activities so that you can "work smarter across silos," you need to start small and work your way up - and let rollouts be done when they're done, not when you thought they'd be done.


If everything is chugging along nicely after you have 3 "Rolodex" subdepartments," 3 "Events" subdepartments, & 2 "Cut Over From Their Own Simple Database" subdepartments all playing nicely with each other, PAT YOURSELF ON THE BACK!

Ask people how they and the university as a whole have become more productive since moving from siloed data stores to a shared data store. Take notes. (If they haven't, this might be a good time to get everybody back to their old systems and pull the plug on SalesForce. It's expensive to keep going. At least you only have to tear down a birdhouse and a shed, though.)

If net productivity is up, ask them what they had to sacrifice in terms of productivity to get these overall productivity gains. Take notes.

Share your notes with decision-makers and decide whether it's better for the university to build more birdhouses/sheds (other similar departments), which you can do quickly, or to keep working upward toward building that house (more likely home-addition). It just might be the case that SalesForce is the system of your "small departments," whereas big organized departments keep using their status-quo systems and people call/email them to find out what's going on in those systems. You're still ahead of where you used to be in the cross-silo visibility arena, and perhaps getting every last birdhouse/shed-size subdepartment into the same SalesForce org is what would bring your university the most benefit.


If your university decides to go for "building the house," remember that you still haven't even built a garage yet.

You haven't dealt with departments with quirky event structures or departments with lots of events.

You haven't dealt with departments that sell people things.
Tip: Don't start with the "admissions" department. Yes, it sells educations, but "degree" is a weird product that a customer has to inquire about, then prove they're good enough to buy, then finally buy. That's a complicated sales model. Building a "garage" is integrating a sales department that sells things anyone can buy as soon as they express interest (such as a department that offers fee-based seminars).

And if you can find more than 1 "garage"-level subdepartment to implement at your university to work with, all the better. I always advocate for seeing how similar departments step on each other's toes and resolving issues that arise before moving up to the next level of complexity.


When you finally do decide to integrate a department with strong ties to an existing central database, you are going to be so experienced at bringing users more complicated than the last into your org without messing up existing users!

Pat yourself on the back. If your goal is to break down database silos, you will be much farther along than if you had started by setting up a complex system for a complex department, only to find out that adding anybody else to the SalesForce org brings your whole house of cards tumbling down.


In my 9 months, the most impressive/"creative"/revenue-increasing higher education projects with SalesForce I've seen involve getting Rolodex/Spreadsheet systems into a proper relational database for the first time ever - NOT reinventing the wheel for super-complicated departments that are already "getting by."

For example, Algonquin's Saudi Arabia "Student Information System" isn't an integration with their main Student Information System. According to Q&A during their presentation at the SalesForce Higher Education Summit 2015, it was the first digitization of a homegrown SIS that previously lived in a bunch of spreadsheets. I'd actually call that a "garage"-level project, but they have some really skilled programmers in their IT department.

And yes, St. Norbert's is probably the standard-setter for the "Admissions-office-first" model, so if you're on that train, call them. But it's not a train ticket I recommend buying if you're just now considering SalesForce.

It seems to me that for schools with an eventual goal of integrating lots of tiny Rolodex/Excel/Access-based sub-departments into the same database, the "start small & work up" approach is best.

If you're a university that has followed this model, please contact me - I'd love to talk further! Also, you should totally present at SalesForce Higher Ed conferences - hope to watch you on YouTube one day!

Monday, April 20, 2015

My First VisualForce: A Table To Simulate A List View

So, a month or two ago, our implementation partner wrote a trigger to keep a field on the Opportunity called "Related Application Record" populated with a Lookup to the Admission Application record it most closely corresponded to.


(Don't forget our old friend the double-sided garden rake!)

Those Admission-Application "rake tines" are actually miniature rakes in and of themselves. They have "Checklist Records" hanging off of them, indicating what documents a Contact has turned in along with their application form.

We wanted to put a Related List at the bottom of the Opportunity page layout to show any "Checklist Records" from the Admission Application record indicated in "Related Application Record."

Only SalesForce wouldn't let us.

So I wrote my first VisualForce page, which allowed me to put a component into the Opportunity page layout that looks close enough to a Related List.

So here's the code

(Please let me know if you have any ideas for making "chkls" in my wrapper class private - it seems like bad design to leave it public. Please also let me know if you see any design or security flaws in this code. I'm still a beginner and appreciate pro tips.)

(Note: Please let me know if my code doesn't seem to flow. I did some manual Find&Replace to obfuscate my org's internal structure just a wee bit, and I might have missed something.)

The VisualForce Page

<apex:page standardController="Opportunity" extensions="OppApplicationChecklistClass">
 <apex:pageBlock >
  <apex:pageBlockTable value="{!app_chkl}" var="a" id="table">
   <apex:column value="{!a.chkls.Requirement__c}"></apex:column>
   <apex:column value="{!a.chkls.Received_Date__c}"></apex:column>
   apex:column value="{!a.chkls.Comment__c}"></apex:column>
  </apex:pageBlockTable>
 </apex:pageBlock>
</apex:page>

The Apex Class (A "Standard Opportunity Controller Extension")

(Ignore the close-tags in line 52 - either Blogger or my new code formatter doesn't seem to like Apex collections and insists on closing them as if they were HTML.

public with sharing class OppApplicationChecklistClass {
 
    // Attributes possessed by all objects made out of this class
    private final Opportunity opp;
    private List chklRecords;
    
    // Constructor for this class
    public OppApplicationChecklistClass(ApexPages.StandardController stdController) {
        this.opp = (Opportunity)stdController.getRecord();
    }
 
    // Methods possessed by all objects made out of this class
    
    public Opportunity getOpportunity() {
        return opp;
    }
    
    public List getchecklist() {
  
  if (chklRecords == null) {
   
   chklRecords = new List();
    
   List tempQueryResult = [SELECT (SELECT Id FROM Opportunities__r),
    (SELECT Applicant_Last_Name__c, Requirement__c, Received_Date__c, 
    Comment__c FROM Checklist__r) FROM ApplicationRecord__c
    WHERE Id IN (SELECT Related_Application_Record__c FROM Opportunity WHERE Id = :opp.id)
    AND Id IN (SELECT RelatedApplication__c FROM Checklist__c)];

    if(tempQueryResult.size() == 1) {
    for (Checklist__c a : (tempQueryResult.get(0)).Checklist__r) {
     chklRecords.add(new wChkl(a));
    }
   }
  
  }
  
  return chklRecords;
  
    }
    
    // Another attribute possessed by all objects made out of this class...
    // ...the wrapper class wChkl surrounding a single Checklist__c object
    public with sharing class wChkl {
        public Checklist__c chkls {get; set;} // I don't seem to be able to find a way to privatize this.
        public wChkl(Checklist__c a) {
            chkls = a;
        }
    }
 
}

My awful test class

(I swear I mean to come back to it and make it meaningful...)

@isTest
private class OppApplicationChecklistTest {
  static testMethod void test() {
        
        Opportunity setupOpp = new Opportunity();
        ApexPages.StandardController sc = new ApexPages.standardController(setupOpp);
        
        // Create an instance of the page controller to test
        OppApplicationChecklistHandler testPageCon = new OppApplicationChecklistHandler(sc);
        
        // Try calling methods/properties of the controller in all possible scenarios to get the best coverage.
        Opportunity testOpp = testPageCon.getOpportunity();
        
        // OppApplicationChecklistHandler works with a blank Opportunity if it can't find a real one from the page it's on.
        System.assertEquals(null, testOpp.Id);
        // Working with a blank Opportunity, the list of Checklist records would also be blank.
        System.assertEquals(0, (testPageCon.getchecklist()).size());
    }
}

Tuesday, March 3, 2015

Background: Our Recruiters Rely On Opportunity Objects

A bit of background on the code we're developing:

The records storing the details of a person's interest in matriculating to a higher education program form a many-to-one relationship with that person.

What does "many-to-one" mean? Here's an example:

I can be interested in matriculating to my undergraduate studies at Harvard, {get rejected or matriculate and hopefully graduate}, 5 years after that decide I want a master's degree in English there, {get rejected or matriculate and hopefully graduate}, and 10 years after THAT decide that what I would really like is a master's degree in French there.

"Records about my interest in Harvard" are "many-to-one" because there is only 1 of me, but there are 3 separate "interactions" I've had with Harvard about my interest in it.

To track these interactions, my peers who went to DreamForce tell me that Admisisons offices using SalesForce typically break down into two categories:
  1. Those that use SalesForce's native "Opportunity" object to track "interest in matriculating."
    • FYI, using "Opportunity" as if it were in a many-to-one relationship with "Contact" involves populating a field on Opportunity called "Primary Contact Role" with the "Contact" record of the person interested in matriculating.  Apex Triggers can then grab that record and treat the relationship like a "lookup" relationship.
  2. Those that create custom "master-detail" objects hanging off of "Contact" objects to track "interest in matriculating." Such objects are often called something like "Admission."


We're an "Opportunity" shop (model #1).

But Banner, our ERP, works on model #2 ("Admission" records).

And we're currently in a position where the rest of the school is maintaining those records in Banner on our behalf, rather than us doing everything in SalesForce the way most "Opportunity" shops would.

So we have "Admission" detail objects hanging off of "Contact" in SalesForce AND "Opportunity" objects acting as if they hung off of "Contact."

The "Admission" objects get populated every morning by a PLSQL dump to .CSV files and automated data loading of those .CSV files into SalesForce.  (We know, we know.  We're looking into JitterBit.)

Finally, we don't have the resources we need, in the timeline we need them before "go-live," to restructure the "Admission" object or its corresponding PLSQL code into something that our recruiters can easily work off of. (We would want more "collapsing" of Banner data to be done before import so that back-to-back applications to the same program update old records rather than creating new ones.)



We're an "Opportunity shop" because:
  • "Opportunity" provides us a "blank slate" in this environment where "Admission" is set in stone.
  • "Opportunity" also plays better with SalesForce plugins like the mass-mailing software Pardot.
We just need to figure out how to "collapse and copy" relevant data from "Admission" records into the "Opportunity" records that are equivalent to them.

Much of the code I'll cover in upcoming posts has revolved around our need to act like an "Opportunity shop" without ignoring the contents of data feeds that think we're an "Admissions object shop."

Stay tuned.

Monday, March 2, 2015

Welcome To "Oracle 2 SalesForce!"

Welcome!  My name is Katie, and I began my journey as a SalesForce system administrator in November 2014.

I work in a large academic department of a mid-size university.  We were the first department to purchase a SalesForce org, although the school at large has now purchased one as well.

Before taking my current position, I worked at the same university managing data entry into, and Cognos Report Studio reporting out of, our Entrprise Resource Planning (ERP) system, Banner, on top of an Oracle back-end.

Although I have not written PL/SQL to control DML (INSERT, UPDATE, DELETE) professionally, I am familiar with it through graduate school and personal experimentation.  In other words, I'm just dangerous enough to think I can talk about my blunders through enterprise-grade database systems without having the chops to really impress people who come here for the word "Oracle."  Hope you enjoy your visit anyway!

The university's ERP is still a major source of information in our SalesForce org, but not the only one.  As of the founding of this blog in March 2015, we have some interesting constraints on our work (such as working around a fixed-structure daily import until we have JitterBit to replace the PL/SQL behind it, and such as the rest of the university still doing primary data entry into the ERP).

I have found it quite a paradigm shift to design solutions to problems integrating our ERP data into our SalesForce org for the Admissions department--one I'm still "shifting" through every day.

SalesForce Success Communities, YouTube videos, and StackExchange boards have been invaluable, but sometimes I feel a bit lonely as an "Oracle person in a SalesForce world."

So...if you do too, welcome!  As I come up with "SalesForce-y" solutions to problems I used to know how to solve in Oracle, I plan to share them here.  May you find them useful in your quest to master your new world as well.  :-)

-Katie