private void UpdateFinancialTransaction(Person updatingPerson) { Dictionary <int, Int64> nominalTransaction = new Dictionary <int, Int64>(); int debtAccountId = Organization.FinancialAccounts.DebtsExpenseClaims.Identity; if (!Claimed) { debtAccountId = Organization.FinancialAccounts.CostsAllocatedFunds.Identity; } if (Validated || Open) { // ...only holds values if not closed as invalid... nominalTransaction[debtAccountId] = -AmountCents; if (this.Organization.VatEnabled) { nominalTransaction[BudgetId] = AmountCents - VatCents; nominalTransaction[Organization.FinancialAccounts.AssetsVatInboundUnreported.Identity] = VatCents; } else { nominalTransaction[BudgetId] = AmountCents; } } FinancialTransaction.RecalculateTransaction(nominalTransaction, updatingPerson); }
private void UpdateTransaction(Person updatingPerson) { Dictionary <int, Int64> nominalTransaction = new Dictionary <int, Int64>(); // Create an image of what the transaction SHOULD look like with changes. if (Attested || Open) { // ...only holds values if not closed as invalid... nominalTransaction[Organization.FinancialAccounts.DebtsInboundInvoices.Identity] = -AmountCents; if (this.Organization.VatEnabled) { nominalTransaction[BudgetId] = AmountCents - VatCents; nominalTransaction[Organization.FinancialAccounts.AssetsVatInboundUnreported.Identity] = VatCents; } else { nominalTransaction[BudgetId] = AmountCents; } } FinancialTransaction.RecalculateTransaction(nominalTransaction, updatingPerson); }
public void DenyAttestation(Person denyingPerson, string reason) { this.Attested = false; this.Open = false; SwarmDb.GetDatabaseForWriting().CreateFinancialValidation(FinancialValidationType.Kill, FinancialDependencyType.ExpenseClaim, Identity, DateTime.UtcNow, denyingPerson.Identity, this.CostTotalCents); OutboundComm.CreateNotificationOfFinancialValidation(Budget, this.PayrollItem.Person, NetSalaryCents / 100.0, this.PayoutDate.ToString("MMMM yyyy"), NotificationResource.Salary_Denied, reason); FinancialTransaction transaction = FinancialTransaction.FromDependency(this); transaction.RecalculateTransaction(new Dictionary <int, long>(), denyingPerson); // zeroes out the tx }
private static void CheckColdStorageRecurse(FinancialAccount parent, Dictionary <string, int> addressAccountLookup) { foreach (FinancialAccount child in parent.Children) { CheckColdStorageRecurse(child, addressAccountLookup); } // After recursing, get all transactions for this account and verify against our records // is the account name a valid bitcoin address on the main network? string address = parent.BitcoinAddress; if (string.IsNullOrEmpty(address)) { if (BitcoinUtility.IsValidBitcoinAddress(parent.Name.Trim())) { parent.BitcoinAddress = address = parent.Name.Trim(); } else { return; // not a bitcoin address but something else; do not process } } Organization organization = parent.Organization; bool organizationLedgerUsesBitcoin = organization.Currency.IsBitcoinCore; JObject addressData = JObject.Parse( new WebClient().DownloadString("https://blockchain.info/address/" + address + "?format=json&api_key=" + SystemSettings.BlockchainSwarmopsApiKey)); //int transactionCount = (int) (addressData["n_tx"]); foreach (JObject txJson in addressData["txs"]) { FinancialTransaction ourTx = null; Dictionary <Int64, Int64> satoshisLookup = new Dictionary <long, long>(); // map from ledgercents to satoshis BlockchainTransaction blockchainTx = BlockchainTransaction.FromBlockchainInfoJson(txJson); try { ourTx = FinancialTransaction.FromBlockchainHash(parent.Organization, blockchainTx.TransactionHash); // If the transaction was fetched fine, we have already seen this transaction, but need to re-check it } catch (ArgumentException) { // We didn't have this transaction, so we need to create it ourTx = FinancialTransaction.Create(parent.Organization, blockchainTx.TransactionDateTimeUtc, "Blockchain tx"); ourTx.BlockchainHash = blockchainTx.TransactionHash; // Did we lose or gain money? // Find all in- and outpoints, determine which are ours (hot and cold wallet) and which aren't } Dictionary <int, long> transactionReconstructedRows = new Dictionary <int, long>(); // Note the non-blockchain rows in this tx, keep them for reconstruction foreach (FinancialTransactionRow row in ourTx.Rows) { if (!addressAccountLookup.ContainsValue(row.FinancialAccountId)) // not optimal but n is small { // This is not a bitcoin address account, so note it for reconstruction if (!transactionReconstructedRows.ContainsKey(row.FinancialAccountId)) { transactionReconstructedRows[row.FinancialAccountId] = 0; // init } transactionReconstructedRows[row.FinancialAccountId] += row.AmountCents; } else { // this is a known blockchain row, note its ledgered value in satoshis if (!organizationLedgerUsesBitcoin) { Money nativeMoney = row.AmountForeignCents; if (nativeMoney != null && nativeMoney.Currency.IsBitcoinCore) // it damn well should be, but just checking { satoshisLookup[row.AmountCents] = row.AmountForeignCents.Cents; } } } } // Reconstruct the blockchain rows: input, output, fees, in that order // -- inputs foreach (BlockchainTransactionRow inputRow in blockchainTx.Inputs) { if (addressAccountLookup.ContainsKey(inputRow.Address)) { // this input is ours int financialAccountId = addressAccountLookup[inputRow.Address]; if (!transactionReconstructedRows.ContainsKey(financialAccountId)) { transactionReconstructedRows[financialAccountId] = 0; // initialize } if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[financialAccountId] += -inputRow.ValueSatoshis; // note the negation! } else { Int64 ledgerCents = new Money(inputRow.ValueSatoshis, Currency.BitcoinCore, ourTx.DateTime).ToCurrency( organization.Currency).Cents; transactionReconstructedRows[financialAccountId] += -ledgerCents; // note the negation! satoshisLookup[ledgerCents] = inputRow.ValueSatoshis; } } } // -- outputs foreach (BlockchainTransactionRow outputRow in blockchainTx.Outputs) { if (addressAccountLookup.ContainsKey(outputRow.Address)) { // this output is ours int financialAccountId = addressAccountLookup[outputRow.Address]; if (!transactionReconstructedRows.ContainsKey(financialAccountId)) { transactionReconstructedRows[financialAccountId] = 0; // initialize } if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[financialAccountId] += outputRow.ValueSatoshis; } else { Int64 ledgerCents = new Money(outputRow.ValueSatoshis, Currency.BitcoinCore, ourTx.DateTime).ToCurrency( organization.Currency).Cents; transactionReconstructedRows[financialAccountId] += ledgerCents; satoshisLookup[ledgerCents] = outputRow.ValueSatoshis; } } } // -- fees if (addressAccountLookup.ContainsKey(blockchainTx.Inputs[0].Address)) { // if the first input is ours, we're paying the fee (is there any case where this is not true?) if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[organization.FinancialAccounts.CostsBitcoinFees.Identity] = blockchainTx.FeeSatoshis; } else { Int64 feeLedgerCents = new Money(blockchainTx.FeeSatoshis, Currency.BitcoinCore, blockchainTx.TransactionDateTimeUtc).ToCurrency(organization.Currency).Cents; transactionReconstructedRows[organization.FinancialAccounts.CostsBitcoinFees.Identity] = feeLedgerCents; } } // Rewrite the transaction (called always, but the function won't do anything if everything matches) ourTx.RecalculateTransaction(transactionReconstructedRows, /* loggingPerson*/ null); // Finally, add foreign cents, if any if (!organizationLedgerUsesBitcoin) { foreach (FinancialTransactionRow row in ourTx.Rows) { if (addressAccountLookup.ContainsValue(row.Account.Identity)) // "ContainsValue" is bad, but n is low { // Do we have a foreign amount for this row already? Money foreignMoney = row.AmountForeignCents; if (foreignMoney == null || foreignMoney.Cents == 0) { // no we didn't; create one if (satoshisLookup.ContainsKey(row.AmountCents)) { row.AmountForeignCents = new Money(satoshisLookup[row.AmountCents], Currency.BitcoinCore, ourTx.DateTime); } else if (satoshisLookup.ContainsKey(-row.AmountCents)) // the negative counterpart { row.AmountForeignCents = new Money(-satoshisLookup[-row.AmountCents], Currency.BitcoinCore, ourTx.DateTime); } else { // There's a last case which may happen if the row is an addition to a previous row; if so, calculate row.AmountForeignCents = new Money(row.AmountCents, organization.Currency, ourTx.DateTime).ToCurrency(Currency.BitcoinCore); } } } } } } }