Local API - Dinner Money Coding
Background
Dinner Money (DM) has been designed to allow a Bursar to make errors without significant consequences rather than to enforce rigorous financial disciplines; the outcome is no less rigorous within DM but a more traditional finance package design would be far easier to interface with from the perspective of an external system.
DM was not written with external payment systems in mind. As such interfacing with any e-Payment product will require careful thought and the reasons are discussed below.
The document intends to be open and straightforward about the current DM interfaces as to how they work and some of the thinking behind them. The research for the training session and associated sample code was a voyage of discovery about what we couldn’t do and the understanding of why this was the case.
Why is it awkward to integrate with Dinner Money?
Historical Editing
Scenario 1: “Dinner money charges were increased on 1/9, all the parents were informed and pupils are bringing in the right money; however the new charges were not updated in dinner money until 2 weeks in to term.”
Scenario 2: “Fred’s free school meal eligibility changed, notification arrived 2 weeks after the effective date of the change.”
It is possible to change data historically. In which case information previously exported to external systems may be wrong. DM does not currently have row versioning; as such at best an external system would need to export all of the meals taken, costs and payments. The nearest API is the report described below but this comes with a health warning about the volumes of data that it may generate.
Traditional finance systems would not have allowed retrospective change and forced the use of journals and other devices to correct errors made.
Balances are recalculated when needed
If you run DM reports or other processes in SIMS .Net, DM will kick off a recalculation of balances. This needs to be done by a user who has access to modify data because there may be invalid entries within the database where say free school meal entitlement has been modified. In these cases DM pops up a list of issues for the DM user to address. In some cases, the update may fail because of error conditions and the DM user interface will in some circumstances allow the user to correct the fault. One example of this might be a student is marked as having taken a Christmas meal in June; typically, this would be corrected to a standard meal before the use can proceed to the requested task. During the preparation of this document, the training data has a student that had had a meal taken at a time where they were not in a year group, the error was shown on entry to DM interfaces and it did not allow the user past this error until it was resolved. Whilst an unusual error, it would have prevented the recalculation of balances.
Unless the error list is ‘terminal’ such as the missing year group, the list is a use once and the errors are not available after the call. It is therefore not viewed as desirable that an external system should force a balance recalculation before say importing payments because the import process would likely be an unattended process and hence offer no opportunity to interact with the user. Even if the process was interactive, it would potentially require the partner to implement a significant chunk of DM logic to replicate what DM does when problems occur. This logic is part of the UI layer which is not available to partners.
The consequence of this is that balances are correct as of the last recalculation, unfortunately there are no interfaces that tell when this was or allow a test update to occur. Whilst it is unusual within SIMS, in effect parts of DM are indirectly tied to the UI.
When a payment is made, the payments totals for a student are updated. This at least means that when an external system pays in £10, the ‘current balance’ is in effect also adjusted (via the payment total).
Conclusion
Dinner money isn't the easiest system to integrate with because of the features above but there are examples of Technical Integrators who have achieved this successfully.
Payments
Split Payments
There is no such thing in DM as payments for an individual. A payment includes a set of splits and a special case is a single split for one person.
The code below shows how a payment is processed:
private static void processDemoPayment3(DMp.EditStudentPayments editProcess)
{
string reff = Guid.NewGuid().ToString().Substring(0, 8);
// paul Andrews is 1884
int Paul = 1884;
int Jordan = 2087;
double total = 17.99;
double jordans = 8.33;
// Jordan Acton is 2087
int financialTransactionID = -1;
DMe.PersonPayment personPayment = new DMe.PersonPayment();
personPayment.Person.Surname = "Transaction: " + financialTransactionID; // Used for identification on excEPTion below, if needed
personPayment.FinancialTransactionIDAttribute.Value = financialTransactionID--;
personPayment.TransactionDate = DateTime.Now.AddDays(-1);
personPayment.TransactionAmount = Convert.ToDouble(total);
personPayment.ExternalRef = "B"+reff;
personPayment.ExternalID = Guid.NewGuid().ToString();
personPayment.PaymentType = DMe.Cache.PaymentTypeExternal;
personPayment.Reference = reff;
personPayment.PayerName = "joint Dad";
personPayment.Notes = "Test";
personPayment.Status = DMe.StatusEnum.New;
int displayOrder = 1;
DMe.TransactionLine transactionLine = new DMe.TransactionLine(DMe.DiscriminatorEnum.Student, InformationDomainEnum.DinnerMoneyBasicFinancial);
transactionLine.FinancialTransactionIDAttribute.Value = personPayment.FinancialTransactionID;
transactionLine.PersonIDAttribute.Value = Paul;
transactionLine.Surname = "."; // Field must contain anything/something as it has a min length of 1, but is not used for our purpose!
transactionLine.LineAmount = total-jordans;
transactionLine.DisplayOrder = displayOrder++;
transactionLine.Status = DMe.StatusEnum.New;
personPayment.TransactionLines.Add(transactionLine);
DMe.TransactionLine transactionLine2 = new DMe.TransactionLine(DMe.DiscriminatorEnum.Student, InformationDomainEnum.DinnerMoneyBasicFinancial);
transactionLine2.FinancialTransactionIDAttribute.Value = personPayment.FinancialTransactionID;
transactionLine2.PersonIDAttribute.Value = Jordan;
transactionLine2.Surname = "."; // Field must contain anything/something as it has a min length of 1, but is not used for our purpose!
transactionLine2.LineAmount = jordans;
transactionLine2.DisplayOrder = displayOrder++;
transactionLine2.Status = DMe.StatusEnum.New;
personPayment.TransactionLines.Add(transactionLine2);
if (!personPayment.Valid())
{
ValidationErrors.Record(string.Format("PersonPayment not Valid {0}", personPayment.ValidationErrors.Item(0)));
return;
}
editProcess.Payments.Add(personPayment);
}
Potential issues
The sample code preparation failed on a number of occasions the line highlighted above is the key one to aid debugging. Look at the validation error in the debugger. (Or improve the details captured by the line). The errors seen included:
- The transaction value does not match the sum of the splits.
- Cannot enter future transactions – Note this failed for Datetime.Now!
- External references may be at most 10 characters.
A stranger error also occurred. The original sample code set the external and internal references to hard coded values. Whilst it was happy to add the values on the first run, trying to add them again failed hence the code gets the references as bits of Guids; Clearly partner systems would need to ensure that their references were unique and compatible.
Saving the payments
public static bool MakeStudentPayments()
{
// Populate the DM cache
DMp.Cache.Populate();
// We need a status for a pseudo call to the browse...
DMe.StudentStatus statusNone;
statusNone = new DMe.StudentStatus();
statusNone.StudentStatusIDAttribute.Value = -1;
statusNone.Code = Cache.WildcardNone;
statusNone.Description = DMe.Cache.NoneText;
bool paymentsMade = false;
// Set up an edit payment for students - MUST be of type external. Supported from Spring 2012 onwards.
DMp.EditStudentPayments editProcess = new DMp.EditStudentPayments(DateTime.Today, DMe.Cache.PaymentTypeExternal);
// Load required DM data
editProcess.LoadArgsOnly(new DMe.SelectStudentsEventArgs(Cache.WildcardAny, Cache.WildcardAny, false, statusNone,
editProcess.TierNone, editProcess.YearGroupNone, editProcess.RegistrationGroupNone, editProcess.HouseNone, DateTime.Today));
// Process the payment
processDemoPayment1(editProcess);
paymentsMade = editProcess.Save(DMe.SaveModeEnum.SaveAndConfirm);
processDemoPayment3(editProcess);
paymentsMade = editProcess.Save(DMe.SaveModeEnum.SaveAndConfirm);
processDemoPayment2(editProcess);
// Save the payments - force the payments
paymentsMade = editProcess.Save(DMe.SaveModeEnum.SaveAndConfirm);
if (!paymentsMade)
{
// get the ValidationErrors from (personPayment.ValidationErrors);
}
return paymentsMade;
}
Please note that the example shows one save per transaction. When the code is amended to try to save multiple transactions it refused to save.
Outcome
In effect we create a new payment with a set of splits. If I pass in the transaction date as 10/07/2012 and an amount of £10 then the payment history it will show the following.
These payments must be of type ‘External’ or they should be rejected (No guarantees of that). If they are not rejected, it will potentially break the banking returns.
Getting the Current Balance
This uses a report, the detailed information is shown below. Typically when the report is shown in SIMS, it will re-calculate the balances as was stated above. It is plausible that the balances may be ‘aged’ if the DM manager (Bursar) does not run the financial reports on a regular basis.
The report has its limitations and we would recommend that it is executed on a per student basis as is the default within DM. Executing the report for a say ‘All pupils’ in a large school for a large time period may take a significant time or possibly fail.
The key XML in the report is:
<Students>
<Student externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceOnly="false" baseGroupCode="" name="Paul Andrews" yearGroup=" 4" regGroup="4SL" openingBalance="33.60" mealsTotal="1.60" transactionsTotal="107.39" />
</Students>
Where the ‘current balance’ is the sum of the opening balance + transaction total less meals taken.
private decimal dmBalance(StringBuilder sb)
{
// add some wrapper xml elements
sb.Insert(0, "<Root>");
sb.AppendLine("</Root>");
XDocument repDoc = XDocument.Parse(sb.ToString());
// get the 3 amount values to do the math
var Student = repDoc.Descendants("Students")
.Select(x => x.Element("Student"))
.ToList();
decimal openBalance = Convert.ToDecimal(Student[0].Attribute("openingBalance").Value);
decimal mealsTotal = Convert.ToDecimal(Student[0].Attribute("mealsTotal").Value);
decimal transactionsTotal = Convert.ToDecimal(Student[0].Attribute("transactionsTotal").Value);
decimal currentBalance = openBalance - mealsTotal + transactionsTotal;
return currentBalance;
}
Running the report in code
public decimal GetStudentStatement(int personID, DateTime start_Date, DateTime end_Date, string outputPath)
{
decimal currentBalance = 0;
List<DMe.StudentEmployeeSummary> selectedPersons = new List<DMe.StudentEmployeeSummary>();
DMe.StudentEmployeeSummary personDMe = new DMe.StudentEmployeeSummary(DMe.DiscriminatorEnum.Student);
personDMe.PersonIDAttribute.Value = personID;
selectedPersons.Add(personDMe);
//params for Load are DateTime startDate, DateTime endDate, SIMS.Entities.DinnerMoney.BaseGroupType baseGrouptype, <AbstractGroupLookup>basegroups, <StudentEmployeeSummary>persons
DMp.ViewStudentDetailedStatementOfAccountReport viewStudSOA
= new DMp.ViewStudentDetailedStatementOfAccountReport();
viewStudSOA.Load(start_Date,
end_Date.AddHours(23).AddMinutes(59),
DMe.Cache.BaseGroupTypeIndividuals,
new List<DMe.AbstractGroupLookup>().AsReadOnly(), selectedPersons.AsReadOnly());
SIMS.XMLServices.IXmlReport xmlrep = viewStudSOA as SIMS.XMLServices.IXmlReport;
if (xmlrep == null)
{ return 0; // Error – do some feedback! Probably a programming one…
}
StringBuilder sb = new StringBuilder();
try
{
xmlrep.GenerateXml(new XmlTextWriter(new StringWriter(sb)));
StreamWriter output = new StreamWriter(outputPath);
output.WriteLine(sb.ToString());
output.Close();
// derive balance
currentBalance = dmBalance(sb);
}
catch (System.Exception e)
{ // Do something with the error
}
return currentBalance;
}
The report also includes activities for the specified period. A sample of a full report is included below.
<Configuration>
<School leaNumber="823" schoolNumber="2999" schoolName="WATERS EDGE PRIMARY SCHOOL" />
<Report printDateTime="2012-07-10T21:41:40.470" loginName="GROSG" fullName="Mrs Gillian Grosvenor" applicationVersion="10.0" />
<DateRange startDate="2012-07-08" endDate="2012-07-10" />
<BaseGroupType code="" description="Individuals" />
<BaseGroups />
<MealTypes />
<Weeks>
<Week weekNumber="1" startDate="2012-07-08" endDate="2012-07-08" />
<Week weekNumber="2" startDate="2012-07-09" endDate="2012-07-10" />
</Weeks>
<DaysOfWeek>
<DayOfWeek dow="2" code="Mon" description="Monday" />
<DayOfWeek dow="3" code="Tue" description="Tuesday" />
<DayOfWeek dow="4" code="Wed" description="Wednesday" />
<DayOfWeek dow="5" code="Thu" description="Thursday" />
<DayOfWeek dow="6" code="Fri" description="Friday" />
</DaysOfWeek>
<Texts students="Pupils" yearGroup="Year Group" regGroup="Class" />
</Configuration>
<Students>
<Student externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceOnly="false" baseGroupCode="" name="Paul Andrews" yearGroup=" 4" regGroup="4SL" openingBalance="33.60" mealsTotal="1.60" transactionsTotal="107.39" />
</Students>
<StudentMeals>
<StudentMeal externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" date="2012-07-10" dayNum="3" cost="1.60" fsm="false" actualCost="1.60">
<MealType code="SM" description="School Meal" />
<Week number="2" />
</StudentMeal>
</StudentMeals>
<StudentMealTotals>
<StudentMealTotal externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" total="1.60">
<Week number="2" />
</StudentMealTotal>
</StudentMealTotals>
<StudentMealTotals></StudentMealTotals>
<StudentTransactions>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9630" transactionAmount="17.99" transactionDate="2012-07-09" transactionExternalRef="B15affa49" transactionExternalID="9A9EA149-C9A6-4F18-8056-0DABD7E6F26C" payerName="joint Dad" reference="15affa49" lineAmount="9.66">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9629" transactionAmount="9.67" transactionDate="2012-07-09" transactionExternalRef="A09cc763b" transactionExternalID="E6A1335E-3609-408F-B6FD-9C6D18B8C82A" payerName="Paul's Dad" reference="09cc763b" lineAmount="9.67">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9627" transactionAmount="17.99" transactionDate="2012-07-09" transactionExternalRef="B1b08d0fe" transactionExternalID="9B6FE27F-F806-404B-985E-1EE4FD237A5C" payerName="joint Dad" reference="1b08d0fe" lineAmount="9.66">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9626" transactionAmount="9.67" transactionDate="2012-07-09" transactionExternalRef="A75601640" transactionExternalID="C85D9970-746C-4B72-A82B-1A9D6FE2FBE9" payerName="Paul's Dad" reference="75601640" lineAmount="9.67">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9624" transactionAmount="17.99" transactionDate="2012-07-09" transactionExternalRef="B0778a9c0" transactionExternalID="F563A58C-1FF2-4C1C-B982-AF03FBCCF53E" payerName="joint Dad" reference="0778a9c0" lineAmount="9.66">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9623" transactionAmount="9.67" transactionDate="2012-07-09" transactionExternalRef="Ada1c6421" transactionExternalID="E6512767-402C-41C4-B7BE-C6D67B6AC8B5" payerName="Paul's Dad" reference="da1c6421" lineAmount="9.67">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9622" transactionAmount="17.99" transactionDate="2012-07-09" transactionExternalRef="001" transactionExternalID="97E549B2-1121-44E8-8480-A60F4812A263" payerName="joint Dad" reference="joint Payment" lineAmount="9.66">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9620" transactionAmount="9.67" transactionDate="2012-07-09" transactionExternalRef="001" transactionExternalID="6FEF3E96-BB16-49F4-928D-137FB570142F" payerName="Paul's Dad" reference="Paul's Payment" lineAmount="9.67">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9619" transactionAmount="9.67" transactionDate="2012-07-09" transactionExternalRef="001" transactionExternalID="0B97B861-3EA1-4B0B-8E4B-FCCA72BED32F" payerName="Paul's Dad" reference="Paul's Payment" lineAmount="9.67">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9618" transactionAmount="1.40" transactionDate="2012-07-10" transactionExternalRef="0000000099" transactionExternalID="C73A50DF-A9BC-4338-AFD2-F61FB88AB676" payerName="this is the Payer Name field" reference="This is the ref for the trans" lineAmount="1.40">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9617" transactionAmount="10.00" transactionDate="2012-07-10" transactionExternalRef="0000000099" transactionExternalID="A7C80421-A3B3-40C3-B45A-1B049D2D8612" payerName="this is the Payer Name field" reference="This is the ref for the trans" lineAmount="10.00">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
<StudentTransaction externalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" sourceExternalID="3E3DC1AF-CAB6-4A16-9931-86756BF1C901" transactionNumber="9616" transactionAmount="9.00" transactionDate="2012-07-09" transactionExternalRef="0000000099" transactionExternalID="9B903581-34B8-4202-8903-4FB258F1EA39" payerName="this is the Payer Name field" reference="This is the ref for the trans" lineAmount="9.00">
<TransactionType code="P" description="Payment" />
<ExtendedTransactionType code="X" description="External" />
</StudentTransaction>
</StudentTransactions>
Permissions
The Bursar has full access to DM. The APIs are likely to need most if not all of the permissions below.