[trep-engine] allow csv export of grand-totals

This commit enables CSV export of split monetary columns and
grand-totals.

Sample CSV output:
"from","2018-01-01"
"to","2018-03-31"
"Debit",4270.88
"Credit",8314.44

* Note dates are iso-format strings.

* If a grand-total has multiple currencies, all will be listed
  e.g. grand-total 100GBP + 50USD will show as: "Amount",100.0,50.0

* Dual subtotals (debit/credit) will not be exportable as CSVs because
  they are not merged
This commit is contained in:
Christopher Lam 2019-05-03 13:19:08 +08:00
parent ca759fb492
commit 497e18c36e
2 changed files with 91 additions and 4 deletions

View File

@ -18,6 +18,7 @@
;; - add subtotal summary grid
;; - by default, exclude closing transactions from the report
;; - converted to module in 2019
;; - CSV export, exports the report headers and totals
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
@ -459,6 +460,46 @@ Credit Card, and Income accounts."))
(and (keylist-get-info (sortkey-list split-action?) sortkey 'split-sortvalue)
(not (keylist-get-info (sortkey-list split-action?) sortkey 'sortkey))))
(define (lists->csv lst)
;; converts a list of lists into CSV
;; this function aims to follow RFC4180, and will pad lists to
;; ensure equal number of items per row.
;; e.g. '(("from" "01/01/2010")
;; ("to" "31/12/2010")
;; ("total" 23500 30000 25/7 'sym))
;; will output
;; "from","01/01/2010",,,
;; "to","31/12/2010",,,
;; "total",23500.0,30000.0,3.5714285714285716,sym
(define (string-sanitize-csv str)
(call-with-output-string
(lambda (port)
(display #\" port)
(string-for-each
(lambda (c)
(if (char=? c #\") (display #\" port))
(display c port))
str)
(display #\" port))))
(define max-items (apply max (map length lst)))
(define (strify obj)
(cond
((not obj) "")
((string? obj) (string-sanitize-csv obj))
((number? obj) (number->string (exact->inexact obj)))
((list? obj) (string-join
(map strify
(append obj
(make-list (- max-items (length obj)) #f)))
","))
((gnc:gnc-monetary? obj) (strify (gnc:gnc-monetary-amount obj)))
(else (object->string obj))))
(string-join (map strify lst) "\n"))
;;
;; Default Transaction Report
;;
@ -1746,7 +1787,18 @@ be excluded from periodic reporting.")
(loop rest (not odd-row?) (1+ work-done)))))
(values table grid)))
(let ((csvlist (cond
((any (lambda (cell) (vector-ref cell 4)) calculated-cells)
;; there are mergeable cells. don't return a list.
(N_ "CSV disabled for double column amounts"))
(else
(map
(lambda (cell coll)
(cons (vector-ref cell 0)
(coll 'format gnc:make-gnc-monetary #f)))
calculated-cells total-collectors)))))
(values table grid csvlist))))
;; grid data structure
(define (make-grid)
@ -1840,7 +1892,8 @@ be excluded from periodic reporting.")
(define* (gnc:trep-renderer
report-obj #:key custom-calculated-cells empty-report-message
custom-split-filter split->date split->date-include-false?)
custom-split-filter split->date split->date-include-false?
export-type filename)
;; the trep-renderer is a define* function which, at minimum, takes
;; the report object
;;
@ -1854,6 +1907,7 @@ be excluded from periodic reporting.")
;; split->date returns #f. useful to include unreconciled splits in reconcile
;; report. it can be useful for alternative date filtering, e.g. filter by
;; transaction->invoice->payment date.
;; #:export-type and #:filename - are provided for CSV export
(define options (gnc:report-options report-obj))
(define (opt-val section name)
@ -2081,7 +2135,7 @@ be excluded from periodic reporting.")
(gnc:html-render-options-changed options))))
(else
(let-values (((table grid)
(let-values (((table grid csvlist)
(make-split-table splits options custom-calculated-cells)))
(gnc:html-document-set-title! document report-title)
@ -2115,6 +2169,30 @@ be excluded from periodic reporting.")
(gnc:html-document-add-object!
document (grid->html-table grid list-of-rows list-of-cols))))
(cond
((and (eq? export-type 'csv)
(string? filename)
(not (string-null? filename)))
(let ((old-date-fmt (qof-date-format-get))
(dummy (qof-date-format-set QOF-DATE-FORMAT-ISO))
(infolist
(list
(list "from" (qof-print-date begindate))
(list "to" (qof-print-date enddate)))))
(qof-date-format-set old-date-fmt)
(if (list? csvlist)
(catch #t
(lambda ()
(with-output-to-file filename
(lambda ()
(display (lists->csv (append infolist csvlist))))))
(lambda (key . args)
;; Translators: ~a error type, ~a filename, ~s error details
(let ((fmt (N_ "error ~a during csv output to ~a: ~s")))
(gnc:gui-error (format #f fmt key filename args)
(format #f (_ fmt) key filename args)))))
(gnc:gui-error csvlist (_ csvlist))))))
(unless (and subtotal-table?
(opt-val pagename-sorting optname-show-subtotals-only))
(gnc:html-document-add-object! document table)))))))

View File

@ -280,4 +280,13 @@ for taxes paid on expenses, and type LIABILITY for taxes collected on sales.")
'name reportname
'report-guid "5bf27f249a0d11e7abc4cec278b6b50a"
'options-generator gst-statement-options-generator
'renderer gst-statement-renderer)
'renderer gst-statement-renderer
'export-types (list (cons "CSV" 'csv))
'export-thunk (lambda (report-obj export-type file-name)
(gnc:trep-renderer
report-obj
#:custom-calculated-cells gst-calculated-cells
#:empty-report-message TAX-SETUP-DESC
#:custom-split-filter gst-custom-split-filter
#:export-type export-type
#:filename file-name)))