Working out Invoice due dates (1 Viewer)

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
Hi All,

I'm trying to work out some invoice due dates. I have the following code:

Code:
DUE DATE: Format(IIf([account terms]="30 days EOM",DateSerial(Year([TaxDate]),Month([TaxDate])+2,1),IIf([account terms]="60 days EOM",DateSerial(Year([TaxDate]),Month([TaxDate])+3,1),[TaxDate])),"Short Date")

My issue is this is not presenting the correct day.

I need the code to add on the days first, then find the end of the month the new date falls in to:

Invoice date 02/01/01 + 30 days is 01/02/01 due date would be 28/02/01

However in the current format it would display the first day of the month following the date it should be due..

I understand I can do
Code:
DUE DATE: Format(IIf([account terms]="30 days EOM",DateSerial(Year([TaxDate]),DAY([TaxDate])+30,

but how to I then get it to display the end of that month?

Edit:

-1 after the following month. Doh.

for anyone hunting:

Code:
DUE DATE: Format(IIf([account terms]="30 days EOM",DateSerial(Year([TaxDate]),Day([TaxDate])+2,1)-1,IIf([account terms]="60 days EOM",DateSerial(Year([TaxDate]),Day([TaxDate])+60,1)-1,[TaxDate])),"Short Date")

TIA
 
Last edited:

ashleedawg

"Here for a good time"
Local time
Today, 04:12
Joined
Jun 22, 2017
Messages
154
This should do the trick:

Code:
 'the last day of the next month:
[DueDate_30dEOM] : dateserial(year(dateadd("m",2,[TaxDate])),month(dateadd("m",2,[TaxDate])),1)-1  
[I]'add 2 months, find the 1st day of that month, and subtract 1 day[/I]

 'the last day of the month after that
[DueDate_60dEOM] : dateserial(year(dateadd("m",3,[TaxDate])),month(dateadd("m",3,[TaxDate])),1)-1 
[I]'add 3 months, find the 1st day of that month, and subtract 1 day[/I]

I didn't update the expression you included since it seems to be missing an 'else' segment. (ie., what happens if [Account Terms] is neither "30 days EOM" nor "60 days EOM"?)
 

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
Hi Ash,

My apologies, I thought I had it licked.

I have a table that has my terms in, two fields, TermID and PaymentTerms
for example TermID is 1 and paymentTerms is 30 Days EOM

There are a total of 13 available payment terms. in order to correct the date value displaced in the query I would need to created a nested iff statement with 14 options. As I understand this isn't possible as it's limited to 7?

So I now I guess I need to create a function to find the correct calculation for the payment terms

What would be the best way to go about this?

Code:
Function CalcDue(TermID As Integer)
 If TermID Like "1" Then
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, 1) - 1"
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
From memory (not always reliable), I think you can have 50 nested IIf statements.
Even so, it would be a nightmare to setup or modify

If payment terms are always the number of days 'grace', then you could use a DLookup function

Code:
Dim N As Integer
N = DLookup("PaymerntTerms","YourTableName", "TermID=" & TermID)
CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, N) - 1"


Otherwise use a Select Case statement for cases 1 to 13

Code:
Function CalcDue

Select Case TermID

Case 1 '30 days
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, 1) - 1"

Case 2 '45 days
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 45, 1) - 1"


Case 3
...

Case 13

End Select

End Function

EDIT: sorry - didn't read the previous reply in this thread - stick with that approach if you prefer
 
Last edited:

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
Thanks for that!

Assuming that the Case number needs to relate to the TermID:

Code:
Function CalcDue()

Select Case TermID

Case 1 '30 days EOM
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, 1) - 1"

Case 2 '60 days EOM
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 60, 1) - 1"


Case 3 'PRO FORMA
        CalcDue = "[TaxDate]"

Case 5 '90 Days EOM
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 90, 1) - 1"

Case 6 '30 Days DOI
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30)"

Case 7 '60 Days DOI
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 60)"
        
Case 8 '45 Days EOM
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 45)"
    
Case 10 '10 Days DOI
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 10)"
        
Case 12 '30 Days EOM +5
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, 1) + 4"
        
Case 13 '60 Days EOM +5
        CalcDue = "DateSerial(Year([TaxDate]), Day([TaxDate]) + 60, 1) + 4"

End Select

I've allowed for where term options have been removed, is this correct?

Then my query expression would be?
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
Do you mean you'd removed cases 4, 9, 11 as they no longer exist?

If so, suggest you add these lines

Code:
 Case 4
'not in use

Similarly for cases 9, 11
Or combine like this

Code:
Case 4, 9, 11
'not in use
 

Minty

AWF VIP
Local time
Today, 12:12
Joined
Jul 26, 2013
Messages
10,371
To add to the excellent advice, if you have a default then add at the end

Code:
Case Else
     CalcDue = Your default goes in here"

Then even if it's not been set for some reason you'll still get an answer.
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
Good point. Forgot that bit


Sent from my iPhone using Tapatalk
 

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
Thanks for the additional advice.

Excuse my ignorance, but how would I use the function in an expression?
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
I assumed you already had that sorted when you suggested a function!

I would suggest you have an after update event in the form used to enter data that will be invoiced.
If TermID is a field on the form, that seems the obvious place

If the field is called InvoiceDueDate and is Date/Time field type then something like

Code:
Private Sub TermID_AfterUpdate
     Me.InvoiceDueDate = #" & CalcDue & "#
End Sub

NOTE: If you aren't US based (mm/dd/yyyy format), you may need to reformat the date to match the default style for your country.
In the UK, its dd/mm/yyyy

If InvoiceDueDate is a text field, then modify the delimiters
 

ashleedawg

"Here for a good time"
Local time
Today, 04:12
Joined
Jun 22, 2017
Messages
154
I'm glad you found my suggestion helpful -- but the formula evolved down the thread, so there are a couple issues:

  1. DateSerial needs it's arguments in the order "Year-Month-Day" regardless of where you live in the world (which is partly why I used it; so your regional settings won't affect the calculation results.)
  2. Some brackets are in the wrong spot. Day([TaxDate]) + 30 versus Day([TaxDate] + 30 )
  3. When adding time to date-parts, you usually will need to add to more than just one part, or else the entire date won't be adjusted (the year in this example).
For "30 days EOM", I suggested: (we'll call this Example A)
DateSerial(Year(DateAdd("m", 2, [TaxDate])), Month(DateAdd("m", 2, [TaxDate])), 1) - 1

as opposed to what it changed to further down this thread: (we'll call this Example B)
DateSerial(Year([TaxDate]), Day([TaxDate]) + 30, 1) - 1


Code:
If we use the date {December 23, 2017}...
[B][I]Example A[/I][/B] will return :
              DateSerial( Year( {February 23, 2018} ), Month( {February 23, 2018} ), 1 )-1 
    which is: DateSerial ( 2018,2,1)-1
    which is: '[U]January 31, 2018[/U]'  [I](Correct, I think?)[/I]
[B][I]Example B[/I][/B] will return :
              DateSerial( Year( {December 23, 2017} ), Day( {December 23, 2017} ) , 1 )-1
    which is: DateSerial ( 2017,23,1)-1
    which is: '[U]October 31, 2018[/U]' [I] (Incorrect, I think?)[/I]
Notice how Example B is always going to return the year of TaxDate even if the DueDate should be in the following year.
Also notice how Example B is putting the DAY in the position where the MONTH should go in DateSerial, which will give some funky results.

Another example is your formula for "60 days EOM" again using {December 23, 2017} :
Code:
Another example is your formula for "60 days EOM" again using {December 23, 2017} :
    CalcDue =  DateSerial(Year([TaxDate]), Day([TaxDate]) + 60, 1) - 1
    which is:  DateSerial(2017, 23+60, 1)-1
    which is:  '[U]October 31, 2023[/U]'  (I need a creditor with terms like that!)
Using DateAdd like I suggested is more reliable than simply adding days since not all months are the same length.

If we need to know the what month it will be one month from {January 30, 2017}:

  • Day([TaxDate]+30) is {March 1, 2017}
    ...so Month(Day([TaxDate]+30))is adding an extra month
  • DateAdd("m",1,[TaxDate]) is {February 28,2017}
    ...so it's (correctly) returning the month of February
I know the DateAdd function makes the formula longer and harder to read, but accuracy first, right? :)

So, I'm afraid some of your formulas need to be re-written... or, now that I see the pattern in the possible Terms, maybe a different method could make the function it a bit more flexible. Also you asked:
how would I use the function in an expression?
...which implies to me that you would like to use the function in a query or on a form, so we can give it some arguments, like this:

Code:
Function CalcDue(fTaxDate As Date, fAccountTerms As String) As Date
'calculate Due Date based on [fTaxDate] with terms of [fAccountTerms]

Dim xDays As Integer, yDays As Integer
fAccountTerms = UCase(fAccountTerms) 'work with uppercase & lowercase letters

If InStr(1, fAccountTerms, "EOM") <> 0 Then 'fAccountTerms contains 'EOM'
    xDays = Val(Left(fAccountTerms, 3)) 'numeric part of the first 3 digits (allows up to 999 days)
    If InStr(1, fAccountTerms, "+") <> 0 Then 'fAccountTerms also contains '+'
        yDays = Val(Right(fAccountTerms, 3)) 'numeric part of the last 3 digits (days to add)
    Else
        yDays = 0 'no '+' so don't add any days
    End If

    'add X days, find the 1st day of that month, and subtract 1 day
    CalcDue = DateSerial(Year(DateAdd("m", 1, DateAdd("d", xDays, fTaxDate))), _
          Month(DateAdd("m", 1, DateAdd("d", xDays, fTaxDate))), 1) - 1
    CalcDue = CalcDue + yDays  'add yDays in case a "+Y" was found
    Exit Function
End If

If Right(fAccountTerms, 3) = "DOI" Then    'Due exactly X days from fTaxDate
    xDays = Val(fAccountTerms) 'returns the numeric part of the string
    CalcDue = DateAdd("d", xDays, fTaxDate)
    Exit Function
End If

If fAccountTerms = "PRO FORMA" Then
    CalcDue = fTaxDate
    Exit Function
End If

'if we made it this far then AccountTerms is unrecognized. Ask user for DueDate by un-commenting this:
'CalcDue = DateValue(InputBox("Unknown Terms: '" & fAccountTerms & "' for TaxDate: '" & fTaxDate & "'." & vbCrLf & vbCrLf & _
    "Enter Due Date: YYYY/MM/DD", "Unknown Terms", fTaxDate))

'or just use default:
CalcDue = fTaxDate 'use default like "PRO FORMA"
    
End Function
You can paste paste that code into a module (Access or Excel), and then call it from a query, form, report, worksheet cell, etc, with:
=CalcDue ( [TaxDate], [AccountTerms] )
for example: CalcDue ( #June 27, 2017#, "30 Days EOM" ) returns 2017-07-31
This function will allow you to specify other 'custom' AccountTerms like "120 Days EOM", "18 Days DOI", "33 Days EOM +12", etc.

;)Of course, the SELECT..CASE method above will work fine too, and may be easier to read, but your formulas need to be fixed up -- test them out with dates like December 30, January 30, and February 28 to see what they calculate. Also keep in mind that it will only be looking for those specific "hardcoded" AccountTerms.
 
Last edited:

Galaxiom

Super Moderator
Staff member
Local time
Today, 21:12
Joined
Jan 20, 2009
Messages
12,853
I wouldn't use Iif() or a custom function. Let the engine do its best work instead.

Firstly, store AccountTerms as a numeric code. Hold the translation for the displayed name string in a lookup table. In that table also include whatever numbers are required as parameters to calculate the whole range of possible due dates for a variety of terms. That might be as simple as the number of days or multiple parameters for a complex EOM formula.

Now join this lookup table to the main table on the AccountTerms numeric code and use the numbers contained there to plug into the formula in the query field that calculates the DueDate. I have no doubt that this technique would vastly outperform the other approaches.

Moreover the AccountTerms calculation data now resides in a table. Any number of new term arrangements can be added by simply adding records to a table. Although custom functions are usually best avoided, if you do ultimately decide on a function to make the calculation, this is a far superior way to feed the parameters.

Always try to think first in terms of data. The term arrangements are data, not mathematical laws and so they belong in a table rather than a function.
 
Last edited:

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
I'm glad you found my suggestion helpful -- but the formula evolved down the thread, so there are a couple issues:

I've included your function in a module and have a query with the expression
Code:
DUE DATE: CalcDue([TaxDate],[PaymentTerms])

however when i view the query results I have a strange scenario:

The invoice number relates to 4 records in another table. the tax date is associated with the invoice number in an separate table.

however, viewing the query brings all the records on that invoice, but with some strange due dates:

https://www.flickr.com/photos/156119646@N07/shares/4Dio45

There are two images in the link, the first is the Query result, the second is a PDF of an invoice created prior to this work, please note I'm trying to rectify the due date being the first of the month instead of the last day of the previous month.

Cheers for the help again!
 

ashleedawg

"Here for a good time"
Local time
Today, 04:12
Joined
Jun 22, 2017
Messages
154
Hmm, it's tough to troubleshoot this at a distance.

Re: the list of 10 items with the same invoice number, ("QryInvoiceNetTotal", I think) I gather there are not actually 10 invoices with the same number?... Could you add a few more fields to that same query, like [TaxDate] and [PaymentTerms], and post the result? There must be "10 of something" joined to that one invoice, and judging by the different DueDates, it's got something to do with the date that CalcDue is looking at.

If you remove the CalcDue expression from that query does it return only the 1 invoice?

Are you possibly using the terms "CalcDue", "TaxDate", or "PaymentTerms" in multiple places, like as both a field name in a table, and a control on a form? That could confuse things as well.
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
Hi

You now have 3 very different solutions to choose from, all of which should work. In the end you need to decide which works best for you.

You say you got some strange results using ashleedawg's solution
I must admit I also couldn't understand what the results you uploaded actually meant.

Just out of interest, did you actually test the select case solution I suggested and if so did that behave in the way you expected? All I know is you populated the function but not whether you tried it out....

If you can't fix this yourself, suggest you upload a stripped down version of your db with some realistic data and stating what the due date results should be for each record.
 

Maclain

Registered User.
Local time
Today, 12:12
Joined
Sep 30, 2008
Messages
109
Hi Everyone, thanks for the help with this.

My error was generated from passing the wrong info to the function.

Please consider this solved.
 

isladogs

MVP / VIP
Local time
Today, 12:12
Joined
Jan 14, 2017
Messages
18,242
Hi

Glad you fixed it.
In the end, did you need any of the 3 solutions offered?

Please can you mark the thread as SOLVED - click the Thread Tools dropdown
 

Users who are viewing this thread

Top Bottom