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();
        }
Ejemplo n.º 2
0
        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());
        }
Ejemplo n.º 6
0
        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);
            }
        }