Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jcs/txn report #411

Merged
merged 10 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]}...";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never seen this w/o using string.substr() but if this works it's ok to me. clear enough what's happening.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it takes the first 13 characters of the string. I'll add a comment

#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
Loading