Skip to content

Commit

Permalink
Persistence is not futile!
Browse files Browse the repository at this point in the history
Basically functional version of add journal entry.  Hit a milestone!
  • Loading branch information
groberts314 committed Dec 16, 2018
1 parent 7f925c1 commit 4bb6d7f
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 168 deletions.
83 changes: 61 additions & 22 deletions src/DashAccountingSystem/Controllers/JournalController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using DashAccountingSystem.Data.Models;
using DashAccountingSystem.Data.Repositories;
using DashAccountingSystem.Extensions;
using DashAccountingSystem.Models;
Expand Down Expand Up @@ -42,18 +43,10 @@ public async Task<IActionResult> Index(int tenantId)
[Route("Ledger/{tenantId:int}/Journal/Entry/Add", Name = "addJournalEntry")]
public async Task<IActionResult> AddEntry(int tenantId)
{
var accounts = await _accountRepository.GetAccountsByTenantAsync(tenantId);

var tenant = accounts.IsEmpty()
? await _tenantRepository.GetTenantAsync(tenantId)
: accounts.Select(a => a.Tenant).First();
await HydrateJournalEntryWriteViewBag(tenantId);

var assetTypes = await _sharedLookupRepository.GetAssetTypesAsync();

ViewBag.AccountList = new CategorizedAccountsViewModel(accounts);
ViewBag.AssetTypes = assetTypes;
ViewBag.Tenant = tenant;
ViewBag.PostBack = false;
ViewBag.SuccessfulSave = false;

var timeZoneId = "America/Los_Angeles"; // TODO: Make this a user preference or something...
var localToday = DateTime.UtcNow.WithTimeZone(timeZoneId).Date;
Expand All @@ -66,23 +59,54 @@ public async Task<IActionResult> AddEntry(int tenantId)
[Route("Ledger/{tenantId:int}/Journal/Entry/Add", Name = "addJournalEntryPost")]
public async Task<IActionResult> AddEntry(
[FromRoute] int tenantId,
JournalEntryBaseViewModel journalEntry)
JournalEntryBaseViewModel journalEntryViewModel)
{
// TODO: Fix this hot mess
var accounts = await _accountRepository.GetAccountsByTenantAsync(tenantId);
ViewBag.PostBack = true;
ViewBag.SuccessfulSave = false;

var tenant = accounts.IsEmpty()
? await _tenantRepository.GetTenantAsync(tenantId)
: accounts.Select(a => a.Tenant).First();
var isJournalEntryValid = ModelState.IsValid;

var assetTypes = await _sharedLookupRepository.GetAssetTypesAsync();
if (isJournalEntryValid)
{
// Basic view model validation has passed
// Perform additional validation (i.e. represents a non-empty, valid, balanced transaction)
// TODO: Move most of this to an attribute hopefully
isJournalEntryValid = journalEntryViewModel.Validate(ModelState);
}

ViewBag.AccountList = new CategorizedAccountsViewModel(accounts);
ViewBag.AssetTypes = assetTypes;
ViewBag.Tenant = tenant;
ViewBag.PostBack = true;
if (!isJournalEntryValid)
{
await HydrateJournalEntryWriteViewBag(tenantId);
return View(journalEntryViewModel);
}

return View();
var contextUserId = User.GetUserId();

var journalEntry = new JournalEntry(
tenantId,
journalEntryViewModel.EntryDate,
journalEntryViewModel.PostDate,
journalEntryViewModel.Description,
journalEntryViewModel.CheckNumber,
contextUserId,
journalEntryViewModel.PostDate.HasValue ? contextUserId : (Guid?)null);

if (!string.IsNullOrWhiteSpace(journalEntryViewModel.Note))
journalEntry.Note = journalEntryViewModel.Note;

journalEntry.Accounts = journalEntryViewModel
.Accounts
.Select(a => a.ToModel())
.ToList();

var savedJournalEntry = await _journalEntryRepository.CreateJournalEntryAsync(journalEntry);

var savedJournalEntryViewModel = JournalEntryDetailedViewModel.FromModel(savedJournalEntry);

ViewBag.Tenant = savedJournalEntry.Tenant;
ViewBag.SuccessfulSave = true;

return View(savedJournalEntryViewModel);
}

[HttpGet]
Expand Down Expand Up @@ -112,5 +136,20 @@ public IActionResult DeleteEntry(int tenantId, int entryId)
{
return View();
}

private async Task HydrateJournalEntryWriteViewBag(int tenantId)
{
var accounts = await _accountRepository.GetAccountsByTenantAsync(tenantId);

var tenant = accounts.IsEmpty()
? await _tenantRepository.GetTenantAsync(tenantId)
: accounts.Select(a => a.Tenant).First();

var assetTypes = await _sharedLookupRepository.GetAssetTypesAsync();

ViewBag.AccountList = new CategorizedAccountsViewModel(accounts);
ViewBag.AssetTypes = assetTypes;
ViewBag.Tenant = tenant;
}
}
}
2 changes: 1 addition & 1 deletion src/DashAccountingSystem/Data/Models/JournalEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class JournalEntry

public string Note { get; set; }

public ICollection<JournalEntryAccount> Accounts { get; private set; } = new List<JournalEntryAccount>();
public ICollection<JournalEntryAccount> Accounts { get; set; } = new List<JournalEntryAccount>();

public bool IsBalanced
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ public async Task<JournalEntry> GetByIdAsync(int journalEntryId)
return await _db
.JournalEntry
.Where(je => je.Id == journalEntryId)
.Include(je => je.Tenant)
.Include(je => je.AccountingPeriod)
.Include(je => je.CreatedBy)
.Include(je => je.UpdatedBy)
.Include(je => je.PostedBy)
.Include(je => je.CanceledBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.FirstOrDefaultAsync();
}

Expand All @@ -50,6 +53,8 @@ public async Task<IEnumerable<JournalEntry>> GetJournalEntriesAsync(int tenantId
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand All @@ -68,6 +73,8 @@ public async Task<IEnumerable<JournalEntry>> GetJournalEntriesForMonthAsync(int
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand All @@ -83,6 +90,8 @@ public async Task<IEnumerable<JournalEntry>> GetJournalEntriesForPeriodAsync(int
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand All @@ -101,6 +110,8 @@ public async Task<IEnumerable<JournalEntry>> GetJournalEntriesForQuarterAsync(in
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand All @@ -116,6 +127,8 @@ public async Task<IEnumerable<JournalEntry>> GetJournalEntriesForYearAsync(int t
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand All @@ -131,6 +144,8 @@ public async Task<IEnumerable<JournalEntry>> GetPendingJournalEntriesAsync(int t
.Include(je => je.PostedBy)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.Account)
.Include(je => je.Accounts)
.ThenInclude(jeAcct => jeAcct.AssetType)
.ToListAsync();
}

Expand Down Expand Up @@ -236,12 +251,12 @@ private void UpdateAccountsForPendingJournalEntry(JournalEntry entry)
if (account.AmountType == BalanceType.Debit)
{
account.Account.PendingDebits =
account.Account.PendingDebits ?? 0.0m + account.Amount;
(account.Account.PendingDebits ?? 0.0m) + account.Amount;
}
else // Credit
{
account.Account.PendingCredits =
account.Account.PendingCredits ?? 0.0m + account.Amount;
(account.Account.PendingCredits ?? 0.0m) + Math.Abs(account.Amount);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* DANGER - Don't do it unless you mean it! */
/* Affects all tenants! */

UPDATE "Account"
SET "PendingDebits" = NULL
,"PendingCredits" = NULL
,"CurrentBalance" = 0
,"BalanceUpdated" = now() AT TIME ZONE 'UTC';

TRUNCATE TABLE "JournalEntry" RESTART IDENTITY CASCADE;
39 changes: 39 additions & 0 deletions src/DashAccountingSystem/Extensions/ClaimsPrincipalExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Security.Claims;

namespace DashAccountingSystem.Extensions
{
public static class ClaimsPrincipalExtensions
{
public static string GetUserFirstName(this ClaimsPrincipal user)
{
return user.FindFirstValue(ClaimTypes.GivenName);
}

public static string GetUserFullName(this ClaimsPrincipal user)
{
return $"{user.GetUserFirstName()} {user.GetUserFullName()}".Trim();
}

public static Guid GetUserId(this ClaimsPrincipal user)
{
var nameIdentifierClaim = user.FindFirstValue(ClaimTypes.NameIdentifier);

if (string.IsNullOrWhiteSpace(nameIdentifierClaim))
throw new ArgumentException("Could not find the 'name identifier' claim");

Guid userId;

if (!Guid.TryParse(nameIdentifierClaim, out userId))
throw new ArgumentException(
$"'name identifier' claim does not seem to be a GUID type value as expected; value is: '{nameIdentifierClaim}'");

return userId;
}

public static string GetUserLastName(this ClaimsPrincipal user)
{
return user.FindFirstValue(ClaimTypes.Surname);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using DashAccountingSystem.Models;

namespace DashAccountingSystem.Filters
{
public class JournalEntryViewModelValidationFilterAttribute : ActionFilterAttribute
{
private readonly ILogger _logger;

public JournalEntryViewModelValidationFilterAttribute(
ILogger<JournalEntryViewModelValidationFilterAttribute> logger)
{
_logger = logger;
}

public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ModelState.IsValid)
{
// TODO: Extract the view model and perform additional validation:
// * Must contain at least two accounts of each asset type with non-zero values
// * Must balance (debits must equal credits)
}

return base.OnActionExecutionAsync(context, next);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using DashAccountingSystem.Data.Models;

namespace DashAccountingSystem.Models
Expand All @@ -26,7 +22,7 @@ public decimal Amount
get
{
if (AmountType == BalanceType.Credit)
return Credit;
return -Credit;
else
return Debit;
}
Expand All @@ -44,5 +40,10 @@ public BalanceType AmountType
}

public bool HasAmount { get { return Debit > 0.0m || Credit > 0.0m; } }

public JournalEntryAccount ToModel()
{
return new JournalEntryAccount(AccountId, Amount, AssetTypeId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using DashAccountingSystem.Data.Models;

namespace DashAccountingSystem.Models
{
public class JournalEntryAccountDetailedViewModel : JournalEntryAccountBaseViewModel
{
[Display(Name = "Account Name")]
public string AccountName { get; set; }

[Display(Name = "Asset Type")]
public string AssetType { get; set; }

public static JournalEntryAccountDetailedViewModel FromModel(JournalEntryAccount model)
{
if (model == null)
return null;

return new JournalEntryAccountDetailedViewModel()
{
AccountId = model.AccountId,
AccountName = model.Account.DisplayName,
AssetType = model.AssetType.Name,
AssetTypeId = model.AssetTypeId,
Credit = model.AmountType == BalanceType.Credit ? Math.Abs(model.Amount) : 0.0m,
Debit = model.AmountType == BalanceType.Debit ? model.Amount : 0.0m
};
}
}
}
Loading

0 comments on commit 4bb6d7f

Please sign in to comment.