finnow/finnow-api/source/util/sample_data.d

186 lines
6.7 KiB
D

module util.sample_data;
import slf4d;
import handy_http_primitives : Optional;
import auth;
import profile;
import account;
import transaction;
import transaction.dto;
import util.money;
import util.data;
import std.random;
import std.conv;
import std.array;
import std.datetime;
import std.typecons;
void generateSampleData() {
UserRepository userRepo = new FileSystemUserRepository;
// Remove all existing user data.
foreach (User user; userRepo.findAll()) {
userRepo.deleteByUsername(user.username);
}
const int userCount = uniform(5, 10);
for (int i = 0; i < userCount; i++) {
try {
generateRandomUser(i, userRepo);
} catch (Throwable t) {
import std.stdio;
stderr.writeln(t);
throw t;
}
}
info("Random sample data generation complete.");
}
void generateRandomUser(int idx, UserRepository userRepo) {
string username = "testuser" ~ idx.to!string;
string password = "testpass";
infoF!"Generating random user %s, password: %s."(username, password);
User user = createNewUser(userRepo, username, password);
ProfileRepository profileRepo = new FileSystemProfileRepository(username);
const int profileCount = uniform(1, 5);
for (int i = 0; i < profileCount; i++) {
generateRandomProfile(i, profileRepo);
}
}
void generateRandomProfile(int idx, ProfileRepository profileRepo) {
string profileName = "test-profile-" ~ idx.to!string;
infoF!" Generating random profile %s."(profileName);
Profile profile = profileRepo.createProfile(profileName);
ProfileDataSource ds = profileRepo.getDataSource(profile);
ds.getPropertiesRepository().setProperty("sample-data-idx", idx.to!string);
Currency preferredCurrency = choice(ALL_CURRENCIES);
const int accountCount = uniform(3, 10);
for (int i = 0; i < accountCount; i++) {
generateRandomAccount(i, ds, preferredCurrency);
}
auto vendorRepo = ds.getTransactionVendorRepository();
const int vendorCount = uniform(5, 30);
for (int i = 0; i < vendorCount; i++) {
vendorRepo.insert("Test Vendor " ~ to!string(i), "Testing vendor for sample data.");
}
infoF!" Generated %d random vendors."(vendorCount);
auto categoryRepo = ds.getTransactionCategoryRepository();
const int categoryCount = uniform(5, 30);
for (int i = 0; i < categoryCount; i++) {
categoryRepo.insert(Optional!ulong.empty, "Test Category " ~ to!string(i), "Testing category.", "FFFFFF");
}
infoF!" Generated %d random categories."(categoryCount);
generateRandomTransactions(ds);
}
void generateRandomAccount(int idx, ProfileDataSource ds, Currency preferredCurrency) {
AccountRepository accountRepo = ds.getAccountRepository();
string idxStr = idx.to!string;
string numberSuffix = "0".replicate(4 - idxStr.length) ~ idxStr;
string name = "Test Account " ~ idxStr;
AccountType type = choice(ALL_ACCOUNT_TYPES);
Currency currency = preferredCurrency;
if (uniform01() < 0.1) {
currency = choice(ALL_CURRENCIES);
}
string description = "This is a testing account generated by util.sample_data.generateRandomAccount().";
Account account = accountRepo.insert(
type,
numberSuffix,
name,
currency,
description
);
infoF!" Generated random account: %s, #%s, %s"(name, numberSuffix, currency.code);
}
void generateRandomTransactions(ProfileDataSource ds) {
const TransactionVendor[] vendors = ds.getTransactionVendorRepository.findAll();
const TransactionCategory[] categories = ds.getTransactionCategoryRepository()
.findAllByParentId(Optional!ulong.empty);
const Account[] accounts = ds.getAccountRepository().findAll();
SysTime timestamp = Clock.currTime(UTC()) - seconds(1);
for (int i = 0; i < 100; i++) {
AddTransactionPayload data;
data.timestamp = timestamp.toISOExtString();
if (uniform01() < 0.7) {
data.vendorId = Optional!ulong.of(choice(vendors).id).toNullable;
}
if (uniform01() < 0.8) {
data.categoryId = Optional!ulong.of(choice(categories).id).toNullable;
}
// Randomly choose an account to credit / debit the transaction to.
Account primaryAccount = choice(accounts);
data.currencyCode = primaryAccount.currency.code;
Optional!ulong secondaryAccountId;
if (uniform01() < 0.25) {
foreach (acc; accounts) {
if (acc.id != primaryAccount.id && acc.currency == primaryAccount.currency) {
secondaryAccountId = Optional!ulong.of(acc.id);
break;
}
}
}
if (uniform01() < 0.5) {
data.creditedAccountId = Optional!ulong.of(primaryAccount.id).toNullable;
if (secondaryAccountId) data.debitedAccountId = secondaryAccountId.toNullable;
} else {
data.debitedAccountId = Optional!ulong.of(primaryAccount.id).toNullable;
if (secondaryAccountId) data.creditedAccountId = secondaryAccountId.toNullable;
}
// Randomly choose some tags to add.
string[] tags;
foreach (n; 1..10) {
if (uniform01 < 0.25) {
tags ~= "tag-" ~ n.to!string;
}
}
data.tags = tags;
data.amount = uniform(0, 1_000_000);
data.description = "This is a sample transaction which was generated as part of sample data.";
// Generate random line items:
if (uniform01 < 0.5) {
long lineItemTotal = 0;
foreach (n; 1..uniform(1, 20)) {
AddTransactionPayload.LineItem item;
item.valuePerItem = uniform(1, 10_000);
item.quantity = uniform(1, 5);
lineItemTotal += item.quantity * item.valuePerItem;
item.description = "Sample item " ~ n.to!string;
if (uniform01 < 0.5) {
TransactionCategory category = choice(categories);
item.categoryId = category.id;
}
data.lineItems ~= item;
}
long diff = data.amount - lineItemTotal;
// Add one final line item that adds up to the transaction total.
if (diff != 0) {
data.lineItems ~= AddTransactionPayload.LineItem(
diff,
1,
"Last item which reconciles line items total with transaction amount.",
Nullable!ulong.init
);
}
}
auto txn = addTransaction(ds, data);
infoF!" Generated transaction %d"(txn.id);
timestamp -= seconds(uniform(10, 1_000_000));
}
}