[Gnucash-changes] Neil Williams' QOF book merge patch #1.
Derek Atkins
warlord at cvs.gnucash.org
Sat Oct 16 11:51:30 EDT 2004
Log Message:
-----------
Neil Williams' QOF book merge patch #1.
Includes
qof_book_merge.c
qof_book_merge.h
test-book-merge.c test routine
New Account Hierarchy druid
Sundry adjustments to QOF support.
Tweaks to several Makefile.am files to support new files.
Tweaks to window-main.c to support new menu item
Changes to druid-hierarchy.c to support the merge druid.
Modified Files:
--------------
gnucash:
ChangeLog
gnucash/src/business/business-core:
gncAddress.c
gncAddress.h
gncBillTerm.c
gncCustomer.c
gncCustomer.h
gncEntry.h
gncJob.h
gncOrder.c
gncTaxTable.c
gncTaxTable.h
gnucash/src/engine:
Makefile.am
gnc-pricedb.c
gnucash/src/engine/test:
Makefile.am
gnucash/src/gnome:
Makefile.am
druid-hierarchy.c
druid-hierarchy.h
window-main.c
gnucash/src/gnome/glade:
Makefile.am
Added Files:
-----------
gnucash/src/engine:
qof_book_merge.c
qof_book_merge.h
gnucash/src/engine/test:
test-book-merge.c
gnucash/src/gnome:
druid-merge.c
druid-merge.h
gnucash/src/gnome/glade:
merge.glade
Revision Data
-------------
Index: ChangeLog
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/ChangeLog,v
retrieving revision 1.1847
retrieving revision 1.1848
diff -LChangeLog -LChangeLog -u -r1.1847 -r1.1848
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,18 @@
+2004-10-16 Derek Atkins <derek at ihtfp.com>
+
+ * Neil Williams' QOF book merge patch #1:
+ qof_book_merge - release 1.
+
+ Includes
+ qof_book_merge.c
+ qof_book_merge.h
+ test-book-merge.c test routine
+ New Account Hierarchy druid
+ Sundry adjustments to QOF support.
+ Tweaks to several Makefile.am files to support new files.
+ Tweaks to window-main.c to support new menu item
+ Changes to druid-hierarchy.c to support the merge druid.
+
2004-10-15 Derek Atkins <derek at ihtfp.com>
* src/business/business-core/gncInvoice.[ch]
Index: gncCustomer.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncCustomer.h,v
retrieving revision 1.22
retrieving revision 1.23
diff -Lsrc/business/business-core/gncCustomer.h -Lsrc/business/business-core/gncCustomer.h -u -r1.22 -r1.23
--- src/business/business-core/gncCustomer.h
+++ src/business/business-core/gncCustomer.h
@@ -31,6 +31,29 @@
#ifndef GNC_CUSTOMER_H_
#define GNC_CUSTOMER_H_
+/** @struct GncCustomer
+
+credit, discount and shipaddr are unique to GncCustomer\n
+id, name, notes, terms, addr, currency, taxtable, taxtable_override
+taxincluded, active and jobs are identical to ::GncVendor.
+
+ at param QofInstance inst;
+ at param char * id;
+ at param char * name;
+ at param char * notes;
+ at param GncBillTerm * terms;
+ at param GncAddress * addr;
+ at param gnc_commodity * currency;
+ at param GncTaxTable* taxtable;
+ at param gboolean taxtable_override;
+ at param GncTaxIncluded taxincluded;
+ at param gboolean active;
+ at param GList * jobs;
+ at param gnc_numeric credit;
+ at param gnc_numeric discount;
+ at param GncAddress * shipaddr;
+
+*/
typedef struct _gncCustomer GncCustomer;
#include "qofbook.h"
@@ -75,10 +98,6 @@
void gncCustomerAddJob (GncCustomer *customer, GncJob *job);
void gncCustomerRemoveJob (GncCustomer *customer, GncJob *job);
-/** added for QOF standardisation */
-void gncCustomerSetTaxIncluded_q (GncCustomer *customer, gint taxincl);
-gint gncCustomerGetTaxIncluded_q (GncCustomer *cust);
-
/** @} */
/** @name Get Functions */
Index: gncJob.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncJob.h,v
retrieving revision 1.15
retrieving revision 1.16
diff -Lsrc/business/business-core/gncJob.h -Lsrc/business/business-core/gncJob.h -u -r1.15 -r1.16
--- src/business/business-core/gncJob.h
+++ src/business/business-core/gncJob.h
@@ -68,7 +68,7 @@
gboolean gncJobIsDirty (GncJob *job);
/** Return a pointer to the instance gncJob that is identified
- * by the guid, and is residing in the book. Returns NULL if the
+ * by the guid, and is residing in the book. Returns NULL if the
* instance can't be found.
* Equivalent function prototype is
* GncJob * gncJobLookup (QofBook *book, const GUID *guid);
Index: gncEntry.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncEntry.h,v
retrieving revision 1.30
retrieving revision 1.31
diff -Lsrc/business/business-core/gncEntry.h -Lsrc/business/business-core/gncEntry.h -u -r1.30 -r1.31
--- src/business/business-core/gncEntry.h
+++ src/business/business-core/gncEntry.h
@@ -100,6 +100,7 @@
void gncEntrySetInvDiscount (GncEntry *entry, gnc_numeric discount);
void gncEntrySetInvDiscountType (GncEntry *entry, GncAmountType type);
void gncEntrySetInvDiscountHow (GncEntry *entry, GncDiscountHow how);
+
/** @} */
/** @name Vendor Bills (and Employee Expenses) */
@@ -208,21 +209,32 @@
void gncEntryCommitEdit (GncEntry *entry);
int gncEntryCompare (GncEntry *a, GncEntry *b);
-#define ENTRY_DATE "date"
-#define ENTRY_DATE_ENTERED "date-entered"
-#define ENTRY_DESC "desc"
-#define ENTRY_ACTION "action"
-#define ENTRY_NOTES "notes"
-#define ENTRY_QTY "qty"
-
-#define ENTRY_IPRICE "iprice"
-#define ENTRY_BPRICE "bprice"
-#define ENTRY_BILLABLE "billable?"
-#define ENTRY_BILLTO "bill-to"
-
-#define ENTRY_ORDER "order"
-#define ENTRY_INVOICE "invoice"
-#define ENTRY_BILL "bill"
+#define ENTRY_DATE "date"
+#define ENTRY_DATE_ENTERED "date-entered"
+#define ENTRY_DESC "desc"
+#define ENTRY_ACTION "action"
+#define ENTRY_NOTES "notes"
+#define ENTRY_QTY "qty"
+
+#define ENTRY_IPRICE "iprice"
+#define ENTRY_BPRICE "bprice"
+#define ENTRY_BILLABLE "billable?"
+#define ENTRY_BILLTO "bill-to"
+
+#define ENTRY_ORDER "order"
+#define ENTRY_INVOICE "invoice"
+#define ENTRY_BILL "bill"
+
+#define ENTRY_INV_DISC_TYPE "discount-type"
+#define ENTRY_INV_DISC_HOW "discount-method"
+
+#define ENTRY_INV_TAXABLE "invoice-taxable"
+#define ENTRY_BILL_TAXABLE "bill-taxable"
+#define ENTRY_INV_TAX_INC "invoice-tax-included"
+#define ENTRY_BILL_TAX_INC "bill-tax-included"
+#define ENTRY_INV_DISCOUNT "invoice-discount"
+#define ENTRY_BILL_PAY_TYPE "bill-payment-type"
+
/* deprecated functions, should be removed */
#define gncEntryGetGUID(x) qof_instance_get_guid(QOF_INSTANCE(x))
Index: gncCustomer.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncCustomer.c,v
retrieving revision 1.61
retrieving revision 1.62
diff -Lsrc/business/business-core/gncCustomer.c -Lsrc/business/business-core/gncCustomer.c -u -r1.61 -r1.62
--- src/business/business-core/gncCustomer.c
+++ src/business/business-core/gncCustomer.c
@@ -56,7 +56,7 @@
#include "gncJobP.h"
#include "gncTaxTableP.h"
-struct _gncCustomer
+struct _gncCustomer
{
QofInstance inst;
@@ -270,13 +270,6 @@
gncCustomerCommitEdit (cust);
}
-void gncCustomerSetTaxIncluded_q (GncCustomer *cust, gint taxincl)
-{
- GncTaxIncluded g = taxincl;
- if(!g) return;
- gncCustomerSetTaxIncluded(cust, g);
-}
-
void gncCustomerSetTaxIncluded (GncCustomer *cust, GncTaxIncluded taxincl)
{
if (!cust) return;
@@ -451,11 +444,6 @@
return cust->terms;
}
-gint gncCustomerGetTaxIncluded_q (GncCustomer *cust)
-{
- return (GncTaxIncluded)gncCustomerGetTaxIncluded(cust);
-}
-
GncTaxIncluded gncCustomerGetTaxIncluded (GncCustomer *cust)
{
if (!cust) return GNC_TAXINCLUDED_USEGLOBAL;
@@ -543,7 +531,7 @@
return c->name;
}
-static QofObject gncCustomerDesc =
+static QofObject gncCustomerDesc =
{
interface_version: QOF_OBJECT_VERSION,
e_type: _GNC_MOD_NAME,
Index: gncBillTerm.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncBillTerm.c,v
retrieving revision 1.40
retrieving revision 1.41
diff -Lsrc/business/business-core/gncBillTerm.c -Lsrc/business/business-core/gncBillTerm.c -u -r1.40 -r1.41
--- src/business/business-core/gncBillTerm.c
+++ src/business/business-core/gncBillTerm.c
@@ -51,7 +51,10 @@
#include "gncBusiness.h"
#include "gncBillTermP.h"
-
+/* doxygen: this definition has been copied and pasted
+into the .h file to make it accessible to doxygen.
+Please keep the two in sync if it is not possible to
+move the struct definition itself into the .h */
struct _gncBillTerm
{
QofInstance inst;
@@ -79,7 +82,6 @@
{
GList * terms; /* visible terms */
};
-
static short module = MOD_BUSINESS;
#define _GNC_MOD_NAME GNC_ID_BILLTERM
@@ -678,7 +680,7 @@
interface_version: QOF_OBJECT_VERSION,
e_type: _GNC_MOD_NAME,
type_label: "Billing Term",
- create: NULL,
+ create: (gpointer)gncBillTermCreate,
book_begin: _gncBillTermCreate,
book_end: _gncBillTermDestroy,
is_dirty: qof_collection_is_dirty,
@@ -691,24 +693,16 @@
gboolean gncBillTermRegister (void)
{
static QofParam params[] = {
- { GNC_BILLTERM_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetName,
- (QofSetterFunc)gncBillTermSetName },
- { GNC_BILLTERM_DESC, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetDescription,
- (QofSetterFunc)gncBillTermSetDescription },
- { GNC_BILLTERM_TYPE, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetType,
- (QofSetterFunc)gncBillTermSetType },
- { GNC_BILLTERM_DUEDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDueDays,
- (QofSetterFunc)gncBillTermSetDueDays },
- { GNC_BILLTERM_DISCDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDiscountDays,
- (QofSetterFunc)gncBillTermSetDiscountDays },
- { GNC_BILLTERM_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount,
- (QofSetterFunc)gncBillTermSetDiscount },
- { GNC_BILLTERM_CUTOFF, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetCutoff,
- (QofSetterFunc)gncBillTermSetCutoff },
- { GNC_BILLTERM_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncBillTermGetRefcount, NULL },
- { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
- { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
- { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
+ { GNC_BILLTERM_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetName, (QofSetterFunc)gncBillTermSetName },
+ { GNC_BILLTERM_DESC, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetDescription, (QofSetterFunc)gncBillTermSetDescription },
+ { GNC_BILLTERM_TYPE, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetType, (QofSetterFunc)gncBillTermSetType },
+ { GNC_BILLTERM_DUEDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDueDays, (QofSetterFunc)gncBillTermSetDueDays },
+ { GNC_BILLTERM_DISCDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDiscountDays, (QofSetterFunc)gncBillTermSetDiscountDays },
+ { GNC_BILLTERM_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount, (QofSetterFunc)gncBillTermSetDiscount },
+ { GNC_BILLTERM_CUTOFF, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetCutoff, (QofSetterFunc)gncBillTermSetCutoff },
+ { GNC_BILLTERM_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncBillTermGetRefcount, NULL },
+ { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
+ { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
{ NULL },
};
Index: gncAddress.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncAddress.c,v
retrieving revision 1.16
retrieving revision 1.17
diff -Lsrc/business/business-core/gncAddress.c -Lsrc/business/business-core/gncAddress.c -u -r1.16 -r1.17
--- src/business/business-core/gncAddress.c
+++ src/business/business-core/gncAddress.c
@@ -285,10 +285,14 @@
{
static QofParam params[] = {
- { ADDRESS_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetName, (QofSetterFunc)gncAddressSetName },
- { ADDRESS_PHONE, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetPhone, (QofSetterFunc)gncAddressSetPhone },
- { ADDRESS_FAX, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetFax, (QofSetterFunc)gncAddressSetFax },
- { ADDRESS_EMAIL, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetEmail, (QofSetterFunc)gncAddressSetEmail },
+ { ADDRESS_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetName, (QofSetterFunc)gncAddressSetName },
+ { ADDRESS_ONE, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr1, (QofSetterFunc)gncAddressSetAddr1 },
+ { ADDRESS_TWO, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr2, (QofSetterFunc)gncAddressSetAddr2 },
+ { ADDRESS_THREE, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr3, (QofSetterFunc)gncAddressSetAddr3 },
+ { ADDRESS_FOUR, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr4, (QofSetterFunc)gncAddressSetAddr4 },
+ { ADDRESS_PHONE, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetPhone, (QofSetterFunc)gncAddressSetPhone },
+ { ADDRESS_FAX, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetFax, (QofSetterFunc)gncAddressSetFax },
+ { ADDRESS_EMAIL, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetEmail, (QofSetterFunc)gncAddressSetEmail },
{ NULL },
};
Index: gncTaxTable.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncTaxTable.c,v
retrieving revision 1.39
retrieving revision 1.40
diff -Lsrc/business/business-core/gncTaxTable.c -Lsrc/business/business-core/gncTaxTable.c -u -r1.39 -r1.40
--- src/business/business-core/gncTaxTable.c
+++ src/business/business-core/gncTaxTable.c
@@ -464,12 +464,6 @@
mod_table (entry->table);
}
}
-void gncTaxTableEntrySetType_q (GncTaxTableEntry *entry, gint type)
-{
- GncAmountType q = type;
- if(!q) return;
- gncTaxTableEntrySetType(entry,q);
-}
void gncTaxTableEntrySetType (GncTaxTableEntry *entry, GncAmountType type)
{
@@ -669,11 +663,6 @@
return entry->account;
}
-gint gncTaxTableEntryGetType_q (GncTaxTableEntry *entry)
-{
- return (GncAmountType)gncTaxTableEntryGetType(entry);
-}
-
GncAmountType gncTaxTableEntryGetType (GncTaxTableEntry *entry)
{
if (!entry) return 0;
@@ -823,10 +812,10 @@
gboolean gncTaxTableRegister (void)
{
static QofParam params[] = {
- { GNC_TT_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncTaxTableGetName, (QofSetterFunc)gncTaxTableSetName },
- { GNC_TT_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncTaxTableGetRefcount, NULL },
- { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
- { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
+ { GNC_TT_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncTaxTableGetName, (QofSetterFunc)gncTaxTableSetName },
+ { GNC_TT_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncTaxTableGetRefcount, (QofSetterFunc)gncTaxTableSetRefcount },
+ { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
+ { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
{ NULL },
};
Index: gncAddress.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncAddress.h,v
retrieving revision 1.11
retrieving revision 1.12
diff -Lsrc/business/business-core/gncAddress.h -Lsrc/business/business-core/gncAddress.h -u -r1.11 -r1.12
--- src/business/business-core/gncAddress.h
+++ src/business/business-core/gncAddress.h
@@ -33,15 +33,35 @@
#include "qofbook.h"
#include "qofid.h"
+#include "qofobject.h"
+#include "qofinstance.h"
+#include "qofid-p.h"
#define GNC_ADDRESS_MODULE_NAME "gncAddress"
+/** \struct GncAddress
+
+ at param QofBook * book;
+ at param QofEntity * parent;
+ at param gboolean dirty;
+ at param char * name;
+ at param char * addr1;
+ at param char * addr2;
+ at param char * addr3;
+ at param char * addr4;
+ at param char * phone;
+ at param char * fax;
+ at param char * email;
+*/
typedef struct _gncAddress GncAddress;
/** @name Create/Destroy functions */
/** @{ */
+/** create a new address */
GncAddress * gncAddressCreate (QofBook *book, QofEntity *parent);
+/** destroy an address */
void gncAddressDestroy (GncAddress *addr);
+
/** @} */
/** @name Set functions */
@@ -73,9 +93,18 @@
gboolean gncAddressIsDirty (const GncAddress *addr);
+/** \brief compare two addresses
+
+\return 0 if identical, -1 if a is empty or less than b
+and +1 if a is more than b or if b is empty.
+*/
int gncAddressCompare (const GncAddress *a, const GncAddress *b);
#define ADDRESS_NAME "name"
+#define ADDRESS_ONE "number"
+#define ADDRESS_TWO "street"
+#define ADDRESS_THREE "locality"
+#define ADDRESS_FOUR "city"
#define ADDRESS_PHONE "phone"
#define ADDRESS_FAX "fax"
#define ADDRESS_EMAIL "email"
Index: gncOrder.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncOrder.c,v
retrieving revision 1.45
retrieving revision 1.46
diff -Lsrc/business/business-core/gncOrder.c -Lsrc/business/business-core/gncOrder.c -u -r1.45 -r1.46
--- src/business/business-core/gncOrder.c
+++ src/business/business-core/gncOrder.c
@@ -425,7 +425,7 @@
interface_version: QOF_OBJECT_VERSION,
e_type: _GNC_MOD_NAME,
type_label: "Order",
- create: NULL,
+ create: (gpointer)gncOrderCreate,
book_begin: NULL,
book_end: NULL,
is_dirty: qof_collection_is_dirty,
Index: gncTaxTable.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncTaxTable.h,v
retrieving revision 1.17
retrieving revision 1.18
diff -Lsrc/business/business-core/gncTaxTable.h -Lsrc/business/business-core/gncTaxTable.h -u -r1.17 -r1.18
--- src/business/business-core/gncTaxTable.h
+++ src/business/business-core/gncTaxTable.h
@@ -31,8 +31,36 @@
#ifndef GNC_TAXTABLE_H_
#define GNC_TAXTABLE_H_
+/** @struct GncTaxTable
+
+modtime is the internal date of the last modtime\n
+See src/doc/business.txt for an explanation of the following\n
+Code that handles refcount, parent, child, invisible and children
+is *identical* to that in ::GncBillTerm
+
+ at param QofInstance inst;
+ at param char * name;
+ at param GList * entries;
+ at param Timespec modtime;
+ at param gint64 refcount;
+ at param GncTaxTable * parent; if non-null, we are an immutable child
+ at param GncTaxTable * child; if non-null, we have not changed
+ at param gboolean invisible;
+ at param GList * children; list of children for disconnection
+*/
typedef struct _gncTaxTable GncTaxTable;
+
+/** @struct GncTaxTableEntry
+
+ at param GncTaxTable * table;
+ at param Account * account;
+ at param GncAmountType type;
+ at param gnc_numeric amount;
+};
+
+*/
typedef struct _gncTaxTableEntry GncTaxTableEntry;
+
typedef struct _gncAccountValue GncAccountValue;
#include "Account.h"
@@ -92,10 +120,6 @@
void gncTaxTableBeginEdit (GncTaxTable *table);
void gncTaxTableCommitEdit (GncTaxTable *table);
-/** added for later QOF standardisation */
-gint gncTaxTableEntryGetType_q (GncTaxTableEntry *entry);
-void gncTaxTableEntrySetType_q (GncTaxTableEntry *entry, gint type);
-
/** @} */
/** @name Get Functions */
--- /dev/null
+++ src/engine/qof_book_merge.h
@@ -0,0 +1,652 @@
+/*********************************************************************
+ * qof_book_merge.h -- api for QofBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program 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 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program 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 this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#define _GNU_SOURCE
+#ifndef QOFBOOKMERGE_H
+#define QOFBOOKMERGE_H
+
+/** @addtogroup QOF
+ @{ */
+/** @addtogroup BookMerge Merging QofBook structures.
+
+<b>Collision handling principles.</b>\n
+\n
+ -# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+ data and therefore ALL objects in the import book will include valid GUID's.
+ -# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+ the GUID values will have been created during the import and will not match any existing
+ GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+ be ::MERGE_NEW - parameter values must be checked.
+ -# If import contains data from closed books, store the data from the closed
+ books in the current book as active. i.e. re-open the books.
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+ -# If ALL parameters in the import object match the target object with the same \a GUID,
+ set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+ -# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+ -# If the same data exists in the target book with a different GUID, the object
+ is tagged as DUPLICATE.
+ -# If the data has changed, the object is tagged as REPORT.
+ -# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only
+used for critical errors that arise from programming errors, not for invalid import data
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeInit, ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return
+any error values to the calling process.
+
+ @{ */
+/**@file qof_book_merge.h
+ @brief API for merging two \c QofBook* structures with collision handling
+ @author Copyright (c) 2004 Neil Williams <linux at codehelp.co.uk>
+*/
+
+
+#include <glib.h>
+#include "qof.h"
+#include "qofinstance-p.h"
+#include "gnc-trace.h"
+
+/** \brief Results of collisions and user resolution.
+
+All rules are initialised as ::MERGE_UNDEF.
+Once the comparison is complete, each object within the import will be
+updated.
+
+::MERGE_ABSOLUTE, ::MERGE_NEW, ::MERGE_DUPLICATE and ::MERGE_UPDATE can be reported
+to the user along with all ::MERGE_REPORT objects for confirmation.
+It may be useful later to allow \a MERGE_ABSOLUTE, \a MERGE_NEW, \a MERGE_DUPLICATE and
+\a MERGE_UPDATE to not be reported, if the user sets a preferences option
+for each result. (Always accept new items: Y/N default NO, ignores all
+MERGE_NEW if set to Y etc.) This option would not require any changes
+to qof_book_merge.
+
+\a MERGE_NEW, \a MERGE_DUPLICATE and \a MERGE_UPDATE are only actioned after
+conflicts are resolved by the user using a dialog and all \a MERGE_REPORT objects are
+re-assigned to one of MERGE_NEW, MERGE_DUPLICATE or MERGE_UPDATE. There is no automatic
+merge, even if no entities are tagged as MERGE_REPORT, the calling process must still
+check for REPORT items using ::qof_book_mergeRuleForeach and call ::qof_book_mergeCommit.
+
+\a MERGE_INVALID data should be rare and allows for user-abort - the imported file/source
+ may be corrupted and the prescence of invalid data should raise concerns that
+ the rest of the data may be corrupted, damaged or otherwise altered. If any entity is
+ tagged as MERGE_INVALID, the merge operation will abort and leave the target book
+ completely unchanged.
+
+\a MERGE_ABSOLUTE is only used for a complete match. The import object contains
+the same data in the same parameters with no omissions or amendments. If any data is missing,
+amended or added, the data is labelled \a MERGE_UPDATE.
+
+ Every piece of data has a corresponding result. Only when the count of items labelled
+ \a MERGE_REPORT is equal to zero are \a MERGE_NEW and \a MERGE_UPDATE
+ items added to the existing book.\n \a MERGE_DUPLICATE items are silently ignored.
+ Aborting the dialog/process (by the user or in a program crash) at any point before the
+ final commit leaves the existing book completely untouched.
+*/
+typedef enum {
+ MERGE_UNDEF, /**< default value before comparison is made. */
+ MERGE_ABSOLUTE, /**< GUID exact match, no new data - \b ignore */
+ MERGE_NEW, /**< import object does \b not exist in the
+ target book - \b add */
+ MERGE_REPORT, /**< import object needs user intervention - \b report */
+ MERGE_DUPLICATE, /**< import object with different GUID exactly
+ matches existing GUID - \b ignore */
+ MERGE_UPDATE, /**< import object matches an existing entity but
+ includes new or modified parameter data - \b update */
+ MERGE_INVALID /**< import object didn't match registered object
+ or parameter types or user decided to abort - \b abort */
+}qof_book_mergeResult;
+
+
+/** \brief mergeData contains the essential data for any merge.
+
+Used to dictate what to merge, how to merge it, where to get the new data and
+where to put the amended data.
+
+Combines lists of \a ::QofParam, \a ::QofEntity and \a ::qof_book_mergeRule into one struct that
+can be easily passed between callbacks. Also holds the pointers to the import and target ::QofBook
+structures.
+
+- targetList and mergeObjectParams change each time a new object type is set for compare.
+- mergeList is the complete list of rules for all objects in the import book.
+
+*/
+typedef struct
+{
+ GSList *mergeObjectParams; /**< GSList of ::QofParam details for each parameter in the current object. */
+ GList *mergeList; /**< GSList of ::qof_book_mergeRule rules for the import data. */
+ GSList *targetList; /**< GSList of ::QofEntity * for each object of this type in the target book */
+ QofBook *mergeBook; /**< pointer to the import book for this merge operation. */
+ QofBook *targetBook; /**< pointer to the target book for this merge operation. */
+ gboolean abort; /**< set to TRUE if MERGE_INVALID is set. */
+}qof_book_mergeData;
+
+
+/** \brief One rule per entity, built into a single GSList for the entire merge
+
+All rules are stored in the GSList qof_book_mergeData::mergeList.
+
+If the ::GUID matches it's the always same semantic object,
+regardless of whether other data fields are changed.
+\n
+The boolean value mergeAbsolute defaults to \c FALSE\n
+
+NOTE 1: if mergeAbsolute == \c TRUE, ::qof_book_mergeResult will still be set to
+::MERGE_UPDATE if parameters within this entity have been modified.
+
+NOTE 2: ::qof_book_merge_param_as_string returns \b string representations of the parameter
+data that is causing a collision. These values must \b NOT be used to set the target
+parameter - the function is provided for display purposes only, to make it simple to
+explain the collision to the user using MERGE_REPORT and the dialog.
+
+*/
+typedef struct
+{
+ /* internal counters and reference variables */
+ gboolean mergeAbsolute; /**< Only set if the GUID of the import matches the target */
+ gint difference; /**< used to find best match in a book where no GUID matches */
+ gboolean updated; /**< prevent the mergeResult from being overwritten. */
+ /* rule objects set from or by external calls */
+ QofIdType mergeType; /**< type of comparison required for check for collision */
+ const char* mergeLabel; /**< Descriptive label for the object type, useful for the
+ user intervention dialog. */
+ GSList *mergeParam; /**< list of usable parameters for the object type */
+ GSList *linkedEntList; /**< list of complex data types included in this object.
+
+ linkedEntList contains an ::QofEntity reference to any parameter that is not
+ one of the core QOF_TYPE data types. This entity must be already registered with QOF
+ and the results of the comparison for the linked entity will modulate the mergeResult
+ of this object. e.g. if an invoice is the same value but for a different customer,
+ the invoice will be set to MERGE_REPORT and the customer as MERGE_NEW.
+ */
+ qof_book_mergeResult mergeResult; /**< result of comparison with main ::QofBook */
+ QofEntity *importEnt; /**< pointer to the current entity in the import book. */
+ QofEntity *targetEnt; /**< pointer to the corresponding entity in the target book, if any. */
+}qof_book_mergeRule;
+
+
+/* ======================================================================== */
+/** @name qof_book_merge API */
+/** @{
+*/
+/** \brief Initialise the qof_book_merge process
+
+ First function of the qof_book_merge API. Every merge must begin with Init.
+
+ Requires the book to import (::QofBook *) and the book to receive the import, the target book
+ (::QofBook *). \n
+Process:
+
+ -# Invoke the callback ::qof_book_mergeForeachType on every registered object class definition.
+ -# Callback obtains the registered parameter list for each object type. This provides run time
+ access to all registered objects and all object parameters without any changes to
+ qof_book_merge - no registered object or parameter is omitted from any merge operation.
+ -# Use ::qof_object_foreach to invoke the callback ::qof_book_mergeForeach, one object at a time
+ on every instance stored in mergeBook. This is the first point where real data from the import
+ book is accessed.
+ -# qof_book_mergeForeach obtains the ::GUID for the object from the import book and runs the first
+ check on the original book, checking for any exact GUID match. With the full parameter list,
+ the rules for this object can be created. If there is a GUID match, the data in each parameter
+ of the import object is compared with the same semantic object in the original book. If there is
+ no GUID in the import object or no GUID match with the original book, the original book is
+ searched to find a parameter match - checking for a ::MERGE_DUPLICATE result.
+ -# ::qof_book_mergeCompare sets the ::qof_book_mergeResult of the comparison.
+ -# Inserts the completed rule into qof_book_mergeData::mergeList GSList.
+
+\return -1 in case of error, otherwise 0.
+
+*/
+int
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook);
+
+
+/** \brief Definition of the dialog control callback routine
+
+All ::MERGE_REPORT rules must be offered for user intervention using this template.\n
+Commit will fail if any rules are still tagged as \a MERGE_REPORT.
+
+Calling processes are free to also offer MERGE_NEW, MERGE_UPDATE, MERGE_DUPLICATE and
+MERGE_ABSOLUTE for user intervention. Attempting to query MERGE_INVALID rules
+will cause an error.
+
+For an example, consider test_rule_loop, declared as:
+
+<tt>void test_rule_loop(qof_book_mergeRule *rule, guint remainder);\n
+void test_rule_loop(qof_book_mergeRule *rule, guint remainder) \n
+{\n
+ g_return_if_fail(rule != NULL);\n
+ printf("Rule Result %s", rule->mergeType);\n
+ qof_book_mergeUpdateResult(rule,MERGE_UPDATE);\n
+}</tt>
+
+The dialog is free to call ::qof_book_mergeUpdateResult in the loop or at the end
+as long as the link between the rule and the result is maintained, e.g. by using a
+GHashTable.
+\n
+The parameters are:
+ - rule : pointer to the ::qof_book_mergeRule that generated the collision report
+ - remainder : guint value returned from g_slist_length for the number of other
+ rules remaining with the same result. This might be useful for a progress dialog, it might not.
+ When updating MERGE_REPORT, remainder must equal zero before calling
+ ::qof_book_mergeCommit or the import will abort.
+\n
+
+If the dialog sets \b any rule result to ::MERGE_INVALID, the import will abort when
+::qof_book_mergeCommit is called. It is the responsibility of the calling
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return to GnuCash. The merge routines in these files will already have halted the merge
+operation and freed any memory allocated to merge structures before returning the error code.
+There is no need for the dialog process to report back to qof_book_merge in this situation.
+
+*/
+typedef void (* qof_book_mergeRuleForeachCB)(qof_book_mergeRule*, guint);
+
+/** \brief Dialog Control Callback
+
+This function is designed to be used to iterate over all rules tagged with a specific
+::qof_book_mergeResult value.
+
+Uses ::qof_book_get_collection with the qof_book_mergeRule::mergeType object type to
+return a collection of ::QofEntity entities from either the qof_book_mergeData::mergeBook or
+qof_book_mergeData::targetBook. Then uses ::qof_collection_lookup_entity to lookup
+the qof_book_mergeRule::importEnt and again the qof_book_mergeRule::targetEnt to
+return the two specific entities.
+
+*/
+void qof_book_mergeRuleForeach( qof_book_mergeRuleForeachCB, qof_book_mergeResult);
+
+
+/** \brief Holds details of each rule as the callbacks iterate over the list.
+
+*/
+struct qof_book_mergeRuleIterate {
+ qof_book_mergeRuleForeachCB fcn;
+ qof_book_mergeRule *data;
+ GList *ruleList;
+ guint remainder;
+};
+
+/** \brief provides easy string access to parameter data for dialog use
+
+<b>Must only be used for display purposes!</b>
+
+Uses the param_getfcn to retrieve the parameter value as a string, suitable for
+display in dialogs and user intervention output. Only the parameters used in the merge
+are available, i.e. parameters where both param_getfcn and param_setfcn are not NULL.
+
+Note that the object type description (a full text version of the object name) is
+also available to the dialog as qof_book_mergeRule::mergeLabel.
+
+This allows the dialog to display the description of the object and all parameter data.
+
+*/
+char* qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt);
+
+/** \brief called by dialog callback to set the result of user intervention
+
+Set \b any rule result to ::MERGE_INVALID to abort the import when
+::qof_book_mergeCommit is called, without changing the target book.
+
+The calling process should make it absolutely clear that a merge operation
+\b cannot be undone and that a backup copy should always be available
+\b before a merge is initialised.
+
+Recommended method: Only offer three options to the user per rule:
+
+-# Allow import data to be merged into target data
+ - change MERGE_REPORT to MERGE_UPDATE
+-# Allow import data without an exact match to be
+ added as new
+ - change MERGE_REPORT to MERGE_NEW \b IF mergeAbsolute = FALSE
+-# Ignore import data and leave target data unchanged
+ - change MERGE_REPORT to MERGE_ABSOLUTE or MERGE_DUPLICATE
+
+Handle the required result changes in code: Check the value of
+qof_book_mergeRule::mergeAbsolute and use these principles:
+
+To ignore entities tagged as:
+- MERGE_REPORT, you must check the value of mergeAbsolute.
+ - if mergeAbsolute is TRUE, change MERGE_REPORT to MERGE_ABSOLUTE
+ - if mergeAbsolute is FALSE, change MERGE_REPORT to MERGE_DUPLICATE
+- MERGE_NEW, set MERGE_DUPLICATE.
+- MERGE_UPDATE, you must check the value of mergeAbsolute.
+ - if mergeAbsolute is TRUE, change MERGE_UPDATE to MERGE_ABSOLUTE
+ - if mergeAbsolute is FALSE, change MERGE_UPDATE to MERGE_DUPLICATE
+
+To merge entities that are not pre-set to MERGE_NEW, set MERGE_UPDATE.\n
+Attempting to merge an entity when the pre-set value was MERGE_NEW will
+force a change back to MERGE_NEW.
+
+To add entities, check mergeAbsolute is FALSE and set MERGE_NEW.\n
+An entity \b only be added if mergeAbsolute is FALSE. Attempting to
+add an entity when mergeAbsolute is TRUE will always force a MERGE_UPDATE.
+
+It is not possible to update the same rule more than once.
+
+-# \b MERGE_NEW is reserved for new objects and is only pre-set if
+all parameters, including GUID, have already failed to match any
+relevant object. ::qof_book_mergeCommit will create new
+entities for all rules tagged as MERGE_NEW.
+ - if mergeAbsolute is TRUE and the user wants to import the
+ data, requests to set MERGE_NEW will be forced to MERGE_UPDATE
+ because an entity with that GUID already exists in the target book.
+ - if MERGE_NEW is pre-set, requests to change to MERGE_UPDATE will be
+ ignored because a new entity is needed.
+-# \b MERGE_UPDATE is reserved for existing objects - ::qof_book_mergeCommit
+will require a matching entity to update and will force a change to back to
+MERGE_NEW if none is known to exist, using the principle above.
+-# \b MERGE_INVALID will cause an abort of the merge process.
+-# \b MERGE_UNDEF and \b MERGE_REPORT cannot be set - the entity result will be unchanged.
+-# \b MERGE_DUPLICATE and \b MERGE_ABSOLUTE are handled identically but are semantically
+ different - qof_book_mergeRule::mergeAbsolute is used to dictate which to set:
+ - if mergeAbsolute is TRUE but MERGE_DUPLICATE is requested,
+ force a change to MERGE_ABSOLUTE.
+ - if mergeAbsolute is FALSE but MERGE_ABSOLUTE is requested,
+ force a change to MERGE_DUPLICATE.
+
+::qof_book_mergeCommit only commits entities tagged
+with MERGE_NEW and MERGE_UPDATE results.
+\n
+Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored.
+
+\return -1 if supplied parameters are invalid or NULL, 0 on success.
+
+*/
+int qof_book_mergeUpdateResult(qof_book_mergeRule *resolved, qof_book_mergeResult tag);
+
+
+/** \brief Commits the import data to the target book
+
+ The last function in the API and the final part of any qof_book_merge operation.
+
+qof_book_mergeCommit will abort the \b entire merge operation if any rule is set to
+::MERGE_INVALID. It is the responsibility of the calling
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return to GnuCash. qof_book_mergeCommit will already have halted the merge
+operation and freed any memory allocated to all merge structures before returning the error
+code. There is no way for the dialog process to report back to qof_book_merge in this situation.
+
+qof_book_mergeCommit checks for any entities still tagged as ::MERGE_REPORT and then proceeds
+to import all entities tagged as ::MERGE_UPDATE or ::MERGE_NEW into the target book.
+\n
+<b>This final process cannot be UNDONE!</b>\n
+\n
+
+\return
+ - -1 if no merge has been initialised with ::qof_book_mergeInit or if any rules
+ are tagged as ::MERGE_INVALID,
+ - +1 if some entities are still tagged as \a MERGE_REPORT
+ - 0 on success.
+*/
+int
+qof_book_mergeCommit( void );
+
+/** \brief Abort the merge and free all memory allocated by the merge
+
+Sometimes, setting ::MERGE_INVALID is insufficient: e.g. if the user aborts the
+merge from outside the functions dealing with the merge ruleset. This function
+causes an immediate abort - the calling process must start again at Init if
+a new merge is required.
+*/
+void
+qof_book_merge_abort(void);
+
+/** @} */
+
+/* ======================================================================== */
+/* Internal callback routines */
+
+/** @name Phase 1: Import book */
+/** @{
+*/
+
+/** \brief Looks up all import objects and calls ::qof_book_mergeCompare for each.
+
+ This callback is used to obtain a list of all objects and their parameters
+ in the book to be imported.\n
+
+ Called by ::qof_book_mergeForeachType.\n
+ Receives all instances of only those objects that exist in the import book,
+ from ::qof_object_foreach. ::qof_book_mergeData contains a full list of all registered
+ parameters for each object in the mergeObjectParams GSList. \n
+ Looks up the live parameter data (via ::QofEntity and ::QofParam), creates the rule,
+ compares the data and stores the result of the comparison.
+
+Process:
+
+ -# Sets default ::qof_book_mergeResult as MERGE_UNDEF - undefined.\n
+ -# Obtains GUID, parameter data, type and rule.
+ -# Compares GUID with original book, sets ::qof_book_mergeData .mergeAbsolute
+ to TRUE if exact match.
+ -# Inserts rule into ::qof_book_mergeData rule list.
+ -# Runs the comparison for that data type using ::qof_book_mergeCompare.
+
+*/
+void qof_book_mergeForeach (QofEntity* mergeEnt, gpointer mergeData);
+
+/** \brief Registered Object Callback.
+
+ Receives one object at a time from ::qof_object_foreach_type.\n
+ Note: generic type data only, no live data accesses.\n
+ ::qof_object_foreach_type called directly by ::qof_book_mergeInit.
+
+ This callback is used to obtain a list of all registered
+ objects, whether or not the objects exist in either the import or
+ original books.\n
+
+ Invokes the callback ::qof_book_mergeForeach on every instance of a particular object type.
+ The callback will be invoked only for those instances stored in the import book and therefore
+ qof_book_mergeForeach gains the first access to any live data.
+*/
+void qof_book_mergeForeachType (QofObject* merge_obj, gpointer mergeData);
+
+/** \brief Iterates over each parameter name within the selected QofObject.
+
+ Receives the list of parameter names from ::QofParam and fills the GSList in
+ ::qof_book_mergeData.\n
+ No live data access - object typing and parameter listing only.\n
+ \b Note: This function is called by ::qof_book_mergeForeachType in the comparison
+ stage and ::qof_book_mergeCommitRuleLoop in the commit stage. Change with care!
+*/
+void qof_book_mergeForeachParam(QofParam* param_name, gpointer user_data);
+
+/** @} */
+/** @name Phase 2: Target book */
+/** @{
+*/
+
+/** \brief Registered Object Callback for the \b target book.
+
+ Receives one object at a time from ::qof_object_foreach_type.\n
+\n
+ This callback is used to iterate through all the registered
+ objects, in the \b Target book. When the target object type
+ matches the object type of the current import object, calls
+ ::qof_book_mergeForeachTarget to store details of the possible target
+ matches in the GSList *targetList in ::qof_book_mergeData .
+ \n
+*/
+void qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer mergeData);
+
+
+/** \brief Looks up all \b target objects of a specific type.
+
+ This callback is used to obtain a list of all suitable objects and their parameters
+ in the \b target book.\n
+\n
+ Called by ::qof_book_mergeForeachTypeTarget.\n
+ Receives all instances of only those objects that exist in the target book,
+ that match the object type of the current \b import object.
+ This is done when there is no GUID match and there is no way to know if
+ a corresponding object exists in the target book that would conflict with the
+ data in the import object.
+\n
+ Stores details of the QofEntity* of each suitable object in the target book
+ for later comparison by ::qof_book_mergeCompare .
+
+*/
+void qof_book_mergeForeachTarget (QofEntity* mergeEnt, gpointer mergeData);
+
+/** \brief Omits target entities that have already been matched.
+
+ It is possible for two entities in the import book to match a single entity in
+ the target book, resulting in a loss of data during commit.
+
+ qof_book_merge_target_check simply checks the GUID of all existing
+ target entities against the full list of all entities of a suitable type
+ in the ::qof_book_mergeForeachTarget iteration. Possible target entity
+ matches are only added to the qof_book_mergeData::targetList if the GUID
+ does not match.
+*/
+void qof_book_merge_target_check (QofEntity* targetEnt);
+
+/** @} */
+/** @name Phase 3: User Intervention
+*/
+/** @{
+*/
+
+/** \brief Iterates over the rules and declares the number of rules left to match
+
+ The second argument is a guint holding the remainder which might be
+ useful for progress feedback in the GUI.
+*/
+void qof_book_mergeRuleCB(gpointer, gpointer);
+
+/** @} */
+/** @name Phase 4: Commit import to target book
+*/
+/** @{
+*/
+
+/** \brief Separates the rules according to the comparison results.
+
+ Used to create a list of all rules that match a particular ::qof_book_mergeResult.
+ Intended for ::MERGE_NEW and ::MERGE_UPDATE, it can be used for ::MERGE_ABSOLUTE or
+ ::MERGE_DUPLICATE if you want to maybe report on what will be ignored in the import.
+
+ It can \b NOT be used for ::MERGE_UNDEF, ::MERGE_INVALID or ::MERGE_REPORT.
+*/
+void qof_book_mergeCommitForeach (qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult );
+
+/** \brief Iterates over the rules and declares the number of rules left to commit
+
+ The second argument is a guint holding the remainder which might be
+ useful for progress feedback in the GUI.
+
+*/
+void qof_book_mergeCommitForeachCB(gpointer, gpointer);
+
+/** \brief Commit the data from the import to the target QofBook.
+
+ Called by ::qof_book_mergeCommit to commit data from each rule in turn.
+ Uses QofParam->param_getfcn - ::QofAccessFunc to query the import book
+ and param_setfcn - ::QofSetterFunc to update the target book.
+\n
+ Note: Not all param_getfcn can have a matching param_setfcn.
+ Getting the balance of an account is obviously necessary to other routines
+ but is pointless in a comparison for a merge - the balance is calculated from
+ transactions, it cannot be set by the account. A discrepancy in the calculated
+ figures for an account object should not cause a MERGE_REPORT.
+\n
+ Limits the comparison routines to only calling param_getfcn if
+ param_setfcn is not NULL.
+
+*/
+
+void qof_book_mergeCommitRuleLoop(qof_book_mergeRule *rule, guint remainder);
+
+/** @} */
+
+
+/** @name Comparison and Rule routines
+*/
+/** @{
+*/
+
+/** \brief Set the ::qof_book_mergeResult for each QofIdType parameter, using ::GUID if any.
+
+\n
+GUID's used in comparisons of entities and instances.
+
+If there is no GUID match, \a mergeData->mergeAbsolute will be set to FALSE.
+::qof_book_mergeCompare will receive a GSList of ::QofEntity * targets instead of one
+unique match and will iterate through the parameter comparisons for each member of
+the GSList *targetList.
+
+Sets function pointers to the parameter_getter and parameter_setter routines from
+::QofParam *mergeObjectParams and matches the comparison to the incoming data type:\n
+
+ at param mergeRule - contains the GUID, the import book reference, ::QofIdType of the object
+that contains the parameter and mergeResult.
+
+\return -1 in case of error, otherwise 0.
+
+*/
+int
+qof_book_mergeCompare( void );
+
+/** \brief Makes the decisions about how matches and conflicts are tagged.
+
+New rules start at:
+ - ::MERGE_ABSOLUTE\n
+ (GUID's match; first parameter matches) OR
+ - ::MERGE_DUPLICATE\n
+ (GUID's do NOT match; first parameter DOES match) OR
+ - ::MERGE_NEW\n
+ (GUID's do NOT match; first parameters does NOT match).
+
+If subsequent parameters in the same object FAIL a match:
+ - \a MERGE_ABSOLUTE fallsback to ::MERGE_UPDATE \n
+ (GUID matches but some parameters differ)\n
+ (guidTarget will be updated with mergeEnt)
+ - \a MERGE_DUPLICATE fallsback to ::MERGE_REPORT\n
+ (GUID does not match and some parameters do NOT match)
+ - \a MERGE_NEW fallsback to \a MERGE_REPORT
+ (GUID does not match and some parameters now DO match)
+
+<b>Comparisons without a GUID match.</b>
+ Only sets a failed match if ALL objects fail to match.
+ when absolute is FALSE, all suitable target objects are compared.
+ mergeResult is not set until all targets checked.
+ Identifies the closest match using a difference rank. This avoids
+ using non-generic tests for object similarities. difference has a
+ maximum value of the total number of comparable parameters and the
+ value closest to zero is used. In the case of a tie, it is
+ currently first-come-first-served. FIXME!
+
+*/
+void qof_book_mergeUpdateRule( gboolean match);
+
+/** @} */
+/** @} */
+#endif // QOFBOOKMERGE_H
--- /dev/null
+++ src/engine/qof_book_merge.c
@@ -0,0 +1,757 @@
+/*********************************************************************
+ * qof_book_merge.c -- api for QoFBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program 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 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program 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 this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#include "qof_book_merge.h"
+static short module = MOD_IMPORT;
+
+/* all qof_book_merge data is held in mergeData. */
+static qof_book_mergeData* mergeData = NULL;
+
+/*
+currentRule is only used when a qof_book_mergeRule is being inspected or
+tested, not when it is created. This is to avoid the need for g_new()
+every time a single rule is checked.
+
+Rules are created and freed separately, via the mergeData GList, mergeList.
+*/
+static qof_book_mergeRule* currentRule = NULL;
+
+/* ================================================================ */
+/* API functions. */
+int
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook)
+{
+ GList *check;
+ g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), -1);
+ mergeData = g_new(qof_book_mergeData, 1);
+ mergeData->abort = FALSE;
+ mergeData->mergeList = NULL;
+ mergeData->targetList = NULL;
+ mergeData->mergeBook = importBook;
+ mergeData->targetBook = targetBook;
+ mergeData->mergeObjectParams = NULL;
+ currentRule = g_new(qof_book_mergeRule, 1);
+ qof_object_foreach_type(qof_book_mergeForeachType, NULL);
+ check = g_list_copy(mergeData->mergeList);
+ while(check != NULL) {
+ currentRule = check->data;
+ if(currentRule->mergeResult == MERGE_INVALID) {
+ qof_book_merge_abort();
+ return(-1);
+ }
+ check = g_list_next(check);
+ }
+ g_list_free(check);
+ return 0;
+}
+
+void
+qof_book_merge_abort (void)
+{
+ if(currentRule) {
+ g_slist_free(currentRule->linkedEntList);
+ g_slist_free(currentRule->mergeParam);
+ g_free(currentRule);
+ }
+ while(mergeData->mergeList != NULL) {
+ g_free(mergeData->mergeList->data);
+ mergeData->mergeList = g_list_next(mergeData->mergeList);
+ }
+ while(mergeData->targetList != NULL) {
+ g_free(mergeData->targetList->data);
+ mergeData->targetList = g_slist_next(mergeData->targetList);
+ }
+ g_list_free(mergeData->mergeList);
+ g_slist_free(mergeData->mergeObjectParams);
+ g_slist_free(mergeData->targetList);
+ g_free(mergeData);
+}
+
+char*
+qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
+{
+ gchar *stringImport;
+ char sa[GUID_ENCODING_LENGTH + 1];
+ KvpFrame *kvpImport;
+ QofType mergeType;
+ const GUID *guidImport;
+ gnc_numeric numericImport, (*numeric_getter) (QofEntity*, QofParam*);
+ Timespec tsImport, (*date_getter) (QofEntity*, QofParam*);
+ double doubleImport, (*double_getter) (QofEntity*, QofParam*);
+ gboolean booleanImport, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 i32Import, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 i64Import, (*int64_getter) (QofEntity*, QofParam*);
+
+ stringImport = NULL;
+ mergeType = qtparam->param_type;
+ if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0) {
+ stringImport = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+ if(stringImport == NULL) { stringImport = ""; }
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+ tsImport = date_getter(qtEnt, qtparam);
+ stringImport = g_strdup_printf("%llu", tsImport.tv_sec);
+ return stringImport;
+ }
+ if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ numericImport = numeric_getter(qtEnt,qtparam);
+ stringImport = g_strdup(gnc_numeric_to_string(numericImport));
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) {
+ guidImport = qtparam->param_getfcn(qtEnt,qtparam);
+ guid_to_string_buff(guidImport, sa);
+ stringImport = g_strdup(sa);
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i32Import = int32_getter(qtEnt, qtparam);
+ stringImport = g_strdup_printf("%u", i32Import);
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i64Import = int64_getter(qtEnt, qtparam);
+ stringImport = g_strdup_printf("%llu", i64Import);
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ doubleImport = double_getter(qtEnt, qtparam);
+ stringImport = g_strdup_printf("%f", doubleImport);
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ booleanImport = boolean_getter(qtEnt, qtparam);
+ if(booleanImport == TRUE) { stringImport = g_strdup("TRUE"); }
+ else { stringImport = g_strdup("FALSE"); }
+ return stringImport;
+ }
+ /* "kvp" */
+ /* FIXME: how can this be a string??? */
+ if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) {
+ kvpImport = kvp_frame_copy(qtparam->param_getfcn(qtEnt,qtparam));
+
+ return stringImport;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) {
+ stringImport = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+ return stringImport;
+ }
+ return NULL;
+}
+
+int
+qof_book_mergeUpdateResult(qof_book_mergeRule *resolved, qof_book_mergeResult tag)
+{
+ g_return_val_if_fail((resolved != NULL), -1);
+ g_return_val_if_fail((tag > 0), -1);
+ g_return_val_if_fail((tag != MERGE_REPORT), -1);
+ currentRule = resolved;
+ if((currentRule->mergeAbsolute == TRUE)&& (tag == MERGE_DUPLICATE)) { tag = MERGE_ABSOLUTE; }
+ if((currentRule->mergeAbsolute == TRUE)&& (tag == MERGE_NEW)) { tag = MERGE_UPDATE; }
+ if((currentRule->mergeAbsolute == FALSE)&& (tag == MERGE_ABSOLUTE)) { tag = MERGE_DUPLICATE; }
+ if((currentRule->mergeResult == MERGE_NEW)&&(tag == MERGE_UPDATE)) { tag = MERGE_NEW; }
+ if(currentRule->updated == FALSE) { currentRule->mergeResult = tag; }
+ currentRule->updated = TRUE;
+ if(tag == MERGE_INVALID) {
+ mergeData->abort = TRUE;
+ qof_book_merge_abort();
+ }
+ return 0;
+}
+
+int
+qof_book_mergeCommit( void )
+{
+ GList *check;
+
+ if(mergeData->abort == TRUE) return -1;
+ g_return_val_if_fail(mergeData != NULL, -1);
+ g_return_val_if_fail(mergeData->mergeList != NULL, -1);
+ currentRule = mergeData->mergeList->data;
+ check = g_list_copy(mergeData->mergeList);
+ while(check != NULL) {
+ currentRule = check->data;
+ if(currentRule->mergeResult == MERGE_INVALID) {
+ qof_book_merge_abort();
+ return(-1);
+ }
+ if(currentRule->mergeResult == MERGE_REPORT) {
+ g_list_free(check);
+ return 1;
+ }
+ check = g_list_next(check);
+ }
+ g_list_free(check);
+ qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_NEW);
+ qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_UPDATE);
+ while(mergeData->mergeList != NULL) {
+ g_free(mergeData->mergeList->data);
+ mergeData->mergeList = g_list_next(mergeData->mergeList);
+ }
+ g_list_free(mergeData->mergeList);
+ g_slist_free(mergeData->mergeObjectParams);
+ g_slist_free(mergeData->targetList);
+ g_free(mergeData);
+ return 0;
+}
+
+/* End of API functions. Internal code follows. */
+/* ==================================================================== */
+
+void qof_book_mergeRuleForeach( qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult)
+{
+ struct qof_book_mergeRuleIterate iter;
+ GList *matching_rules;
+
+ g_return_if_fail(cb != NULL);
+ g_return_if_fail(mergeData != NULL);
+ g_return_if_fail(mergeResult > 0);
+ g_return_if_fail(mergeResult != MERGE_INVALID);
+ g_return_if_fail(mergeData->abort == FALSE);
+ iter.fcn = cb;
+ matching_rules = NULL;
+ iter.ruleList = g_list_copy(mergeData->mergeList);
+ while(iter.ruleList!=NULL) {
+ currentRule = iter.ruleList->data;
+ if(currentRule->mergeResult == mergeResult) {
+ matching_rules = g_list_prepend(matching_rules, currentRule);
+ }
+ iter.ruleList = g_list_next(iter.ruleList);
+ }
+ iter.remainder = g_list_length(matching_rules);
+ g_list_free(iter.ruleList);
+ g_list_foreach (matching_rules, qof_book_mergeRuleCB, &iter);
+ g_list_free(matching_rules);
+}
+
+
+void
+qof_book_mergeUpdateRule(gboolean match)
+{
+ gboolean absolute;
+
+ absolute = currentRule->mergeAbsolute;
+ if(absolute && match && currentRule->mergeResult == MERGE_UNDEF) {
+ currentRule->mergeResult = MERGE_ABSOLUTE;
+ }
+ if(absolute && !match) { currentRule->mergeResult = MERGE_UPDATE; }
+ if(!absolute && match &¤tRule->mergeResult == MERGE_UNDEF) {
+ currentRule->mergeResult = MERGE_DUPLICATE;
+ }
+ if(!absolute && !match) {
+ currentRule->difference++;
+ if(currentRule->mergeResult == MERGE_DUPLICATE) {
+ currentRule->mergeResult = MERGE_REPORT;
+ }
+ }
+}
+
+int
+qof_book_mergeCompare( void )
+{
+ gchar *stringImport, *stringTarget,
+ *charImport, *charTarget;
+ char sa[GUID_ENCODING_LENGTH + 1];
+ const GUID *guidImport, *guidTarget;
+ QofInstance *inst;
+ gpointer unknown_obj;
+ QofParam *qtparam;
+ KvpFrame *kvpImport, *kvpTarget;
+ QofIdType mergeParamName;
+ QofType mergeType;
+ GSList *paramList;
+ QofEntity *mergeEnt, *targetEnt, *childEnt;
+ Timespec tsImport, tsTarget, (*date_getter) (QofEntity*, QofParam*);
+ gnc_numeric numericImport, numericTarget, (*numeric_getter) (QofEntity*, QofParam*);
+ double doubleImport, doubleTarget, (*double_getter) (QofEntity*, QofParam*);
+ gboolean absolute, mergeError,
+ knowntype, mergeMatch,
+ booleanImport, booleanTarget, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 i32Import, i32Target, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 i64Import, i64Target, (*int64_getter) (QofEntity*, QofParam*);
+
+ g_return_val_if_fail((mergeData != NULL)||(currentRule != NULL), -1);
+ absolute = currentRule->mergeAbsolute;
+ mergeEnt = currentRule->importEnt;
+ targetEnt = currentRule->targetEnt;
+ paramList = currentRule->mergeParam;
+ currentRule->difference = 0;
+ currentRule->mergeResult = MERGE_UNDEF;
+ g_return_val_if_fail((targetEnt)||(mergeEnt)||(paramList), -1);
+
+ kvpImport = kvp_frame_new();
+ kvpTarget = kvp_frame_new();
+ mergeError = FALSE;
+ while(paramList != NULL) {
+ mergeMatch = FALSE;
+ knowntype = FALSE;
+ qtparam = paramList->data;
+ mergeParamName = qtparam->param_name;
+
+ g_return_val_if_fail(mergeParamName != NULL, -1);
+ mergeType = qtparam->param_type;
+ if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0) {
+ stringImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ stringTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ /* very strict string matches may need to be relaxed. */
+ if(stringImport == NULL) { stringImport = ""; }
+ if(stringTarget == NULL) { stringTarget = ""; }
+ if(safe_strcmp(stringImport,stringTarget) == 0) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ stringImport = stringTarget = NULL;
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+ tsImport = date_getter(mergeEnt, qtparam);
+ tsTarget = date_getter(targetEnt, qtparam);
+ if(timespec_cmp(&tsImport, &tsTarget) == 0) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ numericImport = numeric_getter(mergeEnt,qtparam);
+ numericTarget = numeric_getter(targetEnt,qtparam);
+ if(gnc_numeric_compare (numericImport, numericTarget) == 0) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) {
+ guidImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ guidTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ if(guid_compare(guidImport, guidTarget) == 0) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i32Import = int32_getter(mergeEnt, qtparam);
+ i32Target = int32_getter(targetEnt, qtparam);
+ if(i32Target == i32Import) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i64Import = int64_getter(mergeEnt, qtparam);
+ i64Target = int64_getter(targetEnt, qtparam);
+ if(i64Target == i64Import) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ doubleImport = double_getter(mergeEnt, qtparam);
+ doubleTarget = double_getter(mergeEnt, qtparam);
+ if(doubleImport == doubleTarget) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ booleanImport = boolean_getter(mergeEnt, qtparam);
+ booleanTarget = boolean_getter(targetEnt, qtparam);
+ if(booleanImport != FALSE && booleanImport != TRUE) { booleanImport = FALSE; }
+ if(booleanTarget != FALSE && booleanTarget != TRUE) { booleanTarget = FALSE; }
+ if(booleanImport == booleanTarget) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) {
+ kvpImport = kvp_frame_copy(qtparam->param_getfcn(mergeEnt,qtparam));
+ kvpTarget = kvp_frame_copy(qtparam->param_getfcn(targetEnt,qtparam));
+ if(kvp_frame_compare(kvpImport, kvpTarget) == 0) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) {
+ charImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ charTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ if(charImport == charTarget) { mergeMatch = TRUE; }
+ qof_book_mergeUpdateRule(mergeMatch);
+ knowntype= TRUE;
+ }
+ /* no need to verify the book */
+ if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE; }
+ /* deal with non-QOF type parameters : */
+ /* references to other registered QOF objects */
+ if(knowntype == FALSE) {
+ if(qof_class_is_registered(currentRule->mergeLabel)) {
+ childEnt = g_new(QofEntity,1);
+ unknown_obj = qtparam->param_getfcn(mergeEnt, qtparam);
+ inst = ((QofInstance*)(unknown_obj));
+ childEnt = &inst->entity;
+ currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, childEnt);
+ guidImport = qof_entity_get_guid(childEnt);
+ if(guidImport != NULL) {
+ guid_to_string_buff(guidImport, sa);
+ stringImport = g_strdup(sa);
+ printf("Test routine GUID: %s\n", stringImport);
+ }
+ }
+ }
+ g_return_val_if_fail(knowntype == TRUE, -1);
+ paramList = g_slist_next(paramList);
+ }
+ g_free(kvpImport);
+ g_free(kvpTarget);
+ return 0;
+}
+
+void
+qof_book_mergeCommitForeach (qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult )
+{
+ struct qof_book_mergeRuleIterate iter;
+ GList *subList;
+
+ g_return_if_fail(cb != NULL);
+ g_return_if_fail(mergeData != NULL);
+ g_return_if_fail(mergeResult > 0);
+ g_return_if_fail((mergeResult != MERGE_INVALID)||(mergeResult != MERGE_UNDEF)||(mergeResult != MERGE_REPORT));
+
+ iter.fcn = cb;
+ subList = NULL;
+ iter.ruleList = g_list_copy(mergeData->mergeList);
+ while(iter.ruleList!=NULL) {
+ currentRule = iter.ruleList->data;
+ if(currentRule->mergeResult == mergeResult) {
+ subList = g_list_prepend(subList, currentRule);
+ }
+ iter.ruleList = g_list_next(iter.ruleList);
+ }
+ iter.remainder = g_list_length(subList);
+ g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
+}
+
+void qof_book_mergeCommitForeachCB(gpointer lister, gpointer arg)
+{
+ struct qof_book_mergeRuleIterate *iter;
+
+ iter = arg;
+ iter->fcn ((qof_book_mergeRule*)lister, iter->remainder);
+ iter->remainder--;
+}
+
+
+void
+qof_book_mergeForeach ( QofEntity* mergeEnt, gpointer user_data)
+{
+ qof_book_mergeRule* mergeRule;
+ QofEntity *targetEnt, *best_matchEnt;
+ GUID *g;
+ gint difference;
+ GSList *c;
+
+ g_return_if_fail(mergeEnt != NULL);
+ g = guid_malloc();
+ *g = mergeEnt->guid;
+ mergeRule = g_new(qof_book_mergeRule,1);
+ mergeRule->importEnt = mergeEnt;
+ mergeRule->difference = difference = 0;
+ mergeRule->mergeAbsolute = FALSE;
+ mergeRule->mergeResult = MERGE_UNDEF;
+ mergeRule->updated = FALSE;
+ mergeRule->mergeType = mergeEnt->e_type;
+ mergeRule->mergeLabel = qof_object_get_type_label(mergeEnt->e_type);
+ mergeRule->mergeParam = g_slist_copy(mergeData->mergeObjectParams);
+ mergeRule->linkedEntList = NULL;
+ currentRule = mergeRule;
+ targetEnt = best_matchEnt = NULL;
+ targetEnt = qof_collection_lookup_entity (
+ qof_book_get_collection (mergeData->targetBook, mergeEnt->e_type), g);
+ if( targetEnt != NULL) {
+ mergeRule->mergeAbsolute = TRUE;
+ mergeRule->targetEnt = targetEnt;
+ g_return_if_fail(qof_book_mergeCompare() != -1);
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+ return;
+ }
+ g_slist_free(mergeData->targetList);
+ mergeData->targetList = NULL;
+ qof_object_foreach_type(qof_book_mergeForeachTypeTarget, NULL);
+ if(g_slist_length(mergeData->targetList) == 0) {
+ mergeRule->mergeResult = MERGE_NEW;
+ }
+ difference = g_slist_length(mergeRule->mergeParam);
+ c = g_slist_copy(mergeData->targetList);
+ gnc_set_log_level(MOD_IMPORT, GNC_LOG_DEBUG);
+ while(c != NULL) {
+ mergeRule->targetEnt = c->data;
+ currentRule = mergeRule;
+ g_return_if_fail(qof_book_mergeCompare() != -1);
+ if(mergeRule->difference == 0) {
+ best_matchEnt = mergeRule->targetEnt;
+ mergeRule->mergeResult = MERGE_DUPLICATE;
+ g_slist_free(c);
+ guid_free(g);
+ return;
+ }
+ if(difference > mergeRule->difference) {
+ best_matchEnt = mergeRule->targetEnt;
+ difference = mergeRule->difference;
+ }
+ c = g_slist_next(c);
+ }
+ g_slist_free(c);
+ if(best_matchEnt != NULL ) {
+ mergeRule->targetEnt = best_matchEnt;
+ mergeRule->difference = difference;
+ }
+ else {
+ mergeRule->targetEnt = NULL;
+ mergeRule->difference = 0;
+ mergeRule->mergeResult = MERGE_NEW;
+ }
+ if(best_matchEnt != NULL ) {
+ g_return_if_fail(qof_book_mergeCompare() != -1);
+ }
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+ guid_free(g);
+ /* return to qof_book_mergeInit */
+}
+
+void qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
+{
+ g_return_if_fail(targetEnt != NULL);
+
+ qof_book_merge_target_check(targetEnt);
+}
+
+
+void qof_book_merge_target_check (QofEntity* targetEnt)
+{
+ GList *checklist;
+ qof_book_mergeRule *destination;
+ const GUID *guid_ent, *guid_dest;
+ gboolean exists;
+
+ exists = FALSE;
+ checklist = NULL;
+ if(mergeData->mergeList == NULL) { return; }
+ guid_ent = qof_entity_get_guid(targetEnt);
+ checklist = g_list_copy(mergeData->mergeList);
+ while(checklist != NULL) {
+ destination = checklist->data;
+ guid_dest = qof_entity_get_guid(destination->targetEnt);
+ if(guid_compare(guid_ent,guid_dest) == 0) { exists = TRUE; }
+ checklist = g_list_next(checklist);
+ }
+ if(exists == FALSE ) {
+ mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
+ }
+}
+
+void
+qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer user_data)
+{
+ g_return_if_fail(merge_obj != NULL);
+ if(safe_strcmp(merge_obj->e_type, currentRule->importEnt->e_type) == 0) {
+ qof_object_foreach(currentRule->importEnt->e_type, mergeData->targetBook,
+ qof_book_mergeForeachTarget, NULL);
+ }
+}
+
+void
+qof_book_mergeForeachType ( QofObject* merge_obj, gpointer user_data)
+{
+ g_return_if_fail((merge_obj != NULL));
+ /* Skip unsupported objects */
+ if((merge_obj->create == NULL)||(merge_obj->foreach == NULL)){
+ DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
+ return;
+ }
+
+ if(mergeData->mergeObjectParams != NULL) g_slist_free(mergeData->mergeObjectParams);
+ mergeData->mergeObjectParams = NULL;
+ qof_class_param_foreach(merge_obj->e_type, qof_book_mergeForeachParam , NULL);
+ qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, qof_book_mergeForeach, NULL);
+}
+
+void
+qof_book_mergeForeachParam( QofParam* param, gpointer user_data)
+{
+ g_return_if_fail(param != NULL);
+ if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+ mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param);
+ }
+}
+
+void
+qof_book_mergeRuleCB(gpointer lister, gpointer arg)
+{
+ struct qof_book_mergeRuleIterate *iter;
+
+ g_return_if_fail(mergeData->abort == FALSE);
+ iter = arg;
+ iter->fcn ((qof_book_mergeRule*)lister, iter->remainder);
+ iter->remainder--;
+}
+
+void qof_book_mergeCommitRuleLoop(qof_book_mergeRule *rule, guint remainder)
+{
+ QofInstance *inst;
+ gboolean registered_type;
+ /* cm_ prefix used for variables that hold the data to commit */
+ QofParam *cm_param;
+ char *cm_string, *cm_char;
+ const GUID *cm_guid;
+ KvpFrame *cm_kvp;
+ /* function pointers and variables for parameter getters that don't use pointers normally */
+ gnc_numeric cm_numeric, (*numeric_getter) (QofEntity*, QofParam*);
+ double cm_double, (*double_getter) (QofEntity*, QofParam*);
+ gboolean cm_boolean, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 cm_i32, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 cm_i64, (*int64_getter) (QofEntity*, QofParam*);
+ Timespec cm_date, (*date_getter) (QofEntity*, QofParam*);
+ /* function pointers to the parameter setters */
+ void (*string_setter) (QofEntity*, const char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*guid_setter) (QofEntity*, const GUID*);
+ void (*double_setter) (QofEntity*, double);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*char_setter) (QofEntity*, char*);
+ void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+
+ g_return_if_fail(rule != NULL);
+ g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
+
+ /* create a new object for MERGE_NEW */
+ if(rule->mergeResult == MERGE_NEW) {
+ inst = (QofInstance*)qof_object_new_instance(rule->importEnt->e_type, mergeData->targetBook);
+ g_return_if_fail(inst != NULL);
+ rule->targetEnt = &inst->entity;
+ }
+ /* currentRule->targetEnt is now set,
+ 1. by an absolute GUID match or
+ 2. by best_matchEnt and difference or
+ 3. by MERGE_NEW.
+ */
+ registered_type = FALSE;
+ while(rule->mergeParam != NULL) {
+ g_return_if_fail(rule->mergeParam->data);
+ cm_param = rule->mergeParam->data;
+ rule->mergeType = cm_param->param_type;
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_STRING) == 0) {
+ cm_string = cm_param->param_getfcn(rule->importEnt, cm_param);
+ string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+ if(string_setter != NULL) { string_setter(rule->targetEnt, cm_string); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_date = date_getter(rule->importEnt, cm_param);
+ date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+ if(date_setter != NULL) { date_setter(rule->targetEnt, cm_date); }
+ registered_type = TRUE;
+ }
+ if((safe_strcmp(rule->mergeType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(rule->mergeType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_numeric = numeric_getter(rule->importEnt, cm_param);
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+ if(numeric_setter != NULL) { numeric_setter(rule->targetEnt, cm_numeric); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_GUID) == 0) {
+ cm_guid = cm_param->param_getfcn(rule->importEnt, cm_param);
+ guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+ if(guid_setter != NULL) { guid_setter(rule->targetEnt, cm_guid); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i32 = int32_getter(rule->importEnt, cm_param);
+ i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+ if(i32_setter != NULL) { i32_setter(rule->targetEnt, cm_i32); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i64 = int64_getter(rule->importEnt, cm_param);
+ i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+ if(i64_setter != NULL) { i64_setter(rule->targetEnt, cm_i64); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_double = double_getter(rule->importEnt, cm_param);
+ double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+ if(double_setter != NULL) { double_setter(rule->targetEnt, cm_double); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_boolean = boolean_getter(rule->importEnt, cm_param);
+ boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+ if(boolean_setter != NULL) { boolean_setter(rule->targetEnt, cm_boolean); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_KVP) == 0) {
+ cm_kvp = kvp_frame_copy(cm_param->param_getfcn(rule->importEnt,cm_param));
+ kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+ if(kvp_frame_setter != NULL) { kvp_frame_setter(rule->targetEnt, cm_kvp); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_CHAR) == 0) {
+ cm_char = cm_param->param_getfcn(rule->importEnt,cm_param);
+ char_setter = (void(*)(QofEntity*, char*))cm_param->param_setfcn;
+ if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
+ registered_type = TRUE;
+ }
+ if(registered_type == FALSE) {
+ if(qof_class_is_registered(rule->mergeLabel)) {
+ /* need to lookup childEnt in the target book to
+ ensure it has the right QofCollection */
+ GSList *linkage = g_slist_copy(rule->linkedEntList);
+ while(linkage != NULL) {
+ QofEntity *childEnt = linkage->data;
+ /* there may be more than one linked QofEntity for this rule */
+ if(safe_strcmp(childEnt->e_type, rule->mergeType) == 0) {
+ cm_guid = qof_entity_get_guid(childEnt);
+ QofCollection *col;
+ col = qof_book_get_collection (mergeData->targetBook, rule->mergeType);
+ childEnt = qof_collection_lookup_entity (col, cm_guid);
+ /* childEnt isn't used here yet. It may be too early to set */
+ /* intention is to set the parameter if childEnt is not null.
+ might have to store the param and set later, after Commit. */
+ }
+ linkage = g_slist_next(linkage);
+ }
+ }
+ }
+ rule->mergeParam = g_slist_next(rule->mergeParam);
+ }
+}
Index: gnc-pricedb.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-pricedb.c,v
retrieving revision 1.80
retrieving revision 1.81
diff -Lsrc/engine/gnc-pricedb.c -Lsrc/engine/gnc-pricedb.c -u -r1.80 -r1.81
--- src/engine/gnc-pricedb.c
+++ src/engine/gnc-pricedb.c
@@ -76,7 +76,7 @@
return p;
}
-static void
+static void
gnc_price_destroy (GNCPrice *p)
{
ENTER(" ");
@@ -151,20 +151,20 @@
/* ==================================================================== */
-void
+void
gnc_price_begin_edit (GNCPrice *p)
{
QOF_BEGIN_EDIT (&p->inst);
}
-static inline void commit_err (QofInstance *inst, QofBackendError errcode)
+static inline void commit_err (QofInstance *inst, QofBackendError errcode)
{
PERR ("Failed to commit: %d", errcode);
}
static inline void noop (QofInstance *inst) {}
-void
+void
gnc_price_commit_edit (GNCPrice *p)
{
QOF_COMMIT_EDIT_PART1 (&p->inst);
@@ -173,13 +173,13 @@
/* ==================================================================== */
-void
+void
gnc_pricedb_begin_edit (GNCPriceDB *pdb)
{
QOF_BEGIN_EDIT (&pdb->inst);
}
-void
+void
gnc_pricedb_commit_edit (GNCPriceDB *pdb)
{
QOF_COMMIT_EDIT_PART1 (&pdb->inst);
@@ -194,10 +194,10 @@
{
if(!p) return;
- if(!gnc_commodity_equiv(p->commodity, c))
+ if(!gnc_commodity_equiv(p->commodity, c))
{
- /* Changing the commodity requires the hash table
- * position to be modified. The easiest way of doing
+ /* Changing the commodity requires the hash table
+ * position to be modified. The easiest way of doing
* this is to remove and reinsert. */
gnc_price_ref (p);
remove_price (p->db, p, TRUE);
@@ -216,10 +216,10 @@
{
if(!p) return;
- if(!gnc_commodity_equiv(p->currency, c))
+ if(!gnc_commodity_equiv(p->currency, c))
{
- /* Changing the currency requires the hash table
- * position to be modified. The easiest way of doing
+ /* Changing the currency requires the hash table
+ * position to be modified. The easiest way of doing
* this is to remove and reinsert. */
gnc_price_ref (p);
remove_price (p->db, p, TRUE);
@@ -236,10 +236,10 @@
gnc_price_set_time(GNCPrice *p, Timespec t)
{
if(!p) return;
- if(!timespec_equal(&(p->tmspec), &t))
+ if(!timespec_equal(&(p->tmspec), &t))
{
- /* Changing the datestamp requires the hash table
- * position to be modified. The easiest way of doing
+ /* Changing the datestamp requires the hash table
+ * position to be modified. The easiest way of doing
* this is to remove and reinsert. */
gnc_price_ref (p);
remove_price (p->db, p, FALSE);
@@ -256,7 +256,7 @@
gnc_price_set_source(GNCPrice *p, const char *s)
{
if(!p) return;
- if(safe_strcmp(p->source, s) != 0)
+ if(safe_strcmp(p->source, s) != 0)
{
GCache *cache;
char *tmp;
@@ -275,7 +275,7 @@
gnc_price_set_type(GNCPrice *p, const char* type)
{
if(!p) return;
- if(safe_strcmp(p->type, type) != 0)
+ if(safe_strcmp(p->type, type) != 0)
{
GCache *cache;
gchar *tmp;
@@ -294,7 +294,7 @@
gnc_price_set_value(GNCPrice *p, gnc_numeric value)
{
if(!p) return;
- if(!gnc_numeric_eq(p->value, value))
+ if(!gnc_numeric_eq(p->value, value))
{
gnc_price_begin_edit (p);
p->value = value;
@@ -319,7 +319,7 @@
gnc_price_lookup (const GUID *guid, QofBook *book)
{
QofCollection *col;
-
+
if (!guid || !book) return NULL;
col = qof_book_get_collection (book, GNC_ID_PRICE);
return (GNCPrice *) qof_collection_lookup_entity (col, guid);
@@ -465,7 +465,7 @@
GList *found_element;
if(!prices || !p) return FALSE;
-
+
found_element = g_list_find(*prices, p);
if(!found_element) return TRUE;
@@ -558,11 +558,11 @@
g_return_val_if_fail (book, NULL);
/* There can only be one pricedb per book. So if one exits already,
- * then use that. Warn user, they shouldn't be creating two ...
+ * then use that. Warn user, they shouldn't be creating two ...
*/
col = qof_book_get_collection (book, GNC_ID_PRICEDB);
result = qof_collection_get_data (col);
- if (result)
+ if (result)
{
PWARN ("A price database already exists for this book!");
return result;
@@ -736,7 +736,7 @@
}
/* ==================================================================== */
-/* The add_price() function is a utility that only manages the
+/* The add_price() function is a utility that only manages the
* dual hash table instertion */
static gboolean
@@ -804,7 +804,7 @@
if (FALSE == add_price(db, p)) return FALSE;
/* If we haven't been able to call the backend before, call it now */
- if (TRUE == p->inst.dirty)
+ if (TRUE == p->inst.dirty)
{
gnc_price_begin_edit(p);
db->inst.dirty = TRUE;
@@ -986,8 +986,8 @@
}
-static void
-hash_values_helper(gpointer key, gpointer value, gpointer data)
+static void
+hash_values_helper(gpointer key, gpointer value, gpointer data)
{
GList ** l = data;
*l = g_list_concat(*l, g_list_copy (value));
@@ -1313,7 +1313,7 @@
Timespec diff_next = timespec_diff(&next_t, &t);
Timespec abs_current = timespec_abs(&diff_current);
Timespec abs_next = timespec_abs(&diff_next);
-
+
if (timespec_cmp(&abs_current, &abs_next) <= 0) {
result = current_price;
} else {
@@ -1368,7 +1368,7 @@
Timespec diff_next = timespec_diff(&next_t, &t);
Timespec abs_current = timespec_abs(&diff_current);
Timespec abs_next = timespec_abs(&diff_next);
-
+
if (timespec_cmp(&abs_current, &abs_next) <= 0) {
result = current_price;
} else {
@@ -1488,10 +1488,10 @@
balance = gnc_numeric_mul (balance, currency_price_value,
gnc_commodity_get_fraction (new_currency),
- GNC_HOW_RND_ROUND);
+ GNC_HOW_RND_ROUND);
balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
gnc_commodity_get_fraction (new_currency),
- GNC_HOW_RND_ROUND);
+ GNC_HOW_RND_ROUND);
gnc_price_list_destroy(price_list);
return balance;
@@ -1564,10 +1564,10 @@
balance = gnc_numeric_mul (balance, currency_price_value,
gnc_commodity_get_fraction (new_currency),
- GNC_HOW_RND_ROUND);
+ GNC_HOW_RND_ROUND);
balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
gnc_commodity_get_fraction (new_currency),
- GNC_HOW_RND_ROUND);
+ GNC_HOW_RND_ROUND);
gnc_price_list_destroy(price_list);
return balance;
@@ -1612,7 +1612,7 @@
gpointer user_data)
{
GNCPriceDBForeachData foreach_data;
-
+
if(!db || !f) return FALSE;
foreach_data.ok = TRUE;
foreach_data.func = f;
@@ -1638,13 +1638,13 @@
if(!a && !b) return 0;
if(!a) return -1;
if(!b) return 1;
-
+
ca = (gnc_commodity *) kvpa->key;
cb = (gnc_commodity *) kvpb->key;
cmp_result = safe_strcmp(gnc_commodity_get_namespace(ca),
gnc_commodity_get_namespace(cb));
-
+
if(cmp_result != 0) return cmp_result;
return safe_strcmp(gnc_commodity_get_mnemonic(ca),
@@ -1659,7 +1659,7 @@
GSList *currency_hashes = NULL;
gboolean ok = TRUE;
GSList *i = NULL;
-
+
if(!db || !f) return FALSE;
currency_hashes = g_hash_table_key_value_pairs(db->commodity_hash);
@@ -1843,13 +1843,13 @@
/* ==================================================================== */
/* gncObject function implementation and registration */
-static void
+static void
pricedb_book_begin (QofBook *book)
{
gnc_pricedb_create(book);
}
-static void
+static void
pricedb_book_end (QofBook *book)
{
/* ????? */
@@ -1870,11 +1870,11 @@
/* ==================================================================== */
/* a non-boolean foreach. Ugh */
-typedef struct
+typedef struct
{
void (*func)(GNCPrice *p, gpointer user_data);
gpointer user_data;
-}
+}
VoidGNCPriceDBForeachData;
static void
@@ -1884,7 +1884,7 @@
GList *node = price_list;
VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
- while(node)
+ while(node)
{
GNCPrice *p = (GNCPrice *) node->data;
foreach_data->func(p, foreach_data->user_data);
@@ -1905,7 +1905,7 @@
gpointer user_data)
{
VoidGNCPriceDBForeachData foreach_data;
-
+
if(!db || !f) return;
foreach_data.func = f;
foreach_data.user_data = user_data;
@@ -1915,11 +1915,11 @@
&foreach_data);
}
-static void
+static void
pricedb_foreach(QofCollection *col, QofEntityForeachCB cb, gpointer data)
{
GNCPriceDB *db = gnc_collection_get_pricedb(col);
- void_unstable_price_traversal(db,
+ void_unstable_price_traversal(db,
(void (*)(GNCPrice *, gpointer)) cb,
data);
}
@@ -1939,7 +1939,7 @@
val = gnc_numeric_to_string (pr->value);
da = qof_print_date (pr->tmspec.tv_sec);
-
+
commodity = gnc_price_get_commodity(pr);
currency = gnc_price_get_currency(pr);
@@ -1952,7 +1952,7 @@
return buff;
}
-static QofObject pricedb_object_def =
+static QofObject pricedb_object_def =
{
interface_version: QOF_OBJECT_VERSION,
e_type: GNC_ID_PRICE,
@@ -1967,7 +1967,7 @@
version_cmp: NULL,
};
-gboolean
+gboolean
gnc_pricedb_register (void)
{
static QofParam params[] = {
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/Makefile.am,v
retrieving revision 1.124
retrieving revision 1.125
diff -Lsrc/engine/Makefile.am -Lsrc/engine/Makefile.am -u -r1.124 -r1.125
--- src/engine/Makefile.am
+++ src/engine/Makefile.am
@@ -1,7 +1,11 @@
+#INCLUDES = /usr/include/glib-1.2 /usr/lib/glib/include
+
+qof_book_merge_lo_INCLUDES = -DG_LOG_DOMAIN=\"qof-book-merge\"
+
SUBDIRS = . test-core test
PWD := $(shell pwd)
-pkglib_LTLIBRARIES = libgw-engine.la libgw-kvp.la libgncmod-engine.la
+pkglib_LTLIBRARIES = libgw-engine.la libgw-kvp.la libgncmod-engine.la
AM_CFLAGS = \
-I${top_srcdir}/lib/libc \
@@ -44,6 +48,7 @@
messages.c \
policy.c \
qofbackend.c \
+ qof_book_merge.c \
qofbook.c \
qofclass.c \
qofid.c \
@@ -51,7 +56,7 @@
qofobject.c \
qofquery.c \
qofquerycore.c \
- qofsession.c
+ qofsession.c
EXTRA_libgncmod_engine_la_SOURCES = iso-4217-currencies.c
@@ -101,6 +106,7 @@
qofbackend.h \
qof-be-utils.h \
qofbook.h \
+ qof_book_merge.h \
qofclass.h \
qofid.h \
qofinstance.h \
@@ -195,6 +201,7 @@
rm -f gnucash g-wrapped
ln -sf . gnucash
ln -sf . g-wrapped
+ ln -sf ${srcdir} qof
if GNUCASH_SEPARATE_BUILDDIR
for X in ${SCM_FILE_LINKS} ; do \
ln -sf ${srcdir}/$$X . ; \
@@ -227,6 +234,5 @@
gw-engine.scm gw-engine.c gw-engine.h \
gw-kvp.scm gw-kvp.c gw-kvp.h
-DISTCLEANFILES = gnucash g-wrapped .scm-links ${SCM_FILE_LINKS} \
+DISTCLEANFILES = gnucash g-wrapped qof .scm-links ${SCM_FILE_LINKS} \
gw-engine.html gw-kvp.html
-
--- /dev/null
+++ src/engine/test/test-book-merge.c
@@ -0,0 +1,473 @@
+/*********************************************************************
+ * test-book-merge.c -- test implementation api for QoFBook merge *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program 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 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program 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 this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+/*
+ * Test the gncBookMerge infrastructure.
+ */
+#include <glib.h>
+#include <libguile.h>
+#define _GNU_SOURCE
+
+#include "qofinstance-p.h"
+#include "gnc-module.h"
+#include "gnc-event-p.h"
+#include "qof.h"
+#include "qof_book_merge.h"
+#include "test-stuff.h"
+
+#include "gnc-engine.h"
+#define TEST_MODULE_NAME "book-merge-test"
+#define TEST_MODULE_DESC "Test Book Merge"
+#define OBJ_NAME "somename"
+#define OBJ_AMOUNT "anamount"
+#define OBJ_DATE "nottoday"
+#define OBJ_GUID "unique"
+#define OBJ_DISCOUNT "hefty"
+#define OBJ_VERSION "early"
+#define OBJ_MINOR "tiny"
+#define OBJ_ACTIVE "ofcourse"
+
+static void test_rule_loop (qof_book_mergeRule*, guint);
+static void test_merge (void);
+gboolean myobjRegister (void);
+
+/* simple object structure */
+typedef struct obj_s
+{
+ QofInstance inst;
+ char *Name;
+ gnc_numeric Amount;
+ const GUID *obj_guid;
+ Timespec date;
+ double discount; /* cheap pun, I know. */
+ gboolean active;
+ gint32 version;
+ gint64 minor;
+}myobj;
+
+myobj* obj_create(QofBook*);
+
+/* obvious setter functions */
+void obj_setName(myobj*, char*);
+void obj_setGUID(myobj*, const GUID*);
+void obj_setAmount(myobj*, gnc_numeric);
+void obj_setDate(myobj*, Timespec h);
+void obj_setDiscount(myobj*, double);
+void obj_setActive(myobj*, gboolean);
+void obj_setVersion(myobj*, gint32);
+void obj_setMinor(myobj*, gint64);
+
+/* obvious getter functions */
+char* obj_getName(myobj*);
+const GUID* obj_getGUID(myobj*);
+gnc_numeric obj_getAmount(myobj*);
+Timespec obj_getDate(myobj*);
+double obj_getDiscount(myobj*);
+gboolean obj_getActive(myobj*);
+gint32 obj_getVersion(myobj*);
+gint64 obj_getMinor(myobj*);
+
+myobj*
+obj_create(QofBook *book)
+{
+ myobj *g;
+ g_return_val_if_fail(book, NULL);
+ g = g_new(myobj, 1);
+ qof_instance_init (&g->inst, TEST_MODULE_NAME, book);
+ obj_setGUID(g,qof_instance_get_guid(&g->inst));
+ g->date.tv_nsec = 0;
+ g->date.tv_sec = 0;
+ g->discount = 0;
+ g->active = TRUE;
+ g->version = 1;
+ g->minor = 1;
+ gnc_engine_gen_event(&g->inst.entity, GNC_EVENT_CREATE);
+ return g;
+}
+
+void
+obj_setMinor(myobj *g, gint64 h)
+{
+ g_return_if_fail(g != NULL);
+ g->minor = h;
+}
+
+gint64
+obj_getMinor(myobj *g)
+{
+ g_return_val_if_fail((g != NULL),0);
+ return g->minor;
+}
+
+void
+obj_setVersion(myobj *g, gint32 h)
+{
+ g_return_if_fail(g != NULL);
+ g->version = h;
+}
+
+gint32
+obj_getVersion(myobj *g)
+{
+ if(!g) return 0;
+ return g->version;
+}
+
+void
+obj_setActive(myobj *g, gboolean h)
+{
+ if(!g) return;
+ g->active = h;
+}
+
+gboolean
+obj_getActive(myobj *g)
+{
+ if(!g) return FALSE;
+ return g->active;
+}
+
+void
+obj_setDiscount(myobj *g, double h)
+{
+ if(!g) return;
+ g->discount = h;
+}
+
+double
+obj_getDiscount(myobj *g)
+{
+ if(!g) return 0;
+ return g->discount;
+}
+
+void
+obj_setDate(myobj *g, Timespec h)
+{
+ if(!g) return;
+ g->date = h;
+}
+
+Timespec
+obj_getDate(myobj *g)
+{
+ Timespec ts;
+ if(!g) return ts;
+ ts = g->date;
+ return ts;
+}
+
+void
+obj_setGUID(myobj* g, const GUID* h)
+{
+ if(!g) return;
+ g->obj_guid = h;
+}
+
+const GUID*
+obj_getGUID(myobj *g)
+{
+ if(!g) return NULL;
+ return g->obj_guid;
+}
+
+void
+obj_setName(myobj* g, char* h)
+{
+ if(!g || !h) return;
+ g->Name = strdup(h);
+}
+
+char*
+obj_getName(myobj *g)
+{
+ if(!g) return NULL;
+ return g->Name;
+}
+
+void
+obj_setAmount(myobj *g, gnc_numeric h)
+{
+ if(!g) return;
+ g->Amount = h;
+}
+
+gnc_numeric
+obj_getAmount(myobj *g)
+{
+ if(!g) return double_to_gnc_numeric(0,0,GNC_HOW_DENOM_EXACT);
+ return g->Amount;
+}
+
+static QofObject obj_object_def = {
+ interface_version: QOF_OBJECT_VERSION,
+ e_type: TEST_MODULE_NAME,
+ type_label: TEST_MODULE_DESC,
+ create: (gpointer)obj_create,
+ book_begin: NULL,
+ book_end: NULL,
+ is_dirty: NULL,
+ mark_clean: NULL,
+ foreach: qof_collection_foreach,
+ printable: NULL,
+ version_cmp: (int (*)(gpointer,gpointer)) qof_instance_version_cmp,
+};
+
+gboolean myobjRegister (void)
+{
+ static QofParam params[] = {
+ { OBJ_NAME, QOF_TYPE_STRING, (QofAccessFunc)obj_getName, (QofSetterFunc)obj_setName },
+ { OBJ_AMOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)obj_getAmount, (QofSetterFunc)obj_setAmount },
+ { OBJ_GUID, QOF_TYPE_GUID, (QofAccessFunc)obj_getGUID, (QofSetterFunc)obj_setGUID },
+ { OBJ_DATE, QOF_TYPE_DATE, (QofAccessFunc)obj_getDate, (QofSetterFunc)obj_setDate },
+ { OBJ_DISCOUNT, QOF_TYPE_DOUBLE, (QofAccessFunc)obj_getDiscount, (QofSetterFunc)obj_setDiscount },
+ { OBJ_ACTIVE, QOF_TYPE_BOOLEAN, (QofAccessFunc)obj_getActive, (QofSetterFunc)obj_setActive },
+ { OBJ_VERSION, QOF_TYPE_INT32, (QofAccessFunc)obj_getVersion, (QofSetterFunc)obj_setVersion },
+ { OBJ_MINOR, QOF_TYPE_INT64, (QofAccessFunc)obj_getMinor, (QofSetterFunc)obj_setMinor },
+ { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
+ { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
+ { NULL },
+ };
+
+ qof_class_register (TEST_MODULE_NAME, NULL, params);
+
+ return qof_object_register (&obj_object_def);
+}
+
+static void
+test_merge (void)
+{
+ QofBook *target, *import;
+ double init_value, discount;
+ myobj *import_obj, *target_obj, *new_obj;
+ int result;
+ Timespec ts, tc;
+ gboolean active;
+ gint32 version;
+ gint64 minor;
+ gchar *import_init, *target_init;
+ gnc_numeric obj_amount;
+
+ target = qof_book_new();
+ import = qof_book_new();
+ init_value = 1.00;
+ result = 0;
+ discount = 0.5;
+ active = TRUE;
+ version = 1;
+ minor = 1;
+ import_init = "test";
+ target_init = "testing";
+ qof_date_format_set(QOF_DATE_FORMAT_UK);
+ timespecFromTime_t(&ts,time(NULL));
+
+ do_test ((NULL != target), "#1 book null");
+
+ /* import book objects - tests used */
+ do_test ((NULL != import), "#2 import null");
+ import_obj = g_new(myobj, 1);
+ do_test ((NULL != import_obj), "#3 new object fail");
+ qof_instance_init (&import_obj->inst, TEST_MODULE_NAME, import);
+ do_test ((NULL != &import_obj->inst), "#4 instance init fail");
+ obj_setGUID(import_obj,qof_instance_get_guid(&import_obj->inst));
+ do_test ((NULL != &import_obj->obj_guid), "#5 guid set fail");
+ gnc_engine_gen_event(&import_obj->inst.entity, GNC_EVENT_CREATE);
+ do_test ((NULL != &import_obj->inst.entity), "#6 gnc event create fail");
+ obj_setName(import_obj, import_init);
+ do_test ((NULL != &import_obj->Name), "#7 string set");
+ obj_amount = double_to_gnc_numeric(init_value,1, GNC_HOW_DENOM_EXACT);
+ obj_setAmount(import_obj, obj_amount);
+ do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "#8 gnc_numeric set");
+ obj_setActive(import_obj, active);
+ do_test ((FALSE != &import_obj->active), "#9 gboolean set");
+ obj_setDiscount(import_obj, discount);
+ do_test ((discount == import_obj->discount), "#10 double set");
+ obj_setVersion(import_obj, version);
+ do_test ((version == import_obj->version), "#11 gint32 set");
+ obj_setMinor(import_obj, minor);
+ do_test ((minor == import_obj->minor), "#12 gint64 set");
+ obj_setDate(import_obj, ts );
+ tc = import_obj->date;
+ do_test ((timespec_cmp(&ts, &tc) == 0), "#13 date set");
+
+ obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT);
+ discount = 0.25;
+ version = 2;
+ minor = 3;
+
+ /* second import object - test results would be the same, so not tested. */
+ new_obj = g_new(myobj, 1);
+ qof_instance_init (&new_obj->inst, TEST_MODULE_NAME, import);
+ obj_setGUID(new_obj,qof_instance_get_guid(&new_obj->inst));
+ gnc_engine_gen_event (&new_obj->inst.entity, GNC_EVENT_CREATE);
+ obj_setName(new_obj, import_init);
+ obj_setAmount(new_obj, obj_amount);
+ obj_setActive(new_obj, active);
+ obj_setDiscount(new_obj, discount);
+ obj_setVersion(new_obj, version);
+ obj_setMinor(new_obj, minor);
+ obj_setDate(new_obj, ts);
+
+ obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT);
+ discount = 0.35;
+ version = 3;
+ minor = 6;
+ tc.tv_sec = ts.tv_sec -1;
+ tc.tv_nsec = 0;
+
+ /* target object - test results would be the same, so not tested. */
+ target_obj = g_new(myobj, 1);
+ qof_instance_init (&target_obj->inst, TEST_MODULE_NAME, target);
+ obj_setGUID(target_obj,qof_instance_get_guid(&target_obj->inst));
+ gnc_engine_gen_event (&target_obj->inst.entity, GNC_EVENT_CREATE);
+ obj_setName(target_obj, target_init);
+ obj_setAmount(target_obj, obj_amount);
+ obj_setActive(target_obj, active);
+ obj_setDiscount(target_obj, discount);
+ obj_setVersion(target_obj, version);
+ obj_setMinor(target_obj, minor);
+ obj_setDate(target_obj, tc );
+
+ result = qof_book_mergeInit(import, target);
+ do_test ( result != -1, "FATAL: Merge could not be initialised!\t aborting . . ");
+ g_return_if_fail(result != -1);
+ qof_book_mergeRuleForeach(test_rule_loop, MERGE_REPORT);
+ qof_book_mergeRuleForeach(test_rule_loop, MERGE_UPDATE);
+ qof_book_mergeRuleForeach(test_rule_loop, MERGE_NEW);
+ /* reserved calls - test only */
+ qof_book_mergeRuleForeach(test_rule_loop, MERGE_ABSOLUTE);
+ qof_book_mergeRuleForeach(test_rule_loop, MERGE_DUPLICATE);
+
+ /* import should not be in the target - pass if import_init fails match with target */
+ do_test (((safe_strcmp(obj_getName(import_obj),obj_getName(target_obj))) != 0), "Init value test #1");
+
+ /* a good commit returns zero */
+ do_test (qof_book_mergeCommit() == 0, "Commit failed");
+
+ /* import should be in the target - pass if import_init matches target */
+ do_test (((safe_strcmp(import_init,obj_getName(target_obj))) == 0), "Merged value test #1");
+
+ /* import should be the same as target - pass if values are the same */
+ do_test (((safe_strcmp(obj_getName(target_obj),obj_getName(import_obj))) == 0), "Merged value test #2");
+
+ /* check that the Amount really is a gnc_numeric */
+ do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "import gnc_numeric check");
+ do_test ((gnc_numeric_check(obj_getAmount(target_obj)) == GNC_ERROR_OK), "target gnc_numeric check");
+
+ /* obj_amount was changed after the import object was set, so expect a difference. */
+ do_test ((gnc_numeric_compare(obj_getAmount(import_obj), obj_amount) != GNC_ERROR_OK),
+ "gnc_numeric value check #1");
+
+ /* obj_amount is in the target object with the import value, expect a difference/ */
+ do_test ((gnc_numeric_compare(obj_getAmount(target_obj), obj_amount) != GNC_ERROR_OK),
+ "gnc_numeric value check #2");
+
+ /* target had a different date, so import date should now be set */
+ /* note: If sensible defaults are not set in the create:
+ an empty Timespec caused problems with the update - fix */
+ tc = target_obj->date;
+ do_test ((timespec_cmp(&ts, &tc) == 0), "date value check: 1");
+ tc = import_obj->date;
+ do_test ((timespec_cmp(&tc, &ts) == 0), "date value check: 2");
+ do_test ((timespec_cmp(&import_obj->date, &target_obj->date) == 0), "date value check: 3");
+
+}
+
+static void
+test_rule_loop (qof_book_mergeRule *rule, guint remainder)
+{
+ GSList *testing;
+ QofParam *eachParam;
+ char *importstring;
+ char *targetstring;
+
+ /* In this test rule_loop, any lines beginning with do_test() can be removed
+ from a working rule_loop routine. It would be wise to still use some of the
+ more obvious checks, e.g. that an entity or rule exists before querying the parameters. */
+
+ importstring = NULL;
+ targetstring = NULL;
+ do_test ((rule != NULL), "loop:#1 Rule is NULL");
+ do_test (remainder >= 0, "loop:#2 remainder too low");
+ do_test ((safe_strcmp(NULL, rule->mergeLabel) != 0), "loop:#3 object label\n");
+ do_test ((rule->importEnt != NULL), "loop:#4 empty import entity");
+ do_test ((rule->targetEnt != NULL), "loop:#5 empty target entity");
+ do_test ((safe_strcmp(rule->importEnt->e_type, rule->targetEnt->e_type) == 0), "loop:#6 entity type mismatch");
+ do_test ((rule->mergeParam != NULL), "loop:#7 empty parameter list");
+ testing = rule->mergeParam;
+
+ while(testing != NULL) { // start of param loop
+ eachParam = testing->data;
+ do_test ((eachParam != NULL), "loop:#8 no QofParam data");
+ do_test ((eachParam->param_name != NULL), "loop:#9 no parameter name");
+ do_test ((eachParam->param_getfcn != NULL), "loop:#10 no get function");
+ do_test ((eachParam->param_setfcn != NULL), "loop:#11 no set function");
+ /* non-generic - test routines only! */
+ if(safe_strcmp(eachParam->param_type, QOF_TYPE_STRING) == 0) {
+ /* if you use this format, you would need to check the QOF_TYPE and
+ configure the get_fcn pointers yourself. This example only works for strings. */
+ importstring = g_strdup(eachParam->param_getfcn(rule->importEnt, eachParam));
+ do_test ((importstring != NULL), "loop:#12 direct get_fcn import");
+ do_test ((safe_strcmp(importstring, "test") == 0), "loop:#13 direct import comparison");
+ targetstring = eachParam->param_getfcn(rule->targetEnt, eachParam);
+ do_test ((targetstring != NULL), "loop:#14 direct get_fcn target");
+ do_test ((safe_strcmp(targetstring, "testing") == 0), "loop:#15 direct target comparison");
+ }
+ /* param_as_string does the conversion for display purposes only */
+ /* do NOT use as_string for calculations or set_fcn */
+ importstring = qof_book_merge_param_as_string(eachParam, rule->importEnt);
+ do_test ((importstring != NULL), "loop:#16 import param_as_string is null");
+// printf("importstring %s\t%s Type\n", importstring, eachParam->param_type);
+
+ targetstring = qof_book_merge_param_as_string(eachParam, rule->targetEnt);
+ do_test ((targetstring != NULL), "loop:#17 target param_as_string is null");
+// printf("targetstring %s\t%s Type\n", targetstring, eachParam->param_type);
+ /* add your own code for user involvement here. */
+ /* either store the importstring and targetstring values and display separately,
+ perhaps in alphabetical/object_type/priority order, or, obtain user input as each
+ string is available. */
+
+ testing = g_slist_next(testing);
+ } // end param loop
+ /* set each rule dependent on the user involvement response above. */
+ /* test routine just sets all to MERGE_UPDATE */
+ qof_book_mergeUpdateResult(rule,MERGE_UPDATE);
+ do_test ((rule->mergeResult == MERGE_UPDATE), "update result fail");
+}
+
+static void
+main_helper (void *closure, int argc, char **argv)
+{
+// gnc_module_load("gnucash/engine", 0);
+ gnc_engine_init(argc, argv);
+ myobjRegister();
+ test_merge();
+ print_test_results();
+ exit(get_rv());
+}
+
+int
+main (int argc, char **argv)
+{
+ scm_boot_guile (argc, argv, main_helper, NULL);
+ return 0;
+}
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/test/Makefile.am,v
retrieving revision 1.36
retrieving revision 1.37
diff -Lsrc/engine/test/Makefile.am -Lsrc/engine/test/Makefile.am -u -r1.36 -r1.37
--- src/engine/test/Makefile.am
+++ src/engine/test/Makefile.am
@@ -1,3 +1,5 @@
+# may need to use ln -s ./ qof in src/engine
+# INCLUDES = -DG_LOG_DOMAIN=\"qof_book_merge\"
AM_CFLAGS = \
-I${top_srcdir}/src \
@@ -12,16 +14,19 @@
${top_builddir}/src/gnc-module/libgncmodule.la \
${top_builddir}/src/test-core/libgncmod-test.la \
../libgncmod-engine.la \
+ ../qof_book_merge.lo \
../libgw-engine.la \
../libgw-kvp.la \
../test-core/libgncmod-test-engine.la \
${GLIB_LIBS} \
- -lltdl
+ -lltdl \
+ -lglib
# these tests are ordered kind more or less in the order
# that they should be executed, with more basic tests coming first.
#
TESTS = \
+ test-book-merge \
test-link \
test-load-engine \
test-guid \
@@ -40,8 +45,10 @@
test-transaction-reversal \
test-transaction-voiding \
test-freq-spec \
- test-scm-query
-
+ test-scm-query \
+ test-book-merge
+
+
GNC_TEST_DEPS := \
--gnc-module-dir ${top_builddir}/src/gnc-module \
--gnc-module-dir ${top_builddir}/src/engine \
@@ -65,6 +72,7 @@
test-load-engine \
test-lots \
test-numeric \
+ test-book-merge \
test-object \
test-period \
test-query \
--- /dev/null
+++ src/gnome/druid-merge.h
@@ -0,0 +1,81 @@
+/********************************************************************\
+ * druid-merge.h -- account hierarchy merge functionality *
+ * Copyright (C) 2001 Gnumatic, Inc. *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program 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 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program 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 this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+#ifndef DRUID_MERGE_H
+#define DRUID_MERGE_H
+/** @addtogroup GUI
+ @{ */
+/** @addtogroup NewHierarchy Merging a new account tree into an existing file
+
+<b>Collision handling principles.</b>\n
+\n
+This druid builds a second ::QofBook in memory using ::QofSession and
+populates the book with accounts created using the usual New Account Tree
+code. The druid then uses ::qof_book_mergeInit to begin the merge
+of the new book (created with QofSession) with the existing QofBook
+(loaded by the user), with user intervention and collision handling.
+
+ -# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+ data and therefore ALL objects in the import book will include valid GUID's.
+ -# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+ the GUID values will have been created by QofSession and will not match any existing
+ GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+ be ::MERGE_NEW - parameter values must be checked.
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+ -# If ALL parameters in the import object match the target object with the same \a GUID,
+ set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+ -# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+ -# If the same data exists in the target book with a different GUID, the object
+ is tagged as DUPLICATE.
+ -# If the data has changed, the object is tagged as REPORT.
+ -# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only
+used for critical errors that arise from programming errors, not for invalid import data
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeInit, ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return
+any error values to the calling process.
+
+ @{ */
+/** @file druid_merge.h
+ @brief API for merging two \c QofBook* structures with collision handling
+ @author Copyright (c) 2004 Neil Williams <linux at codehelp.co.uk>
+*/
+
+void gnc_ui_qof_book_merge_druid (void);
+GtkWidget* qof_book_merge_running (void);
+
+/** @} */
+/** @} */
+
+#endif
Index: window-main.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/window-main.c,v
retrieving revision 1.187
retrieving revision 1.188
diff -Lsrc/gnome/window-main.c -Lsrc/gnome/window-main.c -u -r1.187 -r1.188
--- src/gnome/window-main.c
+++ src/gnome/window-main.c
@@ -44,6 +44,7 @@
#include "dialog-utils.h"
#include "druid-acct-period.h"
#include "druid-loan.h"
+#include "druid-merge.h"
#include "gfec.h"
#include "global-options.h"
#include "gnc-engine.h"
@@ -77,7 +78,6 @@
static GnomeUIInfo * gnc_main_window_toolbar_prefix (void);
static GnomeUIInfo * gnc_main_window_toolbar_suffix (void);
-
/**
* gnc_main_window_get_mdi_child
*
@@ -746,6 +746,12 @@
}
static void
+gnc_main_window_file_hierarchy_merge_cb(GtkWidget *w, gpointer data)
+{
+ gnc_ui_qof_book_merge_druid();
+}
+
+static void
gnc_main_window_file_new_account_tree_cb(GtkWidget * w, gpointer data)
{
gnc_main_window_open_accounts(FALSE);
@@ -789,6 +795,13 @@
gnc_main_window_file_new_account_tree_cb, NULL, NULL,
GNOME_APP_PIXMAP_NONE, NULL, 0, 0, NULL
},
+ {
+ GNOME_APP_UI_ITEM,
+ N_("Add New Account Hierarchy"),
+ N_("Extend the current book by merging with new account type categories"),
+ gnc_main_window_file_hierarchy_merge_cb, NULL, NULL,
+ GNOME_APP_PIXMAP_NONE, NULL, 0, 0, NULL
+ },
GNOMEUIINFO_SEPARATOR,
GNOMEUIINFO_MENU_OPEN_ITEM(gnc_main_window_file_open_cb, NULL),
{
Index: druid-hierarchy.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/druid-hierarchy.c,v
retrieving revision 1.22
retrieving revision 1.23
diff -Lsrc/gnome/druid-hierarchy.c -Lsrc/gnome/druid-hierarchy.c -u -r1.22 -r1.23
--- src/gnome/druid-hierarchy.c
+++ src/gnome/druid-hierarchy.c
@@ -32,6 +32,7 @@
#include "dialog-new-user.h"
#include "dialog-utils.h"
#include "druid-hierarchy.h"
+#include "druid-merge.h"
#include "druid-utils.h"
#include "gnc-amount-edit.h"
#include "gnc-currency-edit.h"
@@ -44,7 +45,11 @@
#include "io-example-account.h"
#include "top-level.h"
+#include "gnc-trace.h"
+static short module = MOD_IMPORT;
+
static GtkWidget *hierarchy_window = NULL;
+GtkWidget *qof_book_merge_window = NULL;
static AccountGroup *our_final_group = NULL;
@@ -907,120 +912,139 @@
gpointer arg1,
gpointer user_data)
{
- gnc_suspend_gui_refresh ();
-
- if (our_final_group)
- xaccGroupForEachAccount (our_final_group, starting_balance_helper,
- NULL, TRUE);
-
- delete_hierarchy_window ();
-
- gncp_new_user_finish ();
-
- gnc_set_first_startup (FALSE);
-
- if (our_final_group)
- xaccGroupConcatGroup (gnc_get_current_group (), our_final_group);
-
- gnc_resume_gui_refresh ();
+ gnc_suspend_gui_refresh ();
+
+ if (our_final_group)
+ xaccGroupForEachAccount (our_final_group, starting_balance_helper,
+ NULL, TRUE);
+
+ ENTER (" ");
+ qof_book_merge_window = gtk_object_get_data (GTK_OBJECT (hierarchy_window), "Merge Druid");
+ if(qof_book_merge_window) {
+ DEBUG ("qof_book_merge_window found");
+ gtk_widget_show(qof_book_merge_window);
+ delete_hierarchy_window ();
+ gnc_resume_gui_refresh ();
+ LEAVE (" ");
+ return;
+ }
+ delete_hierarchy_window ();
+
+ gncp_new_user_finish ();
+
+ gnc_set_first_startup (FALSE);
+
+ if (our_final_group)
+ xaccGroupConcatGroup (gnc_get_current_group (), our_final_group);
+
+ gnc_resume_gui_refresh ();
+ LEAVE (" ");
}
static GtkWidget *
gnc_create_hierarchy_druid (void)
{
- GtkWidget *balance_edit;
- GtkWidget *dialog;
- GtkWidget *druid;
- GtkWidget *clist;
- GtkWidget *box;
- GHashTable *hash;
- GladeXML *xml;
-
- xml = gnc_glade_xml_new ("account.glade", "Hierarchy Druid");
-
- glade_xml_signal_connect
- (xml, "on_choose_currency_prepare",
- GTK_SIGNAL_FUNC (on_choose_currency_prepare));
-
- glade_xml_signal_connect
- (xml, "on_choose_account_types_prepare",
- GTK_SIGNAL_FUNC (on_choose_account_types_prepare));
-
- glade_xml_signal_connect
- (xml, "on_account_types_list_select_row",
- GTK_SIGNAL_FUNC (on_account_types_list_select_row));
-
- glade_xml_signal_connect
- (xml, "on_account_types_list_unselect_row",
- GTK_SIGNAL_FUNC (on_account_types_list_unselect_row));
-
- glade_xml_signal_connect
- (xml, "on_final_account_prepare",
- GTK_SIGNAL_FUNC (on_final_account_prepare));
-
- glade_xml_signal_connect
- (xml, "on_final_account_tree_select_row",
- GTK_SIGNAL_FUNC (on_final_account_tree_select_row));
-
- glade_xml_signal_connect
- (xml, "on_final_account_tree_unselect_row",
- GTK_SIGNAL_FUNC (on_final_account_tree_unselect_row));
-
- glade_xml_signal_connect
- (xml, "on_final_account_tree_placeholder_toggled",
- GTK_SIGNAL_FUNC (on_final_account_tree_placeholder_toggled));
-
- glade_xml_signal_connect
- (xml, "on_final_account_next",
- GTK_SIGNAL_FUNC (on_final_account_next));
-
- glade_xml_signal_connect
- (xml, "select_all_clicked", GTK_SIGNAL_FUNC (select_all_clicked));
-
- glade_xml_signal_connect
- (xml, "clear_all_clicked", GTK_SIGNAL_FUNC (clear_all_clicked));
-
- glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
-
- glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
-
- dialog = glade_xml_get_widget (xml, "Hierarchy Druid");
- gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
-
- druid = glade_xml_get_widget (xml, "hierarchy_druid");
- gnc_druid_set_colors (GNOME_DRUID (druid));
-
- balance_edit = gnc_amount_edit_new ();
- gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT (balance_edit), TRUE);
- gtk_widget_show (balance_edit);
-
- gtk_signal_connect (GTK_OBJECT (balance_edit), "amount_changed",
- GTK_SIGNAL_FUNC(on_balance_changed), NULL);
-
- clist = glade_xml_get_widget (xml, "account_types_clist");
- gtk_clist_column_titles_passive (GTK_CLIST (clist));
-
- box = glade_xml_get_widget (xml, "start_balance_box");
- gtk_box_pack_start (GTK_BOX (box), balance_edit, TRUE, TRUE, 0);
-
- gtk_object_set_data (GTK_OBJECT(dialog), "balance_editor", balance_edit);
-
- hash = g_hash_table_new (g_str_hash, g_str_equal);
-
- gtk_object_set_data (GTK_OBJECT(dialog), "balance_hash", hash);
-
- gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
- GTK_SIGNAL_FUNC(gnc_hierarchy_destroy_cb), NULL);
+ GtkWidget *balance_edit;
+ GtkWidget *dialog;
+ GtkWidget *druid;
+ GtkWidget *clist;
+ GtkWidget *box;
+ GHashTable *hash;
+ GladeXML *xml;
+
+ xml = gnc_glade_xml_new ("account.glade", "Hierarchy Druid");
+
+ glade_xml_signal_connect
+ (xml, "on_choose_currency_prepare",
+ GTK_SIGNAL_FUNC (on_choose_currency_prepare));
+
+ glade_xml_signal_connect
+ (xml, "on_choose_account_types_prepare",
+ GTK_SIGNAL_FUNC (on_choose_account_types_prepare));
+
+ glade_xml_signal_connect
+ (xml, "on_account_types_list_select_row",
+ GTK_SIGNAL_FUNC (on_account_types_list_select_row));
+
+ glade_xml_signal_connect
+ (xml, "on_account_types_list_unselect_row",
+ GTK_SIGNAL_FUNC (on_account_types_list_unselect_row));
+
+ glade_xml_signal_connect
+ (xml, "on_final_account_prepare",
+ GTK_SIGNAL_FUNC (on_final_account_prepare));
+
+ glade_xml_signal_connect
+ (xml, "on_final_account_tree_select_row",
+ GTK_SIGNAL_FUNC (on_final_account_tree_select_row));
+
+ glade_xml_signal_connect
+ (xml, "on_final_account_tree_unselect_row",
+ GTK_SIGNAL_FUNC (on_final_account_tree_unselect_row));
+
+ glade_xml_signal_connect
+ (xml, "on_final_account_tree_placeholder_toggled",
+ GTK_SIGNAL_FUNC (on_final_account_tree_placeholder_toggled));
+
+ glade_xml_signal_connect
+ (xml, "on_final_account_next",
+ GTK_SIGNAL_FUNC (on_final_account_next));
+
+ glade_xml_signal_connect
+ (xml, "select_all_clicked", GTK_SIGNAL_FUNC (select_all_clicked));
+
+ glade_xml_signal_connect
+ (xml, "clear_all_clicked", GTK_SIGNAL_FUNC (clear_all_clicked));
+
+ glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
+
+ glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
+
+ dialog = glade_xml_get_widget (xml, "Hierarchy Druid");
+ gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
+
+ druid = glade_xml_get_widget (xml, "hierarchy_druid");
+ gnc_druid_set_colors (GNOME_DRUID (druid));
+
+ balance_edit = gnc_amount_edit_new ();
+ gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT (balance_edit), TRUE);
+ gtk_widget_show (balance_edit);
+
+ gtk_signal_connect (GTK_OBJECT (balance_edit), "amount_changed",
+ GTK_SIGNAL_FUNC(on_balance_changed), NULL);
+
+ clist = glade_xml_get_widget (xml, "account_types_clist");
+ gtk_clist_column_titles_passive (GTK_CLIST (clist));
+
+ box = glade_xml_get_widget (xml, "start_balance_box");
+ gtk_box_pack_start (GTK_BOX (box), balance_edit, TRUE, TRUE, 0);
+
+ gtk_object_set_data (GTK_OBJECT(dialog), "balance_editor", balance_edit);
+
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ gtk_object_set_data (GTK_OBJECT(dialog), "balance_hash", hash);
+
+ gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
+ GTK_SIGNAL_FUNC(gnc_hierarchy_destroy_cb), NULL);
+
+ return dialog;
+}
- return dialog;
+GtkWidget*
+gnc_ui_hierarchy_running (void)
+{
+ if (hierarchy_window) return hierarchy_window;
+ return NULL;
}
void
gnc_ui_hierarchy_druid (void)
{
- if (hierarchy_window) return;
+ if (hierarchy_window) return;
- hierarchy_window = gnc_create_hierarchy_druid ();
+ hierarchy_window = gnc_create_hierarchy_druid ();
- return;
+// qof_book_merge_window = qof_book_merge_running();
+ return;
}
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/Makefile.am,v
retrieving revision 1.110
retrieving revision 1.111
diff -Lsrc/gnome/Makefile.am -Lsrc/gnome/Makefile.am -u -r1.110 -r1.111
--- src/gnome/Makefile.am
+++ src/gnome/Makefile.am
@@ -39,6 +39,7 @@
dialog-scheduledxaction.c \
druid-acct-period.c \
druid-hierarchy.c \
+ druid-merge.c \
druid-loan.c \
druid-stock-split.c \
gnc-split-reg.c \
@@ -76,6 +77,7 @@
dialog-scheduledxaction.h \
druid-acct-period.h \
druid-hierarchy.h \
+ druid-merge.h \
druid-loan.h \
druid-stock-split.h \
gnc-network.h \
--- /dev/null
+++ src/gnome/druid-merge.c
@@ -0,0 +1,436 @@
+/********************************************************************\
+ * druid-merge.c -- account hierarchy merge functionality *
+ * Copyright (C) 2001 Gnumatic, Inc. *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program 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 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program 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 this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+#include "config.h"
+
+#include <gnome.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <libgnomeui/gnome-window-icon.h>
+
+#include "dialog-utils.h"
+#include "druid-merge.h"
+#include "druid-utils.h"
+#include "gnc-component-manager.h"
+#include "gnc-gui-query.h"
+#include "qof_book_merge.h"
+#include "druid-hierarchy.h"
+
+#include "gnc-trace.h"
+//static short module = MOD_IMPORT;
+
+static GtkWidget *qof_book_merge_window = NULL;
+static GtkWidget *druid_hierarchy_window = NULL;
+static QofSession *previous_session = NULL;
+static gint count = 0;
+static qof_book_mergeRule *currentRule = NULL;
+static QofSession *merge_session = NULL;
+static QofBook *mergeBook = NULL;
+static QofBook *targetBook = NULL;
+static gchar *buffer = "";
+
+void collision_rule_loop ( qof_book_mergeRule*, guint );
+void progress_rule_loop ( qof_book_mergeRule*, guint );
+void summary_ForeachParam ( QofParam*, gpointer );
+void summary_ForeachType ( QofObject*, gpointer );
+void summary_Foreach ( QofEntity*, gpointer );
+
+void
+summary_ForeachParam( QofParam* param, gpointer user_data)
+{
+ QofEntity *ent;
+ char *importstring;
+
+ ent = (QofEntity*)user_data;
+ /* To control the amount of output, only strings are
+ printed in this example. Remove the loop for all
+ data.
+ */
+ if(safe_strcmp(param->param_type,QOF_TYPE_STRING) == 0) {
+ importstring = NULL;
+ importstring = qof_book_merge_param_as_string(param, ent);
+ printf("%-20s\t\t%s\t\t%s\n", param->param_name, param->param_type, importstring);
+ }
+}
+
+void
+summary_Foreach ( QofEntity* ent, gpointer user_data)
+{
+ qof_class_param_foreach(ent->e_type, summary_ForeachParam , ent);
+}
+
+void
+summary_ForeachType ( QofObject* obj, gpointer user_data)
+{
+ QofBook *book;
+
+ book = (QofBook*)user_data;
+ printf("\n%s\n", obj->e_type);
+ printf("Parameter name\t\t\tData type\t\tValue\n");
+ qof_object_foreach(obj->e_type, book, summary_Foreach, NULL);
+}
+
+
+static GtkWidget*
+merge_get_widget (const char *name)
+{
+ if (!qof_book_merge_window) return NULL;
+
+ return gnc_glade_lookup_widget (qof_book_merge_window, name);
+}
+
+static void
+delete_merge_window (void)
+{
+ if (!qof_book_merge_window) return;
+
+ gtk_widget_destroy (qof_book_merge_window);
+ qof_book_merge_window = NULL;
+}
+
+static void
+gnc_merge_destroy_cb (GtkObject *obj, gpointer user_data)
+{
+
+}
+
+static gboolean
+on_qof_start_page_next(GnomeDruidPage *gnomedruidpage,
+ gpointer arg1,
+ gpointer user_data)
+{
+ gtk_widget_show(druid_hierarchy_window);
+ gtk_widget_hide(qof_book_merge_window);
+ return FALSE;
+}
+
+static void
+on_MergeUpdate_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ qof_book_mergeUpdateResult(currentRule, MERGE_UPDATE);
+ count = 0;
+ qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static void
+on_MergeDuplicate_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ if(currentRule->mergeAbsolute == FALSE) {
+ qof_book_mergeUpdateResult(currentRule, MERGE_DUPLICATE);
+ count = 0;
+ }
+ if(currentRule->mergeAbsolute == TRUE) {
+ qof_book_mergeUpdateResult(currentRule, MERGE_ABSOLUTE);
+ count = 0;
+ }
+ qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static void
+on_MergeNew_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ if(currentRule->mergeAbsolute == FALSE) {
+ qof_book_mergeUpdateResult(currentRule, MERGE_NEW);
+ }
+ count = 0;
+ qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static gboolean
+on_qof_book_merge_next (GnomeDruidPage *gnomedruidpage,
+ gpointer arg1,
+ gpointer user_data)
+{
+ GtkWidget *top;
+ GtkLabel *output;
+ const char *message = _("You must resolve all collisions.");
+
+ if(count > 0) {
+ top = gtk_widget_get_toplevel (GTK_WIDGET (gnomedruidpage));
+ gnc_error_dialog(top, message);
+ return TRUE;
+ }
+ buffer = "";
+ gnc_suspend_gui_refresh ();
+ output = GTK_LABEL(merge_get_widget("OutPut"));
+ gtk_label_set_text(output, buffer);
+ gtk_widget_show(GTK_WIDGET(output));
+ gnc_resume_gui_refresh ();
+ return FALSE;
+}
+
+static void
+on_cancel (GnomeDruid *gnomedruid,
+ gpointer user_data)
+{
+ gnc_suspend_gui_refresh ();
+ delete_merge_window();
+ qof_session_set_current_session(previous_session);
+ qof_book_destroy(mergeBook);
+ qof_session_end(merge_session);
+ gnc_resume_gui_refresh ();
+}
+
+static void
+on_finish (GnomeDruidPage *gnomedruidpage,
+ gpointer arg1,
+ gpointer user_data)
+{
+ gnc_suspend_gui_refresh ();
+ qof_book_mergeCommit();
+ delete_merge_window ();
+ qof_session_set_current_session(previous_session);
+ qof_book_destroy(mergeBook);
+ qof_session_end(merge_session);
+ gnc_resume_gui_refresh ();
+}
+
+static void
+on_qof_book_merge_prepare (GnomeDruidPage *gnomedruidpage,
+ gpointer arg1,
+ gpointer user_data)
+{
+ gint result;
+ GtkLabel *progress;
+
+ gnc_suspend_gui_refresh ();
+ progress = GTK_LABEL (merge_get_widget("ResultsBox"));
+ /* blank out old data */
+ gtk_label_set_text(progress, "");
+ result = 0;
+ g_return_if_fail(mergeBook != NULL);
+ g_return_if_fail(targetBook != NULL);
+ result = qof_book_mergeInit(mergeBook, targetBook);
+ g_return_if_fail(result == 0);
+ qof_book_mergeRuleForeach(progress_rule_loop, MERGE_NEW);
+ qof_book_mergeRuleForeach(progress_rule_loop, MERGE_ABSOLUTE);
+ qof_book_mergeRuleForeach(progress_rule_loop, MERGE_DUPLICATE);
+ qof_book_mergeRuleForeach(progress_rule_loop, MERGE_UPDATE);
+ gtk_label_set_text(progress, buffer);
+ qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+ gnc_resume_gui_refresh ();
+}
+
+static GtkWidget *
+gnc_create_merge_druid (void)
+{
+ GtkWidget *dialog;
+ GtkWidget *druid;
+ GladeXML *xml;
+
+ xml = gnc_glade_xml_new ("merge.glade", "Merge Druid");
+
+ glade_xml_signal_connect(xml, "on_start_page_next",
+ GTK_SIGNAL_FUNC (on_qof_start_page_next));
+
+// Please resolve these conflicts in the merge
+// on_qof_book_merge_prepare
+
+ glade_xml_signal_connect(xml, "on_qof_book_merge_prepare",
+ GTK_SIGNAL_FUNC (on_qof_book_merge_prepare));
+
+ glade_xml_signal_connect(xml, "on_qof_book_merge_next",
+ GTK_SIGNAL_FUNC (on_qof_book_merge_next));
+
+ glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
+
+ glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
+
+ glade_xml_signal_connect(xml, "on_MergeUpdate_clicked",
+ GTK_SIGNAL_FUNC (on_MergeUpdate_clicked));
+
+ glade_xml_signal_connect(xml, "on_MergeDuplicate_clicked",
+ GTK_SIGNAL_FUNC (on_MergeDuplicate_clicked));
+
+ glade_xml_signal_connect(xml, "on_MergeNew_clicked",
+ GTK_SIGNAL_FUNC (on_MergeNew_clicked));
+
+ dialog = glade_xml_get_widget (xml, "Merge Druid");
+ gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
+
+ druid = glade_xml_get_widget (xml, "merge_druid");
+ gnc_druid_set_colors (GNOME_DRUID (druid));
+
+ gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
+ GTK_SIGNAL_FUNC(gnc_merge_destroy_cb), NULL);
+ return dialog;
+}
+
+void progress_rule_loop(qof_book_mergeRule *rule, guint remainder)
+{
+ GtkLabel *progress;
+
+ progress = GTK_LABEL(merge_get_widget("ResultsBox"));
+ buffer = "";
+ g_return_if_fail(rule != NULL);
+ currentRule = rule;
+ if(rule->mergeResult == MERGE_NEW) {
+ if (remainder == 1) {
+ buffer = g_strconcat(buffer,
+ g_strdup_printf("%i %s tagged as NEW.\n", remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ else {
+ buffer = g_strconcat(buffer,
+ g_strdup_printf("%i entities of type %s are tagged as NEW.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ gtk_widget_show(GTK_WIDGET(progress));
+ return;
+ }
+ if(rule->mergeResult == MERGE_ABSOLUTE) {
+ if (remainder == 1) {
+ buffer = g_strconcat(buffer,
+ g_strdup_printf("%i %s tagged as an absolute GUID match.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ else {
+ buffer = g_strconcat(buffer,
+ g_strdup_printf("%i entities of type %s tagged as an absolute GUID match.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ gtk_widget_show(GTK_WIDGET(progress));
+ return;
+ }
+ if(rule->mergeResult == MERGE_DUPLICATE) {
+ if (remainder == 1) {
+ buffer = g_strconcat(buffer, g_strdup_printf("%i %s tagged as a duplicate.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ else {
+ buffer = g_strconcat(buffer, g_strdup_printf("%i entities of type %s tagged as a duplicate.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ gtk_widget_show(GTK_WIDGET(progress));
+ return;
+ }
+ if(rule->mergeResult == MERGE_UPDATE) {
+ if (remainder == 1) {
+ buffer = g_strconcat(buffer, g_strdup_printf("%i %s tagged as to be updated.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ else {
+ buffer = g_strconcat(buffer, g_strdup_printf("%i entities of type %s tagged as to be updated.\n",
+ remainder, rule->mergeLabel), NULL);
+ gtk_label_set_text (progress, buffer);
+ }
+ gtk_widget_show(GTK_WIDGET(progress));
+ return;
+ }
+ g_free(buffer);
+}
+
+void collision_rule_loop(qof_book_mergeRule *rule, guint remainder)
+{
+ GSList *user_reports;
+ QofParam *one_param;
+ gchar *importstring, *targetstring;
+ gchar *buffer;
+ GtkLabel *output;
+
+ g_return_if_fail(rule != NULL);
+ buffer = "";
+ /* there is a rule awaiting resolution, don't print any more */
+ if(count > 0) return;
+ gnc_suspend_gui_refresh ();
+ user_reports = rule->mergeParam;
+ currentRule = rule;
+ output = GTK_LABEL(merge_get_widget("OutPut"));
+ gtk_label_set_text(output, buffer);
+ gtk_widget_show(GTK_WIDGET(output));
+ gnc_resume_gui_refresh ();
+ count = 1; /* user display text counts from 1, not zero */
+ importstring = targetstring = NULL;
+ gnc_suspend_gui_refresh ();
+ if(remainder == 1) {
+ buffer = g_strdup_printf("\n%i conflict needs to be resolved.\n", remainder);
+ }
+ else {
+ buffer = g_strdup_printf("\n%i conflicts need to be resolved.\n", remainder);
+ }
+ buffer = g_strconcat(buffer, g_strdup_printf("\n%i parameter values for this \"%s\" object.\n",
+ g_slist_length(user_reports), rule->targetEnt->e_type), NULL);
+ while(user_reports != NULL) {
+ one_param = user_reports->data;
+ buffer = g_strconcat(buffer, g_strdup_printf("%i:Parameter name: %s ",
+ count, one_param->param_name), NULL);
+ importstring = qof_book_merge_param_as_string(one_param, rule->importEnt);
+ buffer = g_strconcat(buffer, g_strdup_printf("Import data : %s ", importstring), NULL);
+ targetstring = qof_book_merge_param_as_string(one_param, rule->targetEnt);
+ buffer = g_strconcat(buffer, g_strdup_printf("Original data : %s\n", targetstring), NULL);
+ user_reports = g_slist_next(user_reports);
+ count++;
+ }
+ gtk_label_set_text(output,buffer);
+ gtk_widget_show(GTK_WIDGET(output));
+ gnc_resume_gui_refresh ();
+ g_free(buffer);
+}
+
+GtkWidget*
+qof_book_merge_running (void)
+{
+ if (qof_book_merge_window) return qof_book_merge_window;
+ return NULL;
+}
+
+
+void
+gnc_ui_qof_book_merge_druid (void)
+{
+
+ if (qof_book_merge_window) return;
+ /* QofSession changes to avoid using current book */
+ gnc_engine_suspend_events ();
+ previous_session = qof_session_get_current_session();
+ targetBook = qof_session_get_book(previous_session);
+ merge_session = qof_session_new();
+ qof_session_set_current_session(merge_session);
+ mergeBook = qof_session_get_book(merge_session);
+ gnc_engine_resume_events ();
+ g_return_if_fail(targetBook != NULL);
+ g_return_if_fail(mergeBook != NULL);
+ g_return_if_fail(merge_session != NULL);
+ qof_book_merge_window = gnc_create_merge_druid();
+ g_return_if_fail(qof_book_merge_window != NULL);
+ gnc_ui_hierarchy_druid();
+ druid_hierarchy_window = gnc_ui_hierarchy_running();
+ gtk_widget_hide (druid_hierarchy_window);
+ gtk_object_set_data (GTK_OBJECT (druid_hierarchy_window), "Merge Druid", qof_book_merge_window);
+ gtk_widget_show (qof_book_merge_window);
+ g_return_if_fail(targetBook != NULL);
+ g_return_if_fail(mergeBook != NULL);
+ g_return_if_fail(merge_session != NULL);
+ gnc_set_log_level(MOD_IMPORT, GNC_LOG_WARNING);
+ return;
+}
Index: druid-hierarchy.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/druid-hierarchy.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -Lsrc/gnome/druid-hierarchy.h -Lsrc/gnome/druid-hierarchy.h -u -r1.1 -r1.2
--- src/gnome/druid-hierarchy.h
+++ src/gnome/druid-hierarchy.h
@@ -24,5 +24,5 @@
#define DRUID_HIERARCHY_H
void gnc_ui_hierarchy_druid (void);
-
+GtkWidget* gnc_ui_hierarchy_running (void);
#endif
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/glade/Makefile.am,v
retrieving revision 1.23
retrieving revision 1.24
diff -Lsrc/gnome/glade/Makefile.am -Lsrc/gnome/glade/Makefile.am -u -r1.23 -r1.24
--- src/gnome/glade/Makefile.am
+++ src/gnome/glade/Makefile.am
@@ -6,6 +6,7 @@
fincalc.glade \
help.glade \
lots.glade \
+ merge.glade \
newuser.glade \
price.glade \
print.glade \
--- /dev/null
+++ src/gnome/glade/merge.glade
@@ -0,0 +1,341 @@
+<?xml version="1.0"?>
+<GTK-Interface>
+
+<project>
+ <name>Glade</name>
+ <program_name>glade</program_name>
+ <directory></directory>
+ <source_directory></source_directory>
+ <pixmaps_directory></pixmaps_directory>
+ <language>C</language>
+ <gnome_support>True</gnome_support>
+ <gettext_support>True</gettext_support>
+ <output_main_file>False</output_main_file>
+ <output_support_files>False</output_support_files>
+ <output_build_files>False</output_build_files>
+ <backup_source_files>False</backup_source_files>
+</project>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>Merge Druid</name>
+ <height>560</height>
+ <title>Merge Account Hierarchy Setup</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_CENTER</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GnomeDruid</class>
+ <name>merge_druid</name>
+ <signal>
+ <name>cancel</name>
+ <handler>on_cancel</handler>
+ <last_modification_time>Sat, 16 Jun 2001 23:54:54 GMT</last_modification_time>
+ </signal>
+
+ <widget>
+ <class>GnomeDruidPageStart</class>
+ <name>start_page</name>
+ <signal>
+ <name>next</name>
+ <handler>on_start_page_next</handler>
+ <last_modification_time>Mon, 20 Sep 2004 11:46:19 GMT</last_modification_time>
+ </signal>
+ <title>Merge Account Hierarchy Setup</title>
+ <text>This druid will merge your new hierarchy into the currently open
+GnuCash file.
+
+You will be asked how to proceed if some accounts clash with the
+account tree in your existing GnuCash data file.
+
+There is NO way to undo this operation! Please ensure you
+have a backup of your file BEFORE continuing! You will be
+given the option to cancel the merge at all stages until the
+final merge operation. Once you click Finish, the new
+account tree will be committed to your current data file.
+
+There is currently no currency or price support in the merge
+operation, the new accounts will inherit any default currency
+ or you can change the currency after the merge is complete.
+
+Click 'Cancel' if you do not wish to merge your new
+account types now.</text>
+ <title_color>255,255,255</title_color>
+ <text_color>0,0,0</text_color>
+ <background_color>25,25,112</background_color>
+ <logo_background_color>255,255,255</logo_background_color>
+ <textbox_color>255,255,255</textbox_color>
+ </widget>
+
+ <widget>
+ <class>GnomeDruidPageStandard</class>
+ <name>summary_page</name>
+ <title>Your new accounts are ready to merge</title>
+ <title_color>255,255,255</title_color>
+ <background_color>25,25,112</background_color>
+ <logo_background_color>255,255,255</logo_background_color>
+
+ <widget>
+ <class>GtkVBox</class>
+ <child_name>GnomeDruidPageStandard:vbox</child_name>
+ <name>druid-vbox7</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label2</name>
+ <label>The next screen will allow you to resolve
+any conflicts in merging your new account
+tree into your current GnuCash file.</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GnomeDruidPageStandard</class>
+ <name>qof_book_merge</name>
+ <signal>
+ <name>prepare</name>
+ <handler>on_qof_book_merge_prepare</handler>
+ <last_modification_time>Tue, 14 Sep 2004 10:06:26 GMT</last_modification_time>
+ </signal>
+ <signal>
+ <name>next</name>
+ <handler>on_qof_book_merge_next</handler>
+ <last_modification_time>Tue, 14 Sep 2004 14:19:07 GMT</last_modification_time>
+ </signal>
+ <title>Please resolve any conflicts in the merge</title>
+ <title_color>255,255,255</title_color>
+ <background_color>25,25,112</background_color>
+ <logo_background_color>255,255,255</logo_background_color>
+
+ <widget>
+ <class>GtkVBox</class>
+ <child_name>GnomeDruidPageStandard:vbox</child_name>
+ <name>druid-vbox6</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox105</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox121</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>ResultsBox</name>
+ <label>Number of reports still to be reconciled.</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox106</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow27</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkViewport</class>
+ <name>viewport1</name>
+ <border_width>5</border_width>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>OutPut</name>
+ <label></label>
+ <justify>GTK_JUSTIFY_LEFT</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <focus_target>MergeUpdate</focus_target>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>Static</name>
+ <label>You have three choices for each collision.
+The import object can be allowed to overwrite the target - use this to update your existing book.
+The import object can be ignored - use this if the import is a duplicate of an object in the existing book.
+The import object can be created as a new object in the existing book.</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>MergeUpdate</name>
+ <border_width>5</border_width>
+ <tooltip>overwrite the original with the import data</tooltip>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_MergeUpdate_clicked</handler>
+ <last_modification_time>Wed, 22 Sep 2004 09:42:48 GMT</last_modification_time>
+ </signal>
+ <label>1. Update your existing book with the import data</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>MergeDuplicate</name>
+ <border_width>5</border_width>
+ <tooltip>ignore the import, leave the original untouched</tooltip>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_MergeDuplicate_clicked</handler>
+ <last_modification_time>Wed, 22 Sep 2004 09:43:50 GMT</last_modification_time>
+ </signal>
+ <label>2. Ignore the import data, leave original unchanged</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>MergeNew</name>
+ <border_width>5</border_width>
+ <tooltip>Add the import as a new object, leave original in place</tooltip>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_MergeNew_clicked</handler>
+ <last_modification_time>Wed, 22 Sep 2004 09:44:59 GMT</last_modification_time>
+ </signal>
+ <label>3. Import the data as a NEW object</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GnomeDruidPageFinish</class>
+ <name>MergeDruidFinishPage</name>
+ <signal>
+ <name>finish</name>
+ <handler>on_finish</handler>
+ <last_modification_time>Sat, 16 Jun 2001 23:41:40 GMT</last_modification_time>
+ </signal>
+ <title>Commit Merged Account Hierachy to data file</title>
+ <text>Press `Finish' to merge your new accounts into the current GnuCash file.
+
+Press `Back' to review your selections.
+
+Press `Cancel' to close this dialog without creating any new accounts.
+
+REMEMBER: There is no way to undo this final operation!
+Make sure you have a backup before clicking Finish.</text>
+ <background_color>25,25,112</background_color>
+ <logo_background_color>255,255,255</logo_background_color>
+ <textbox_color>255,255,255</textbox_color>
+ <text_color>0,0,0</text_color>
+ <title_color>255,255,255</title_color>
+ </widget>
+ </widget>
+</widget>
+
+</GTK-Interface>
More information about the gnucash-changes
mailing list