|
| 1 | +#include "LibraryManagement.h" |
| 2 | + |
| 3 | +// ------------ helpers ------------ |
| 4 | +static inline string trim(const string &s) { |
| 5 | + size_t a = s.find_first_not_of(" \t\r\n"); |
| 6 | + if (a == string::npos) return ""; |
| 7 | + size_t b = s.find_last_not_of(" \t\r\n"); |
| 8 | + return s.substr(a, b - a + 1); |
| 9 | +} |
| 10 | + |
| 11 | +static inline string lower(const string &s) { |
| 12 | + string t = s; |
| 13 | + transform(t.begin(), t.end(), t.begin(), ::tolower); |
| 14 | + return t; |
| 15 | +} |
| 16 | + |
| 17 | +// ------------ Book ------------ |
| 18 | +Book::Book() : id(-1), totalCopies(0), availableCopies(0) {} |
| 19 | +Book::Book(int i, string t, string a, string c, int copies) |
| 20 | + : id(i), title(move(t)), author(move(a)), category(move(c)), |
| 21 | + totalCopies(copies), availableCopies(copies) {} |
| 22 | + |
| 23 | +int Book::getId() const { return id; } |
| 24 | +string Book::getTitle() const { return title; } |
| 25 | +string Book::getAuthor() const { return author; } |
| 26 | +string Book::getCategory() const { return category; } |
| 27 | +int Book::getTotalCopies() const { return totalCopies; } |
| 28 | +int Book::getAvailableCopies() const { return availableCopies; } |
| 29 | + |
| 30 | +void Book::setTitle(const string &t) { title = t; } |
| 31 | +void Book::setAuthor(const string &a) { author = a; } |
| 32 | +void Book::setCategory(const string &c) { category = c; } |
| 33 | +void Book::setTotalCopies(int c) { |
| 34 | + if (c < 0) throw runtime_error("Total copies cannot be negative"); |
| 35 | + int issued = totalCopies - availableCopies; |
| 36 | + if (c < issued) throw runtime_error("Cannot set total copies less than already issued copies"); |
| 37 | + totalCopies = c; |
| 38 | + availableCopies = c - issued; |
| 39 | +} |
| 40 | + |
| 41 | +bool Book::issueOne() { |
| 42 | + if (availableCopies <= 0) return false; |
| 43 | + --availableCopies; |
| 44 | + return true; |
| 45 | +} |
| 46 | + |
| 47 | +bool Book::returnOne() { |
| 48 | + if (availableCopies >= totalCopies) return false; |
| 49 | + ++availableCopies; |
| 50 | + return true; |
| 51 | +} |
| 52 | + |
| 53 | +string Book::toCSV() const { |
| 54 | + return to_string(id) + "," + title + "," + author + "," + category + "," + |
| 55 | + to_string(totalCopies) + "," + to_string(availableCopies); |
| 56 | +} |
| 57 | + |
| 58 | +Book Book::fromCSV(const string &line) { |
| 59 | + stringstream ss(line); |
| 60 | + string id, t, a, c, total, avail; |
| 61 | + getline(ss, id, ','); |
| 62 | + getline(ss, t, ','); |
| 63 | + getline(ss, a, ','); |
| 64 | + getline(ss, c, ','); |
| 65 | + getline(ss, total, ','); |
| 66 | + getline(ss, avail, ','); |
| 67 | + Book b; |
| 68 | + b = Book(stoi(id), trim(t), trim(a), trim(c), stoi(total)); |
| 69 | + b.setTotalCopies(stoi(total)); |
| 70 | + while (b.getAvailableCopies() > stoi(avail)) b.issueOne(); |
| 71 | + return b; |
| 72 | +} |
| 73 | + |
| 74 | +// ------------ Member ------------ |
| 75 | +Member::Member() : id(-1) {} |
| 76 | +Member::Member(int i, string n, string e) : id(i), name(move(n)), email(move(e)) {} |
| 77 | + |
| 78 | +int Member::getId() const { return id; } |
| 79 | +string Member::getName() const { return name; } |
| 80 | +string Member::getEmail() const { return email; } |
| 81 | + |
| 82 | +void Member::setName(const string &n) { name = n; } |
| 83 | +void Member::setEmail(const string &e) { email = e; } |
| 84 | + |
| 85 | +string Member::toCSV() const { return to_string(id) + "," + name + "," + email; } |
| 86 | + |
| 87 | +Member Member::fromCSV(const string &line) { |
| 88 | + stringstream ss(line); |
| 89 | + string id, n, e; |
| 90 | + getline(ss, id, ','); |
| 91 | + getline(ss, n, ','); |
| 92 | + getline(ss, e, ','); |
| 93 | + return Member(stoi(id), trim(n), trim(e)); |
| 94 | +} |
| 95 | + |
| 96 | +// ------------ Library ------------ |
| 97 | +Library::Library() { loadAll(); } |
| 98 | +Library::~Library() { saveAll(); } |
| 99 | + |
| 100 | +void Library::loadAll() { |
| 101 | + ifstream fb(booksFile); |
| 102 | + if (fb) { |
| 103 | + string line; |
| 104 | + while (getline(fb, line)) |
| 105 | + if (!trim(line).empty()) { |
| 106 | + Book b = Book::fromCSV(line); |
| 107 | + books[b.getId()] = b; |
| 108 | + nextBookId = max(nextBookId, b.getId() + 1); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + ifstream fm(membersFile); |
| 113 | + if (fm) { |
| 114 | + string line; |
| 115 | + while (getline(fm, line)) |
| 116 | + if (!trim(line).empty()) { |
| 117 | + Member m = Member::fromCSV(line); |
| 118 | + members[m.getId()] = m; |
| 119 | + nextMemberId = max(nextMemberId, m.getId() + 1); |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +void Library::saveAll() { |
| 125 | + ofstream fb(booksFile); |
| 126 | + for (auto &[_, b] : books) |
| 127 | + fb << b.toCSV() << "\n"; |
| 128 | + |
| 129 | + ofstream fm(membersFile); |
| 130 | + for (auto &[_, m] : members) |
| 131 | + fm << m.toCSV() << "\n"; |
| 132 | +} |
| 133 | + |
| 134 | +// Books |
| 135 | +int Library::addBook(const string &t, const string &a, const string &c, int copies) { |
| 136 | + int id = nextBookId++; |
| 137 | + books[id] = Book(id, t, a, c, copies); |
| 138 | + return id; |
| 139 | +} |
| 140 | + |
| 141 | +void Library::updateBook(int id, const optional<string> &t, const optional<string> &a, |
| 142 | + const optional<string> &c, const optional<int> &copies) { |
| 143 | + auto it = books.find(id); |
| 144 | + if (it == books.end()) throw runtime_error("Book not found"); |
| 145 | + if (t) it->second.setTitle(*t); |
| 146 | + if (a) it->second.setAuthor(*a); |
| 147 | + if (c) it->second.setCategory(*c); |
| 148 | + if (copies) it->second.setTotalCopies(*copies); |
| 149 | +} |
| 150 | + |
| 151 | +void Library::deleteBook(int id) { |
| 152 | + if (!books.count(id)) throw runtime_error("Book not found"); |
| 153 | + books.erase(id); |
| 154 | +} |
| 155 | + |
| 156 | +vector<Book> Library::listBooks() const { |
| 157 | + vector<Book> v; |
| 158 | + for (auto &[_, b] : books) v.push_back(b); |
| 159 | + sort(v.begin(), v.end(), [](auto &x, auto &y){return x.getId()<y.getId();}); |
| 160 | + return v; |
| 161 | +} |
| 162 | + |
| 163 | +vector<Book> Library::searchByTitle(const string &q) const { |
| 164 | + vector<Book> r; |
| 165 | + for (auto &[_, b] : books) |
| 166 | + if (lower(b.getTitle()).find(lower(q)) != string::npos) |
| 167 | + r.push_back(b); |
| 168 | + return r; |
| 169 | +} |
| 170 | + |
| 171 | +vector<Book> Library::searchByAuthor(const string &q) const { |
| 172 | + vector<Book> r; |
| 173 | + for (auto &[_, b] : books) |
| 174 | + if (lower(b.getAuthor()).find(lower(q)) != string::npos) |
| 175 | + r.push_back(b); |
| 176 | + return r; |
| 177 | +} |
| 178 | + |
| 179 | +vector<Book> Library::searchByCategory(const string &q) const { |
| 180 | + vector<Book> r; |
| 181 | + for (auto &[_, b] : books) |
| 182 | + if (lower(b.getCategory()).find(lower(q)) != string::npos) |
| 183 | + r.push_back(b); |
| 184 | + return r; |
| 185 | +} |
| 186 | + |
| 187 | +// Members |
| 188 | +int Library::addMember(const string &n, const string &e) { |
| 189 | + int id = nextMemberId++; |
| 190 | + members[id] = Member(id, n, e); |
| 191 | + return id; |
| 192 | +} |
| 193 | + |
| 194 | +void Library::updateMember(int id, const optional<string>& n, const optional<string>& e) { |
| 195 | + auto it = members.find(id); |
| 196 | + if (it == members.end()) throw runtime_error("Member not found"); |
| 197 | + if (n) it->second.setName(*n); |
| 198 | + if (e) it->second.setEmail(*e); |
| 199 | +} |
| 200 | + |
| 201 | +void Library::deleteMember(int id) { |
| 202 | + if (!members.count(id)) throw runtime_error("Member not found"); |
| 203 | + members.erase(id); |
| 204 | +} |
| 205 | + |
| 206 | +vector<Member> Library::listMembers() const { |
| 207 | + vector<Member> v; |
| 208 | + for (auto &[_, m] : members) v.push_back(m); |
| 209 | + sort(v.begin(), v.end(), [](auto &x, auto &y){return x.getId()<y.getId();}); |
| 210 | + return v; |
| 211 | +} |
| 212 | + |
| 213 | +// Transactions |
| 214 | +void Library::issueBook(int memberId, int bookId) { |
| 215 | + if (!members.count(memberId)) throw runtime_error("Invalid member"); |
| 216 | + if (!books.count(bookId)) throw runtime_error("Invalid book"); |
| 217 | + if (!books[bookId].issueOne()) throw runtime_error("No available copies"); |
| 218 | + transactions.push_back({memberId, bookId, "2025-10-23", false}); |
| 219 | +} |
| 220 | + |
| 221 | +void Library::returnBook(int memberId, int bookId) { |
| 222 | + if (!members.count(memberId)) throw runtime_error("Invalid member"); |
| 223 | + if (!books.count(bookId)) throw runtime_error("Invalid book"); |
| 224 | + books[bookId].returnOne(); |
| 225 | + for (auto &tx : transactions) |
| 226 | + if (tx.memberId == memberId && tx.bookId == bookId && !tx.returned) { |
| 227 | + tx.returned = true; |
| 228 | + return; |
| 229 | + } |
| 230 | +} |
0 commit comments