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
 **/