Skip to content

GdPDU Export#2116

Draft
jhs-s wants to merge 9 commits intoGnucash:stablefrom
jhs-s:gdpdu-export
Draft

GdPDU Export#2116
jhs-s wants to merge 9 commits intoGnucash:stablefrom
jhs-s:gdpdu-export

Conversation

@jhs-s
Copy link

@jhs-s jhs-s commented Aug 4, 2025

German tax authorities expect an standardized export of the electronic accounting in a Format called GoBD or GdPDU. This is essentially an CSV Format which can contain multiple files and an index.xml file describing those files. The format is rather flexible but it still expects that a booking is always on Record.

This branch adds an Option for exporting all bookings in a certain CSV-Format and takes care that "multiple bookings" are exported as several single bookings. The standard CSV export cannot do that and the extended CSV export is not able to but a booking into a single line.

This involved a refactoring of the CSV-Export, creating a new class that takes care of the different options.

There might be open points as I could only test with my accounting data which might not be fully representative. It also cannot cope with multiple bookings that do not have a single origin account. In this case it will abort. However, normal invoice bookings with VAT shouldn't have any issues.

There is a (german) description here: https://wiki.gnucash.org/wiki/De/howto

@jralls
Copy link
Member

jralls commented Aug 4, 2025

Merge branch 'master' into gdpdu-export

Don't do that, it messes up history. Always rebase your feature branch on the main branch (in this case stable).

contrib: Add necessary files for full gdpdu-export

I think these files would fit better in data.

Copy link
Member

@jralls jralls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of time for now, and I got through only one commit. More later.

Comment on lines -48 to +50
bool
gnc_csv_add_line (std::ostream& ss, const StringVec& str_vec,
bool use_quotes, const char* sep)
bool gnc_csv_add_line(std::ostream &ss, const StringVec &str_vec,
bool use_quotes, const char *sep)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the Coding Standard: In a definition the function name should start on a new line to make it easy to grep for, e.g. grep -rl ^gnc_csv_add_line gnucash/import-export. Declarations should have the return type, storage class, etc. on the same line as the function name.

}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GnuCash is primarily a GUI application that runs without a console so output to std::cout and std::cerr are lost; on Windows they're lost even with a console. We have a logging library that writes to a file instead with handy macros PERR, PWARN, PINFO, and PDEBUG. In this case PWARN seems appropriate: `PWARN("Exception %s", e.what(). The macro will add a timestamp and the name of the function.

Comment on lines +2 to +5
* csv-transactions-export-line.cpp -- Convert Transaction to line *
* *
* Copyright (C) 2025 Johannes Triegel *
* *
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix up the whitespace so the ending *s line up.

Comment on lines 45 to 55
bool simple,
std::ofstream &ss) : m_split(split),
m_transaction(transaction),
m_separator(separator),
m_use_quotes(use_quotes),
m_simple(simple),
m_ss(ss),
m_is_debit_split(false),
m_is_credit_split(false),
m_base_split(NULL),
m_base_split_account(NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please start the initializers on a new line and put several on a line to cut down on the empty real estate.

}
if (debit > 1 && credit > 1)
{
std::cerr << "Multi split booking that needs to be booked on an intermediate account, no implemented!" << std::endl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use PINFO instead of std::cerr.

Also a typo: "not implemented" instead of "no implemented".


/* Run the query */
for (GList *splits = qof_query_run (info->query); !info->failed && splits;
for (GList *splits = qof_query_run(info->query); /*!info->failed && */ splits;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't comment out code, remove it.

This should be a separate commit with the commit message explaining why you don't want to abort the loop on a print_csv failure.

Comment on lines +74 to +77
if (!trans_set.emplace(trans).second)
{
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add braces around single-line conditional or loop bodies..

CsvTransactionExportLine line(split, trans, info->separator_str, info->use_quotes, info->simple_layout, ss);
info->failed = !line.print_csv();

#if 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#if 0 is the same as comments: Just remove the code.

Copy link
Member

@jralls jralls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how CsvTransactionExportLine contributes anything except unnecessary overhead, and a major refactoring like this demands that you start by adding tests that prove the behavior of the existing code, then do the refactor with the tests still passing.

A PR shouldn't have "fix" commits in it: All of that should be cleaned up before submission.

But even if this were well designed and coded--and it isn't--I'd be hesitant to merge it because it's of use in only in Germany and we have no German core developers to maintain it.


bool print_csv();

protected:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected is to allow access to derived classes. CsvTransactionExportLine isn't an abstract class--it doesn't even have any virtual members--so there should be no protected members, only private and public. These are all getters so there's no good reason for them to be private.

Comment on lines 78 to 81
const char *m_separator;
bool m_use_quotes;
bool m_simple;
std::ofstream &m_ss;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be passed as parameters to the relevant function, not held as object state.

Comment on lines 54 to 55
m_base_split(NULL),
m_base_split_account(NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is C++. NULL is spelled nullptr.

}

// Find the base_split
Split *m_base_split = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already did that in the initializers.

}

using TransSet = std::unordered_set<Transaction*>;
using TransSet = std::unordered_set<Transaction *>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's innapropriate whitespace. Transaction* is a C++ type. It has been C++ convention basically forever that type names are kept together, though because C++'s backward compatibility with C allows a line like
Transaction* foo, bar;
would make foo a Transaction* and bar a Transaction it's necessary to either write
Transaction *foo, *bar; with the pointer indicator stuck to the variables for consistency or

Transaction *foo;
Transaction *bar;

Stroustroup (Bjarne Stroustoup, The C++ Programming Language Fourth Edition, Addison Wesley, 2013, p 155) writes of the first way "Such declarations with multiple names and nontrivial declarators make a program harder to read and should be avoided." While GnuCash has a C heritage and most of the C++ files are largely renamed C code containing C conventions that's no reason to use C coding conventions in new C++ code.

Comment on lines 86 to 87
bool m_simple;
bool m_gdpdu;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case where an enum would be better than an accumulation of bools plus a hidden default when both are false.

Comment on lines +150 to +151
/* Translators: The following symbols will build the header
line of exported CSV files for german GdPDU-Export: */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't work: Each translatable string is presented separately on Weblate; translator comments are presented only when the very next line is a translatable string and this one would be applied to only "Date". Also all of these strings are already in use without context. On the one hand that's good because you don't have to worry about the translations. Just check de.po to make sure that the msgstrs for each one are correct for this case.

auto type = xaccAccountGetType(account);
bool pos = gnc_numeric_positive_p(amount);

/* It is possible that other types of accounts need to be taken into account here */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a FIXME: GnuCash's account types aren't tested hierarchically so even though ACCT_TYPE_INCOME and ACCT_TYPE_EXPENSE are Equity they won't set exchange true.

A comment explaining why the presence of an Equity account justifies switching the order of the account numbers in a single transaction would be useful.

Comment on lines 382 to 401
StringVec
CsvTransactionExportLine::make_simple_trans_split_line(Split *split)
{
auto t_void{xaccTransGetVoidStatus(m_transaction)};
gnc_numeric amount = xaccSplitGetAmount(split);
bool pos = gnc_numeric_positive_p(amount);
return {
get_date(m_transaction),
pos ? xaccAccountGetName(m_base_split_account) : get_account_name(split, true),
get_number(m_transaction),
get_description(m_transaction),
pos ? get_account_name(split, true) : xaccAccountGetName(m_base_split_account),
get_reconcile(split),
get_amount(split, t_void, true),
get_amount(split, t_void, false),
get_value(split, t_void, true),
get_value(split, t_void, false),
get_rate(split, t_void)};
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed from the second commit instead of adding it there and throwing it away later.

// Amount with Symbol or not
std::string
CsvTransactionExportLine::get_amount(Split *split, bool t_void, bool symbol)
CsvTransactionExportLine::get_amount(Split *split, bool t_void, bool symbol, bool positive)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple boolean function args is poor design, it's too easy to screw up the order.

@jhs-s
Copy link
Author

jhs-s commented Oct 13, 2025

Thanks for all your comments and sorry for not replying - I was assuming that github would somehow send me Mails on comments.

I will try to resolve the issues...

@jhs-s
Copy link
Author

jhs-s commented Oct 13, 2025

Merge branch 'master' into gdpdu-export

Don't do that, it messes up history. Always rebase your feature branch on the main branch (in this case stable).

Is there a clever way to do that without creating a new pull request. I guess I cannot change the existing history of the branch.

contrib: Add necessary files for full gdpdu-export

I think these files would fit better in data.

ok, no problem.

@jhs-s jhs-s marked this pull request as draft October 13, 2025 19:23
@jralls
Copy link
Member

jralls commented Oct 13, 2025

Is there a clever way to do that without creating a new pull request. I guess I cannot change the existing history of the branch.

You can change the branch history as much as you want back to the branch point, then force-push it (git push -f github gdpdu-export where github stands in for whatever you call this remote). That will update the PR, no new PR required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants