parent
f32b07b2b8
commit
4c69e4989c
@ -1,109 +0,0 @@
|
||||
namespace AAIntegration.SimmonsBank.API.Controllers;
|
||||
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
using AAIntegration.SimmonsBank.API.Services;
|
||||
using AAIntegration.SimmonsBank.API.Config;
|
||||
using System.Runtime.InteropServices;
|
||||
using AAIntegration.SimmonsBank.API.Entities;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Security.Claims;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class TransactionsController : ControllerBase
|
||||
{
|
||||
private ITransactionService _transactionService;
|
||||
private IUserService _userService;
|
||||
private IMapper _mapper;
|
||||
private readonly AppSettings _appSettings;
|
||||
private readonly ILogger<TransactionsController> _logger;
|
||||
|
||||
public TransactionsController(
|
||||
ITransactionService transactionService,
|
||||
IUserService userService,
|
||||
IMapper mapper,
|
||||
IOptions<AppSettings> appSettings,
|
||||
ILogger<TransactionsController> logger)
|
||||
{
|
||||
_transactionService = transactionService;
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
_appSettings = appSettings.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetAll(int? accountId = null)
|
||||
{
|
||||
List<TransactionDto> transactionDtos = new List<TransactionDto>();
|
||||
|
||||
foreach (Transaction tran in _transactionService.GetAll(this.GetCurrentUserId()))
|
||||
{
|
||||
if (accountId.HasValue
|
||||
&& (tran.DebitAccount == null || tran.DebitAccount.Id != accountId)
|
||||
&& (tran.CreditAccount == null || tran.CreditAccount.Id != accountId))
|
||||
continue;
|
||||
|
||||
transactionDtos.Add(_mapper.Map<Transaction, TransactionDto>(tran));
|
||||
}
|
||||
|
||||
// Sort by Date
|
||||
transactionDtos.Sort((t1, t2) => t2.Date.CompareTo(t1.Date));
|
||||
|
||||
return Ok(transactionDtos);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetById(int id)
|
||||
{
|
||||
Transaction tran = _transactionService.GetById(id, this.GetCurrentUserId());
|
||||
return Ok(_mapper.Map<Transaction, TransactionDto>(tran));
|
||||
}
|
||||
|
||||
[HttpPost("BulkAdd")]
|
||||
public IActionResult BulkCreate([FromBody]List<TransactionCreate> model)
|
||||
{
|
||||
List<Transaction> trans = _transactionService.BulkCreate(model, this.GetCurrentUserId()).ToList();
|
||||
return Ok(new { message = $"{trans.Count()} transaction(s) created." });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult Create([FromBody]TransactionCreate model)
|
||||
{
|
||||
Transaction tran = _transactionService.Create(model, this.GetCurrentUserId());
|
||||
return Ok(new { message = $"transaction '{tran.Description}' created with id '{tran.Id}'." });
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public IActionResult Update(int id, [FromBody]TransactionUpdateRequest model)
|
||||
{
|
||||
_transactionService.Update(id, model, this.GetCurrentUserId());
|
||||
return Ok(new { message = $"transaction with id '{id}' updated" });
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
_transactionService.Delete(id, this.GetCurrentUserId());
|
||||
return Ok(new { message = "transaction deleted" });
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
string apiKey = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
if (apiKey is null)
|
||||
_logger.LogInformation($"ApiKey: is null");
|
||||
|
||||
_logger.LogInformation($"apiKey: {apiKey}");
|
||||
|
||||
return _userService.GetUser(apiKey).Id;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Entities;
|
||||
|
||||
public class Account
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public decimal Balance { get; set; }
|
||||
public string ExternalAccountNumber { get; set; }
|
||||
public User Owner { get; set; }
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Entities;
|
||||
|
||||
public class Transaction
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
public DateTime UpdatedOn { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public Account? DebitAccount { get; set; }
|
||||
public Account? CreditAccount { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public bool IsPending { get; set; }
|
||||
public User Owner { get; set; }
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Accounts;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.InteropServices;
|
||||
using AAIntegration.SimmonsBank.API.Entities;
|
||||
|
||||
public class AccountCreateRequest
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string InitialBalance { get; set; }
|
||||
public int Currency { get; set; }
|
||||
public string ExternalAccountNumber { get; set; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Accounts;
|
||||
|
||||
public class AccountUpdateRequest
|
||||
{
|
||||
public string? Name { get; set; } = null;
|
||||
public string? Balance { get; set; } = null;
|
||||
public string? ExternalAccountNumber { get; set; } = null;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime;
|
||||
using AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
|
||||
public class TransactionCreate
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int? DebitAccount { get; set; }
|
||||
public int? CreditAccount { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public bool IsPending { get; set; }
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime;
|
||||
using AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
|
||||
public class TransactionDTO
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string AccountId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public decimal? Amount { get; set; }
|
||||
public decimal? RunningBalance { get; set; }
|
||||
public DateTime? DatePosted { get; set; }
|
||||
public DateTime? Date { get; set; }
|
||||
public DateTime? LastUpdated { get; set; }
|
||||
public string PendingStatus { get; set; }
|
||||
public string Memo { get; set; }
|
||||
public string FilteredMemo { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime;
|
||||
using AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
|
||||
public class TransactionDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
public DateTime UpdatedOn { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int DebitAccountId { get; set; }
|
||||
public int CreditAccountId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public bool IsPending { get; set; }
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AAIntegration.SimmonsBank.API.Entities;
|
||||
|
||||
namespace AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
|
||||
public class TransactionUpdateRequest
|
||||
{
|
||||
public DateTime? Date { get; set; } = null;
|
||||
public string? ExternalId { get; set; } = null;
|
||||
public string? Description { get; set; } = null;
|
||||
public int? DebitAccount { get; set; } = null;
|
||||
public int? CreditAccount { get; set; } = null;
|
||||
public decimal? Amount { get; set; } = null;
|
||||
public bool? IsPending { get; set; } = null;
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
namespace AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
using AutoMapper;
|
||||
using BCrypt.Net;
|
||||
using AAIntegration.SimmonsBank.API.Entities;
|
||||
using AAIntegration.SimmonsBank.API.Config;
|
||||
using AAIntegration.SimmonsBank.API.Models.Accounts;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public interface IAccountService
|
||||
{
|
||||
IEnumerable<Account> GetAll(int ownerId);
|
||||
Account GetById(int accountId, int ownerId);
|
||||
void Create(AccountCreateRequest model, int ownerId);
|
||||
void Update(int accountId, AccountUpdateRequest model, int ownerId);
|
||||
void Delete(int accountId, int ownerId);
|
||||
}
|
||||
|
||||
public class AccountService : IAccountService
|
||||
{
|
||||
private DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private IUserService _userService;
|
||||
|
||||
public AccountService(
|
||||
DataContext context,
|
||||
IMapper mapper,
|
||||
IUserService userService)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public IEnumerable<Account> GetAll(int ownerId)
|
||||
{
|
||||
return _context.Accounts
|
||||
.Include(x => x.Owner)
|
||||
.Where(x => x.Owner.Id == ownerId);
|
||||
}
|
||||
|
||||
public Account GetById(int accountId, int ownerId)
|
||||
{
|
||||
return getAccount(accountId, ownerId);
|
||||
}
|
||||
|
||||
public void Create(AccountCreateRequest model, int ownerId)
|
||||
{
|
||||
// Check that account with same name or same external number doesn't exist
|
||||
IEnumerable<Account> accountsWithSameName = _context.Accounts
|
||||
.Include(x => x.Owner)
|
||||
.Where(x => x.Name.ToUpper() == model.Name.ToUpper() && x.Owner.Id == ownerId);
|
||||
|
||||
if (accountsWithSameName.Count() > 0)
|
||||
throw new AppException("Account with name '" + model.Name + "' already exists");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
|
||||
{
|
||||
IEnumerable<Account> matches = _context.Accounts
|
||||
.Include(x => x.Owner)
|
||||
.Where(x => x.ExternalAccountNumber == model.ExternalAccountNumber && x.Owner.Id == ownerId);
|
||||
|
||||
if (matches.Count() > 0)
|
||||
throw new AppException("Account with external account number '" + model.ExternalAccountNumber + "' already exists under account named '" + matches.First().Name + "'");
|
||||
}
|
||||
|
||||
Account account = new Account {
|
||||
Name = model.Name,
|
||||
Balance = Convert.ToDecimal(model.InitialBalance),
|
||||
ExternalAccountNumber = model.ExternalAccountNumber,
|
||||
Owner = getOwner(ownerId)
|
||||
};
|
||||
|
||||
_context.Accounts.Add(account);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
public void Update(int accountId, AccountUpdateRequest model, int ownerId)
|
||||
{
|
||||
Account account = getAccount(accountId, ownerId);
|
||||
|
||||
// validate
|
||||
if (model.Name != account.Name && _context.Accounts
|
||||
.Include(x => x.Owner)
|
||||
.Any(x => x.Name == model.Name && x.Owner.Id == ownerId))
|
||||
throw new AppException("Account with the name '" + model.Name + "' already exists");
|
||||
|
||||
// Name
|
||||
if (!string.IsNullOrWhiteSpace(model.Name))
|
||||
account.Name = model.Name;
|
||||
|
||||
// External Account Number
|
||||
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
|
||||
account.ExternalAccountNumber = model.ExternalAccountNumber;
|
||||
|
||||
_context.Accounts.Update(account);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(int accountId, int ownerId)
|
||||
{
|
||||
var account = getAccount(accountId, ownerId);
|
||||
_context.Accounts.Remove(account);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private Account getAccount(int id, int ownerId)
|
||||
{
|
||||
var account = _context.Accounts
|
||||
.Include(x => x.Owner)
|
||||
.FirstOrDefault(x => x.Id == id && x.Owner.Id == ownerId);
|
||||
if (account == null) throw new KeyNotFoundException("Account not found");
|
||||
return account;
|
||||
}
|
||||
|
||||
private User getOwner(int ownerId)
|
||||
{
|
||||
User? owner = _context.Users.Find(ownerId);
|
||||
|
||||
if (owner == null)
|
||||
throw new AppException($"Owner with ID of '{ownerId}' could not be found");
|
||||
|
||||
return owner;
|
||||
}
|
||||
}
|
@ -1,230 +0,0 @@
|
||||
namespace AAIntegration.SimmonsBank.API.Services;
|
||||
|
||||
using AutoMapper;
|
||||
using BCrypt.Net;
|
||||
using AAIntegration.SimmonsBank.API.Entities;
|
||||
using AAIntegration.SimmonsBank.API.Config;
|
||||
using AAIntegration.SimmonsBank.API.Models.Transactions;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Internal;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
public interface ITransactionService
|
||||
{
|
||||
IEnumerable<Transaction> GetAll(int ownerId);
|
||||
Transaction GetById(int id, int ownerId);
|
||||
IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model, int ownerId);
|
||||
Transaction Create(TransactionCreate model, int ownerId, bool errorOnFail = true);
|
||||
void Update(int id, TransactionUpdateRequest model, int ownerId);
|
||||
void Delete(int id, int ownerId);
|
||||
}
|
||||
|
||||
public class TransactionService : ITransactionService
|
||||
{
|
||||
private DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILogger<TransactionService> _logger;
|
||||
|
||||
public TransactionService(
|
||||
DataContext context,
|
||||
IMapper mapper,
|
||||
ILogger<TransactionService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IEnumerable<Transaction> GetAll(int ownerId)
|
||||
{
|
||||
return _context.Transactions
|
||||
.Include(t => t.DebitAccount)
|
||||
.Include(t => t.CreditAccount)
|
||||
.Include(t => t.Owner)
|
||||
.Where(x => x.Owner.Id == ownerId);
|
||||
}
|
||||
|
||||
public Transaction GetById(int id, int ownerId)
|
||||
{
|
||||
return getTransaction(id, ownerId);
|
||||
}
|
||||
|
||||
private Account prepareAccount(int? accountId)
|
||||
{
|
||||
if (accountId == null || accountId.Value == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Account account = _context.Accounts.Find(accountId.Value);
|
||||
if (account == null)
|
||||
throw new AppException("Could not find account with ID of '" + accountId.Value + "'.");
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
public IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model, int ownerId)
|
||||
{
|
||||
List<Transaction> transactions = new List<Transaction>();
|
||||
|
||||
foreach (TransactionCreate tr in model)
|
||||
{
|
||||
var tran = this.Create(tr, ownerId, false);
|
||||
if (tran != null)
|
||||
transactions.Add(tran);
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public Transaction Create(TransactionCreate model, int ownerId, bool errorOnFail = true)
|
||||
{
|
||||
Transaction transaction = new Transaction {
|
||||
Description = model.Description,
|
||||
Date = model.Date.Date.ToUniversalTime(),
|
||||
CreatedOn = DateTime.UtcNow,
|
||||
UpdatedOn = DateTime.UtcNow,
|
||||
ExternalId = string.IsNullOrWhiteSpace(model.ExternalId) ? "" : model.ExternalId,
|
||||
DebitAccount = prepareAccount(model.DebitAccount),
|
||||
CreditAccount = prepareAccount(model.CreditAccount),
|
||||
Amount = Convert.ToDecimal(model.Amount),
|
||||
Owner = this.getOwner(ownerId),
|
||||
IsPending = model.IsPending
|
||||
};
|
||||
|
||||
if (this.ValidateTransaction(transaction, ownerId, errorOnFail) == false)
|
||||
{
|
||||
_logger.LogInformation($"Aborted adding transaction '{transaction.Description}'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// At this point transaction itself is valid
|
||||
|
||||
_context.Transactions.Add(transaction);
|
||||
_context.SaveChanges();
|
||||
|
||||
_logger.LogInformation("New transaction successfully created.");
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public void Update(int id, TransactionUpdateRequest model, int ownerId)
|
||||
{
|
||||
Transaction transaction = getTransaction(id, ownerId);
|
||||
|
||||
// Transaction.Date
|
||||
if (model.Date.HasValue)
|
||||
transaction.Date = model.Date.Value;
|
||||
|
||||
// Transaction.ExternalId
|
||||
if (model.ExternalId != null)
|
||||
transaction.ExternalId = model.ExternalId;
|
||||
|
||||
// Transaction.Description
|
||||
if (model.Description != null)
|
||||
transaction.Description = model.Description;
|
||||
|
||||
// Transaction.DebitAccount
|
||||
if (model.DebitAccount.HasValue)
|
||||
transaction.DebitAccount = prepareAccount(model.DebitAccount);
|
||||
|
||||
// Transaction.CreditAccount
|
||||
if (model.CreditAccount.HasValue)
|
||||
transaction.CreditAccount = prepareAccount(model.CreditAccount.Value);
|
||||
|
||||
// Transaction.Amount
|
||||
if (model.Amount.HasValue)
|
||||
transaction.Amount = model.Amount.Value;
|
||||
|
||||
// Transaction.IsPending
|
||||
if (model.IsPending.HasValue)
|
||||
transaction.IsPending = model.IsPending.Value;
|
||||
|
||||
this.ValidateTransaction(transaction, ownerId);
|
||||
|
||||
transaction.UpdatedOn = DateTime.UtcNow;
|
||||
|
||||
_context.Transactions.Update(transaction);
|
||||
_context.SaveChanges();
|
||||
|
||||
_logger.LogInformation($"Transaction '{id}' successfully updated.");
|
||||
}
|
||||
|
||||
public void Delete(int id, int ownerId)
|
||||
{
|
||||
var transaction = getTransaction(id, ownerId);
|
||||
_context.Transactions.Remove(transaction);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private bool ErrorOrFalse(bool error, string errorMessage)
|
||||
{
|
||||
if (error)
|
||||
throw new AppException(errorMessage);
|
||||
|
||||
_logger.LogWarning(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ValidateTransaction(Transaction transaction, int ownerId, bool errorOnFail = true)
|
||||
{
|
||||
// There has to be at least 1 specified account
|
||||
if (transaction.DebitAccount == null && transaction.CreditAccount == null)
|
||||
return ErrorOrFalse(errorOnFail, "There must be an envelope or account chosen for a transaction.");
|
||||
|
||||
// Accounts cannot be the same
|
||||
if (transaction.DebitAccount != null && transaction.CreditAccount != null &&
|
||||
transaction.DebitAccount.Id == transaction.CreditAccount.Id)
|
||||
return ErrorOrFalse(errorOnFail, "The debit and credit accounts of a transaction cannot be the same.");
|
||||
|
||||
// Transaction Duplication Check - External ID
|
||||
if (!string.IsNullOrWhiteSpace(transaction.ExternalId)
|
||||
&& _context.Transactions
|
||||
.Include(x => x.Owner)
|
||||
.Any(x => x.ExternalId == transaction.ExternalId && x.Owner.Id == ownerId))
|
||||
return ErrorOrFalse(errorOnFail, "Transaction with the external ID '" + transaction.ExternalId + "' already exists");
|
||||
|
||||
// Transaction Duplication Check - All other fields
|
||||
/*if (_context.Transactions.Any(x =>
|
||||
x.Description == transaction.Description
|
||||
&& x.Date == transaction.Date
|
||||
&& x.DebitAccount == transaction.DebitAccount
|
||||
&& x.CreditAccount == transaction.CreditAccount
|
||||
&& x.Amount == transaction.Amount))
|
||||
{
|
||||
return ErrorOrFalse(errorOnFail, "Transaction with the same fields already exists");
|
||||
}*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Transaction getTransaction(int id, int ownerId)
|
||||
{
|
||||
var transaction = _context.Transactions
|
||||
.Include(t => t.DebitAccount)
|
||||
.Include(t => t.CreditAccount)
|
||||
.Include(t => t.Owner)
|
||||
.FirstOrDefault(t => t.Id == id && t.Owner.Id == ownerId);
|
||||
|
||||
if (transaction == null)
|
||||
throw new KeyNotFoundException("Transaction not found");
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private User getOwner(int ownerId)
|
||||
{
|
||||
User? owner = _context.Users.Find(ownerId);
|
||||
|
||||
if (owner == null)
|
||||
throw new AppException($"Owner with ID of '{ownerId}' could not be found");
|
||||
|
||||
return owner;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue