commit 9a07bb7df6e733c8c0d7b7eeb9f03bd0a9fa60e8
parent 9bce6572cf4e8760695dc3d5f33b2b60891341e4
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 4 Jan 2026 16:14:56 +0100
fix report generation logic
Diffstat:
6 files changed, 237 insertions(+), 45 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -1152,6 +1152,8 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
resume_pay_with_error (pc,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
"batch_deposit_transaction");
+ TMH_db->rollback (TMH_db->cls);
+ return;
}
qs = TMH_db->commit (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -1250,7 +1252,8 @@ batch_deposit_cb (
case MHD_HTTP_OK:
handle_batch_deposit_ok (eg,
dr);
- if (0 == pc->batch_deposits.pending_at_eg)
+ if ( (GNUNET_YES == pc->suspended) &&
+ (0 == pc->batch_deposits.pending_at_eg) )
{
pc->phase = PP_COMPUTE_MONEY_POTS;
pay_resume (pc);
diff --git a/src/backend/taler-merchant-httpd_private-get-statistics-report-transactions.c b/src/backend/taler-merchant-httpd_private-get-statistics-report-transactions.c
@@ -295,8 +295,7 @@ static enum GNUNET_GenericReturnValue
make_transaction_volume_report (struct ResponseContext *rctx,
json_t *charts)
{
- // FIXME: this is from example-statistics, we probably want to hard-wire the stats from this endpoint!
- const char *bucket_name = "sales (before refunds)";
+ const char *bucket_name = "deposits-received";
enum GNUNET_DB_QueryStatus qs;
json_t *chart;
json_t *labels;
@@ -319,7 +318,7 @@ make_transaction_volume_report (struct ResponseContext *rctx,
rctx->hc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_statistics_amount_by_bucket"))
+ "lookup_statistics_amount_by_bucket2"))
? GNUNET_NO : GNUNET_SYSERR;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@@ -449,7 +448,7 @@ static enum GNUNET_GenericReturnValue
make_transaction_count_report (struct ResponseContext *rctx,
json_t *charts)
{
- const char *prefix = "transaction-state-";
+ const char *prefix = "orders-paid";
enum GNUNET_DB_QueryStatus qs;
json_t *chart;
json_t *labels;
@@ -472,7 +471,7 @@ make_transaction_count_report (struct ResponseContext *rctx,
rctx->hc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_statistics_XXX"))
+ "lookup_statistics_counter_by_bucket2"))
? GNUNET_NO : GNUNET_SYSERR;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
diff --git a/src/backend/taler-merchant-report-generator-file b/src/backend/taler-merchant-report-generator-file
@@ -0,0 +1,114 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# Copyright (C) 2025 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+#
+
+#
+# Script to write Taler merchant reports to a file.
+# Reads report data from stdin and writes it to a file.
+#
+# Usage: taler-merchant-report-generator-file -d DESCRIPTION -m MIME_TYPE -t TARGET_ADDRESS
+#
+
+set -eu
+
+DESCRIPTION=""
+MIME_TYPE=""
+TARGET_ADDRESS=""
+TMPDIR="${TMPDIR:-/tmp}"
+
+while getopts "d:m:t:h" opt; do
+ case $opt in
+ d)
+ DESCRIPTION="$OPTARG"
+ ;;
+ m)
+ MIME_TYPE="$OPTARG"
+ ;;
+ t)
+ TARGET_ADDRESS="$OPTARG"
+ ;;
+ h)
+ echo "Usage: $0 -d DESCRIPTION -m MIME_TYPE -t EMAIL_ADDRESS"
+ echo ""
+ echo "Sends reports via email."
+ echo ""
+ echo "Options:"
+ echo " -d DESCRIPTION Subject line for the email"
+ echo " -m MIME_TYPE MIME type of the report (e.g., text/plain, application/pdf)"
+ echo " -t EMAIL_ADDRESS Email address to send the report to"
+ echo " -h Show this help message"
+ echo ""
+ echo "The report data is read from stdin."
+ exit 0
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2
+ echo "Use -h for help" >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [ -z "$DESCRIPTION" ];
+then
+ echo "Error: Description (-d) is required" >&2
+ exit 1
+fi
+
+if [ -z "$MIME_TYPE" ];
+then
+ echo "Error: MIME type (-m) is required" >&2
+ exit 1
+fi
+
+if [ -z "$TARGET_ADDRESS" ];
+then
+ echo "Error: Target address (-t) is required" >&2
+ exit 1
+fi
+
+# Normalize MIME type to lowercase for comparison
+MIME_TYPE=$(echo "$MIME_TYPE" | tr '[:upper:]' '[:lower:]')
+
+# Handle different MIME types
+case "$MIME_TYPE" in
+ text/plain)
+ cat - > "$TARGET_ADDRESS.txt"
+ ;;
+ application/pdf)
+ cat - > "$TARGET_ADDRESS.pdf"
+ ;;
+ application/json)
+ cat - > "$TARGET_ADDRESS.json"
+ ;;
+ application/xml)
+ cat - > "$TARGET_ADDRESS.xml"
+ ;;
+ text/csv)
+ cat - > "$TARGET_ADDRESS.csv"
+ ;;
+ image/jpeg)
+ cat - > "$TARGET_ADDRESS.jpeg"
+ ;;
+ image/png)
+ cat - > "$TARGET_ADDRESS.png"
+ ;;
+ *)
+ cat - > "$TARGET_ADDRESS"
+ ;;
+esac
+
+exit 0
diff --git a/src/backenddb/example-statistics-0001.sql b/src/backenddb/example-statistics-0001.sql
@@ -61,37 +61,6 @@ VALUES
,ARRAY[1,1, 60, 60 * 60, 24 * 60 * 60] -- second, second, minute, hour, day
);
-CREATE FUNCTION merchant_deposits_statistics_trigger()
-RETURNS trigger
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_instance INT8;
-BEGIN
- SELECT mct.merchant_serial
- INTO my_instance
- FROM merchant_contract_terms mct
- JOIN merchant_deposit_confirmations mdc
- USING (order_serial)
- WHERE mdc.deposit_confirmation_serial = NEW.deposit_confirmation_serial;
- CALL merchant_do_bump_amount_stat
- ('deposits'
- ,my_instance
- ,NEW.amount_with_fee);
- RETURN NEW;
-END $$;
-COMMENT ON FUNCTION merchant_deposits_statistics_trigger
- IS 'adds the deposited amount to the deposit statistics';
-
--- Whenever a deposit is made, call our trigger to bump statistics
-CREATE TRIGGER merchant_deposits_on_insert
- AFTER INSERT
- ON merchant.merchant_deposits
- FOR EACH ROW EXECUTE FUNCTION merchant_deposits_statistics_trigger();
-
--- This is for now just an example for how to use the API. --- END EXAMPLE
-
-
-- This is just another example for how to use the API. -- BEGIN EXAMPLE
@@ -124,6 +93,7 @@ BEGIN
CALL merchant_do_bump_number_stat
('products-sold'
,NEW.merchant_serial
+ ,CURRENT_TIMESTAMP(0)::TIMESTAMP
,my_sold);
END IF;
RETURN NEW;
diff --git a/src/backenddb/merchant-0028.sql b/src/backenddb/merchant-0028.sql
@@ -130,6 +130,102 @@ COMMENT ON COLUMN merchant_inventory.price_is_net
IS 'If true, the price given is the net price; if false, it is the gross price.';
+CREATE FUNCTION merchant_deposits_insert_statistics_trigger()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_instance INT8;
+BEGIN
+ SELECT mct.merchant_serial
+ INTO my_instance
+ FROM merchant_contract_terms mct
+ JOIN merchant_deposit_confirmations mdc
+ USING (order_serial)
+ WHERE mdc.deposit_confirmation_serial = NEW.deposit_confirmation_serial;
+ CALL merchant_do_bump_amount_stat
+ ('deposits-received'
+ ,my_instance
+ ,CURRENT_TIMESTAMP(0)::TIMESTAMP
+ ,NEW.amount_with_fee);
+ CALL merchant_do_bump_amount_stat
+ ('deposits-fees-paid'
+ ,my_instance
+ ,CURRENT_TIMESTAMP(0)::TIMESTAMP
+ ,NEW.deposit_fee);
+ RETURN NEW;
+END $$;
+
+
+-- Whenever a contract is updated, call our trigger to bump statistics
+CREATE TRIGGER merchant_deposits_on_insert_statistic
+ AFTER INSERT
+ ON merchant_deposits
+ FOR EACH ROW EXECUTE FUNCTION merchant_deposits_insert_statistics_trigger();
+
+
+CREATE FUNCTION merchant_expected_transfers_insert_statistics_trigger()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_instance INT8;
+BEGIN
+ SELECT mct.merchant_serial
+ INTO my_instance
+ FROM merchant_expected_transfers met
+ JOIN merchant_accounts ma
+ USING (account_serial)
+ WHERE met.expected_credit_serial = NEW.expected_credit_serial;
+ CALL merchant_do_bump_amount_stat
+ ('wire-fees-paid'
+ ,my_instance
+ ,CURRENT_TIMESTAMP(0)::TIMESTAMP
+ ,NEW.wire_fee);
+ RETURN NEW;
+END $$;
+
+
+-- Whenever a contract is updated, call our trigger to bump statistics
+CREATE TRIGGER merchant_expected_transfers_on_insert_statistic
+ AFTER INSERT
+ ON merchant_expected_transfers
+ FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_insert_statistics_trigger();
+
+
+
+-- Enable additional bucket statistics
+INSERT INTO merchant_statistic_bucket_meta
+ (slug
+ ,description
+ ,stype
+ ,ranges
+ ,ages)
+VALUES
+ ('deposits-received'
+ ,'total amount customers deposited to us (including deposit fees)'
+ ,'amount'
+ ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY[72, 14, 12, 24, 12, 10]
+ ),
+ ('deposits-fees-paid'
+ ,'total amount in deposit fees paid by us or our customers'
+ ,'amount'
+ ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY[72, 14, 12, 24, 12, 10]
+ ),
+ ('wire-fees-paid'
+ ,'amount in wire fees paid by'
+ ,'amount'
+ ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY[72, 14, 12, 24, 12, 10]
+ ),
+ ('orders-paid'
+ ,'number of orders paid (but not necessarily settled by the exchange)'
+ ,'number'
+ ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY[72, 14, 12, 24, 12, 10]
+ );
diff --git a/src/backenddb/pg_lookup_statistics_counter_by_bucket2.c b/src/backenddb/pg_lookup_statistics_counter_by_bucket2.c
@@ -107,13 +107,23 @@ lookup_statistics_counter_by_bucket_cb2 (void *cls,
GNUNET_PQ_cleanup_result (rs);
return;
}
-
- bucket_start = GNUNET_TIME_timestamp_from_s (bucket_start_epoch);
- tflc->cb (tflc->cb_cls,
- bucket_start,
- num_slugs,
- (const char **) slugs,
- counters);
+ {
+ const char *slugptrs[num_slugs];
+ const char *pos = slugs;
+
+ for (size_t j = 0; j<num_slugs; j++)
+ {
+ slugptrs[j] = pos;
+ pos += strlen (pos) + 1;
+ }
+
+ bucket_start = GNUNET_TIME_timestamp_from_s (bucket_start_epoch);
+ tflc->cb (tflc->cb_cls,
+ bucket_start,
+ num_slugs,
+ slugptrs,
+ counters);
+ }
GNUNET_PQ_cleanup_result (rs);
}
}
@@ -159,7 +169,7 @@ TMH_PG_lookup_statistics_counter_by_bucket2 (
" JOIN merchant_instances mi"
" USING (merchant_serial)"
" WHERE mi.merchant_id=$1"
- " AND msbm.slug LIKE $2 || '%'"
+ " AND msbm.slug LIKE $2"
" AND msbc.bucket_range=$3::TEXT::statistic_range"
" AND msbm.stype = 'number'"
" GROUP BY msbc.bucket_start"