summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-05-31 00:00:12 -0700
committerFuwn <[email protected]>2024-05-31 00:20:41 -0700
commit968e1c4af014b7f40bfaa4f57fcc0f38e5e0d847 (patch)
treedf80525ad0cc9e4b561bfb0772c30ccc5b64b4a2 /source
parentfeat: initial commit (diff)
downloadcst_136_assignment_eight-main.tar.xz
cst_136_assignment_eight-main.zip
feat: final releaseHEADmain
Diffstat (limited to 'source')
-rw-r--r--source/book.cc4
-rw-r--r--source/book_count.cc7
-rw-r--r--source/customer.cc29
-rw-r--r--source/library.cc179
-rw-r--r--source/main.cc107
-rw-r--r--source/member.cc23
-rw-r--r--source/person.cc16
-rw-r--r--source/price.cc7
-rw-r--r--source/purchase_error.cc21
-rw-r--r--source/random.cc32
-rw-r--r--source/store.cc235
-rw-r--r--source/test.cc96
-rw-r--r--source/utility.cc23
13 files changed, 638 insertions, 141 deletions
diff --git a/source/book.cc b/source/book.cc
index 6b0bd6a..a2c0a13 100644
--- a/source/book.cc
+++ b/source/book.cc
@@ -81,8 +81,8 @@ auto book::copies(book::size_type copies) noexcept -> book & {
return *this;
}
-auto operator<<(std::ostream &output_stream, const book &book)
- -> std::ostream & {
+auto operator<<(std::ostream &output_stream,
+ const book &book) -> std::ostream & {
output_stream << "Title: " << book._title << '\n';
output_stream << "Authors: ";
diff --git a/source/book_count.cc b/source/book_count.cc
new file mode 100644
index 0000000..7e03acd
--- /dev/null
+++ b/source/book_count.cc
@@ -0,0 +1,7 @@
+#include <book_store/book_count.hh>
+
+namespace book_store::product {
+[[nodiscard]] auto book_count::value() const noexcept -> book_count_type {
+ return this->_count;
+}
+} // namespace book_store::product
diff --git a/source/customer.cc b/source/customer.cc
new file mode 100644
index 0000000..c4bcf97
--- /dev/null
+++ b/source/customer.cc
@@ -0,0 +1,29 @@
+#include <book_store/book.hh>
+#include <book_store/customer.hh>
+#include <book_store/price.hh>
+
+namespace book_store::consumer {
+using namespace product;
+
+auto customer::books_bought() const noexcept -> book::size_type {
+ return this->_books_bought;
+}
+
+auto customer::amount_spent() const noexcept -> price::usd {
+ return this->_amount_spent;
+}
+
+auto customer::is_member() const noexcept -> bool { return this->_is_member; }
+
+auto customer::books_bought(book::size_type books_bought) noexcept -> void {
+ this->_books_bought = books_bought;
+}
+
+auto customer::amount_spent(price::usd amount_spent) noexcept -> void {
+ this->_amount_spent = amount_spent;
+}
+
+auto customer::is_member(bool is_member) noexcept -> void {
+ this->_is_member = is_member;
+}
+} // namespace book_store::consumer
diff --git a/source/library.cc b/source/library.cc
new file mode 100644
index 0000000..1ff1f7c
--- /dev/null
+++ b/source/library.cc
@@ -0,0 +1,179 @@
+#include <cstddef>
+#include <iostream>
+#include <random>
+#include <string>
+
+#include <book_store/book.hh>
+#include <book_store/customer.hh>
+#include <book_store/library.hh>
+#include <book_store/random.hh>
+#include <book_store/store.hh>
+#include <book_store/utility.hh>
+
+namespace book_store::library {
+auto purchase_handler(book_store::store &store) -> void {
+ using namespace book_store;
+ using namespace book_store::utility;
+
+ std::cout << "You may now purchase books. Press enter to exit.";
+
+ for (;;) {
+ std::string isbn;
+ std::size_t customer_id;
+ std::size_t copy_count;
+
+ isbn =
+ prompt("\n\nEnter the ISBN of the book you would like to purchase: ");
+
+ if (isbn.empty()) {
+ break;
+ }
+
+ customer_id = std::stoul(prompt("Enter your ID: "));
+ copy_count =
+ std::stoul(prompt("Enter the number of copies you would like to "
+ "purchase: "));
+
+ auto book = store.find_book_by_isbn(isbn);
+
+ if (!book.has_value()) {
+ clear_cerr();
+
+ std::cerr << "\nBook not found.";
+
+ continue;
+ }
+
+ auto person = store.find_person_by_id(customer_id);
+
+ if (!person.has_value()) {
+ clear_cerr();
+
+ std::cerr << "\nPerson not found.";
+
+ continue;
+ }
+
+ auto &book_reference = book.value();
+ auto purchase_book = store.purchase_book(customer_id, isbn, copy_count);
+
+ if (purchase_book.has_value()) {
+ clear_cerr();
+
+ std::cerr << '\n' << purchase_book.value().what();
+
+ continue;
+ }
+
+ std::cout << "\nPurchase successful.\nRemaining copies: "
+ << book_reference.get().copies();
+ }
+}
+
+auto populate_books(book_store::store &store,
+ std::size_t library_size) -> void {
+ utility::random::book_random_engine random(
+ std::mt19937(std::random_device{}()));
+
+ for (std::size_t i = 0; i < library_size; ++i) {
+ store.append_book(product::book{random.title(), random.authors(),
+ random.publisher(), random.isbn(),
+ random.price_usd(), random.copy_count()});
+ }
+}
+
+auto populate_consumers(book_store::store &store) -> void {
+ using namespace book_store;
+ using namespace book_store::utility;
+
+ std::string first_name;
+ std::string last_name;
+ std::size_t person_id;
+ std::string member_type;
+
+ std::cout << "Enter a person's information. Press enter for the first name "
+ "field to complete data entry.\n\n";
+
+ for (;;) {
+ if (store.people_size() == store.people_max_size()) {
+ clear_cerr();
+
+ std::cerr << "Maximum number of people reached.\n";
+
+ break;
+ }
+
+ first_name = prompt("First Name: ");
+
+ if (first_name.empty()) {
+ break;
+ }
+
+ last_name = prompt("Last Name: ");
+ person_id = std::stoul(prompt("ID: "));
+ member_type = prompt("Member Type (1 = person, 2 = member): ");
+
+ if (member_type == "1") {
+ store.append_customer(
+ consumer::customer(first_name, last_name, person_id, false));
+ } else if (member_type == "2") {
+ store.append_customer(
+ consumer::customer(first_name, last_name, person_id, true));
+ } else {
+ clear_cerr();
+
+ std::cerr << "Invalid member type. Try again.\n";
+ }
+
+ std::cout << '\n';
+ }
+}
+
+auto manage(book_store::store &store) -> void {
+ using namespace book_store::utility;
+
+ std::cout << "\nYou may now search for books by title, author, purchase by "
+ "ISBN, view a person's transactions by ID, or tick a year "
+ "forward. Press enter to "
+ "exit.\n\n";
+
+ for (;;) {
+ std::string search_type;
+ std::string search_term;
+
+ search_type =
+ prompt("Operation (title, author, purchase, transactions, tick): ");
+
+ if (search_type.empty()) {
+ break;
+ }
+
+ if (search_type == "title") {
+ search_term = prompt("\nEnter the title: ");
+
+ for (const auto &book : store.find_books_by_title(search_term)) {
+ std::cout << book << '\n';
+ }
+ } else if (search_type == "author") {
+ search_term = prompt("\nEnter the author: ");
+
+ for (const auto &book : store.find_books_by_author(search_term)) {
+ std::cout << book << '\n';
+ }
+ } else if (search_type == "purchase") {
+ purchase_handler(store);
+ } else if (search_type == "tick") {
+ store.tick_year();
+ } else if (search_type == "transactions") {
+ for (const auto &book : store.transactions_by_id(
+ std::stoul(prompt("Enter the person's ID: ")))) {
+ std::cout << '\n' << book << "\n";
+ }
+ } else {
+ clear_cerr();
+
+ std::cerr << "Invalid search type. Try again.\n\n";
+ }
+ }
+}
+} // namespace book_store::library
diff --git a/source/main.cc b/source/main.cc
index 5d262f7..39cff8e 100644
--- a/source/main.cc
+++ b/source/main.cc
@@ -1,99 +1,22 @@
-#include <algorithm>
-#include <array>
-#include <cstdlib>
-#include <exception>
-#include <iostream>
-#include <random>
-#include <ranges>
-#include <string>
-#include <string_view>
+#include <cstddef>
-#include <book_store/book.hh>
-#include <book_store/member.hh>
-#include <book_store/random.hh>
+#include <book_store/library.hh>
+#include <book_store/store.hh>
auto main() -> int {
using namespace book_store;
-
- std::array<product::book, 100> books;
- std::array<std::string, 100> book_titles;
- std::array<consumer::member, 100> members;
- std::array<std::size_t, 100> member_ids;
- std::mt19937 random_number_generator(std::random_device{}());
- utility::random::book_random_engine random(random_number_generator);
- auto perform = [](std::string_view name, auto action) {
- std::cout << name << " ...";
-
- action();
-
- std::cout << " ok.\n";
- };
- auto clear_cerr = []() {
- if (!std::cerr.good()) {
- std::cerr.clear();
- }
- };
-
- perform("populating books and book_titles", [&]() {
- for (auto [index, book] : std::ranges::views::enumerate(books)) {
- auto random_title = random.title();
-
- book_titles[static_cast<std::size_t>(index)] = random_title;
- book =
- product::book{random_title, random.authors(), random.publisher(),
- random.isbn(), random.price_usd(), random.copy_count()};
- }
- });
- perform("verifying all books are present", [&]() {
- for (const auto &title : book_titles) {
- if (std::ranges::find(book_titles, title) == book_titles.end()) {
- clear_cerr();
-
- std::cerr << "error: title not found" << '\n';
-
- std::terminate();
- }
- }
- });
- perform("verifying book copy count increment", [&]() {
- for (int i = 0; i < 100; ++i) {
- auto &book = books[std::uniform_int_distribution<std::size_t>(
- 0, books.size() - 1)(random_number_generator)];
- auto copy_count = book.copies();
- auto random_copy_count = static_cast<product::book::size_type>(
- std::uniform_int_distribution<std::size_t>(0, 100)(
- random_number_generator));
-
- book.copies(copy_count + random_copy_count);
-
- if (book.copies() != copy_count + random_copy_count) {
- clear_cerr();
-
- std::cerr << "error: invalid copy count after increment" << '\n';
-
- std::terminate();
- }
- }
- });
- perform("populating members and member_ids", [&]() {
- for (auto [index, member] : std::views::enumerate(members)) {
- auto random_name = random.name();
- auto random_surname = random.name();
- auto random_id = random.id();
-
- member_ids[static_cast<std::size_t>(index)] = random_id;
- member = consumer::member{random_name, random_surname, random_id};
- }
- });
- perform("verifying all members are present", [&]() {
- for (auto member_id : member_ids) {
- if (std::ranges::find(member_ids, member_id) == member_ids.end()) {
- std::cerr << "error: id not found" << '\n';
-
- std::terminate();
- }
- }
- });
+ using namespace book_store::library;
+
+ constexpr std::size_t LIBRARY_BOOK_SIZE = 1000;
+ constexpr std::size_t LIBRARY_CUSTOMER_SIZE = 500;
+ constexpr std::size_t YEARLY_MEMBERSHIP_FEE_USD = 10;
+ constexpr std::size_t MEMBER_DISCOUNT_PERCENT = 5;
+ book_store::store store(LIBRARY_BOOK_SIZE, LIBRARY_CUSTOMER_SIZE,
+ YEARLY_MEMBERSHIP_FEE_USD, MEMBER_DISCOUNT_PERCENT);
+
+ populate_books(store, LIBRARY_BOOK_SIZE);
+ populate_consumers(store);
+ manage(store);
return 0;
}
diff --git a/source/member.cc b/source/member.cc
deleted file mode 100644
index 0e8a41c..0000000
--- a/source/member.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-#include <book_store/book.hh>
-#include <book_store/member.hh>
-#include <book_store/price.hh>
-
-namespace book_store::consumer {
-using namespace product;
-
-auto member::books_bought() const noexcept -> book::size_type {
- return this->_books_bought;
-}
-
-auto member::amount_spent() const noexcept -> price::usd {
- return this->_amount_spent;
-}
-
-auto member::books_bought(book::size_type books_bought) noexcept -> void {
- this->_books_bought = books_bought;
-}
-
-auto member::amount_spent(price::usd amount_spent) noexcept -> void {
- this->_amount_spent = amount_spent;
-}
-} // namespace book_store::consumer
diff --git a/source/person.cc b/source/person.cc
index e80e6df..542bfe7 100644
--- a/source/person.cc
+++ b/source/person.cc
@@ -5,14 +5,14 @@
#include <book_store/person.hh>
namespace book_store::consumer {
-auto person::last_name() const noexcept -> std::string_view {
- return this->_last_name;
-}
-
auto person::first_name() const noexcept -> std::string_view {
return this->_first_name;
}
+auto person::last_name() const noexcept -> std::string_view {
+ return this->_last_name;
+}
+
auto person::full_name(bool last_first) const noexcept -> std::string {
if (last_first) {
return this->_last_name + ", " + this->_first_name;
@@ -23,14 +23,14 @@ auto person::full_name(bool last_first) const noexcept -> std::string {
auto person::id() const noexcept -> std::size_t { return this->_id; }
-auto person::last_name(std::string_view last_name) noexcept -> person & {
- this->_last_name = last_name;
+auto person::first_name(std::string_view first_name) noexcept -> person & {
+ this->_first_name = first_name;
return *this;
}
-auto person::first_name(std::string_view first_name) noexcept -> person & {
- this->_first_name = first_name;
+auto person::last_name(std::string_view last_name) noexcept -> person & {
+ this->_last_name = last_name;
return *this;
}
diff --git a/source/price.cc b/source/price.cc
new file mode 100644
index 0000000..2914bd3
--- /dev/null
+++ b/source/price.cc
@@ -0,0 +1,7 @@
+#include <book_store/price.hh>
+
+namespace book_store::product::price {
+[[nodiscard]] auto usd::value() const noexcept -> price_type {
+ return this->_price;
+}
+} // namespace book_store::product::price
diff --git a/source/purchase_error.cc b/source/purchase_error.cc
new file mode 100644
index 0000000..f9ae931
--- /dev/null
+++ b/source/purchase_error.cc
@@ -0,0 +1,21 @@
+#include <string_view>
+
+#include <book_store/purchase_error.hh>
+
+namespace book_store {
+[[nodiscard]] auto
+purchase_error::error() const noexcept -> purchase_error_type {
+ return this->_error;
+}
+
+[[nodiscard]] auto purchase_error::what() const noexcept -> std::string_view {
+ switch (this->_error) {
+ case person_not_found:
+ return "Person not found";
+ case book_not_found:
+ return "Book not found";
+ case not_enough_stock:
+ return "Not enough stock";
+ }
+}
+} // namespace book_store
diff --git a/source/random.cc b/source/random.cc
index d237b9f..e27ffa7 100644
--- a/source/random.cc
+++ b/source/random.cc
@@ -9,12 +9,12 @@
namespace book_store::utility::random {
auto book_random_engine::title() -> std::string {
- static std::uniform_int_distribution<> distrubtion{0, 49};
+ static std::uniform_int_distribution<> distribution{0, 49};
std::string book_name;
for (int i = 0; i < 3; ++i) {
book_name += book_title_parts[static_cast<std::size_t>(
- distrubtion(this->_random_number_generator))];
+ distribution(this->_random_number_generator))];
book_name += ' ';
}
@@ -24,10 +24,10 @@ auto book_random_engine::title() -> std::string {
}
auto book_random_engine::name() -> std::string {
- static std::uniform_int_distribution<> distrubtion{0, 49};
+ static std::uniform_int_distribution<> distribution{0, 49};
return std::string(names[static_cast<std::size_t>(
- distrubtion(this->_random_number_generator))]);
+ distribution(this->_random_number_generator))]);
}
auto book_random_engine::author() -> consumer::person {
@@ -36,8 +36,8 @@ auto book_random_engine::author() -> consumer::person {
auto book_random_engine::authors() -> std::vector<consumer::person> {
std::vector<consumer::person> authors;
- static std::uniform_int_distribution<> distrubtion{1, 4};
- auto author_count = distrubtion(this->_random_number_generator);
+ static std::uniform_int_distribution<> distribution{1, 4};
+ auto author_count = distribution(this->_random_number_generator);
authors.reserve(static_cast<std::size_t>(author_count));
@@ -49,39 +49,39 @@ auto book_random_engine::authors() -> std::vector<consumer::person> {
}
auto book_random_engine::publisher() -> std::string {
- static std::uniform_int_distribution<> distrubtion{0, 9};
+ static std::uniform_int_distribution<> distribution{0, 9};
return std::string(publishers[static_cast<std::size_t>(
- distrubtion(this->_random_number_generator))]);
+ distribution(this->_random_number_generator))]);
}
auto book_random_engine::isbn() -> std::string {
- static std::uniform_int_distribution<> distrubtion{0, 9};
+ static std::uniform_int_distribution<> distribution{0, 9};
std::string isbn;
for (int i = 0; i < 13; ++i) {
- isbn += std::to_string(distrubtion(this->_random_number_generator));
+ isbn += std::to_string(distribution(this->_random_number_generator));
}
return isbn;
}
auto book_random_engine::price_usd() -> double {
- static std::uniform_real_distribution<> distrubtion{0.0, 100.0};
+ static std::uniform_real_distribution<> distribution{0.0, 100.0};
- return distrubtion(this->_random_number_generator);
+ return distribution(this->_random_number_generator);
}
auto book_random_engine::copy_count() -> product::book::size_type {
- static std::uniform_int_distribution<> distrubtion{0, 10000};
+ static std::uniform_int_distribution<> distribution{0, 10000};
return static_cast<product::book::size_type::book_count_type>(
- distrubtion(this->_random_number_generator));
+ distribution(this->_random_number_generator));
}
auto book_random_engine::id() -> std::size_t {
- static std::uniform_int_distribution<> distrubtion{0, 1000000};
+ static std::uniform_int_distribution<> distribution{0, 1000000};
- return static_cast<std::size_t>(distrubtion(this->_random_number_generator));
+ return static_cast<std::size_t>(distribution(this->_random_number_generator));
}
} // namespace book_store::utility::random
diff --git a/source/store.cc b/source/store.cc
new file mode 100644
index 0000000..528e358
--- /dev/null
+++ b/source/store.cc
@@ -0,0 +1,235 @@
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <book_store/book.hh>
+#include <book_store/book_count.hh>
+#include <book_store/customer.hh>
+#include <book_store/price.hh>
+#include <book_store/purchase_error.hh>
+#include <book_store/store.hh>
+
+namespace book_store {
+auto store::append_book(const product::book &book) -> void {
+ if (this->books_max_size() != 0 &&
+ this->_books.size() < this->books_max_size()) {
+ this->_books.push_back(book);
+ }
+}
+
+auto store::append_customer(const customer_type &person) -> void {
+ if (this->people_max_size() != 0 &&
+ this->_customers.size() < this->people_max_size()) {
+ this->_customers.push_back(person);
+ }
+}
+
+auto store::purchase_book(size_type person_id, std::string_view isbn,
+ product::book_count count)
+ -> std::optional<purchase_error> {
+ auto person = this->find_person_by_id(person_id);
+
+ if (!person) {
+ return purchase_error(
+ purchase_error::purchase_error_type::person_not_found);
+ }
+
+ auto book = this->find_book_by_isbn(isbn);
+
+ if (!book) {
+ return purchase_error(purchase_error::purchase_error_type::book_not_found);
+ }
+
+ if (book->get().copies() < count) {
+ return purchase_error(
+ purchase_error::purchase_error_type::not_enough_stock);
+ }
+
+ auto &customer = *person;
+ auto book_price = book.value().get().price_usd();
+
+ if (customer.is_member()) {
+ auto discount = 1.0 - (this->_membership_discount_percent * 0.01);
+
+ book_price =
+ static_cast<product::price::usd::price_type>(book_price * discount);
+
+ if (customer.books_bought() % 11 == 0) {
+ auto transaction_count = 0;
+ auto transaction_total = 0.0;
+
+ for (auto &transaction : this->_transactions) {
+ if (transaction.first == person_id) {
+ transaction_total += transaction.second.price_usd();
+ transaction_count += 1;
+ }
+ }
+
+ if (transaction_count >= 10) {
+ book_price = (transaction_total / transaction_count) * discount;
+ }
+
+ customer.amount_spent(0);
+ }
+ }
+
+ customer.books_bought(customer.books_bought() + count);
+ // There's some fun casting going on here. It's all safe, though ... probably.
+ customer.amount_spent(customer.amount_spent() +
+ static_cast<product::price::usd::price_type>(
+ (static_cast<product::book_count::book_count_type>(
+ book_price.value()) *
+ count.value())));
+ book.value().get().copies(book.value().get().copies() - count);
+ this->_transactions.emplace_back(
+ person_id, product::book{book.value().get().title().data(),
+ book.value().get().authors(),
+ book.value().get().publisher().data(),
+ book.value().get().isbn().data(), book_price,
+ product::book_count(count)});
+
+ return std::nullopt;
+}
+
+auto store::tick_year() -> void {
+ for (auto &customer : this->_customers) {
+ customer.amount_spent(customer.amount_spent() + this->_membership_fee);
+ }
+}
+
+auto store::membership_fee(product::price::usd fee) -> void {
+ this->_membership_fee = fee;
+}
+
+auto store::membership_discount_percent(discount_percent_type discount_percent)
+ -> void {
+ this->_membership_discount_percent = discount_percent;
+}
+
+auto store::books_max_size() const noexcept -> size_type {
+ return this->_books_max_size;
+}
+
+auto store::people_max_size() const noexcept -> size_type {
+ return this->_customers_max_size;
+}
+
+auto store::books_size() const noexcept -> size_type {
+ return this->_books.size();
+}
+
+auto store::people_size() const noexcept -> size_type {
+ return this->_customers.size();
+}
+
+auto store::membership_fee() const noexcept -> product::price::usd {
+ return this->_membership_fee;
+}
+
+auto store::membership_discount_percent() const noexcept
+ -> discount_percent_type {
+ return this->_membership_discount_percent;
+}
+
+auto store::find_person_by_id(size_type person_id)
+ -> std::optional<customer_type> {
+ for (auto &person : this->_customers) {
+ if (person.id() == person_id) {
+ return person;
+ }
+ }
+
+ return std::nullopt;
+}
+
+auto store::find_book_by_isbn(std::string_view isbn)
+ -> std::optional<std::reference_wrapper<product::book>> {
+ for (auto &book : this->_books) {
+ std::string book_isbn = book.isbn().data();
+ std::string search_isbn = isbn.data();
+
+ std::transform(
+ book_isbn.begin(), book_isbn.end(), book_isbn.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+ std::transform(
+ search_isbn.begin(), search_isbn.end(), search_isbn.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+
+ if (book_isbn == search_isbn) {
+ return book;
+ }
+ }
+
+ return std::nullopt;
+}
+
+auto store::find_books_by_title(std::string_view title)
+ -> std::vector<product::book> {
+ std::vector<product::book> books;
+
+ for (auto &book : this->_books) {
+ std::string book_title = book.title().data();
+ std::string search_title = title.data();
+
+ std::transform(
+ book_title.begin(), book_title.end(), book_title.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+ std::transform(
+ search_title.begin(), search_title.end(), search_title.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+
+ if (book_title.find(search_title) != std::string::npos) {
+ books.push_back(book);
+ }
+ }
+
+ return books;
+}
+
+auto store::find_books_by_author(std::string_view name)
+ -> std::vector<product::book> {
+ std::vector<product::book> books;
+
+ for (auto &book : this->_books) {
+ for (auto &author : book.authors()) {
+ std::string author_name = author.full_name();
+ std::string search_name = name.data();
+
+ std::transform(
+ author_name.begin(), author_name.end(), author_name.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+ std::transform(
+ search_name.begin(), search_name.end(), search_name.begin(),
+ [](unsigned char character) { return std::tolower(character); });
+
+ if (author_name.find(search_name) != std::string::npos) {
+ books.push_back(book);
+ }
+ }
+ }
+
+ return books;
+}
+
+auto store::transactions_by_id(size_type person_id)
+ -> std::vector<product::book> {
+ std::vector<product::book> books;
+
+ for (auto &transaction : this->_transactions) {
+ if (transaction.first == person_id) {
+ books.push_back(transaction.second);
+ }
+ }
+
+ return books;
+}
+
+auto store::books() -> books_type & { return this->_books; }
+
+auto store::people() -> customer_container & { return this->_customers; }
+} // namespace book_store
diff --git a/source/test.cc b/source/test.cc
new file mode 100644
index 0000000..09e6bc5
--- /dev/null
+++ b/source/test.cc
@@ -0,0 +1,96 @@
+#include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <exception>
+#include <iostream>
+#include <random>
+#include <ranges>
+#include <string>
+#include <string_view>
+
+#include <book_store/book.hh>
+#include <book_store/customer.hh>
+#include <book_store/random.hh>
+#include <book_store/utility.hh>
+
+auto main() -> int {
+ using namespace book_store;
+ using namespace book_store::utility;
+
+ std::array<product::book, 100> books;
+ std::array<std::string, 100> book_titles;
+ std::array<consumer::customer, 100> members;
+ std::array<std::size_t, 100> member_ids;
+ std::mt19937 random_number_generator(std::random_device{}());
+ utility::random::book_random_engine random(random_number_generator);
+ auto perform = [](std::string_view name, auto action) {
+ std::cout << name << " ...";
+
+ action();
+
+ std::cout << " ok.\n";
+ };
+
+ perform("populating books and book_titles", [&]() {
+ for (auto [index, book] : std::ranges::views::enumerate(books)) {
+ auto random_title = random.title();
+
+ book_titles[static_cast<std::size_t>(index)] = random_title;
+ book =
+ product::book{random_title, random.authors(), random.publisher(),
+ random.isbn(), random.price_usd(), random.copy_count()};
+ }
+ });
+ perform("verifying all books are present", [&]() {
+ for (const auto &title : book_titles) {
+ if (std::ranges::find(book_titles, title) == book_titles.end()) {
+ clear_cerr();
+
+ std::cerr << "error: title not found" << '\n';
+
+ std::terminate();
+ }
+ }
+ });
+ perform("verifying book copy count increment", [&]() {
+ for (int i = 0; i < 100; ++i) {
+ auto &book = books[std::uniform_int_distribution<std::size_t>(
+ 0, books.size() - 1)(random_number_generator)];
+ auto copy_count = book.copies();
+ auto random_copy_count = static_cast<product::book::size_type>(
+ std::uniform_int_distribution<std::size_t>(0, 100)(
+ random_number_generator));
+
+ book.copies(copy_count + random_copy_count);
+
+ if (book.copies() != copy_count + random_copy_count) {
+ clear_cerr();
+
+ std::cerr << "error: invalid copy count after increment" << '\n';
+
+ std::terminate();
+ }
+ }
+ });
+ perform("populating members and member_ids", [&]() {
+ for (auto [index, member] : std::views::enumerate(members)) {
+ auto random_name = random.name();
+ auto random_surname = random.name();
+ auto random_id = random.id();
+
+ member_ids[static_cast<std::size_t>(index)] = random_id;
+ member = consumer::customer{random_name, random_surname, random_id};
+ }
+ });
+ perform("verifying all members are present", [&]() {
+ for (auto member_id : member_ids) {
+ if (std::ranges::find(member_ids, member_id) == member_ids.end()) {
+ std::cerr << "error: id not found" << '\n';
+
+ std::terminate();
+ }
+ }
+ });
+
+ return 0;
+}
diff --git a/source/utility.cc b/source/utility.cc
new file mode 100644
index 0000000..0cfd818
--- /dev/null
+++ b/source/utility.cc
@@ -0,0 +1,23 @@
+#include <iostream>
+#include <string>
+#include <string_view>
+
+#include <book_store/utility.hh>
+
+namespace book_store::utility {
+auto prompt(std::string_view message) -> std::string {
+ std::string input;
+
+ std::cout << message;
+
+ std::getline(std::cin, input);
+
+ return input;
+}
+
+auto clear_cerr() -> void {
+ if (!std::cerr.good()) {
+ std::cerr.clear();
+ }
+}
+} // namespace book_store::utility