SeriesCollection GetDonationData(OrganizationMetadata metadata, out decimal donationsTotal, out int donationCount)
    {
        SeriesCollection collection   = new SeriesCollection();
        DateTime         dateIterator = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
        Organization     org          = Organization.FromIdentity(metadata.OrganizationId);

        if (DateTime.Now.Day > 4)
        {
            dateIterator = dateIterator.AddMonths(-1);
        }
        else
        {
            dateIterator = dateIterator.AddMonths(-1);  // -2
        }

        DateTime dateStop = dateIterator.AddMonths(1);

        FinancialAccountRows rows = org.FinancialAccounts.IncomeDonations.GetRows(dateIterator, dateIterator.AddMonths(1));

        Series series = new Series();

        series.Name = "";
        DateTime today    = DateTime.Now.Date;
        int      rowIndex = 0;

        donationsTotal = 0.0m;
        donationCount  = rows.Count;

        Dictionary <int, int> personMembershipCountLookup = new Dictionary <int, int>();

        while (dateIterator < dateStop)
        {
            decimal donationsToday = 0.0m;

            DateTime nextDate = dateIterator.AddDays(1);
            while (rowIndex < rows.Count && rows[rowIndex].TransactionDateTime < nextDate)
            {
                donationsToday -= rows[rowIndex].Amount;
                donationsTotal -= rows[rowIndex].Amount;

                rowIndex++;
            }

            Element newElement = new Element();
            newElement.XDateTime = dateIterator;
            newElement.YValue    = (double)donationsToday;
            series.Elements.Add(newElement);
            dateIterator = nextDate;
        }

        collection.Add(series);

        collection[0].DefaultElement.Color = metadata.Color;

        return(collection);
    }
    SeriesCollection GetDonationData(OrganizationMetadata metadata)
    {
        string cacheDataKey = "Financials-DonationData-PiratpartietSE";

        FinancialAccountRows rows = (FinancialAccountRows)Cache.Get(cacheDataKey);
        Organization         org  = Organization.FromIdentity(metadata.OrganizationId);

        if (rows == null)
        {
            rows = org.FinancialAccounts.IncomeDonations.GetRows(new DateTime(2008, 1, 1), DateTime.Today);
            Cache.Insert(cacheDataKey, rows, null, DateTime.Today.AddHours(1).ToUniversalTime(), System.Web.Caching.Cache.NoSlidingExpiration);
        }

        SeriesCollection collection   = new SeriesCollection();
        DateTime         dateIterator = new DateTime(2008, 1, 1);

        DateTime today   = DateTime.Now.Date;
        DateTime endDate = today.AddDays(-today.Day);

        Series series = new Series();

        series.Name = "";
        int rowIndex = 0;

        while (dateIterator <= endDate)
        {
            DateTime nextDate           = dateIterator.AddMonths(1);
            decimal  donationsThisMonth = 0.0m;

            while (rowIndex < rows.Count && rows[rowIndex].TransactionDateTime < nextDate)
            {
                donationsThisMonth -= rows[rowIndex].Amount;              // Subtracting because the donation account is sign reversed; we want a positive graph

                rowIndex++;
            }

            Element newElement = new Element();
            newElement.XDateTime = dateIterator;
            newElement.YValue    = (double)donationsThisMonth;
            series.Elements.Add(newElement);
            dateIterator = nextDate;
        }

        collection.Add(series);

        collection[0].DefaultElement.Color = metadata.Color;

        return(collection);
    }
Beispiel #3
0
    public void Populate()
    {
        FinancialAccountRows      rows           = _accounts.GetRows(this._dateStart, this._dateEnd.AddDays(1));
        Dictionary <int, decimal> accountBalance = new Dictionary <int, decimal>();

        foreach (FinancialAccount account in _accounts)
        {
            _accountNames[account.Identity] = account.Name;

            if (account.AccountType == FinancialAccountType.Asset || account.AccountType == FinancialAccountType.Debt)
            {
                _accountInboundBalance[account.Identity] = account.GetDelta(new DateTime(2006, 1, 1),
                                                                            this._dateStart);
            }
            else
            {
                _accountInboundBalance[account.Identity] = account.GetDelta(new DateTime(this._dateStart.Year, 1, 1),
                                                                            this._dateStart);
            }

            accountBalance[account.Identity] = _accountInboundBalance[account.Identity];
        }

        LedgerRows ledgerRows = new LedgerRows();

        foreach (FinancialAccountRow row in rows)
        {
            accountBalance[row.FinancialAccountId] += row.Amount;

            if (row.Amount <= this._maxAmount)
            {
                ledgerRows.Add(new LedgerRow(row, accountBalance[row.FinancialAccountId]));
            }
        }

        this.GridTransactions.DataSource = ledgerRows;
        Session[_sessionObjectName]      = ledgerRows;
        Session["FinancialAccountNames"] = _accountNames;
        this.GridTransactions.DataBind();

        this.GridTransactions.Columns[0].Visible  = !_simplifiedView;
        this.GridTransactions.Columns[1].Visible  = _simplifiedView;
        this.GridTransactions.Columns[2].Visible  = !_simplifiedView;
        this.GridTransactions.Columns[4].Visible  = !_simplifiedView;
        this.GridTransactions.Columns[8].Visible  = !_simplifiedView;
        this.GridTransactions.Columns[9].Visible  = !_simplifiedView;
        this.GridTransactions.Columns[10].Visible = !_simplifiedView;
    }
    SeriesCollection GetFinancialData()
    {
        FinancialAccounts accounts = FinancialAccounts.ForOrganization(Organization.PPSE, FinancialAccountType.Balance);

        accounts.Remove(Organization.PPSE.FinancialAccounts.DebtsCapital);

        FinancialAccountRows rows = accounts.GetRows(new DateTime(2006, 1, 1), DateTime.Today.AddDays(1));



        SeriesCollection collection   = new SeriesCollection();
        DateTime         dateIterator = new DateTime(2009, 1, 1);

        DateTime endDate = DateTime.Now.Date;

        Series series = new Series();

        series.Name = "Each event";

        decimal maxValue       = 0;
        decimal minValue       = Decimal.MaxValue;
        int     rowIndex       = 0;
        decimal currentBalance = 0;

        while (rows[rowIndex].TransactionDateTime < dateIterator && rowIndex < rows.Count)
        {
            currentBalance += rows[rowIndex].Amount;

            rowIndex++;
        }

        Element newElement = new Element();

        newElement.XDateTime = dateIterator;
        newElement.YValue    = (double)currentBalance;

        while (dateIterator < endDate)
        {
            if (rowIndex >= rows.Count)
            {
                break;
            }

            DateTime nextDate = dateIterator.AddDays(1);
            while (rowIndex < rows.Count && rows[rowIndex].TransactionDateTime < nextDate)
            {
                currentBalance += rows[rowIndex].Amount;

                rowIndex++;
            }

            if (currentBalance < minValue)
            {
                minValue = currentBalance;
            }

            if (currentBalance > maxValue)
            {
                maxValue = currentBalance;
            }

            newElement           = new Element();
            newElement.XDateTime = dateIterator;
            newElement.YValue    = (double)currentBalance;
            series.Elements.Add(newElement);
            dateIterator = nextDate;
        }

        /*
         * Chart.YAxis.Maximum = (maxValue + 90) / 10000 * 10000;
         * Chart.YAxis.Minimum = minValue / 100 * 100;*/

        series.Type                 = SeriesType.Line;
        series.Line.Width           = 5;
        series.DefaultElement.Color = Color.MidnightBlue;
        //series.DefaultElement.Marker = new ElementMarker(ElementMarkerType.None);
        collection.Add(series);

        return(collection);
    }
Beispiel #5
0
        private static void ProcessUploadThread(object args)
        {
            string guid = ((ProcessThreadArguments)args).Guid;

            Documents documents = Documents.RecentFromDescription(guid);

            if (documents.Count != 1)
            {
                return; // abort
            }

            Document uploadedDoc = documents[0];

            FinancialAccount account = ((ProcessThreadArguments)args).Account;

            ExternalBankData externalData = new ExternalBankData();

            externalData.Profile = ExternalBankDataProfile.FromIdentity(ExternalBankDataProfile.SESebId);
            // TODO: HACK HACK HACK HACK LOAD

            using (
                StreamReader reader = new StreamReader(StorageRoot + uploadedDoc.ServerFileName,
                                                       Encoding.GetEncoding(1252)))
            {
                externalData.LoadData(reader, ((ProcessThreadArguments)args).Organization, account.ForeignCurrency);
            }

            _staticDataLookup[guid + "FirstTx"] = externalData.Records[0].DateTime.ToLongDateString();
            _staticDataLookup[guid + "LastTx"]  =
                externalData.Records[externalData.Records.Length - 1].DateTime.ToLongDateString();
            _staticDataLookup[guid + "TxCount"] = externalData.Records.Length.ToString("N0");

            _staticDataLookup[guid + "Profile"] = externalData.Profile;

            _staticDataLookup[guid + "PercentRead"] = 1;

            DateTime timeWalker         = externalData.Records[0].DateTime;
            int      currentRecordIndex = 0;

            // Walk past the first identical time records, and verify that we have at least one balance record that matches
            // our own for this timestamp. There may be several transactions in the master file, but at least one should have
            // a post-transaction balance that matches our records, or something is much more broken.

            long swarmopsCentsStart = account.GetDeltaCents(Constants.DateTimeLow, timeWalker.AddSeconds(1));

            // At least one of the transactions for this timestamp should match.

            bool foundMatch = false;

            while (externalData.Records[currentRecordIndex].DateTime == timeWalker)
            {
                if (externalData.Records[currentRecordIndex].AccountBalanceCents == swarmopsCentsStart)
                {
                    foundMatch = true;

                    // continue loop until on first record past initial conditions
                }
                currentRecordIndex++; // no test for array overrun in while
            }

            if (!foundMatch)
            {
                throw new InvalidOperationException("Unable to locate stable initial conditions for resynchronization");
            }

            // From here on out, every new timestamp should have the exact same delta in Swarmops as it has in the master file.

            List <ExternalBankMismatchingDateTime> mismatchList = new List <ExternalBankMismatchingDateTime>();

            while (currentRecordIndex < externalData.Records.Length)
            {
                DateTime lastTimestamp = timeWalker;

                timeWalker = externalData.Records[currentRecordIndex].DateTime;

                long swarmopsDeltaCents = account.GetDeltaCents(lastTimestamp.AddSeconds(1), timeWalker.AddSeconds(1));
                // "AddSeconds" because DeltaCents operates on ">= lowbound, < highbound"
                int  timestampStartIndex    = currentRecordIndex;
                long masterDeltaCents       = externalData.Records[currentRecordIndex++].TransactionNetCents;
                int  masterTransactionCount = 1;

                while (currentRecordIndex < externalData.Records.Length &&
                       externalData.Records[currentRecordIndex].DateTime == timeWalker)
                {
                    masterDeltaCents += externalData.Records[currentRecordIndex++].TransactionNetCents;
                    masterTransactionCount++;
                }

                if (masterDeltaCents != swarmopsDeltaCents)
                {
                    // We have a mismatch. Add it to the list.

                    ExternalBankMismatchingDateTime newMismatch = new ExternalBankMismatchingDateTime();
                    newMismatch.DateTime                 = timeWalker;
                    newMismatch.MasterDeltaCents         = masterDeltaCents;
                    newMismatch.MasterTransactionCount   = masterTransactionCount;
                    newMismatch.SwarmopsDeltaCents       = swarmopsDeltaCents;
                    newMismatch.SwarmopsTransactionCount = 0; // TODO

                    // Load transactions from both sources. First, create the interim construction object.

                    ExternalBankMismatchConstruction mismatchConstruction = new ExternalBankMismatchConstruction();

                    // Load from Master

                    for (int innerRecordIndex = timestampStartIndex;
                         innerRecordIndex < currentRecordIndex;
                         innerRecordIndex++)
                    {
                        string description = externalData.Records[innerRecordIndex].Description.Replace("  ", " ");

                        if (!mismatchConstruction.Master.ContainsKey(description))
                        {
                            mismatchConstruction.Master[description] =
                                new ExternalBankMismatchingRecordConstruction();
                        }

                        mismatchConstruction.Master[description].Cents.Add(
                            externalData.Records[innerRecordIndex].TransactionNetCents);
                        mismatchConstruction.Master[description].Transactions.Add(null);
                        // no dependencies on the master side, only on swarmops side
                    }

                    // Load from Swarmops

                    FinancialAccountRows swarmopsTransactionRows =
                        account.GetRowsFar(lastTimestamp, timeWalker);
                    // the "select far" is a boundary < x <= boundary selector. Default is boundary <= x < boundary.

                    Dictionary <int, FinancialTransaction> lookupTransactions =
                        new Dictionary <int, FinancialTransaction>();

                    // note all transaction IDs, then sum up per transaction

                    foreach (FinancialAccountRow swarmopsTransactionRow in swarmopsTransactionRows)
                    {
                        lookupTransactions[swarmopsTransactionRow.FinancialTransactionId] =
                            swarmopsTransactionRow.Transaction;
                    }


                    foreach (FinancialTransaction transaction in lookupTransactions.Values)
                    {
                        string description = transaction.Description.Replace("  ", " ");
                        // for legacy compatibility with new importer

                        if (!mismatchConstruction.Swarmops.ContainsKey(description))
                        {
                            mismatchConstruction.Swarmops[description] =
                                new ExternalBankMismatchingRecordConstruction();
                        }

                        long cents = transaction[account];

                        if (cents != 0) // only add nonzero records
                        {
                            mismatchConstruction.Swarmops[description].Cents.Add(transaction[account]);
                            mismatchConstruction.Swarmops[description].Transactions.Add(transaction);
                        }
                    }

                    // Then, parse the intermediate construction object to the presentation-and-action object.

                    Dictionary <string, ExternalBankMismatchingRecordDescription> mismatchingRecordList =
                        new Dictionary <string, ExternalBankMismatchingRecordDescription>();

                    foreach (string masterKey in mismatchConstruction.Master.Keys)
                    {
                        Dictionary <int, bool> checkMasterIndex   = new Dictionary <int, bool>();
                        Dictionary <int, bool> checkSwarmopsIndex = new Dictionary <int, bool>();

                        // For each key and entry for each key;

                        // 1) locate an exact corresponding amount in swarmops records and log; failing that,
                        // 2) if exactly one record left in master and swarmops records, log; failing that,
                        // 3) log the rest of the master OR rest of swarmops records with no corresponding
                        //    equivalent with counterpart. (May produce bad results if 2 consistent mismatches
                        //    for every description.)

                        ExternalBankMismatchingRecordDescription newRecord =
                            new ExternalBankMismatchingRecordDescription();
                        newRecord.Description = masterKey;

                        List <long>   masterCentsList   = new List <long>();
                        List <long>   swarmopsCentsList = new List <long>();
                        List <object> dependenciesList  = new List <object>();
                        List <FinancialTransaction>             transactionsList = new List <FinancialTransaction>();
                        List <ExternalBankMismatchResyncAction> actionsList      =
                            new List <ExternalBankMismatchResyncAction>();

                        // STEP 1 - locate all identical matches

                        if (mismatchConstruction.Swarmops.ContainsKey(masterKey))
                        {
                            for (int masterIndex = 0;
                                 masterIndex < mismatchConstruction.Master[masterKey].Cents.Count;
                                 masterIndex++)
                            {
                                // no "continue" necessary on first run-through; nothing has been checked off yet

                                long findMasterCents = mismatchConstruction.Master[masterKey].Cents[masterIndex];

                                for (int swarmopsIndex = 0;
                                     swarmopsIndex < mismatchConstruction.Swarmops[masterKey].Cents.Count;
                                     swarmopsIndex++)
                                {
                                    if (checkSwarmopsIndex.ContainsKey(swarmopsIndex))
                                    {
                                        continue;
                                        // may have been checked off already in the rare case of twin identical amounts
                                    }

                                    if (findMasterCents == mismatchConstruction.Swarmops[masterKey].Cents[swarmopsIndex])
                                    {
                                        // There is a match as per case 1. Record both, mark both as used, continue.

                                        masterCentsList.Add(findMasterCents);
                                        swarmopsCentsList.Add(
                                            mismatchConstruction.Swarmops[masterKey].Cents[swarmopsIndex]);
                                        // should be equal, we're defensive here
                                        transactionsList.Add(
                                            mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex]);
                                        dependenciesList.Add(
                                            mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex]
                                            .Dependency);
                                        actionsList.Add(ExternalBankMismatchResyncAction.NoAction);

                                        checkMasterIndex[masterIndex]     = true;
                                        checkSwarmopsIndex[swarmopsIndex] = true;

                                        break;
                                    }
                                }
                            }
                        }

                        // STEP 2 - if exactly one record left on both sides, connect and log as mismatching record

                        // TODO: improve logic to handle same number of records left on both sides

                        if (mismatchConstruction.Swarmops.ContainsKey(masterKey) &&
                            mismatchConstruction.Master[masterKey].Cents.Count - checkMasterIndex.Keys.Count == 1 &&
                            mismatchConstruction.Swarmops[masterKey].Cents.Count - checkSwarmopsIndex.Keys.Count == 1)
                        {
                            for (int masterIndex = 0;
                                 masterIndex < mismatchConstruction.Master[masterKey].Cents.Count;
                                 masterIndex++)
                            {
                                if (checkMasterIndex.ContainsKey(masterIndex))
                                {
                                    continue; // This will fire for all but one indexes
                                }

                                long findMasterCents = mismatchConstruction.Master[masterKey].Cents[masterIndex];

                                for (int swarmopsIndex = 0;
                                     swarmopsIndex < mismatchConstruction.Swarmops[masterKey].Cents.Count;
                                     swarmopsIndex++)
                                {
                                    if (checkSwarmopsIndex.ContainsKey(swarmopsIndex))
                                    {
                                        continue;
                                    }

                                    masterCentsList.Add(findMasterCents);
                                    swarmopsCentsList.Add(mismatchConstruction.Swarmops[masterKey].Cents[swarmopsIndex]);
                                    dependenciesList.Add(
                                        mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex].Dependency);
                                    transactionsList.Add(
                                        mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex]);
                                    actionsList.Add(ExternalBankMismatchResyncAction.RewriteSwarmops);

                                    checkMasterIndex[masterIndex]     = true;
                                    checkSwarmopsIndex[swarmopsIndex] = true;
                                }
                            }
                        }

                        // STEP 3 - log remaining records on both sides as missing counterparts. Only one of these should fire.

                        // STEP 3a - log remaining on Master side

                        if (mismatchConstruction.Master[masterKey].Cents.Count > checkMasterIndex.Keys.Count)
                        {
                            for (int masterIndex = 0;
                                 masterIndex < mismatchConstruction.Master[masterKey].Cents.Count;
                                 masterIndex++)
                            {
                                if (checkMasterIndex.ContainsKey(masterIndex))
                                {
                                    continue;
                                }

                                masterCentsList.Add(mismatchConstruction.Master[masterKey].Cents[masterIndex]);
                                swarmopsCentsList.Add(0);  // null equivalent; invalid value
                                dependenciesList.Add(null);
                                transactionsList.Add(null);
                                actionsList.Add(ExternalBankMismatchResyncAction.CreateSwarmops);

                                checkMasterIndex[masterIndex] = true;
                            }
                        }

                        // STEP 3b - log remaining on Swarmops side

                        if (mismatchConstruction.Swarmops.ContainsKey(masterKey) &&
                            mismatchConstruction.Swarmops[masterKey].Cents.Count > checkSwarmopsIndex.Keys.Count)
                        {
                            for (int swarmopsIndex = 0;
                                 swarmopsIndex < mismatchConstruction.Swarmops[masterKey].Cents.Count;
                                 swarmopsIndex++)
                            {
                                if (checkSwarmopsIndex.ContainsKey(swarmopsIndex))
                                {
                                    continue;
                                }

                                masterCentsList.Add(0);  // null equivalent; invalid value
                                swarmopsCentsList.Add(mismatchConstruction.Swarmops[masterKey].Cents[swarmopsIndex]);
                                transactionsList.Add(
                                    mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex]);

                                if (mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex].Dependency !=
                                    null)
                                {
                                    dependenciesList.Add(
                                        mismatchConstruction.Swarmops[masterKey].Transactions[swarmopsIndex].Dependency);
                                    actionsList.Add(ExternalBankMismatchResyncAction.ManualAction);  // can't auto
                                }
                                else
                                {
                                    dependenciesList.Add(null);
                                    actionsList.Add(ExternalBankMismatchResyncAction.DeleteSwarmops);
                                }

                                checkMasterIndex[swarmopsIndex] = true;
                            }
                        }

                        newRecord.MasterCents   = masterCentsList.ToArray();
                        newRecord.SwarmopsCents = swarmopsCentsList.ToArray();
                        newRecord.ResyncActions = actionsList.ToArray();
                        // newRecord.TransactionDependencies = dependenciesList.ToArray();
                        newRecord.Transactions = transactionsList.ToArray();

                        mismatchingRecordList[masterKey] = newRecord;
                    }

                    // Finally, add the transactions that were (described) in Swarmops but not in Master

                    foreach (string swarmopsKey in mismatchConstruction.Swarmops.Keys)
                    {
                        if (!mismatchingRecordList.ContainsKey(swarmopsKey))
                        {
                            mismatchingRecordList[swarmopsKey]             = new ExternalBankMismatchingRecordDescription();
                            mismatchingRecordList[swarmopsKey].Description = swarmopsKey;

                            mismatchingRecordList[swarmopsKey].SwarmopsCents =
                                mismatchConstruction.Swarmops[swarmopsKey].Cents.ToArray();
                            mismatchingRecordList[swarmopsKey].Transactions =
                                mismatchConstruction.Swarmops[swarmopsKey].Transactions.ToArray();
                            mismatchingRecordList[swarmopsKey].MasterCents =
                                new long[mismatchConstruction.Swarmops[swarmopsKey].Cents.Count]; // inits to zero

                            mismatchingRecordList[swarmopsKey].ResyncActions =
                                new ExternalBankMismatchResyncAction[
                                    mismatchConstruction.Swarmops[swarmopsKey].Cents.Count];
                            for (int index = 0;
                                 index < mismatchingRecordList[swarmopsKey].ResyncActions.Length;
                                 index++)
                            {
                                mismatchingRecordList[swarmopsKey].ResyncActions[index] =
                                    ExternalBankMismatchResyncAction.DeleteSwarmops;
                            }
                        }
                    }

                    newMismatch.MismatchingRecords = mismatchingRecordList.Values.ToArray();

                    mismatchList.Add(newMismatch);
                }

                int percentProcessed = (int)(currentRecordIndex * 100L / externalData.Records.Length);

                lock (_staticDataLookup)
                {
                    if (percentProcessed > 1)
                    {
                        _staticDataLookup[guid + "PercentRead"] = percentProcessed;
                        // for the progress bar to update async
                    }

                    if (percentProcessed > 99)
                    {
                        // Placed inside loop to have a contiguous lock block, even though it decreases performance.
                        // Should normally be placed just outside.
                        _staticDataLookup[guid + "MismatchArray"] = mismatchList.ToArray();
                        _staticDataLookup[guid + "Account"]       = account;
                    }
                }
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.ContentType = "application/json";

            string yearString  = Request.QueryString["Year"];
            string monthString = Request.QueryString["Month"];

            int year  = Int32.Parse(yearString);
            int month = Int32.Parse(monthString);

            if (!CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Read)))
            {
                throw new UnauthorizedAccessException("Access denied because security tokens say so");
            }

            DateTime periodStart, periodEnd;
            bool     zeroStart          = false;
            bool     zeroEnd            = false;
            bool     displayDescription = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.BookkeepingDetails, AccessType.Read));

            // HACK HACK HACK
            if (CurrentOrganization.Identity == 8 && CurrentOrganization.Name.StartsWith("Rick Falkvinge"))
            {
                // TODO
                displayDescription = true; // FIX THIS WITH A DAMN SETTING, YOU MORON, DON'T HARDCODE IT
            }

            bool canSeeAudit = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Auditing, AccessType.Read));

            if (month > 0 && month <= 12)
            {
                periodStart = new DateTime(year, month, 1);
                periodEnd   = periodStart.AddMonths(1);
            }
            else if (month > 20 && month < 25) // quarters 1..4 are coded as months 21..24
            {
                periodStart = new DateTime(year, (month - 21) * 3 + 1, 1);
                periodEnd   = periodStart.AddMonths(3);
            }
            else if (month == 31) // "whole year" is coded as month 31
            {
                periodStart = new DateTime(year, 1, 1);
                periodEnd   = new DateTime(year + 1, 1, 1);
            }
            else
            {
                throw new ArgumentException("Invalid month supplied: " + month.ToString(CultureInfo.InvariantCulture));
            }

            DateTime profitLossStart = new DateTime(periodStart.Year, 1, 1);
            DateTime balanceStart    = Constants.DateTimeLow;

            FinancialAccounts    accounts = FinancialAccounts.ForOrganization(CurrentOrganization);
            FinancialAccountRows rows     = accounts.GetRows(periodStart, periodEnd);

            StringBuilder result = new StringBuilder(16384);

            Dictionary <int, Int64>            runningBalanceLookup = new Dictionary <int, long>();
            Dictionary <int, FinancialAccount> accountLookup        = new Dictionary <int, FinancialAccount>();

            foreach (FinancialAccount accountLoop in accounts)
            {
                accountLookup[accountLoop.Identity] = accountLoop;
            }

            int currentTransactionId = 0;
            int rowCount             = 0;

            foreach (FinancialAccountRow row in rows)
            {
                string creditString = string.Empty;
                string debitString  = string.Empty;

                FinancialAccount account = accountLookup[row.FinancialAccountId];
                if (!runningBalanceLookup.ContainsKey(row.FinancialAccountId))
                {
                    if (account.AccountType == FinancialAccountType.Asset || account.AccountType == FinancialAccountType.Debt)
                    {
                        runningBalanceLookup[account.Identity] = account.GetDeltaCents(balanceStart, periodStart);
                    }
                    else
                    {
                        runningBalanceLookup[account.Identity] = account.GetDeltaCents(profitLossStart, periodStart);
                    }
                }

                if (row.FinancialTransactionId != currentTransactionId)
                {
                    // We're starting a new transaction here

                    // If it's not the first one, close the previous one first
                    if (currentTransactionId != 0)
                    {
                        result.Append("]},");
                    }

                    FinancialTransaction transaction = row.Transaction;
                    string description = transaction.Description;

                    string hasDoxString =
                        "<img src='/Images/Icons/iconshock-search-256px.png' onmouseover=\"this.src='/Images/Icons/iconshock-search-hot-256px.png';\" onmouseout=\"this.src='/Images/Icons/iconshock-search-256px.png';\" data-txid='{0}' class='LocalViewDox' style='cursor:pointer' height='20' width='20' />" +
                        "<img src='/Images/Icons/iconshock-download-240px.png' onmouseover=\"this.src='/Images/Icons/iconshock-download-hot-240px.png';\" onmouseout=\"this.src='/Images/Icons/iconshock-download-240px.png';\" data-docid='{1}' data-docname=\"{2}\" class='LocalDownloadDox' style='cursor:pointer' height='18' width='18' />";

                    string actionHtml = string.Empty;

                    Documents documents = Documents.ForObject(transaction.Dependency ?? transaction);

                    if (documents.Count > 0)
                    {
                        foreach (Document doc in documents)
                        {
                            actionHtml += String.Format("<a href='/Pages/v5/Support/StreamUpload.aspx?DocId={0}&hq=1' data-caption=\"{1}\" class='FancyBox_Gallery' data-fancybox='{2}'></a>",
                                                        doc.Identity, doc.ClientFileName.Replace("\"", "'"), transaction.Identity);
                        }

                        actionHtml = String.Format(hasDoxString, row.FinancialTransactionId.ToString(CultureInfo.InvariantCulture), documents[0].Identity, CurrentOrganization.Name + " - " + Resources.Global.Financial_GeneralLedger + " " + transaction.DateTime.ToShortDateString() + " - " + Resources.Global.Financial_TransactionIdShort + transaction.OrganizationSequenceId.ToString("N0")) + "<span class='hiddenDocLinks'>" + actionHtml + "</span>";
                    }

                    result.Append("{" + String.Format(
                                      "\"id\":\"{0:N0}\",\"idDisplay\":\"<span class='weight-more-emphasis'>{0:N0}</span>\",\"datetime\":\"<span class='weight-more-emphasis'>{1:MMM-dd HH:mm}</span>\",\"txDescription\":\"<span class='weight-more-emphasis tx-description'>{2}</span>\",\"action\":\"{3}\"," +
                                      "\"state\":\"open\",\"children\":[",
                                      row.Transaction.OrganizationSequenceId,
                                      row.TransactionDateTime,
                                      JsonSanitize(description),
                                      JsonSanitize(actionHtml)));

                    if (transaction.Dependency != null)
                    {
                        IHasIdentity dependency = transaction.Dependency;
                        string       info       = string.Empty;
                        Documents    docs       = null;

                        if (dependency is VatReport)
                        {
                            VatReport report = (VatReport)dependency;
                            if (report.OpenTransactionId == transaction.Identity)
                            {
                                info = String.Format(Resources.Pages.Ledgers.InspectLedgers_TxInfo_OpenVatReport,
                                                     report.DescriptionShort);
                            }
                            else if (report.CloseTransactionId == transaction.Identity)
                            {
                                info = String.Format(Resources.Pages.Ledgers.InspectLedgers_TxInfo_CloseVatReport,
                                                     report.DescriptionShort);
                            }
                        }

                        // TODO: Continue here with adding Info row under transactions where it's helpful
                        // TODO: Remember that the Info row needs cell merging, colspan=5 or =6
                    }


                    /*
                     * result.Append("{" + String.Format(
                     *  "\"id\":\"{0:N0}\",\"datetime\":\"{1:MMM-dd HH:mm}\",\"description\":\"{2}\"," +
                     *  "\"action\":\"{6}\",\"state\":\"open\",\"children\":[",
                     *  row.Transaction.OrganizationSequenceId,
                     *  row.TransactionDateTime,
                     *  JsonSanitize(description),
                     *  debitString,
                     *  creditString,
                     *  runningBalanceLookup[row.FinancialAccountId]/100.0,
                     *  JsonSanitize(actionHtml)));
                     */

                    currentTransactionId = row.FinancialTransactionId;
                    rowCount             = 1;
                }
                else
                {
                    // still same transaction
                    result.Append(",");
                    rowCount++;
                }

                if (row.AmountCents < 0)
                {
                    creditString = String.Format("{0:N2}", row.AmountCents / 100.0);
                }
                else if (row.AmountCents > 0)
                {
                    debitString = String.Format("{0:N2}", row.AmountCents / 100.0);
                }

                runningBalanceLookup[row.FinancialAccountId] += row.AmountCents;

                /*
                 * if (canSeeAudit)
                 * {
                 *  actionHtml +=
                 *      String.Format (
                 *          "&nbsp;<img src=\"/Images/Icons/iconshock-flag-white-16px.png\" class=\"LocalIconFlag\" txId=\"{0}\" />",
                 *          row.FinancialTransactionId.ToString (CultureInfo.InvariantCulture));
                 * }*/

                //result.Append("{\"description\":\"child\"}");

                string accountClass;
                switch (accountLookup[row.FinancialAccountId].AccountType)
                {
                case FinancialAccountType.Asset:
                    accountClass = Resources.Global.Financial_Asset;
                    break;

                case FinancialAccountType.Debt:
                    accountClass = Resources.Global.Financial_Debt;
                    break;

                case FinancialAccountType.Income:
                    accountClass = Resources.Global.Financial_Income;
                    break;

                case FinancialAccountType.Cost:
                    accountClass = Resources.Global.Financial_Cost;
                    break;

                default:
                    throw new NotImplementedException();
                }

                string accountName = account.Name;

                if (account.ParentIdentity != 0)
                {
                    if (!accountLookup.ContainsKey(account.ParentIdentity))
                    {
                        accountLookup[account.ParentIdentity] = account.Parent;
                    }

                    accountName = accountLookup[account.ParentIdentity].Name + " » " + accountName;
                }

                result.Append("{" + String.Format(
                                  "\"id\":\"{0:N0}-{6:N0}\",\"idDisplay\":\"{0:N0}:{6:N0}\",\"datetime\":\"{1}\",\"txDescription\":\"{2}\"," +
                                  "\"deltaPos\":\"{3}\",\"deltaNeg\":\"{4}\",\"balance\":\"{5:N2}\"",
                                  row.Transaction.OrganizationSequenceId,
                                  JsonSanitize(accountClass),
                                  JsonSanitize(accountName),
                                  debitString,
                                  creditString,
                                  runningBalanceLookup[row.FinancialAccountId] / 100.0,
                                  rowCount) + "}");
            }

            if (rows.Count == 0)
            {
                // If there are no transactions in this time period, say so

                result.Append("{\"description\":\"" +
                              JsonSanitize(Resources.Pages.Ledgers.InspectLedgers_NoTransactions) + "\"},");
            }

            Response.Output.WriteLine("[" + result.ToString().TrimEnd(',') + "]}]");
            Response.End();
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.ContentType = "application/json";

            string accountIdString = Request.QueryString["AccountId"];
            string yearString      = Request.QueryString["Year"];
            string monthString     = Request.QueryString["Month"];

            string emptyResponse = "[{\"id\":\"-\",\"description\":\"" +
                                   JsonSanitize(Resources.Pages.Ledgers.InspectLedgers_PleaseSelectAccount) + "\"}]";

            if (string.IsNullOrEmpty(accountIdString) || string.IsNullOrEmpty(yearString) ||
                string.IsNullOrEmpty(monthString) || accountIdString == "undefined")
            {
                Response.Output.WriteLine(emptyResponse);
                Response.End();
            }

            int accountId = Int32.Parse(accountIdString);
            int year      = Int32.Parse(yearString);
            int month     = Int32.Parse(monthString);

            DateTime dawnOfMankind = new DateTime(1901, 1, 1);

            // no org will ever import bookkeeping from before this date

            if (accountId <= 0)
            {
                Response.Output.WriteLine(emptyResponse);
                Response.End();
            }

            FinancialAccount account = FinancialAccount.FromIdentity(accountId);

            if (account.OrganizationId != CurrentOrganization.Identity)
            {
                throw new UnauthorizedAccessException("All the nopes in the world");
            }

            if (!CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Read)))
            {
                throw new UnauthorizedAccessException("Access denied because security tokens say so");
            }

            DateTime periodStart, periodEnd;
            DateTime balanceStart       = dawnOfMankind;
            bool     zeroStart          = false;
            bool     zeroEnd            = false;
            bool     displayDescription = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.BookkeepingDetails, AccessType.Read));
            bool     canSeeAudit        = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Auditing, AccessType.Read));

            if (month > 0 && month <= 12)
            {
                periodStart = new DateTime(year, month, 1);
                periodEnd   = periodStart.AddMonths(1);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    balanceStart = new DateTime(year, 1, 1);

                    if (month == 1)
                    {
                        zeroStart = true;
                    }
                    if (month == 12)
                    {
                        zeroEnd = true;
                    }
                }
            }
            else if (month > 20 && month < 25) // quarters 1..4 are coded as months 21..24
            {
                periodStart = new DateTime(year, (month - 21) * 3 + 1, 1);
                periodEnd   = periodStart.AddMonths(3);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    balanceStart = new DateTime(year, 1, 1);

                    if (month == 21)
                    {
                        zeroStart = true;
                    }
                    if (month == 24)
                    {
                        zeroEnd = true;
                    }
                }
            }
            else if (month == 31) // "whole year" is coded as month 31
            {
                periodStart = new DateTime(year, 1, 1);
                periodEnd   = new DateTime(year + 1, 1, 1);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    zeroStart = true;
                    zeroEnd   = true;
                }
            }
            else
            {
                throw new ArgumentException("Invalid month supplied: " + month.ToString(CultureInfo.InvariantCulture));
            }


            FinancialAccountRows rows = account.GetRows(periodStart, periodEnd);

            StringBuilder result = new StringBuilder(16384);

            Int64  runningBalance = 0L;
            string startString    = Resources.Pages.Ledgers.InspectLedgers_InboundBalanceZero;
            string endString      = Resources.Pages.Ledgers.InspectLedgers_OutboundBalanceZero;

            if (!zeroStart)
            {
                runningBalance = account.GetDeltaCents(balanceStart, periodStart);
                startString    = Resources.Pages.Ledgers.InspectLedgers_InboundBalance;
            }
            if (!zeroEnd)
            {
                endString = Resources.Pages.Ledgers.InspectLedgers_OutboundBalance;
            }
            else if (periodEnd > DateTime.Now)
            {
                // account is zeroed at end of this period, but we're not yet at end of period, so add a "to date" disclaimer
                endString = Resources.Pages.Ledgers.InspectLedgers_OutboundBalanceZeroToDate;
            }

            result.Append("{" +
                          String.Format("\"description\":\"{0}\",\"balance\":\"{1:N0}\"", JsonSanitize(startString),
                                        runningBalance / 100.0) + "},");

            foreach (FinancialAccountRow row in rows)
            {
                string creditString = string.Empty;
                string debitString  = string.Empty;
                string description  = row.Description;

                if (!displayDescription)
                {
                    description = Resources.Pages.Ledgers.InspectLedgers_TxDetail_DescriptionWithheld;
                }

                if (row.AmountCents < 0)
                {
                    creditString = String.Format("{0:N0}", row.AmountCents / 100.0);
                }
                else if (row.AmountCents > 0)
                {
                    debitString = String.Format("{0:N0}", row.AmountCents / 100.0);
                }

                runningBalance += row.AmountCents;

                string actionHtml = String.Format(
                    "<img src=\"/Images/Icons/iconshock-magnifyingglass-16px.png\" class=\"LocalIconInspect\" txId=\"{0}\" />", row.FinancialTransactionId.ToString(CultureInfo.InvariantCulture));

                if (canSeeAudit)
                {
                    actionHtml +=
                        String.Format(
                            "&nbsp;<img src=\"/Images/Icons/iconshock-flag-white-16px.png\" class=\"LocalIconFlag\" txId=\"{0}\" />",
                            row.FinancialTransactionId.ToString(CultureInfo.InvariantCulture));
                }

                result.Append("{" + String.Format(
                                  "\"id\":\"{0:N0}\",\"datetime\":\"{1:MMM-dd HH:mm}\",\"description\":\"{2}\"," +
                                  "\"deltaPos\":\"{3}\",\"deltaNeg\":\"{4}\",\"balance\":\"{5:N0}\",\"action\":\"{6}\"",
                                  row.FinancialTransactionId,
                                  row.TransactionDateTime,
                                  JsonSanitize(description),
                                  debitString,
                                  creditString,
                                  runningBalance / 100.0,
                                  JsonSanitize(actionHtml)) + "},");
            }

            if (rows.Count == 0)
            {
                // If there are no transactions in this time period, say so

                result.Append("{\"description\":\"" +
                              JsonSanitize(Resources.Pages.Ledgers.InspectLedgers_NoTransactions) + "\"},");
            }

            result.Append("{" +
                          String.Format("\"description\":\"{0}\",\"balance\":\"{1:N0}\"", JsonSanitize(endString),
                                        runningBalance / 100.0) + "},");

            Response.Output.WriteLine("[" + result.ToString().TrimEnd(',') + "]");
            Response.End();
        }
Beispiel #8
0
        public static ChangeAccountDataResult SetAccountInitialBalance(int accountId, string newInitialBalanceString)
        {
            try
            {
                AuthenticationData authData = GetAuthenticationDataAndCulture();
                FinancialAccount   account  = FinancialAccount.FromIdentity(accountId);

                if (!PrepareAccountChange(account, authData, false) || authData.CurrentOrganization.Parameters.FiscalBooksClosedUntilYear >= authData.CurrentOrganization.FirstFiscalYear)
                {
                    return(new ChangeAccountDataResult
                    {
                        Result = ChangeAccountDataOperationsResult.NoPermission
                    });
                }

                Int64 desiredInitialBalanceCents =
                    (Int64)
                    (Double.Parse(newInitialBalanceString,
                                  NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint,
                                  CultureInfo.CurrentCulture) * 100.0);

                Int64 currentInitialBalanceCents = account.GetDeltaCents(new DateTime(1900, 1, 1),
                                                                         new DateTime(authData.CurrentOrganization.FirstFiscalYear, 1, 1));

                Int64 deltaCents = desiredInitialBalanceCents - currentInitialBalanceCents;

                // Find or create "Initial Balances" transaction

                FinancialAccountRows testRows = FinancialAccountRows.ForOrganization(authData.CurrentOrganization,
                                                                                     new DateTime(1900, 1, 1), new DateTime(authData.CurrentOrganization.FirstFiscalYear, 1, 1));

                FinancialTransaction initialBalancesTransaction = null;

                foreach (FinancialAccountRow row in testRows)
                {
                    if (row.Transaction.Description == "Initial Balances")
                    {
                        initialBalancesTransaction = row.Transaction;
                        break;
                    }
                }

                if (initialBalancesTransaction == null)
                {
                    // create transaction

                    initialBalancesTransaction = FinancialTransaction.Create(authData.CurrentOrganization.Identity,
                                                                             new DateTime(authData.CurrentOrganization.FirstFiscalYear - 1, 12, 31), "Initial Balances");
                }

                Dictionary <int, Int64> recalcBase = initialBalancesTransaction.GetRecalculationBase();
                int equityAccountId = authData.CurrentOrganization.FinancialAccounts.DebtsEquity.Identity;

                if (!recalcBase.ContainsKey(accountId))
                {
                    recalcBase[accountId] = 0;
                }
                if (!recalcBase.ContainsKey(equityAccountId))
                {
                    recalcBase[equityAccountId] = 0;
                }

                recalcBase[accountId]       += deltaCents;
                recalcBase[equityAccountId] -= deltaCents;
                initialBalancesTransaction.RecalculateTransaction(recalcBase, authData.CurrentUser);
                return(new ChangeAccountDataResult
                {
                    Result = ChangeAccountDataOperationsResult.Changed,
                    NewData = (desiredInitialBalanceCents / 100.0).ToString("N2", CultureInfo.CurrentCulture)
                });
            }
            catch (Exception weirdException)
            {
                SwarmDb.GetDatabaseForWriting()
                .CreateExceptionLogEntry(DateTime.UtcNow, "AccountPlan-SetInitBalance", weirdException);

                throw;
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.ContentType = "application/json";

            string yearString  = Request.QueryString["Year"];
            string monthString = Request.QueryString["Month"];

            string emptyResponse = "[{\"id\":\"-\",\"description\":\"" +
                                   JsonSanitize(Resources.Pages.Ledgers.InspectLedgers_PleaseSelectAccount) + "\"}]";

            if (string.IsNullOrEmpty(yearString) || string.IsNullOrEmpty(monthString))
            {
                Response.Output.WriteLine(emptyResponse);
                Response.End();
            }

            int year  = Int32.Parse(yearString);
            int month = Int32.Parse(monthString);

            FinancialAccount account = CurrentOrganization.FinancialAccounts.AssetsBitcoinHot;

            if (account == null || account.OrganizationId != CurrentOrganization.Identity)
            {
                throw new UnauthorizedAccessException("All the nopes in the world");
            }

            if (!CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Read)) && CurrentOrganization.HasOpenLedgers)
            {
                throw new UnauthorizedAccessException("Access denied because security tokens say so");
            }

            DateTime periodStart, periodEnd;
            DateTime balanceStart       = Constants.DateTimeLow;
            bool     zeroStart          = false;
            bool     zeroEnd            = false;
            bool     displayDescription = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.BookkeepingDetails, AccessType.Read));

            // HACK HACK HACK
            if (CurrentOrganization.Identity == 8 && CurrentOrganization.Name.StartsWith("Rick Falkvinge"))
            {
                // TODO
                displayDescription = true; // FIX THIS WITH A DAMN SETTING, YOU MORON, DON'T HARDCODE IT
            }

            bool canSeeAudit = CurrentAuthority.HasAccess(new Access(CurrentOrganization, AccessAspect.Auditing, AccessType.Read));

            if (month > 0 && month <= 12)
            {
                periodStart = new DateTime(year, month, 1);
                periodEnd   = periodStart.AddMonths(1);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    balanceStart = new DateTime(year, 1, 1);

                    if (month == 1)
                    {
                        zeroStart = true;
                    }
                    if (month == 12)
                    {
                        zeroEnd = true;
                    }
                }
            }
            else if (month > 20 && month < 25) // quarters 1..4 are coded as months 21..24
            {
                periodStart = new DateTime(year, (month - 21) * 3 + 1, 1);
                periodEnd   = periodStart.AddMonths(3);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    balanceStart = new DateTime(year, 1, 1);

                    if (month == 21)
                    {
                        zeroStart = true;
                    }
                    if (month == 24)
                    {
                        zeroEnd = true;
                    }
                }
            }
            else if (month == 31) // "whole year" is coded as month 31
            {
                periodStart = new DateTime(year, 1, 1);
                periodEnd   = new DateTime(year + 1, 1, 1);
                if (account.AccountType == FinancialAccountType.Income ||
                    account.AccountType == FinancialAccountType.Cost)
                {
                    zeroStart = true;
                    zeroEnd   = true;
                }
            }
            else
            {
                throw new ArgumentException("Invalid month supplied: " + month.ToString(CultureInfo.InvariantCulture));
            }

            FinancialAccountRows rows = account.GetRows(periodStart, periodEnd);

            StringBuilder result = new StringBuilder(16384);

            Int64  runningBalance        = 0L;
            Int64  runningBitcoinBalance = 0;
            string startString           = Resources.Pages.Ledgers.InspectLedgers_InboundBalanceZero;
            string endString             = Resources.Pages.Ledgers.InspectLedgers_OutboundBalanceZero;

            if (!zeroStart)
            {
                runningBitcoinBalance = account.GetForeignCurrencyBalanceDeltaCents(balanceStart, periodStart).Cents;
                runningBalance        = account.GetDeltaCents(balanceStart, periodStart);
                startString           = Resources.Pages.Ledgers.InspectLedgers_InboundBalance;
            }
            if (!zeroEnd)
            {
                endString = Resources.Pages.Ledgers.InspectLedgers_OutboundBalance;
            }
            else if (periodEnd > DateTime.Now)
            {
                // account is zeroed at end of this period, but we're not yet at end of period, so add a "to date" disclaimer
                endString = Resources.Pages.Ledgers.InspectLedgers_OutboundBalanceZeroToDate;
            }

            result.Append("{" +
                          String.Format("\"description\":\"{0}\",\"balanceBitcoin\":\"{1:N2}\"", JsonSanitize(startString),
                                        runningBitcoinBalance / 100.0) + "},");

            foreach (FinancialAccountRow row in rows)
            {
                string description = row.Description;

                if (!displayDescription)
                {
                    description = Resources.Pages.Ledgers.InspectLedgers_TxDetail_DescriptionWithheld;
                }

                Int64 deltaSatoshis = row.TransactionRow.AmountForeignCents.Cents;
                runningBitcoinBalance += deltaSatoshis;

                string hasDoxString =
                    "<img src='/Images/Icons/iconshock-search-256px.png' onmouseover=\"this.src='/Images/Icons/iconshock-search-hot-256px.png';\" onmouseout=\"this.src='/Images/Icons/iconshock-search-256px.png';\" data-txid='{0}' class='LocalIconInspect' style='cursor:pointer' height='20' width='20' />";

                string actionHtml = String.Format(hasDoxString, row.FinancialTransactionId.ToString(CultureInfo.InvariantCulture));

                /*
                 * if (canSeeAudit)
                 * {
                 *  actionHtml +=
                 *      String.Format(
                 *          "&nbsp;<img src=\"/Images/Icons/iconshock-flag-white-16px.png\" class=\"LocalIconFlag\" txId=\"{0}\" />",
                 *          row.FinancialTransactionId.ToString(CultureInfo.InvariantCulture));
                 * }*/

                result.Append("{" + String.Format(
                                  "\"id\":\"{0:N0}\",\"datetime\":\"{1:MMM-dd HH:mm}\",\"description\":\"{2}\"," +
                                  "\"deltaPresentation\":\"{3}\",\"deltaBitcoin\":\"{4}\",\"balanceBitcoin\":\"{5:N2}\",\"action\":\"{6}\"",
                                  row.Transaction.OrganizationSequenceId,
                                  row.TransactionDateTime,
                                  JsonSanitize(description),
                                  row.AmountCents != 0? (row.AmountCents / 100.0).ToString("N2") : string.Empty,
                                  deltaSatoshis != 0? (deltaSatoshis / 100.0).ToString("N2"): string.Empty,
                                  runningBitcoinBalance / 100.0,
                                  JsonSanitize(actionHtml)) + "},");
            }

            if (rows.Count == 0)
            {
                // If there are no transactions in this time period, say so

                result.Append("{\"description\":\"" +
                              JsonSanitize(Resources.Pages.Ledgers.InspectLedgers_NoTransactions) + "\"},");
            }

            result.Append("{" +
                          String.Format("\"description\":\"{0}\",\"balanceBitcoin\":\"{1:N2}\"", JsonSanitize(endString),
                                        runningBitcoinBalance / 100.0) + "},");

            Response.Output.WriteLine("[" + result.ToString().TrimEnd(',') + "]");
            Response.End();
        }
Beispiel #10
0
        public static ImportExternalTransactionDataResults ImportExternalTransactionData(ExternalBankData import, ImportExternalTransactionDataArgs args)
        {
            FinancialAccount assetAccount       = args.Account;
            FinancialAccount autoDepositAccount = args.Organization.FinancialAccounts.IncomeDonations;
            int autoDepositLimit = 0; // Disabled; TODO: this.CurrentOrganization.Parameters.AutoDonationLimit;

            bool autosetInitialBalance = false;
            ImportExternalTransactionDataResults result = new ImportExternalTransactionDataResults();
            int   count = 0;
            int   progressUpdateInterval = import.Records.Length / 40;
            Int64 importedCentsTotal     = 0;

            if (progressUpdateInterval > 100)
            {
                progressUpdateInterval = 100;
            }

            ProgressBarBackend progressDisplay = new ProgressBarBackend(args.Guid);

            Currency organizationCurrency = assetAccount.Organization.Currency;
            Currency accountCurrency      = assetAccount.ForeignCurrency;

            if (accountCurrency == null)
            {
                accountCurrency = organizationCurrency;
            }

            FinancialAccountRows existingRows = assetAccount.GetRows(Constants.DateTimeLow, Constants.DateTimeHigh);

            // gets all
            if (existingRows.Count == 0)
            {
                autosetInitialBalance = true;
            }


            foreach (ExternalBankDataRecord row in import.Records)
            {
                // Update progress.

                count++;
                if (progressUpdateInterval < 2 || count % progressUpdateInterval == 0)
                {
                    int percent = (count * 99) / import.Records.Length;

                    progressDisplay.Set(percent);
                }

                // Update high- and low-water marks.

                if (row.DateTime < result.EarliestTransaction)
                {
                    result.EarliestTransaction = row.DateTime;
                }

                if (row.DateTime > result.LatestTransaction)
                {
                    result.LatestTransaction = row.DateTime;
                }


                string importKey = row.ImportHash;

                Int64 amountCents = row.TransactionNetCents;

                if (amountCents == 0)
                // defensive programming - these _should_ be duplicated in the interpreter if no "fee" field
                {
                    amountCents = row.TransactionGrossCents;
                }

                Int64 foreignCents = amountCents;
                importedCentsTotal += amountCents;

                if (accountCurrency.Identity != organizationCurrency.Identity)
                {
                    amountCents =
                        new Money(amountCents, accountCurrency, row.DateTime).ToCurrency(organizationCurrency).Cents;
                }

                FinancialTransaction transaction = FinancialTransaction.ImportWithStub(args.Organization.Identity,
                                                                                       row.DateTime,
                                                                                       assetAccount.Identity, amountCents,
                                                                                       row.Description, importKey, Sha256.Compute(row.RawData),
                                                                                       args.CurrentUser.Identity);

                if (transaction != null)
                {
                    // The transaction was created.

                    result.TransactionsImported++;

                    // If non-presentation currency, log the account currency amount as well.

                    if (accountCurrency.Identity != organizationCurrency.Identity)
                    {
                        transaction.Rows[0].AmountForeignCents = new Money(foreignCents, accountCurrency);
                    }

                    if (row.Description.ToLowerInvariant().StartsWith(args.Organization.IncomingPaymentTag))
                    {
                        // Check for previously imported payment group

                        // TODO: MAKE FLEXIBLE - CALL PAYMENTREADERINTERFACE!
                        // HACK HACK HACK HACK

                        PaymentGroup group = PaymentGroup.FromTag(args.Organization,
                                                                  "SEBGM" + DateTime.Today.Year + // TODO: Get tags from org
                                                                  row.Description.Substring(args.Organization.IncomingPaymentTag.Length).Trim());

                        if (group != null && group.Open)
                        {
                            // There was a previously imported and not yet closed payment group matching this transaction
                            // Close the payment group and match the transaction against accounts receivable

                            transaction.Dependency = group;
                            group.Open             = false;
                            transaction.AddRow(args.Organization.FinancialAccounts.AssetsOutboundInvoices, -amountCents,
                                               args.CurrentUser);
                        }
                    }
                    else if (amountCents < 0)
                    {
                        // Autowithdrawal mechanisms removed, condition kept because of downstream else-if conditions
                    }
                    else if (amountCents > 0)
                    {
                        if (row.FeeCents < 0)
                        {
                            // This is always an autodeposit, if there is a fee (which is never > 0.0)

                            transaction.AddRow(args.Organization.FinancialAccounts.CostsBankFees, -row.FeeCents,
                                               args.CurrentUser);
                            transaction.AddRow(autoDepositAccount, -row.TransactionGrossCents, args.CurrentUser);
                        }
                        else if (amountCents < autoDepositLimit * 100)
                        {
                            // Book against autoDeposit account.

                            transaction.AddRow(autoDepositAccount, -amountCents, args.CurrentUser);
                        }
                    }
                }
                else
                {
                    // Transaction was not imported; assume duplicate

                    result.DuplicateTransactions++;
                }
            }

            // Import complete. Return true if the bookkeeping account matches the bank data.

            Int64 databaseAccountBalanceCents;

            if (accountCurrency.Identity == organizationCurrency.Identity)
            {
                databaseAccountBalanceCents = assetAccount.BalanceTotalCents;
            }
            else
            {
                // foreign-currency account
                databaseAccountBalanceCents = assetAccount.ForeignCurrencyBalance.Cents;
            }


            // Subtract any transactions made after the most recent imported transaction.
            // This is necessary in case of Paypal and others which continuously feed the
            // bookkeeping account with new transactions; it will already have fed transactions
            // beyond the end-of-file.

            Int64 beyondEofCents = assetAccount.GetDeltaCents(result.LatestTransaction.AddSeconds(1),
                                                              DateTime.Now.AddDays(2));

            // Caution: the "AddSeconds(1)" is not foolproof, there may be other new txs on the same second.

            if (databaseAccountBalanceCents - beyondEofCents == import.LatestAccountBalanceCents)
            {
                Payouts.AutomatchAgainstUnbalancedTransactions(args.Organization);
                OutboundInvoices.AutomatchAgainstUnbalancedTransactions(args.Organization, args.CurrentUser);
                result.AccountBalanceMatchesBank = true;
                result.BalanceMismatchCents      = 0;
            }
            else
            {
                result.AccountBalanceMatchesBank = false;
                result.BalanceMismatchCents      = (databaseAccountBalanceCents - beyondEofCents) -
                                                   import.LatestAccountBalanceCents;

                if (autosetInitialBalance)
                {
                    Int64 newInitialBalanceCents = -result.BalanceMismatchCents;
                    Money initialBalance         = new Money(newInitialBalanceCents, accountCurrency);

                    assetAccount.InitialBalance       = initialBalance;
                    result.InitialBalanceCents        = newInitialBalanceCents;
                    result.InitialBalanceCurrencyCode = accountCurrency.Code;

                    // make an approximation of conversion rate set for initial balance in presentation to tell user
                    initialBalance.ValuationDateTime = new DateTime(assetAccount.Organization.FirstFiscalYear, 1, 1);
                    result.BalanceMismatchCents      = initialBalance.ToCurrency(assetAccount.Organization.Currency).Cents;
                }
            }

            result.CurrencyCode = args.Organization.Currency.Code;
            GuidCache.Set(args.Guid + "-Results", result);
            return(result);
        }