Exemplo n.º 1
0
        private void AddDocuments(VatReport report)
        {
            VatReportItems items = report.Items;

            foreach (VatReportItem item in items)
            {
                FinancialTransaction tx = item.Transaction;

                Documents documents = Documents.ForObject(tx.Dependency ?? tx); // tx.Dependency if not null, else tx

                int pageCounter = 0;
                int pagesTotal  = documents.Count;

                foreach (Document doc in documents)
                {
                    pageCounter++;

                    VatReportDocuments.Add(new RepeatedDocument
                    {
                        BaseId = item.FinancialTransactionId.ToString(CultureInfo.InvariantCulture),
                        DocId  = doc.Identity,
                        Title  = tx.Description + " " + Document.GetLocalizedPageCounter(pageCounter, pagesTotal)
                    });
                }
            }
        }
Exemplo n.º 2
0
        public static void MatchTransactionOpenVatReport(int transactionId, int vatReportId)
        {
            if (transactionId == 0 || vatReportId == 0)
            {
                return;
            }

            AuthenticationData authData = GetAuthenticationDataAndCulture();

            if (
                !authData.Authority.HasAccess(new Access(authData.CurrentOrganization,
                                                         AccessAspect.BookkeepingDetails)))
            {
                throw new UnauthorizedAccessException();
            }

            FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId);
            VatReport            vatReport   = VatReport.FromIdentity(vatReportId);

            if (authData.CurrentOrganization.Identity != transaction.OrganizationId ||
                authData.CurrentOrganization.Identity != vatReport.OrganizationId)
            {
                throw new InvalidOperationException("Organization mismatch");
            }

            Int64 diffCents = vatReport.VatInboundCents - vatReport.VatOutboundCents;

            if (transaction.Rows.AmountCentsTotal != diffCents)
            {
                throw new InvalidOperationException("Amount mismatch");
            }

            // Go ahead and close

            vatReport.CloseTransaction = transaction; // throws if already closed
            vatReport.Open             = false;

            if (diffCents > 0)
            {
                // Positive amount - asset account
                transaction.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsVatInboundReported, -diffCents,
                                   null);
            }
            else
            {
                // Negative amount - liability account
                transaction.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsVatOutboundReported, -diffCents,
                                   null);
            }
        }
Exemplo n.º 3
0
        public static AjaxCallResult CreateVatReport(string itemId)
        {
            AuthenticationData authData = GetAuthenticationDataAndCulture();

            if (!authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails)))
            {
                throw new UnauthorizedAccessException();
            }

            VatReport.CreateNext(authData.CurrentOrganization);

            return(new AjaxCallResult {
                Success = true
            });
        }
Exemplo n.º 4
0
        protected void Page_Load(object sender, EventArgs e)
        {
            // Get auth data

            this._authenticationData = GetAuthenticationDataAndCulture();

            string reportIdParameter  = Request.QueryString["ReportId"];
            string reportKeyParameter = Request.QueryString["ReportKey"];

            if (!string.IsNullOrEmpty(reportIdParameter))
            {
                this._reportId = Int32.Parse(reportIdParameter);  // will throw if non-numeric - don't matter for app
            }
            else if (!string.IsNullOrEmpty(reportKeyParameter))
            {
                this._reportId =
                    SwarmDb.GetDatabaseForReading().GetVatReportIdFromGuid(reportKeyParameter.ToLowerInvariant().Trim());
            }
            else
            {
                throw new ArgumentException("No Report Requested");
            }

            VatReport report = VatReport.FromIdentity(_reportId);

            // TODO: AUTH CHECK

            Response.ContentType = "application/json";

            Int64 turnoverCentsTotal = 0;
            Int64 inboundCentsTotal  = 0;
            Int64 outboundCentsTotal = 0;

            StringBuilder response = new StringBuilder(16384);

            VatReportItems items = report.Items;

            items.Sort(VatItemSorterByDate);
            List <string> lines = new List <string>();

            string hasDoxString =
                "<img data-txid='{0}' class='LocalViewDox action-icon' />" +
                "<img data-docid='{1}' data-docname=\\\"{2}\\\" class='LocalDownloadDox action-icon' />";

            foreach (VatReportItem item in items)
            {
                FinancialTransaction transaction = item.Transaction;
                bool include = false;

                string element = string.Format("\"id\":\"{0}\",\"txid\":\"{1}\",\"datetime\":\"{2:MMM dd}\",\"description\":\"{3}\"",
                                               transaction.Identity,
                                               transaction.OrganizationSequenceId,
                                               transaction.DateTime,
                                               JsonSanitize(transaction.Description));

                if (item.TurnoverCents != 0)
                {
                    element            += String.Format(",\"turnover\":\"{0:N2}\"", item.TurnoverCents / 100.0);
                    turnoverCentsTotal += item.TurnoverCents;
                    include             = true;
                }

                if (item.VatInboundCents != 0)
                {
                    element           += String.Format(",\"inbound\":\"{0:N2}\"", item.VatInboundCents / 100.0);
                    inboundCentsTotal += item.VatInboundCents;
                    include            = true;
                }

                if (item.VatOutboundCents != 0)
                {
                    element            += String.Format(",\"outbound\":\"{0:N2}\"", item.VatOutboundCents / 100.0);
                    outboundCentsTotal += item.VatOutboundCents;
                    include             = true;
                }

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

                if (documents.Count > 0)
                {
                    element += String.Format(",\"dox\":\"" + hasDoxString + "\"", transaction.Identity, documents[0].Identity, CurrentOrganization.Name + " - " + report.Description + " - " + Resources.Global.Financial_TransactionIdShort + transaction.OrganizationSequenceId.ToString("N0"));
                }

                if (include)
                {
                    lines.Add("{" + element + "}");
                }
            }

            if (lines.Count == 0) // add empty report line
            {
                lines.Add("{" + String.Format("\"id\":\"0\",\"description\":\"{0}\"",
                                              JsonSanitize(Resources.Pages.Ledgers.ViewVatReports_EmptyReport)) + "}");
            }

            Response.Write("{\"rows\":[" + String.Join(",", lines.ToArray()) + "]");

            Response.Write(",\"footer\":[{"); // needs to be on separate line to not trigger a String.Format warning

            Response.Write(String.Format("\"id\":\"0\",\"description\":\"{0}\",\"turnover\":\"{1:N2}\",\"inbound\":\"{2:N2}\",\"outbound\":\"{3:N2}\"",
                                         JsonSanitize(Resources.Pages.Ledgers.ViewVatReports_Footer_Total.ToUpperInvariant()), turnoverCentsTotal / 100.0, inboundCentsTotal / 100.0, outboundCentsTotal / 100.0));

            Response.Write("}]}"); // needs to be separate to not trigger String.Format warning

            Response.End();
        }
        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();
        }
Exemplo n.º 6
0
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!CurrentOrganization.IsEconomyEnabled)
            {
                Response.Redirect("/Pages/v5/Financial/EconomyNotEnabled.aspx", true);
                return;
            }

            PageTitle      = Resources.Pages.Ledgers.ViewVatReports_PageTitle;
            InfoBoxLiteral = Resources.Pages.Ledgers.ViewVatReports_Info;

            VatReportDocuments = new List <RepeatedDocument>();

            // Security: If the org has open ledgers, or key was given, then anyone may read. Otherwise, Financials.Read.

            int       specificReportId = 0;
            VatReport initialReport    = null;
            VatReport specificReport   = null;
            string    reportKey        = Request.QueryString["ReportKey"];

            if (!string.IsNullOrEmpty(reportKey))
            {
                specificReport    = VatReport.FromGuid(reportKey);
                specificReportId  = specificReport.Identity;
                this.VatReportKey = reportKey;
            }

            if (CurrentOrganization.HasOpenLedgers || specificReportId > 0)
            {
                PageAccessRequired = new Access(AccessAspect.Null, AccessType.Read);
            }
            else
            {
                PageAccessRequired = new Access(CurrentOrganization, AccessAspect.Financials, AccessType.Read);
            }


            if (!Page.IsPostBack)
            {
                Localize();

                // Populate VAT report dropdown

                if (specificReportId > 0 && !CurrentOrganization.HasOpenLedgers)
                {
                    // Show one single report

                    this.LabelContentHeader.Text = specificReport.Description;
                    this.DropReports.Visible     = false;
                    this.InitialReportId         = specificReport.Identity;

                    AddDocuments(specificReport);
                }
                else
                {
                    // Populate dropdown and documents list

                    VatReports reports = VatReports.ForOrganization(CurrentOrganization, true);

                    if (reports.Count > 0)
                    {
                        reports.Sort(VatReports.VatReportSorterByDate);

                        foreach (VatReport report in reports)
                        {
                            this.DropReports.Items.Add(new ListItem(report.Description,
                                                                    report.Identity.ToString(CultureInfo.InvariantCulture)));

                            AddDocuments(report);
                        }

                        initialReport                  = reports.Last();
                        this.InitialReportId           = initialReport.Identity;
                        this.DropReports.SelectedValue = this.InitialReportId.ToString(CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        // There are no VAT reports for this organization (yet?) so display an error instead

                        this.PanelShowVatReports.Visible   = false;
                        this.PanelShowNoVatReports.Visible = true;
                    }
                }
            }

            RegisterControl(EasyUIControl.DataGrid | EasyUIControl.Tree);

            this.RepeaterLightboxItems.DataSource = this.VatReportDocuments;
            this.RepeaterLightboxItems.DataBind();
        }
Exemplo n.º 7
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);
            }
        }