/** * The transaction registers the negated sum of the shares with the submitter. Additionally all * the shares in the map are registered with the same entry id. This transaction does not change * the total. It registers an allocation of a certain amount. If submitter is an empty <code> * String</code> it means there is an internal reallocation which is not an expense. Such an * internal reallocation is not allowed if the transaction causes a negative total. * * @param submitter the name of the participant who expended an amount matching the negated sum of * the shares * @param comment a <code>String</code> to make the transaction recognizable * @param shares a <code>ShareMap</code> containing the shares of participants involved * @return the entry id of the transaction or -1 in case the transaction failed or -2 if the * transaction violates the 'no negative total' rule */ public int performExpense(String submitter, String comment, ShareMap shares) { double amount = -shares.sum(); boolean internal = isInternal(submitter); if (internal) { if (breaksDebtLimit(-amount)) return -2; } int transactionCode = 3; int flags = composeFlags(transactionCode, !internal); int entryId = addEntry(submitter, amount, currency, comment, flags); if (entryId < 0) return -1; if (allocate(flags, entryId, shares.rawMap)) { String action = internal ? "reallocation of" : submitter + " expended"; Log.i( TAG, String.format( "entry %d: %s %f %s for '%s' shared by %s", entryId, action, Math.abs(amount), currency, comment, shares.toString())); } else { removeEntry(entryId); entryId = -1; } return entryId; }
/** * The transaction registers multiple submissions. This transaction as a whole is not allowed if * it causes a negative total. * * @param deals a <code>ShareMap</code> containing the deals of participants involved * @param comment a <code>String</code> to make the transactions recognizable * @return the entry id of the transaction or -1 in case the transaction failed or -2 if the * transactions in the sum violate the 'no negative total' rule */ public int performMultiple(ShareMap deals, String comment) { if (breaksDebtLimit(-deals.sum())) return -2; int transactionCode = 4; int flags = composeFlags(transactionCode, false); int entryId = getNewEntryId(); if (entryId > -1) { for (Map.Entry<String, Double> share : deals.rawMap.entrySet()) if (addRecord( entryId, share.getKey(), share.getValue(), currency, Helper.timestampNow(), comment, flags) < 0) { removeEntry(entryId); return -1; } Log.i( TAG, String.format("entry %d: '%s' submissions : %s", entryId, comment, deals.toString())); } return entryId; }
/** * The transaction registers the negated sum of the shares as an expense by a group of * contributors. Additionally all the shares in the map are registered with the same entry id. * Those names in the deals map that are empty <code>String</code> trigger transfers of internal * amounts. Such an internal transfer is not allowed if the transaction causes a negative total. * * @param deals the names of the contributors and their contributions who expended an amount * matching the negated sum of the deals * @param comment a <code>String</code> to make the transaction recognizable * @param shares a <code>ShareMap</code> containing the shares of participants involved * @return the entry id of the transaction or a negative value in case the transaction failed */ public int performComplexExpense(ShareMap deals, String comment, ShareMap shares) { int entryId = -1; int transactionCode = 3; int flags = composeFlags(transactionCode, true); double amount = deals.sum(); if (Math.abs(amount + shares.sum()) < ShareUtil.delta) { if (deals.option != null) switch (deals.option) { default: setDebtLimit(1000. * deals.option); break; } if (deals.rawMap.containsKey(kitty)) entryId = performTransfer(kitty, -deals.rawMap.get(kitty), comment, kitty); else entryId = getNewEntryId(); Collection<String> dealers = ShareUtil.filter(deals.rawMap.keySet(), true, ShareUtil.isKitty); for (String name : dealers) { long rowId = addRecord( entryId, name, deals.rawMap.get(name), currency, Helper.timestampNow(), comment, flags); if (rowId < 0) { entryId = -1; break; } } } else Log.w( TAG, String.format( "the sum of the deals (%f) for '%s' doesn't match the sum of the shares (%f)", amount, comment, shares.sum())); if (entryId > -1) { if (allocate(flags, entryId, shares.rawMap)) { Log.i( TAG, String.format( "entry %d: %s expended %f %s for '%s' shared by %s", entryId, deals.toString(), Math.abs(amount), currency, comment, shares.toString())); } else { removeEntry(entryId); entryId = -1; } } if (entryId < 0) removeEntry(entryId); return entryId; }
/** * calculates a compensation for each participant from the balance and the all-over cash flow at * this point. The sharing policy is taken into account for this calculation. An example: A * sharing policy 1:2:3 splits the volume of all the cash flows minus the current total which is * the remaining amount among the first, second and third participants out of the sorted list of * names. The first portion is 1/6, the second 2/6 and the third is 3/6 of the volume. * * @return a sorted map containing the names as keys and the compensations as values */ public ShareMap compensations(String sharingPolicy) { String[] sortedNames = sortedNames(); ShareMap cashFlow = cashFlow(); double volume = cashFlow.sum() - total(); ShareMap differences = new ShareMap(sortedNames, volume); ShareMap sharedCosts = new ShareMap(sortedNames, volume, sharingPolicy); differences.minus(sharedCosts.rawMap.values()); ShareMap compensations = balances().negated(); compensations.minus(differences.negated().rawMap.values()); return compensations; }