|
GnuCash 2.4.99
|
00001 /********************************************************************\ 00002 * Scrub2.c -- Convert Stock Accounts to use Lots * 00003 * * 00004 * This program is free software; you can redistribute it and/or * 00005 * modify it under the terms of the GNU General Public License as * 00006 * published by the Free Software Foundation; either version 2 of * 00007 * the License, or (at your option) any later version. * 00008 * * 00009 * This program is distributed in the hope that it will be useful, * 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00012 * GNU General Public License for more details. * 00013 * * 00014 * You should have received a copy of the GNU General Public License* 00015 * along with this program; if not, contact: * 00016 * * 00017 * Free Software Foundation Voice: +1-617-542-5942 * 00018 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * 00019 * Boston, MA 02110-1301, USA gnu@gnu.org * 00020 \********************************************************************/ 00021 00033 #include "config.h" 00034 00035 #include <glib.h> 00036 00037 #include "qof.h" 00038 #include "Account.h" 00039 #include "AccountP.h" 00040 #include "Transaction.h" 00041 #include "TransactionP.h" 00042 #include "Scrub2.h" 00043 #include "ScrubP.h" 00044 #include "cap-gains.h" 00045 #include "gnc-engine.h" 00046 #include "gnc-lot.h" 00047 #include "policy-p.h" 00048 00049 static QofLogModule log_module = GNC_MOD_LOT; 00050 00051 /* ============================================================== */ 00057 void 00058 xaccAccountAssignLots (Account *acc) 00059 { 00060 SplitList *splits, *node; 00061 00062 if (!acc) return; 00063 00064 ENTER ("acc=%s", xaccAccountGetName(acc)); 00065 xaccAccountBeginEdit (acc); 00066 00067 restart_loop: 00068 splits = xaccAccountGetSplitList(acc); 00069 for (node = splits; node; node = node->next) 00070 { 00071 Split * split = node->data; 00072 00073 /* If already in lot, then no-op */ 00074 if (split->lot) continue; 00075 00076 /* Skip voided transactions */ 00077 if (gnc_numeric_zero_p (split->amount) && 00078 xaccTransGetVoidStatus(split->parent)) continue; 00079 00080 if (xaccSplitAssign (split)) goto restart_loop; 00081 } 00082 xaccAccountCommitEdit (acc); 00083 LEAVE ("acc=%s", xaccAccountGetName(acc)); 00084 } 00085 00086 /* ============================================================== */ 00087 00095 void 00096 xaccLotFill (GNCLot *lot) 00097 { 00098 Account *acc; 00099 Split *split; 00100 GNCPolicy *pcy; 00101 00102 if (!lot) return; 00103 acc = gnc_lot_get_account(lot); 00104 pcy = gnc_account_get_policy(acc); 00105 00106 ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc)); 00107 00108 /* If balance already zero, we have nothing to do. */ 00109 if (gnc_lot_is_closed (lot)) return; 00110 00111 split = pcy->PolicyGetSplit (pcy, lot); 00112 if (!split) return; /* Handle the common case */ 00113 00114 /* Reject voided transactions */ 00115 if (gnc_numeric_zero_p(split->amount) && 00116 xaccTransGetVoidStatus(split->parent)) return; 00117 00118 xaccAccountBeginEdit (acc); 00119 00120 /* Loop until we've filled up the lot, (i.e. till the 00121 * balance goes to zero) or there are no splits left. */ 00122 while (1) 00123 { 00124 Split *subsplit; 00125 00126 subsplit = xaccSplitAssignToLot (split, lot); 00127 if (subsplit == split) 00128 { 00129 PERR ("Accounting Policy gave us a split that " 00130 "doesn't fit into this lot\n" 00131 "lot baln=%s, isclosed=%d, aplit amt=%s", 00132 gnc_num_dbg_to_string (gnc_lot_get_balance(lot)), 00133 gnc_lot_is_closed (lot), 00134 gnc_num_dbg_to_string (split->amount)); 00135 break; 00136 } 00137 00138 if (gnc_lot_is_closed (lot)) break; 00139 00140 split = pcy->PolicyGetSplit (pcy, lot); 00141 if (!split) break; 00142 } 00143 xaccAccountCommitEdit (acc); 00144 LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc)); 00145 } 00146 00147 /* ============================================================== */ 00148 00149 void 00150 xaccLotScrubDoubleBalance (GNCLot *lot) 00151 { 00152 gnc_commodity *currency = NULL; 00153 SplitList *snode; 00154 GList *node; 00155 gnc_numeric zero = gnc_numeric_zero(); 00156 gnc_numeric value = zero; 00157 00158 if (!lot) return; 00159 00160 ENTER ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title")); 00161 00162 for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next) 00163 { 00164 Split *s = snode->data; 00165 xaccSplitComputeCapGains (s, NULL); 00166 } 00167 00168 /* We double-check only closed lots */ 00169 if (FALSE == gnc_lot_is_closed (lot)) return; 00170 00171 for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next) 00172 { 00173 Split *s = snode->data; 00174 Transaction *trans = s->parent; 00175 00176 /* Check to make sure all splits in the lot have a common currency */ 00177 if (NULL == currency) 00178 { 00179 currency = trans->common_currency; 00180 } 00181 if (FALSE == gnc_commodity_equiv (currency, trans->common_currency)) 00182 { 00183 /* This lot has mixed currencies. Can't double-balance. 00184 * Silently punt */ 00185 PWARN ("Lot with multiple currencies:\n" 00186 "\ttrans=%s curr=%s", xaccTransGetDescription(trans), 00187 gnc_commodity_get_fullname(trans->common_currency)); 00188 break; 00189 } 00190 00191 /* Now, total up the values */ 00192 value = gnc_numeric_add (value, xaccSplitGetValue (s), 00193 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT); 00194 PINFO ("Split=%p value=%s Accum Lot value=%s", s, 00195 gnc_num_dbg_to_string (s->value), 00196 gnc_num_dbg_to_string (value)); 00197 00198 } 00199 00200 if (FALSE == gnc_numeric_equal (value, zero)) 00201 { 00202 /* Unhandled error condition. Not sure what to do here, 00203 * Since the ComputeCapGains should have gotten it right. 00204 * I suppose there might be small rounding errors, a penny or two, 00205 * the ideal thing would to figure out why there's a rounding 00206 * error, and fix that. 00207 */ 00208 PERR ("Closed lot fails to double-balance !! lot value=%s", 00209 gnc_num_dbg_to_string (value)); 00210 for (node = gnc_lot_get_split_list(lot); node; node = node->next) 00211 { 00212 Split *s = node->data; 00213 PERR ("s=%p amt=%s val=%s", s, 00214 gnc_num_dbg_to_string(s->amount), 00215 gnc_num_dbg_to_string(s->value)); 00216 } 00217 } 00218 00219 LEAVE ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title")); 00220 } 00221 00222 /* ================================================================= */ 00223 00224 static inline gboolean 00225 is_subsplit (Split *split) 00226 { 00227 KvpValue *kval; 00228 00229 /* generic stop-progress conditions */ 00230 if (!split) return FALSE; 00231 g_return_val_if_fail (split->parent, FALSE); 00232 00233 /* If there are no sub-splits, then there's nothing to do. */ 00234 kval = kvp_frame_get_slot (split->inst.kvp_data, "lot-split"); 00235 if (!kval) return FALSE; 00236 00237 return TRUE; 00238 } 00239 00240 /* ================================================================= */ 00241 00242 00243 /* ================================================================= */ 00244 00245 /* Remove the guid of b from a. Note that a may not contain the guid 00246 * of b, (and v.v.) in which case, it will contain other guids which 00247 * establish the links. So merge them back in. */ 00248 00249 static void 00250 remove_guids (Split *sa, Split *sb) 00251 { 00252 KvpFrame *ksub; 00253 00254 /* Find and remove the matching guid's */ 00255 ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sa->inst.kvp_data, "lot-split", 00256 "peer_guid", qof_instance_get_guid(sb)); 00257 if (ksub) 00258 { 00259 gnc_kvp_bag_remove_frame (sa->inst.kvp_data, "lot-split", ksub); 00260 kvp_frame_delete (ksub); 00261 } 00262 00263 /* Now do it in the other direction */ 00264 ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sb->inst.kvp_data, "lot-split", 00265 "peer_guid", qof_instance_get_guid(sa)); 00266 if (ksub) 00267 { 00268 gnc_kvp_bag_remove_frame (sb->inst.kvp_data, "lot-split", ksub); 00269 kvp_frame_delete (ksub); 00270 } 00271 00272 /* Finally, merge b's lot-splits, if any, into a's */ 00273 /* This is an important step, if it got busted into many pieces. */ 00274 gnc_kvp_bag_merge (sa->inst.kvp_data, "lot-split", 00275 sb->inst.kvp_data, "lot-split"); 00276 } 00277 00278 /* The merge_splits() routine causes the amount & value of sb 00279 * to be merged into sa; it then destroys sb. It also performs 00280 * some other misc cleanup */ 00281 00282 static void 00283 merge_splits (Split *sa, Split *sb) 00284 { 00285 Account *act; 00286 Transaction *txn; 00287 gnc_numeric amt, val; 00288 00289 act = xaccSplitGetAccount (sb); 00290 xaccAccountBeginEdit (act); 00291 00292 txn = sa->parent; 00293 xaccTransBeginEdit (txn); 00294 00295 /* Remove the guid of sb from the 'gemini' of sa */ 00296 remove_guids (sa, sb); 00297 00298 /* Add amount of sb into sa, ditto for value. */ 00299 amt = xaccSplitGetAmount (sa); 00300 amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb)); 00301 xaccSplitSetAmount (sa, amt); 00302 00303 val = xaccSplitGetValue (sa); 00304 val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb)); 00305 xaccSplitSetValue (sa, val); 00306 00307 /* Set reconcile to no; after this much violence, 00308 * no way its reconciled. */ 00309 xaccSplitSetReconcile (sa, NREC); 00310 00311 /* If sb has associated gains splits, trash them. */ 00312 if ((sb->gains_split) && 00313 (sb->gains_split->gains & GAINS_STATUS_GAINS)) 00314 { 00315 Transaction *t = sb->gains_split->parent; 00316 xaccTransBeginEdit (t); 00317 xaccTransDestroy (t); 00318 xaccTransCommitEdit (t); 00319 } 00320 00321 /* Finally, delete sb */ 00322 xaccSplitDestroy(sb); 00323 00324 xaccTransCommitEdit (txn); 00325 xaccAccountCommitEdit (act); 00326 } 00327 00328 gboolean 00329 xaccScrubMergeSubSplits (Split *split) 00330 { 00331 gboolean rc = FALSE; 00332 Transaction *txn; 00333 SplitList *node; 00334 GNCLot *lot; 00335 const GncGUID *guid; 00336 00337 if (FALSE == is_subsplit (split)) return FALSE; 00338 00339 txn = split->parent; 00340 lot = xaccSplitGetLot (split); 00341 00342 ENTER ("(Lot=%s)", gnc_lot_get_title(lot)); 00343 restart: 00344 for (node = txn->splits; node; node = node->next) 00345 { 00346 Split *s = node->data; 00347 if (xaccSplitGetLot (s) != lot) continue; 00348 if (s == split) continue; 00349 if (qof_instance_get_destroying(s)) continue; 00350 00351 /* OK, this split is in the same lot (and thus same account) 00352 * as the indicated split. Make sure it is really a subsplit 00353 * of the split we started with. It's possible to have two 00354 * splits in the same lot and transaction that are not subsplits 00355 * of each other, the test-period test suite does this, for 00356 * example. Only worry about adjacent sub-splits. By 00357 * repeatedly merging adjacent subsplits, we'll get the non- 00358 * adjacent ones too. */ 00359 guid = qof_instance_get_guid(s); 00360 if (gnc_kvp_bag_find_by_guid (split->inst.kvp_data, "lot-split", 00361 "peer_guid", guid) == NULL) 00362 continue; 00363 00364 merge_splits (split, s); 00365 rc = TRUE; 00366 goto restart; 00367 } 00368 if (gnc_numeric_zero_p (split->amount)) 00369 { 00370 PWARN ("Result of merge has zero amt!"); 00371 } 00372 LEAVE (" splits merged=%d", rc); 00373 return rc; 00374 } 00375 00376 gboolean 00377 xaccScrubMergeLotSubSplits (GNCLot *lot) 00378 { 00379 gboolean rc = FALSE; 00380 SplitList *node; 00381 00382 if (!lot) return FALSE; 00383 00384 ENTER (" "); 00385 restart: 00386 for (node = gnc_lot_get_split_list(lot); node; node = node->next) 00387 { 00388 Split *s = node->data; 00389 if (!xaccScrubMergeSubSplits(s)) continue; 00390 00391 rc = TRUE; 00392 goto restart; 00393 } 00394 LEAVE (" splits merged=%d", rc); 00395 return rc; 00396 } 00397 00398 /* =========================== END OF FILE ======================= */
1.7.4