Skip to content

Commit

Permalink
Merge pull request #411 from ucdavis/JCS/TxnReport
Browse files Browse the repository at this point in the history
Jcs/txn report
  • Loading branch information
jSylvestre authored Mar 11, 2024
2 parents a516bab + 0643d84 commit 3be2cd7
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 31 deletions.
2 changes: 2 additions & 0 deletions Sloth.Core/Domain/Transfer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ public string FullObjectToString()
}
return result;
}

public string ShortFinancialSegmentString => $"{FinancialSegmentString?[0..13]}..."; //Takes the first 13 characters of the FinancialSegmentString. Works with nulls
#endregion

public enum CreditDebit
Expand Down
29 changes: 29 additions & 0 deletions Sloth.Web/Controllers/ReportsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Sloth.Web.Models.ReportViewModels;
using Sloth.Web.Models.TransactionViewModels;
using Sloth.Web.Resources;
using Sloth.Web.Helpers;

namespace Sloth.Web.Controllers
{
Expand Down Expand Up @@ -65,6 +66,34 @@ public async Task<IActionResult> FailedTransactions()
return View("FailedTransactions", model);
}

[Route("{team}/reports/downloadabletransactions/{filter?}")]
[HttpGet]
public async Task<IActionResult> DownloadableTransactions(TransactionsFilterModel filter = null)
{
if (string.IsNullOrWhiteSpace(TeamSlug))
{
return BadRequest("TeamSlug is required");
}

if (filter == null)
filter = new TransactionsFilterModel();

FilterHelpers.SanitizeTransactionsFilter(filter);

var model = new TransfersReportViewModel
{
Filter = filter,
};

model.Transactions = await DbContext.Transactions.Include(a => a.Transfers).Include(a => a.Metadata)
.Where(t => t.Source.Team.Slug == TeamSlug && t.TransactionDate >= filter.From && t.TransactionDate <= filter.To).OrderBy(a => a.Id).ThenBy(a => a.TransactionDate).Select(TransactionWithTransfers.Projection()).ToListAsync();

var team = await DbContext.Teams.FirstAsync(t => t.Slug == TeamSlug);
ViewBag.Title = $"Transactions with Transfers - {team.Name}";

return View(model);
}

[Authorize(Roles = Roles.SystemAdmin)]
public async Task<IActionResult> FailedTransactionsAllTeams()
{
Expand Down
33 changes: 2 additions & 31 deletions Sloth.Web/Controllers/TransactionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Sloth.Web.Models.BlobViewModels;
using Sloth.Web.Models.TransactionViewModels;
using Sloth.Web.Resources;
using Sloth.Web.Helpers;

namespace Sloth.Web.Controllers
{
Expand All @@ -44,7 +45,7 @@ public async Task<IActionResult> Index(TransactionsFilterModel filter = null)
if (filter == null)
filter = new TransactionsFilterModel();

SanitizeTransactionsFilter(filter);
FilterHelpers.SanitizeTransactionsFilter(filter);

IQueryable<Transaction> query;

Expand Down Expand Up @@ -683,35 +684,5 @@ public async Task<IActionResult> Search(TransactionsFilterModel filter = null)
return RedirectToAction("Index");

}


private static void SanitizeTransactionsFilter(TransactionsFilterModel model)
{
var fromUtc = (model.From ?? DateTime.Now.AddMonths(-1)).ToUniversalTime().Date;
var throughUtc = (model.To ?? DateTime.Now).ToUniversalTime().AddDays(1).Date;

if (fromUtc > DateTime.UtcNow || fromUtc < DateTime.UtcNow.AddYears(-100))
{
// invalid, so default to filtering from one month ago
var from = DateTime.Now.AddMonths((-1)).Date;
model.From = from;
fromUtc = from.ToUniversalTime();
}
else
{
model.From = fromUtc.ToLocalTime();
}

if (fromUtc >= throughUtc)
{
// invalid, so default to filtering through one month after fromUtc
throughUtc = fromUtc.AddMonths(1).AddDays(1).Date;
model.To = throughUtc.AddDays(-1).ToLocalTime();
}
else
{
model.To = throughUtc.ToLocalTime();
}
}
}
}
37 changes: 37 additions & 0 deletions Sloth.Web/Helpers/FilterHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Sloth.Web.Models.TransactionViewModels;
using System;

namespace Sloth.Web.Helpers
{
public class FilterHelpers
{
public static void SanitizeTransactionsFilter(TransactionsFilterModel model)
{
var fromUtc = (model.From ?? DateTime.Now.AddMonths(-1)).ToUniversalTime().Date;
var throughUtc = (model.To ?? DateTime.Now).ToUniversalTime().AddDays(1).Date;

if (fromUtc > DateTime.UtcNow || fromUtc < DateTime.UtcNow.AddYears(-100))
{
// invalid, so default to filtering from one month ago
var from = DateTime.Now.AddMonths((-1)).Date;
model.From = from;
fromUtc = from.ToUniversalTime();
}
else
{
model.From = fromUtc.ToLocalTime();
}

if (fromUtc >= throughUtc)
{
// invalid, so default to filtering through one month after fromUtc
throughUtc = fromUtc.AddMonths(1).AddDays(1).Date;
model.To = throughUtc.AddDays(-1).ToLocalTime();
}
else
{
model.To = throughUtc.ToLocalTime();
}
}
}
}
76 changes: 76 additions & 0 deletions Sloth.Web/Models/ReportViewModels/TransfersReportViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using Sloth.Core.Models;
using System.Collections.Generic;
using Sloth.Web.Models.TransactionViewModels;
using System.Linq.Expressions;
using System.Linq;

namespace Sloth.Web.Models.ReportViewModels
{
public class TransfersReportViewModel
{
public TransactionsFilterModel Filter { get; set; }

public IList<TransactionWithTransfers> Transactions { get; set; }
}

public class TransactionWithTransfers
{
[DisplayName("Txn Id")]
public string Id { get; set; }
[DisplayName("Txn Id")]
public string DisplayId => $"...{Id[^4..]}"; // last 4 characters of the id

public string Status { get; set; }

[DisplayName("Transaction Date")]
public DateTime TransactionDate { get; set; }

[DisplayName("Kfs Tracking Number")]
public string KfsTrackingNumber { get; set; }
[DisplayName("Document Number")]
public string DocumentNumber { get; set; }

[DisplayName("Processor Tracking Number")]
public string ProcessorTrackingNumber { get; set; }

[DisplayName("Merchant Tracking Number")]
public string MerchantTrackingNumber { get; set; }
[DisplayName("Transaction Description")]
public string TxnDescription { get; set; }

public string MetaDataString { get; set; }

[DisplayName("Transaction Amount")]
public decimal Amount { get; set; }

[DisplayName("Transfer Count")]
public int TransferCount { get; set; }

public IList<Transfer> Transfers { get; set; } // don't need all the info in here, but it shouldn't be too big.

public static Expression<Func<Transaction, TransactionWithTransfers>> Projection()
{

return txn => new TransactionWithTransfers
{
Id = txn.Id,
Status = txn.Status,
TransactionDate = txn.TransactionDate,
KfsTrackingNumber = txn.KfsTrackingNumber,
DocumentNumber = txn.DocumentNumber,
ProcessorTrackingNumber = txn.ProcessorTrackingNumber,
MerchantTrackingNumber = txn.MerchantTrackingNumber,
TxnDescription = txn.Description,
MetaDataString = string.Join(", ", txn.Metadata.Select(kv => $"{kv.Name}: {kv.Value}")),
Amount = txn.Transfers.Where(a => a.Direction == Transfer.CreditDebit.Credit).Sum(a => a.Amount),
TransferCount = txn.Transfers.Count,
Transfers = txn.Transfers.OrderBy(a => a.Direction).ToList(),
};

}
}
}
128 changes: 128 additions & 0 deletions Sloth.Web/Views/Reports/DownloadableTransactions.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
@using Humanizer
@using Sloth.Core.Extensions
@using Sloth.Core.Resources
@model Sloth.Web.Models.ReportViewModels.TransfersReportViewModel

<div class="container">
<h2>@ViewBag.Title</h2>

<h3>Filters</h3>
<form class="row" id="transactionFilterForm" asp-controller="Reports" asp-action="DownloadableTransactions" method="get">
<div class="row justify-content-between filters-wrapper">
<div class="col-8 col-md-5">
<div id="filter-date-range" class="input-group">
<input name="from" type="text" class="form-control" placeholder="MM/DD/YYYY" asp-for="Filter.From" asp-format="{0:MM/dd/yyyy}" aria-label="" />
<span class="input-group-text">to</span>
<input name="to" type="text" class="form-control" placeholder="MM/DD/YYYY" asp-for="Filter.To" asp-format="{0:MM/dd/yyyy}" aria-label="" />
</div>

<button type="submit" class="btn btn-link mt-1">Apply filter</button>
</div>

</div>
</form>

<div class="responsive-table">
<table id="transactionsTable" class="table sloth-table active-table">
<thead>
<tr>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().Id)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().DisplayId)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().Status)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().TransactionDate)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().KfsTrackingNumber)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().DocumentNumber)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().ProcessorTrackingNumber)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().MerchantTrackingNumber)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().TxnDescription)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().MetaDataString)</th>
<th>@Html.DisplayNameFor(x => x.Transactions.FirstOrDefault().TransferCount)</th>
<th>Total Amount</th>
<th>Direction</th>
<th>Description</th>
<th>Amount</th>
<th>Chart String</th>
<th>Chart String</th>
</tr>
</thead>
<tbody>
@foreach (var txn in Model.Transactions)
{
@foreach (var transfer in txn.Transfers)
{
<tr>
<td>@txn.Id</td>
<td>@txn.DisplayId</td>
<td><span class="badge @(TransactionStatuses.GetBadgeClass(txn.Status))">@txn.Status.Humanize(LetterCasing.Title)</span></td>
<td>@txn.TransactionDate.ToPacificTime()</td>
<td>@txn.KfsTrackingNumber</td>
<td>@txn.DocumentNumber</td>
<td>@txn.ProcessorTrackingNumber</td>
<td>@txn.MerchantTrackingNumber</td>
<td>@txn.TxnDescription</td>
<td>@txn.MetaDataString</td>
<td>@txn.TransferCount</td>
<td>@txn.Amount</td>
<td><span class="badge @(Transfer.GetDirectionBadgeClass(transfer.Direction))">@transfer.Direction</span></td>
<td>@transfer.Description</td>
<td>@transfer.Amount</td>
<td>@transfer.ShortFinancialSegmentString</td>
<td>@transfer.FinancialSegmentString</td>
</tr>
}
}
</tbody>
</table>

</div>

</div>

@section AdditionalScripts {
<script>
//setup datepickers
$('#filter-date-range').datepicker({
inputs: $('#filter-date-range input'),
keepEmptyValues: true,
});
</script>

<script>
$('#transactionsTable').dataTable({
"dom": 'Bfrtip',
"columnDefs": [
{
"targets": [0, 5, 6, 7, 9, 16],
"visible": false,
},
{ "type": "date", "targets": [3] },
],
order: false,
"buttons": [
{
extend: 'copyHtml5',
exportOptions: {
columns: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16]
}
},
{
extend: 'excelHtml5',
exportOptions: {
columns: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16]
}
},
{
extend: 'csvHtml5',
exportOptions: {
columns: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16]
}
},
],
language: {
searchPlaceholder: "Search Table",
search: "",
},
});
</script>
}
12 changes: 12 additions & 0 deletions Sloth.Web/Views/Reports/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
</div>
</a>
</div>
<div class="card job">
<a href="~/@(teamSlug)/Reports/DownloadableTransactions">
<div class="card-body">

<h5 class="card-title">Downloadable Transactions</h5>

<p class="card-text">
Shows transactions and their transfers info in a downloading format.
</p>
</div>
</a>
</div>
</div>
}

Expand Down

0 comments on commit 3be2cd7

Please sign in to comment.