Archive for the 'Formulas' Category



The number of business hours between two DateTimes (updated)

Thursday 26 March 2020 @ 10:19 pm

A customer recently wanted to adapt my “Business Hours Between” calculation for their environment. The main challenge was that their business day ends early on Friday. My formula doesn’t currenlty support working days that are different lengths, so they paid me to redo the formula so it would work for them. Once I had their version I decided to take it a bit further and create a new “Business Hours Between” formula for my web site.

This new version lets you separately specify a business start time and business end time for each of the 7 days of the week. It also includes additional logic to deal with events that start and/or end outside the business day (e.g. on a weekend, a holiday or after hours). The formula is now about 70 lines long. Fortunately, you only need to make changes in the first 20 lines or so. Here you can specify:

  • The field to use for the BeginDateTime
  • The field to use for the EndDateTime
  • The Start and End times assigned to each day of the week that you consider a business day
  • The list of Holiday dates which can be for multiple years

The output is a numeric value in minutes which you can use in subtotals and grand totals. You can also write a separate formula to divide this value by 60 to get the value in hours as a decimal.

If you want to show the value in HH:MM format you can use the “Elapsed Time String” formula on my site to convert this value into that format. Remember to multiply this formula’s result by 60 since the input for the “Elapsed Time String” formula is seconds.

If you need help implementing this formula or any of my formulas you can always call to schedule a short consult.




Cross-tabs can total formulas that you can’t normally total

Tuesday 10 March 2020 @ 1:44 pm

One of my first 10 blog posts explained why some formulas could be totaled and others could not. Two of the things that prevent a formula from being summarized (totaled) are if the formula itself refers to a subtotal, or if it uses the functions Previous() or Next().

But I was reminded recently that both of these types of formulas can be summarized in a Cross-tab. Take these two formula examples:

// Rebate:
if Sum ({Orders.Order Amount}, {Customer.Customer Name}) > 25000
then {Orders.Order Amount} *.05
else 0

//Days Between Orders:
if {Customer.Customer Name} = Previous({Customer.Customer Name})
then {Orders.Order Date} - Previous ({Orders.Order Date})
else val({@null})

If I wanted to do a grand total of my rebates or an average of the days between orders I wouldn’t be able to use normal summary functions.   Even Crystal running total fields won’t work with these. In most cases people would resort to using variable to accumulate these totals. However, both of these formulas can be summarized using a cross-tab. You could do a simple cross-tab with a single cell to show the grand total and no row or column fields.  Or you could do breakdowns by other fields.

Not only does this save you dealing with variables, but a cross-tab can put these totals on the first page (Report Header), while variables will only be complete on the last page (Report Footer).  One more reason to use my favorite objects.




Charting based on a parameter value

Saturday 29 February 2020 @ 10:51 am

I have had several customers over the years who wanted to add a fixed line to a bar/line chart. This is typically some type of target or standard that they want to compare the actual data. If this value is in the report’s query results, then it is pretty straightforward. But what if the user wants to be prompted to enter the value to be used in the chart? Crystal won’t let you add the parameter directly to the chart. Even if you put the parameter value in a formula, Crystal might not allow that formula in the chart.

The reason is that parameters come before the data is read. This means that they are evaluated “BeforeReadingRecords” by default. Values that come before the data is being read are not eligible for grouping, sorting, totaling or charting. But if you put the parameter in a formula you can change the evaluation time of the formula to “WhileReadingRecords”. This will make it into a column of values. And even though they are all the same, they will be available in the chart expert as a value to be summarized. Something like this:

WhileReadingRecords;
{?YourParameter}

Then in your chart you can either summarize it by using a Maximum, or checking the “Do Not Summarize” check mark.

If you need to do this and have trouble then call me to schedule a short consult, or Email me your report and explain your requirement.




“New Page After” on group 2 orphans group footer 1

Tuesday 18 February 2020 @ 8:24 am

If you have two group levels and you put a “New Page After” on Group Footer 2, you will find that Group Footer 1 gets orphaned onto it’s own page. To fix this put a “New Page After” on Group Footer 1. Then instead of putting the same check mark into Group Footer 2 you use the following condition on the “New Page After” property:

{Group1.DatabaseField} = Next ({Group1.DatabaseField})

Of course you use your own database field from Group 1 in the formula. This prevents Group 2 from doing a page break on the last record of the group. On that record the next value for Group 1 is different, so the page break is handled by Group 1.




Grouping times into half hour periods

Saturday 25 January 2020 @ 9:37 pm

When you create a group in Crystal using a Date field you get grouping options for different periods like by day, week or month. If the field is a Time or DateTime you get options like by Hour, by minute or AM/PM. But there isn’t an automatic option to group on the half hour.

Below is a formula that will divide all the time values in an hour into two groups. For example, it will turn all time values between 6:00 and 6:29 into 06:00. It will, and will turn all time values between 6:30 and 6:59 into 06:30. You can use this for creating groups, charts or cross-tabs.  Note that the leading zero will keep the groups in the chronological order.

Totext (Hour({@time}),'00') &
(if Minute({@time}) < 30
then ':00'
else ':30')




Sage 50 GetPeach() functions in Windows 10

Monday 9 December 2019 @ 7:38 pm

For some reason the combination of Sage 50 (PeachTree) and Windows 10 generated many calls from customers.  Most of these calls were for reports that used the special GetPeach() functions and that stopped working.  These functions have come with Sage/PeachTree for years. I was told by several Sage consultants that these functions would no longer work in the current versions of Sage 50.  But one of my customers ended up solving the problem on his own and has allowed me to share what he found (he didn’t want to be cited).

So here is how he got these functions working again:

  1. A system PATH variable must be assigned to C:\Program Files (x86)\Sage\Peachtree. This folder contains the DDFs (data dictionary files) for the interface between Peachtree and Crystal Reports and other special functions. Without these DDFs, any effort to run a report containing GetPeach() functions will fail with the error message “The Specified Module Could Not Be Found”.
  2. In a single user environment U2LPeach.dll and the associated .ini file (U2LPeach.ini) must be included in the folder C:\Windows\Crystal. This is the default location the files are placed by Sage 50 when data functions are updated.
  3. If operating in a terminal services environment, U2LPeach.dll and the associated .ini file (U2LPeach.ini) must be included in C:\Users\\Windows\Crystal.
  4. When U2LPeach.dll is loaded correctly, the .dll will appear in the list of dlls loaded by Crystal Reports. This list can be found by going to “Help > About Crystal Reports” and clicking “more information”. Alternately you can edit any formula and look in the additional functions node for the GetPeach functions.

If you are having problems with Sage 50, and the above doesn’t help you, let me know. I have several colleagues who are Sage 50 specialists so we should be able to resolve it for you.




Crystal Reports formula function libraries (2019)

Wednesday 27 November 2019 @ 11:10 pm

It is time for my annual comparison of formula function libraries. If you aren’t familiar with User Function Libraries (or UFLs) they are DLL files that add new formula functions to your Crystal Reports formula editor. With these functions your formulas can do some pretty amazing things like:

1) Carry values from today’s report to tomorrow’s report
2) Carry values from one report to another.
3) Append lines of text to an external text file.
4) Automatically copy a value to the clipboard.
5) Check the user name of the user running the report.
6) See if a file or folder exists (on your network or on the internet).
7) Rename/copy/delete a file on your hard drive or network drive.
8) Launch an application or run a batch file.
9) Execute a SQL statement (Select/Insert/Delete).
10) Send an Email using information in the report.
11) Create a table of contents or an index for your report.
12) Generate bar codes without having to install any fonts

If this sounds interesting you can read my complete comparison including a list of all the functions provided by each DLL. The five UFL providers are:

Bjarke Viksoe (U2lwin32)
Maginus Software (CRUFLMAG)
Millet Software (CUT Light)
Chelsea Tech (File Mgt, Text, Share and others)
CrystalKiwi (Export, Table of Contents)

The only product that has changed since last year is CUT Light, which can now convert numbers to Arabic text and provides more robust encoding for Barcode 128, along with some other enhancements to existing capabilities.

If you need help deploying one of these functions in a project let me know.




Creating a “Distinct Sum” when the duplicates not grouped together

Sunday 24 November 2019 @ 3:36 pm

Crystal has a distinct count function but not a distinct sum. A distinct sum would be a sum that skips values in one field whenever there is a duplicate in a separate “key” field. It would work something like this: “only add each customer’s balance into the total once – even if a customer shows up in several different places in the report. The customer ID would be the “key” field while the balance would be the field you are totaling. It should only count the value on the first instance of the key field.

If you can group the duplicates together the solution is simpler. You can use a running total and set it to “evaluate on change of group” where the group is the key field. I teach this method in my advanced material. But a long ago student showed me a clever way to identify duplicates, even when they were scattered. I had never seen that method and couldn’t find it described anywhere else. I published it in my newsletter in 2004. When I needed to use it last week I decided to post it here.

Last week my challenge was a payroll report that showed employees grouped by department, showing pay and withholding. Pay was split by Dept but the withholding was combined. So when an employee worked in two different departments, his withholding would show up twice in the totals. I needed the totals to count the withholding once per employee, even if the employee showed up in several departments (on different pages).

To use this method you first create a running total field that is a distinct count of the “key” field. Then you create another running total, this time using variables, to total the numeric field. This formula has logic to only add the value when the first running total has changed from the record before. Any time there is a change in the first running total it must mean there is a new value for the “key” field. When the first running total doesn’t change it means that the “key” field value has appeared before. The formula I used looks like this:

WhilePrintingRecords;
NumberVar Prior;
NumberVar LYS;
if {#CustCount} = Prior + 1 //test if first record for this customer
then LYS := LYS + {Customer.Last Year's Sales};
Prior := {#CustCount}; //store current count to use for next record
LYS

If you need a Distinct Sum as a Subtotal you would reset the running total and both of the variables with each group.

I have seen other developers solve this problem by creating an array of all of the “key” values and checking each “key” value against the array before adding the numeric. This method lets Crystal handle that duplicate check so there is no reason to maintain an array.




Correction to “elapsed time string” formula

Saturday 9 November 2019 @ 11:54 pm

My web site has a page for commonly used formulas.  Many have been there for years.  Formula page 9 has two versions of a formula that will convert a number of seconds into an elapsed time string. The long version has days, hours, minutes and seconds.  The shorter version is just hours and minutes.

I was using the short version in a customer’s report and we noticed that the minutes value was sometimes off by one.  After some testing we found that if the remaining seconds were between 30 and 60 the formula would always round down because the formula used a Truncate() function. The Truncate() works correctly in the long version of the formula, because you truncate down to the whole minutes and then display any remaining seconds. But since the shorter version doesn’t display seconds it is more accurate to Round () the seconds to the nearest minute.  So I have updated the short version formula on my site to use the Round () function for minutes.

So those of you who have used that formula should update your reports by changing the last Truncate to a Round, or just taking the updated formula from page 9.




No column headings on the last page (v2.0)

Tuesday 29 October 2019 @ 7:29 pm

Last month I wrote an article about suppressing the page header on the last page when there are no details. This is handy if your last page is a subreport, a chart or a cross-tab. After my newsletter went out one of my readers shared her approach to the same problem. She uses the group header of a dummy group, and sets it to “repeat” on each page.

Any time you have a Group Header you can set it to repeat on each page. One feature of  a repeating GH is that it won’t appear on the last page of a group, unless that page has at least one detail record.  I wrote about that behavior in another article long ago.  So the only trick is to create a primary group that includes every record in the report. Then you set this group header to repeat on each page and it behaves just like a page header, with the exception of not printing on the last page.

So how do you create a group that includes all the records in the report? You group on a value that doesn’t change.  If you have a DB field like “company” that doesn’t change you can use it.  But you can always create a formula that isn’t tied to any data fields.  My favorite dummy group formula is:

WhileReadingRecords;
"All"

The word “All” can be any value. Just keep in mind it will appear as the overall node of the group tree so you might want it to make some sense. The WhileReadingRecords function allows the report to see this static value as a recurring value, which makes it eligible for grouping.

Once you create the formula you use it as Group 1 in the report and then go into Group Options and check “Repeat Group Header on each new page”.  If you put your column headings in this Group Header they will appear on every page, but won’t appear on the last page (unless there are details printing on that page).

And thanks to Tina Nordyke, the DBA for Advocates for Basic Legal Equality, Inc for suggesting this method.




«« Previous Posts
Jeff-Net

Recrystallize Pro

Crystal Reports Server