commit c601d1cc47eea447d15c7dec6c62d99a0be89cc9
parent a13d30ecd89da7cf234f6a2bab9d16d5a785d553
Author: Christian Grothoff <christian@grothoff.org>
Date: Fri, 7 Aug 2026 00:18:53 +0200
properly track annual totals in history
Diffstat:
2 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/src/donaudb/do_insert_receipt_issued.sql b/src/donaudb/do_insert_receipt_issued.sql
@@ -28,8 +28,10 @@ AS $$
DECLARE
old_receipts_to_date taler_amount;
new_receipts_to_date taler_amount;
+ prior_receipts_to_date taler_amount;
max_per_year taler_amount;
my_year INT4;
+ rolled_over BOOLEAN DEFAULT FALSE;
BEGIN
-- Get charity values
SELECT
@@ -62,7 +64,12 @@ BEGIN
END IF;
IF (my_year < in_year)
THEN
- -- bump current year
+ -- bump current year; the closing total of the year we are leaving
+ -- must be preserved in `history' before it is reset (see the comment
+ -- on charities.receipts_to_date). The row is only written if the
+ -- request actually succeeds, see below.
+ rolled_over = TRUE;
+ prior_receipts_to_date = old_receipts_to_date;
old_receipts_to_date.val = 0;
old_receipts_to_date.frac = 0;
END IF;
@@ -77,6 +84,18 @@ BEGIN
(max_per_year.frac >= new_receipts_to_date.frac) ) )
THEN
out_smaller_than_max_per_year=TRUE;
+ IF (rolled_over)
+ THEN
+ INSERT INTO history
+ (charity_id
+ ,final_amount
+ ,donation_year
+ ) VALUES (
+ in_charity_id
+ ,prior_receipts_to_date
+ ,my_year
+ ) ON CONFLICT (charity_id, donation_year) DO NOTHING;
+ END IF;
UPDATE charities
SET receipts_to_date=new_receipts_to_date,
current_year=in_year
diff --git a/src/donaudb/test_donaudb.c b/src/donaudb/test_donaudb.c
@@ -168,6 +168,59 @@ iterate_active_signkeys_cb (
/**
+ * Closure for #collect_history_cb().
+ */
+struct HistoryCheck
+{
+ /**
+ * Charity we are interested in.
+ */
+ unsigned long long charity_id;
+
+ /**
+ * Year we expect a history entry for.
+ */
+ uint64_t donation_year;
+
+ /**
+ * Amount we expect that entry to carry.
+ */
+ struct TALER_Amount expected;
+
+ /**
+ * Set to true if the expected entry was found.
+ */
+ bool found;
+};
+
+
+/**
+ * Look for the history entry described by @a cls.
+ *
+ * @param cls a `struct HistoryCheck *`
+ * @param charity_id charity of this entry
+ * @param final_amount closing total of that year
+ * @param donation_year year of this entry
+ * @return #GNUNET_OK to continue
+ */
+static enum GNUNET_GenericReturnValue
+collect_history_cb (void *cls,
+ unsigned long long charity_id,
+ struct TALER_Amount final_amount,
+ uint64_t donation_year)
+{
+ struct HistoryCheck *hc = cls;
+
+ if ( (charity_id == hc->charity_id) &&
+ (donation_year == hc->donation_year) &&
+ (0 == TALER_amount_cmp (&final_amount,
+ &hc->expected)) )
+ hc->found = true;
+ return GNUNET_OK;
+}
+
+
+/**
* Main function that will be run by the scheduler.
*
* @param cls closure with config
@@ -510,6 +563,62 @@ run (void *cls)
2027));
}
+ /* D-4: the first request of a new year resets receipts_to_date; the
+ closing total of the year being left must be preserved in `history'
+ rather than discarded. */
+ {
+ uint64_t rollover_charity_id;
+ struct DONAUDB_CharityMetaData rollover_meta;
+ struct HistoryCheck hc;
+ struct TALER_Amount zero;
+ bool under_limit = false;
+ uint32_t this_year = GNUNET_TIME_get_current_year ();
+
+ FAILIF (GNUNET_OK !=
+ make_charity (CURRENCY ":1000",
+ &rollover_charity_id));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ issue_receipt (rollover_charity_id,
+ this_year,
+ CURRENCY ":90",
+ &under_limit));
+ FAILIF (! under_limit);
+ /* First request of the following year. */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ issue_receipt (rollover_charity_id,
+ this_year + 1,
+ CURRENCY ":5",
+ &under_limit));
+ FAILIF (! under_limit);
+
+ memset (&hc,
+ 0,
+ sizeof (hc));
+ hc.charity_id = (unsigned long long) rollover_charity_id;
+ hc.donation_year = this_year;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":90",
+ &hc.expected));
+ FAILIF (0 >
+ DONAUDB_iterate_history_entries (ctx,
+ &collect_history_cb,
+ &hc));
+ FAILIF (! hc.found);
+
+ /* ... and the new year did start from zero. */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ DONAUDB_get_charity (ctx,
+ rollover_charity_id,
+ &rollover_meta));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":5",
+ &zero));
+ FAILIF (0 != TALER_amount_cmp (&rollover_meta.receipts_to_date,
+ &zero));
+ GNUNET_free (rollover_meta.charity_name);
+ GNUNET_free (rollover_meta.charity_url);
+ }
+
result = 0;
drop: