frequency spec. for sched xactions [and budgeting]
Joshua Sled
jsled@asynchronous.org
Sat, 10 Feb 2001 16:08:42 -0800
Okay... more development of the important part: frequency specification...
Thanks to Pete Yadlowsky <petey@uu.net> for suggesting the frequency
divider common to elec.eng. stuff... here it shows up as a multiplier.
Thanks to Dr. Dave Merrill for eliminating a special "yearly" enum value,
and suggesting that, yes, it really can be that simple... :)
If you take issue with this, it's preferred that you reply to the list.
This isn't perfect, and I list some still-undealt-with issues [difference
between "yearly:360" and "yearly:365", for instance. I've talked to a
coworker of mine who mentioned he was working on the corporate budget
recently, and need to sit down with him to get business-specific issues,
but those can happen in time.
I'm going to call this "good enough for now", and start working on a
generic frequency-editing UI component [for both sched transactions and
budgeting] and the rest of a scheduled transaction editor...
Please forgive the loose-syntax and OO paradigm; that will go away in
implementation.
Once we can create/edit and save/restore scheduled xactions, we'll
integrate them into the rest of GnuCash; recommendations about how to
save/restore the data correctly is appreciated...
More examples which can't be represented by this [see the end] are
encouraged.
...jsled
-----
/**
* Updated frequency enum.
**/
enum FrequencyType {
DAILY,
WEEKLY,
// BI_WEEKLY: use WEEKLY[2]
// SEMI_MONTHLY: use composite
MONTHLY,
// YEARLY: use MONTHLY[12]
MONTH_RELATIVE,
COMPOSITE,
RELATIVE,
};
/**
* A single scheduled transaction.
*
* Scheduled transactions have a list of transactions, and a frequency
* [and associated date anchors] with which they are scheduled.
*
* Things that make sense to have in a template transaction:
* [not] Date [though eventually some/multiple template transactions
* might have relative dates].
* Memo
* Account
* Funds In/Out... or an expr involving 'amt' [A, x, y, a?] for
* variable expenses.
*
* Template transactions are instantiated by:
* . copying the fields of the template
* . setting the date to the calculated "due" date.
*
* We should be able to use the GeneralLedger [or, yet-another-subtype
* of the internal ledger] for this editing.
**/
struct ScheduledTransaction {
FreqSpecifier freq;
// start_date should have a valid value.
time_t start_date;
// If end_date is 0, then no end.
time_t end_date;
// if num_occurances_total is -1, then no limit [and the rest are undef]
int num_occurances_total;
int num_occurances_remaining;
time_t as_of;
/**
* The template transactions.
**/
GList /* <Transaction *> */ *templateTrans;
};
/**
* Integration possibilities:
*
* . create files/interface for scheduled transaction data structs.
* . structs
* . interface suitable for UI
* . programatic interface
* . user configuration:
* . show future scheduled transactions?
* . scheduled transaction post-blue-line window
* . auto-instantiate scheduled transactions?
* . some "since-last-run" UI for instantiating scheduled
* transactions.
* . create stand-alone UI for scheduled transactions.
* . register modifications for scheduled transactions.
* . register indication of recurring/scheduled transaction.
* . post-blue-line display
* . instantiation of pre/post-blue-line scheduled transactions
**/
// -----
/**
* Scheduled transactions have a frequency defined by a frequency
* specifier. This specifier, given a start date, end date [present
* in the scheduled transaction] and last occurance date [possibly not
* present] can be used to determine that a s.transaction should be
* instantiated on a given date [the given query date].
*
* Frequency specifiers have two query operators: one tests a given
* date, the other computes the next date from a given previous or
* start date.
*
*
* This still needs to deal with:
* . exceptions
* . relative dates
* . yearly 360/365?
* . re-based frequencies [based around a non-standard [read:
* not-Jan-1-based/fiscal] year]
* . "business days" -- m-f sans holidays [per-user list thereof]
*
* . user interface...
*
**/
class FreqSpecifier {
FrequencyType freq;
union specData {
/**
* The dateAnchor anchors the spec to determinable days.
*
* ONCE:
* dA[0] contains time_t
* DAILY:
* dA[0] contains day multiplier
* WEEKLY:
* dA[0] contains week multiplier
* dA[1] contains 0..6 [sun-based]
* SEMI_MONTHLY:
* dA[0] contains month multiplier
* dA[1] contains the first date-of-month,
* dA[2] the second.
* MONTHLY:
* dA[0] contains month multiplier
* dA[1] contains the date-of-month
* MONTH_RELATIVE:
* dA[0] continas month multiplier
* dA[1] contains week number [1..5, 6=="last"]
* [1..5 is really 1..4.428 [31/7], but it's a UI issue]
* dA[2] contains 0..6 [sun-based day of week]
* COMPOSITE:
* ... list ...
* RELATIVE:
* ... don't know yet ...
**/
time_t dateAnchor[3];
// a list of specs for a composite freq.
GList *freqSpecs;
};
time_t getNext( time_t start, time_t end, time_t previous = NULL );
bool dueP( time_t start, time_t end, time_t previous, time_t query );
};
/**
* Examples
*
* Here we have a set of plain-text frequency recurrances, and how we
* would represent them.
*
* every day
* DAILY
* every business day
* <not yet:bdays>
* or
* <not yet:"mon-fri" as below + exceptions for holidays>
* mon-fri
* COMPOSITE
* WEEKLY:mon
* WEEKLY:tue
* WEEKLY:wed
* WEEKLY:thu
* WEEKLY:fri
* sat,sun
* COMPOSITE
* WEEKLY:sat
* WEEKLY:sun
* every <day-of-week>
* WEEKLY:<day-of-week>
* the second thursday of the month
* MONTH_RELATIVE:2[2nd],4[thursday]
* the last friday of the month
* MONTH_RELATIVE:6[last],5[friday]
* The 1st and the 17th
* COMPOSITE:
* MONTHLY:1
* MONTHLY:17
* The 15th and the last day of the month
* COMPOSITE:
* MONTHLY:15
* MONTHLY:32[last day]
* every month on the 15th
* MONTHLY:15
* the 4th monday after the beginning of ea. quarter
* MONTHLY_RELATIVE[3]:4[4th],1[monday]; start:Jan
* Jan-Jun-Dec on the 3rd [semi-yearly]
* MONTHLY[6]:3,start:Jan
* Every year on March 18th
* MONTHLY[12]:18,starting on March 18th
**/