private void AddExpenseClaims(Person person, Organization organization) { TaskGroup group = new TaskGroup(TaskGroupType.AttestExpenseClaims); // TODO: Loop over roles, get all open claims for roles where person can attest ExpenseClaims claims = ExpenseClaims.ForOrganization(organization); foreach (ExpenseClaim claim in claims) { try { if (claim.Budget.OwnerPersonId == person.Identity && !claim.Attested) { group.Tasks.Add(new TaskExpenseClaim(claim)); } } catch (Exception) { // ignore fn } } if (group.Tasks.Count > 0) { Add(group); } }
private void PopulateExpenses() { ExpenseClaims expenses = ExpenseClaims.ForOrganization(CurrentOrganization); foreach (ExpenseClaim expenseClaim in expenses) { if (this._attestationRights.ContainsKey(expenseClaim.BudgetId) || expenseClaim.Budget.OwnerPersonId == Person.NobodyId) { Documents dox = expenseClaim.Documents; bool hasDox = (dox.Count > 0 ? true : false); AttestableItem item = new AttestableItem( "E" + expenseClaim.Identity.ToString(CultureInfo.InvariantCulture), expenseClaim.ClaimerCanonical, expenseClaim.AmountCents, expenseClaim.Budget, expenseClaim.Description, "Financial_ExpenseClaim", hasDox, expenseClaim); if (expenseClaim.Attested) { this._attestedItems.Add(item); } else { this._items.Add(item); } } } }
private void AddReceiptValidations(Person person, Organization organization) { if (!person.HasAccess(new Access(organization, AccessAspect.Financials, AccessType.Write))) { return; } ExpenseClaims claims = ExpenseClaims.ForOrganization(organization); claims = claims.WhereUnvalidated; if (claims.Count == 0) { // nothing to add return; } DashboardTodo todo = new DashboardTodo(); if (claims.Count > 1) { todo.Description = String.Format(App_GlobalResources.Logic_Swarm_DashboardTodos.Validate_Receipts_Many, Formatting.GenerateRangeString(claims.Identities)); } else { todo.Description = String.Format(App_GlobalResources.Logic_Swarm_DashboardTodos.Validate_Receipts_One, claims[0].Identity); } todo.Icon = "/Images/PageIcons/iconshock-invoice-greentick-16px.png"; todo.Url = "/Pages/v5/Financial/ValidateReceipts.aspx"; this.Add(todo); }
private void AddReceiptValidation(Person person) { TaskGroup group = new TaskGroup(TaskGroupType.ValidateExpenseClaims); // TODO: Loop over roles, get all open claims for roles where person can attest if (!person.GetAuthority().HasPermission(Permission.CanDoEconomyTransactions, Organization.PPSEid, Geography.RootIdentity, Authorization.Flag.AnyGeographyExactOrganization)) { // no permission, no tasks return; } ExpenseClaims claims = ExpenseClaims.ForOrganization(Organization.PPSE); foreach (ExpenseClaim claim in claims) { if (!claim.Validated) { group.Tasks.Add(new TaskReceiptValidation(claim)); } } if (group.Tasks.Count > 0) { this.Add(group); } }
private void PopulateGrid() { Dictionary <int, AdvanceDebt> debtLookup = new Dictionary <int, AdvanceDebt>(); ExpenseClaims claims = ExpenseClaims.ForOrganization(Organization.PPSE); foreach (ExpenseClaim claim in claims) { // If ready for payout, add to list. if (claim.Open) { if (claim.Attested && claim.Validated && !claim.Repaid) { // this should be added to the list. Check if we already have some open claims // for this person: if (debtLookup.ContainsKey(claim.ClaimingPersonId)) { // Yes. Add claim to list. debtLookup[claim.ClaimingPersonId].DebtCents -= claim.AmountCents; debtLookup[claim.ClaimingPersonId].ClaimIds.Add(claim.Identity); } else { // No. Create a new debt for this person. AdvanceDebt debt = new AdvanceDebt(claim.Claimer, -claim.AmountCents); debt.ClaimIds.Add(claim.Identity); debtLookup[claim.ClaimingPersonId] = debt; } if (debtLookup[claim.ClaimingPersonId].EarliestDate > claim.CreatedDateTime) { debtLookup[claim.ClaimingPersonId].EarliestDate = claim.CreatedDateTime; } } } } // Now, we have grouped all ready but unsettled expenses per person. Let's add only those with a positive debt to the final list. List <AdvanceDebt> debtList = new List <AdvanceDebt>(); foreach (int personId in debtLookup.Keys) { if (debtLookup[personId].DebtCents > 0) { debtList.Add(debtLookup[personId]); } } debtList.Sort(SortGridItems); this.GridDebts.DataSource = debtList; }
private void PopulateExpenses() { ExpenseClaims expenses = ExpenseClaims.ForOrganization(this.CurrentOrganization).WhereUnvalidated; foreach (var expenseClaim in expenses) { AddDocuments(expenseClaim.Documents, "E" + expenseClaim.Identity.ToString(CultureInfo.InvariantCulture), String.Format(Resources.Global.Financial_ExpenseClaimSpecification + " - ", expenseClaim.Identity) + Resources.Global.Financial_ReceiptSpecification); } }
// TODO: Refactor the attest-X functions into one function with minimal differences private void AddExpenseClaimAttestations(Person person, Organization organization) { ExpenseClaims claims = ExpenseClaims.ForOrganization(organization); List <int> expenseClaimIds = new List <int>(); bool isPersonOrgAdmin = false; if (person.Identity == 1) { isPersonOrgAdmin = true; // TODO: Make more advanced, obviously } foreach (ExpenseClaim claim in claims) { if (claim.Attested) { continue; } bool attestable = false; if (claim.Budget.OwnerPersonId == 0 && isPersonOrgAdmin) { attestable = true; } else if (claim.Budget.OwnerPersonId == person.Identity) { attestable = true; } if (attestable) { expenseClaimIds.Add(claim.Identity); } } if (expenseClaimIds.Count > 0) { DashboardTodo todo = new DashboardTodo(); if (expenseClaimIds.Count > 1) { todo.Description = String.Format(Logic_Swarm_DashboardTodos.Attest_ExpenseClaim_Many, Formatting.GenerateRangeString(expenseClaimIds)); } else { todo.Description = String.Format(Logic_Swarm_DashboardTodos.Attest_ExpenseClaim_One, expenseClaimIds[0]); } todo.Icon = "/Images/PageIcons/iconshock-stamped-paper-16px.png"; todo.Url = "/Pages/v5/Financial/AttestCosts.aspx"; Add(todo); } }
private void PopulateExpenses() { ExpenseClaims expenses = ExpenseClaims.ForOrganization(this.CurrentOrganization).WhereUnattested; foreach (var expenseClaim in expenses) { if (_attestationRights.ContainsKey(expenseClaim.BudgetId) || expenseClaim.Budget.OwnerPersonId == Person.NobodyId) { AddDocuments(expenseClaim.Documents, "E" + expenseClaim.Identity.ToString(CultureInfo.InvariantCulture), String.Format(Resources.Global.Financial_ExpenseClaimSpecificationWithClaimer + " - ", expenseClaim.Identity, expenseClaim.ClaimerCanonical) + Resources.Global.Financial_ReceiptSpecification); } } }
protected void Page_Load(object sender, EventArgs e) { if (!CurrentUser.HasAccess(new Access(CurrentOrganization, AccessAspect.Financials, AccessType.Read))) { throw new UnauthorizedAccessException(); } ExpenseClaims claims = ExpenseClaims.ForOrganization(CurrentOrganization); claims = claims.WhereUnvalidated; // Format as JSON and return Response.ContentType = "application/json"; string json = FormatAsJson(claims); Response.Output.WriteLine(json); Response.End(); }
private void PopulateGrid() { ExpenseClaims allClaims = ExpenseClaims.ForOrganization(Organization.PPSE); ExpenseClaims unvalidatedClaims = new ExpenseClaims(); // LINQ would be nice here. "Where Validated=0". foreach (ExpenseClaim claim in allClaims) { if (!claim.Validated) { unvalidatedClaims.Add(claim); } } unvalidatedClaims.Sort(SortGridItems); this.GridExpenseClaims.DataSource = unvalidatedClaims; }
private void PopulateExpenses() { ExpenseClaims expenses = ExpenseClaims.ForOrganization(CurrentOrganization); bool vatEnabled = CurrentOrganization.VatEnabled; foreach (ExpenseClaim expenseClaim in expenses) { if (this._approvalRights.ContainsKey(expenseClaim.BudgetId) || expenseClaim.Budget.OwnerPersonId == Person.NobodyId) { Documents dox = expenseClaim.Documents; bool hasDox = (dox.Count > 0 ? true : false); ApprovableCost cost = null; if (vatEnabled) { cost = new ApprovableCost( "E" + expenseClaim.Identity.ToString(CultureInfo.InvariantCulture), expenseClaim.ClaimerCanonical, expenseClaim.AmountCents - expenseClaim.VatCents, expenseClaim.Budget, expenseClaim.Description, "Financial_ExpenseClaim", hasDox, expenseClaim); } else { cost = new ApprovableCost( "E" + expenseClaim.Identity.ToString(CultureInfo.InvariantCulture), expenseClaim.ClaimerCanonical, expenseClaim.AmountCents, expenseClaim.Budget, expenseClaim.Description, "Financial_ExpenseClaim", hasDox, expenseClaim); } if (expenseClaim.Attested) { this._approvedCosts.Add(cost); } else { this._approvableCosts.Add(cost); } } } }
private OutstandingAccounts GetOutstandingExpenseClaims(bool renderPresentTime, DateTime targetDateTime) { OutstandingAccounts outstandingAccounts = new OutstandingAccounts(); if (renderPresentTime) { ExpenseClaims claims = ExpenseClaims.ForOrganization(_authenticationData.CurrentOrganization); Payouts payouts = Payouts.ForOrganization(_authenticationData.CurrentOrganization); foreach (ExpenseClaim claim in claims) { outstandingAccounts.Add(OutstandingAccount.FromExpenseClaim(claim, DateTime.MinValue)); } foreach (Payout payout in payouts) { foreach (ExpenseClaim claim in payout.DependentExpenseClaims) { outstandingAccounts.Add(OutstandingAccount.FromExpenseClaim(claim, payout.ExpectedTransactionDate)); } } } else { // This is a very expensive op. We need to load all expense claims and process them in logic, looking at their // payouts and end financial transactions, as the traceability was built to be efficient backwards (tracing money // paid out to its documentation), rather than forwards. // A possible optimization could be to load the payouts into a hash table initially instead of looking them up // with two dbroundtrips per expense claim. It should be more efficient to have three dbroundtrips to load expenses, // payouts, and the relevant transactions, then stuff it all into hash tables keyed by identity and process it // in-memory. // A future optimization involves adding "ClosedDateTime" to ExpenseClaims and Payouts at the database level. // Load all (ALL) expense claims for org ExpenseClaims allClaims = ExpenseClaims.ForOrganization(_authenticationData.CurrentOrganization, true); // includes closed // For each claim, determine whether it was open or not at targetDateTime foreach (ExpenseClaim claim in allClaims) { // if it wasn't opened until after target datetime, discard if (claim.CreatedDateTime > targetDateTime) { continue; } // At this point, we are iterating over full set of expense claims opened before targetDateTime. We want the // set of claims that were still open - as determined by the ledger account Expense Claims - on targetDateTime. // // For the expense claims that are still open, this is trivially true. // // However, for others, we need to look at the corresponding Payout and its FinancialTransaction to determine // whetherthe transaction's date is on the other side of targetDateTime. bool includeThisClaim = false; DateTime dateTimeClosed = DateTime.MinValue; if (claim.Open) { includeThisClaim = true; } else { // claim is closed. This is where we need to look first at payout then at financial transaction. Payout payout = claim.Payout; if (payout == null) { // TODO: Find outbound invoice item that depends on this expense claim continue; // some legacy from when Swarmops was primitive - earliest claims don't have payouts } if (payout.Open) { // Transaction is not yet closed. Include this claim, set closed to expected-closed. includeThisClaim = true; dateTimeClosed = payout.ExpectedTransactionDate; } else { FinancialTransaction transaction = payout.FinancialTransaction; if (transaction == null) { throw new InvalidOperationException( "This should not happen (transaction not found on closed payout)"); } if (transaction.DateTime > targetDateTime) { // yes, the claim was opened before targetDateTime and closed after it. This should be included. includeThisClaim = true; dateTimeClosed = transaction.DateTime; } } // TODO: An expense claim can also be closed through an outbound invoice, in case there was a larger cash // advance that wasn't fully covered, and where the two are added together. Check for this condition as well. // // OutboundInvoiceItem should have a dependency on the expense claim if this is the case. } if (includeThisClaim) { outstandingAccounts.Add(OutstandingAccount.FromExpenseClaim(claim, dateTimeClosed)); } } } return(outstandingAccounts); }