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)); } }