protected void Page_Load(object sender, EventArgs e) { PageAccessRequired = new Access(CurrentOrganization, AccessAspect.BookkeepingDetails, AccessType.Read); this._authenticationData = GetAuthenticationDataAndCulture(); HotBitcoinAddresses addresses = HotBitcoinAddresses.ForOrganization(_authenticationData.CurrentOrganization); foreach (HotBitcoinAddress address in addresses) { if (address.Chain == BitcoinChain.Core) { // These shouldn't exist much, so make sure that we have the equivalent Cash address registered try { HotBitcoinAddress.FromAddress(BitcoinChain.Cash, address.ProtocolLevelAddress); } catch (ArgumentException) { // We didn't have it, so create it BitcoinUtility.TestUnspents(BitcoinChain.Cash, address.ProtocolLevelAddress); } } } Response.ContentType = "application/json"; Response.Output.WriteLine(FormatJson(addresses)); Response.End(); }
public static AjaxCallResult ResyncSatoshisInLedger(string itemId) { AuthenticationData authData = GetAuthenticationDataAndCulture(); if (!authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } Int64 cashSatoshisInLedger = authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.GetForeignCurrencyBalanceDeltaCents( Constants.DateTimeLow, Constants.DateTimeHigh).Cents; Int64 cashSatoshisInHotwallet = HotBitcoinAddresses.GetSatoshisInHotwallet(authData.CurrentOrganization)[BitcoinChain.Cash]; Int64 adjustment = cashSatoshisInHotwallet - cashSatoshisInLedger; // positive if ledger needs upward adjustment FinancialTransaction adjustmentTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, Resources.Pages.Ledgers.EndOfMonth_LedgerBitcoinBalanceTransactionDescription); adjustmentTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, 0, authData.CurrentUser).AmountForeignCents = new Money(adjustment, Currency.BitcoinCash); return(new AjaxCallResult { Success = true, DisplayMessage = String.Format(Resources.Pages.Ledgers.EndOfMonth_Dialog_LedgerBitcoinBalanceMismatch, cashSatoshisInHotwallet / 100.0, cashSatoshisInLedger / 100.0) }); }
protected void Page_Load(object sender, EventArgs e) { PageAccessRequired = new Access(CurrentOrganization, AccessAspect.BookkeepingDetails, AccessType.Read); this._authenticationData = GetAuthenticationDataAndCulture(); HotBitcoinAddresses addresses = HotBitcoinAddresses.ForOrganization(_authenticationData.CurrentOrganization); Response.ContentType = "application/json"; Response.Output.WriteLine(FormatJson(addresses)); Response.End(); }
private string FormatJson(HotBitcoinAddresses addresses) { StringBuilder result = new StringBuilder(16384); Dictionary <BitcoinChain, double> conversionRateLookup = new Dictionary <BitcoinChain, double>(); Dictionary <BitcoinChain, Int64> satoshisTotalLookup = new Dictionary <BitcoinChain, long>(); long fiatCentsPerCoreCoin = new Money(BitcoinUtility.SatoshisPerBitcoin, Currency.BitcoinCore).ToCurrency(_authenticationData.CurrentOrganization.Currency).Cents; long fiatCentsPerCashCoin = new Money(BitcoinUtility.SatoshisPerBitcoin, Currency.BitcoinCash).ToCurrency(_authenticationData.CurrentOrganization.Currency).Cents; conversionRateLookup[BitcoinChain.Cash] = fiatCentsPerCashCoin / 1.0 / BitcoinUtility.SatoshisPerBitcoin; // the "/1.0" converts to double implicitly conversionRateLookup[BitcoinChain.Core] = fiatCentsPerCoreCoin / 1.0 / BitcoinUtility.SatoshisPerBitcoin; result.Append("{\"rows\":["); int addressesWithFunds = 0; foreach (HotBitcoinAddress address in addresses) { HotBitcoinAddressUnspents unspents = HotBitcoinAddressUnspents.ForAddress(address); Int64 satoshisUnspentAddress = 0; StringBuilder childResult = new StringBuilder(16384); foreach (HotBitcoinAddressUnspent unspent in unspents) { childResult.Append("{"); childResult.AppendFormat( "\"id\":\"UTXO{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\"", unspent.Identity, Resources.Pages.Ledgers.BitcoinHotWallet_UnspentTransaction, unspent.TransactionHash, (unspent.AmountSatoshis / 100.0).ToString("N2"), (unspent.AmountSatoshis / 100.0 * conversionRateLookup[unspent.Address.Chain]).ToString("N2") ); satoshisUnspentAddress += unspent.AmountSatoshis; childResult.Append("},"); } if (unspents.Count > 0) { childResult.Remove(childResult.Length - 1, 1); // remove last comma } if (satoshisUnspentAddress > 0) { result.Append("{"); result.AppendFormat( "\"id\":\"{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\",", address.Identity, address.Chain.ToString() + " " + address.DerivationPath, (address.Chain == BitcoinChain.Cash? address.HumanAddress.Substring("bitcoincash:".Length): address.ProtocolLevelAddress), JsonExpandingString(address.Identity, satoshisUnspentAddress), JsonExpandingString(address.Identity, (Int64)(satoshisUnspentAddress * conversionRateLookup[address.Chain])) ); result.Append("\"state\":\"closed\",\"children\":[" + childResult.ToString() + "]"); result.Append("},"); addressesWithFunds++; if (!satoshisTotalLookup.ContainsKey(address.Chain)) { satoshisTotalLookup[address.Chain] = 0; } satoshisTotalLookup[address.Chain] += satoshisUnspentAddress; } } if (addressesWithFunds > 0) { result.Remove(result.Length - 1, 1); // remove last comma } else // no funds at all in hotwallet { result.Append("{"); result.AppendFormat( "\"id\":\"0\"," + "\"derivePath\":\"0\"," + "\"address\":\"{0}\"," + "\"balanceMicrocoins\":\"{1}\"," + "\"balanceFiat\":\"{2}\"", Resources.Pages.Ledgers.BitcoinHotWallet_Empty, 0.0.ToString("N2"), 0.0.ToString("N2") ); result.Append("}"); } result.Append("]"); if (satoshisTotalLookup.Count > 0) { // We should also have a footer, because we need a total result.Append(",\"footer\":["); bool previousFooterRow = false; foreach (BitcoinChain chain in satoshisTotalLookup.Keys) { if (previousFooterRow) { result.Append(","); } result.Append("{"); result.AppendFormat( "\"derivePath\":\"" + Resources.Global.Global_Total.ToUpperInvariant() + " " + (string)chain.ToString().ToUpperInvariant() + "\",\"balanceMicrocoins\":\"{0}\",\"balanceFiat\":\"{1}\"", (satoshisTotalLookup[chain] / 100.0).ToString("N2"), (satoshisTotalLookup[chain] / 100.0 * conversionRateLookup[chain]).ToString("N2")); result.Append("}"); // on separate line to suppress warning*/ previousFooterRow = true; } result.Append("]"); } result.Append("}"); return(result.ToString()); }
private string FormatJson(HotBitcoinAddresses addresses) { StringBuilder result = new StringBuilder(16384); double conversionRate = 1.0; if (!this._authenticationData.CurrentOrganization.Currency.IsBitcoin) { long fiatCentsPerCoin = new Money(100000000, Currency.Bitcoin).ToCurrency(_authenticationData.CurrentOrganization.Currency).Cents; conversionRate = fiatCentsPerCoin / 100000000.0; // on satoshi level } result.Append("{\"rows\":["); Int64 satoshisTotal = 0; int addressesWithFunds = 0; foreach (HotBitcoinAddress address in addresses) { HotBitcoinAddressUnspents unspents = HotBitcoinAddressUnspents.ForAddress(address); Int64 satoshisUnspentAddress = 0; StringBuilder childResult = new StringBuilder(16384); foreach (HotBitcoinAddressUnspent unspent in unspents) { childResult.Append("{"); childResult.AppendFormat( "\"id\":\"UTXO{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\"", unspent.Identity, Resources.Pages.Ledgers.BitcoinHotWallet_UnspentTransaction, unspent.TransactionHash, (unspent.AmountSatoshis / 100.0).ToString("N2"), (unspent.AmountSatoshis / 100.0 * conversionRate).ToString("N2") ); satoshisUnspentAddress += unspent.AmountSatoshis; childResult.Append("},"); } if (unspents.Count > 0) { childResult.Remove(childResult.Length - 1, 1); // remove last comma } if (satoshisUnspentAddress > 0) { result.Append("{"); result.AppendFormat( "\"id\":\"{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\",", address.Identity, address.DerivationPath, address.Address, JsonExpandingString(address.Identity, satoshisUnspentAddress), JsonExpandingString(address.Identity, (Int64)(satoshisUnspentAddress * conversionRate)) ); result.Append("\"state\":\"closed\",\"children\":[" + childResult.ToString() + "]"); result.Append("},"); satoshisTotal += satoshisUnspentAddress; addressesWithFunds++; } } if (addressesWithFunds > 0) { result.Remove(result.Length - 1, 1); // remove last comma } else // no funds at all in hotwallet { result.Append("{"); result.AppendFormat( "\"id\":\"0\"," + "\"derivePath\":\"0\"," + "\"address\":\"{0}\"," + "\"balanceMicrocoins\":\"{1}\"," + "\"balanceFiat\":\"{2}\"", Resources.Pages.Ledgers.BitcoinHotWallet_Empty, 0.0.ToString("N2"), 0.0.ToString("N2") ); result.Append("}"); } result.Append("],\"footer\":["); result.Append("{"); result.AppendFormat("\"derivePath\":\"" + Resources.Global.Global_Total.ToUpperInvariant() + "\",\"balanceMicrocoins\":\"{0}\",\"balanceFiat\":\"{1}\"", (satoshisTotal / 100.0).ToString("N2"), (satoshisTotal / 100.0 * conversionRate).ToString("N2")); result.Append("}]}"); // on separate line to suppress warning return(result.ToString()); }
protected void Page_Load(object sender, EventArgs e) { this.PageAccessRequired = new Access(this.CurrentOrganization, AccessAspect.BookkeepingDetails); this.PageTitle = this.Title = this.LabelHeader.Text = String.Format(Resources.Pages.Ledgers.EndOfMonth_Title, DateTime.UtcNow.AddMonths(-1)); this.InfoBoxLiteral = Resources.Pages.Ledgers.EndOfMonth_Info; // Check which reports are required ItemGroups = new List <EomItemGroup>(); // Group I: External data & accounts EomItemGroup group1 = new EomItemGroup(); group1.Header = Resources.Pages.Ledgers.EndOfMonth_Header_ExternalData; group1.Id = "ExternalData"; string lastUploadItemId = string.Empty; // Iterate over all Balance accounts and check for automation; // if so, add it to an upload sequence FinancialAccounts assetAccounts = FinancialAccounts.ForOrganization(this.CurrentOrganization, FinancialAccountType.Asset); DateTime lastMonthEndDate = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1).AddDays(-1); int lastMonth = lastMonthEndDate.Year * 100 + lastMonthEndDate.Month; bool skippable = true; foreach (FinancialAccount assetAccount in assetAccounts) { if (assetAccount.AutomationProfileId != 0) { // This account has automation // If automation has Bank Account Statement enabled (assume true for now): FinancialAccountDocument lastBankStatement = assetAccount.GetMostRecentDocument(FinancialAccountDocumentType.BankStatement); int lastStatementMonth = (this.CurrentOrganization.FirstFiscalYear - 1) * 100 + 12; // December of the year before if (lastBankStatement != null) { lastStatementMonth = lastBankStatement.ConcernsPeriodStart.Year * 100 + lastBankStatement.ConcernsPeriodStart.Month; skippable = false; } string lastId = string.Empty; int monthIterator = lastStatementMonth; while (monthIterator < lastMonth) { monthIterator++; if (monthIterator % 100 == 13) { monthIterator += 88; } EomItem bankStatement = new EomItem(); bankStatement.DependsOn = lastId; // empty for first record bankStatement.Id = lastId = "BankStatement-" + assetAccount.Identity.ToString(CultureInfo.InvariantCulture) + '-' + monthIterator; bankStatement.Name = string.Format(Resources.Pages.Ledgers.EndOfMonth_UploadBankStatementFor, assetAccount.Name, "PDF", new DateTime(monthIterator / 100, monthIterator % 100, 15).ToString("MMMM yyyy")); bankStatement.Completed = false; // TODO bankStatement.Icon = "upload"; bankStatement.Skippable = skippable; group1.Items.Add(bankStatement); } // Add data upload item // TODO: Check if the last data upload was into the current month EomItem dataUploadItem = new EomItem(); dataUploadItem.Id = "BankTransactionData-" + assetAccount.Identity.ToString(CultureInfo.InvariantCulture); dataUploadItem.Icon = "upload"; dataUploadItem.Completed = false; // todo dataUploadItem.Name = String.Format(Resources.Pages.Ledgers.EndOfMonth_UploadTransactionDataFor, assetAccount.Name, "CSV"); dataUploadItem.Skippable = false; group1.Items.Add(dataUploadItem); // Check if we need to add a resync of the hotwallet's native ledger if (CurrentOrganization.FinancialAccounts.AssetsBitcoinHot != null) // has hotwallet { Int64 cashSatoshisInLedger = CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.GetForeignCurrencyBalanceDeltaCents( Constants.DateTimeLow, Constants.DateTimeHigh).Cents; Int64 cashSatoshisInHotwallet = HotBitcoinAddresses.GetSatoshisInHotwallet(CurrentOrganization)[BitcoinChain.Cash]; if (cashSatoshisInHotwallet != cashSatoshisInLedger) { // Resync required EomItem resyncSatoshiCountItem = new EomItem(); resyncSatoshiCountItem.Id = "ResyncSatoshisInLedger"; resyncSatoshiCountItem.Icon = "approve"; resyncSatoshiCountItem.Completed = false; resyncSatoshiCountItem.Skippable = false; resyncSatoshiCountItem.Callback = "ResyncSatoshisInLedger"; resyncSatoshiCountItem.Name = Resources.Pages.Ledgers.EndOfMonth_CheckLedgerAgainstHotWallet; group1.Items.Add(resyncSatoshiCountItem); } } } } // If there are any items in Group 1, OR if an account matching is necessary, then // add that as an action item if (group1.Items.Count() > 0 || FinancialTransactions.GetUnbalanced(this.CurrentOrganization).Count() > 0) { EomItem matchAccounts = new EomItem(); matchAccounts.DependsOn = lastUploadItemId; // may be empty if there's nothing to upload and that's ok matchAccounts.Id = "MatchAccounts"; matchAccounts.Completed = false; // we already know there's at least one unbalanced matchAccounts.Icon = "wrench"; matchAccounts.Skippable = false; matchAccounts.Name = Resources.Pages.Ledgers.EndOfMonth_MatchAccounts; group1.Items.Add(matchAccounts); } if (group1.Items.Count > 0) { ItemGroups.Add(group1); } // Group: Payroll and Taxes EomItemGroup group2 = new EomItemGroup(); group2.Header = Resources.Pages.Ledgers.EndOfMonth_Header_PayrollTaxes; group2.Id = "PayrollTaxes"; ReportRequirement vatRequired = VatReport.IsRequired(this.CurrentOrganization); if (vatRequired == ReportRequirement.Required || vatRequired == ReportRequirement.Completed) { EomItem vatReport = new EomItem(); vatReport.Id = "VatReport"; vatReport.Callback = "CreateVatReport"; vatReport.Completed = (vatRequired == ReportRequirement.Completed ? true : false); vatReport.Name = String.Format(Resources.Pages.Ledgers.EndOfMonth_CreateVatReport, (vatReport.Completed ? VatReport.LastReportDescription(this.CurrentOrganization) : VatReport.NextReportDescription(this.CurrentOrganization))); vatReport.Icon = "document"; group2.Items.Add(vatReport); } Payroll payroll = Payroll.ForOrganization(this.CurrentOrganization); if (payroll.Any()) { // There is active payroll // TODO: Taxes for last month and processing for this month } else { EomItem payrollInactive = new EomItem(); payrollInactive.Id = "PayrollInactive"; payrollInactive.Completed = true; payrollInactive.Name = Resources.Pages.Ledgers.EndOfMonth_PayrollInactive; payrollInactive.Icon = "document"; group2.Items.Add(payrollInactive); } if (group2.Items.Count > 0) { ItemGroups.Add(group2); } // Group: Closure of Ledgers and Annual Reports int lastClosedYear = CurrentOrganization.Parameters.FiscalBooksClosedUntilYear; if (lastClosedYear - 1 < DateTime.UtcNow.Year) { EomItemGroup groupReports = new EomItemGroup(); groupReports.Header = Resources.Pages.Ledgers.EndOfMonth_Header_AnnualReports; EomItem itemCloseYear = new EomItem(); itemCloseYear.Id = "CloseLedgers"; itemCloseYear.Callback = "CloseLedgers"; itemCloseYear.Icon = "document"; itemCloseYear.Name = String.Format(Resources.Pages.Ledgers.EndOfMonth_CloseLedgersFor, lastClosedYear + 1); groupReports.Items.Add(itemCloseYear); ItemGroups.Add(groupReports); } }