diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a0a822b..ad070faa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,9 @@ This file documents all notable changes made to ITFlow. ### Upgrading to 26.11 -Update from Maintenance > Update — Queue Update hands the job to cron, which updates the files and applies this release's database changes (2.7.9 to 3.0.6) in the same pass. Nothing else is required. +Update from Maintenance > Update — Queue Update hands the job to cron, which updates the files and applies this release's database changes (2.7.9 to 3.0.7) in the same pass. Nothing else is required. -This is a big release, with twenty-seven database steps in one go, so take a backup before you update. +This is a big release, with twenty-eight database steps in one go, so take a backup before you update. The update also clears out data that deletes in older versions left behind, all of it pointing at something that no longer exists: @@ -29,6 +29,8 @@ Trips already logged keep the miles they were entered with (see Breaking Changes Other income already entered (money in that isn't for an invoice, which used to be called revenue) takes who entered it from the audit log, where it has a record of it. Income with no category now counts in the dashboard and reports, so their totals can go up by that much (see Breaking Changes, Income). +Every account is given a kind - checking, savings, credit card, cash or payment processor - guessed from its name: Visa, Amex or card is a credit card, savings or reserve is savings, cash or petty cash is cash, and Stripe, PayPal, Square or merchant is a payment processor. An account given a liability type back when accounts had types (2023) is a credit card, so what is on it reads as owed. Anything else is checking. Change any that came out wrong with Edit on the Accounts page. An account's balance now leaves out archived payments, income and expenses everywhere, so the Accounts page can show less or more than before (see Bug Fixes). + If you are coming from 26.08 or earlier, the database update still has to be run from the command line one time, exactly as described under Upgrading to 26.09 below. ### Breaking Changes and Notes @@ -103,6 +105,13 @@ If you are coming from 26.08 or earlier, the database update still has to be run - The Income list's Bulk menu has Set Category, Set Account, Set Payment Method and the new Set Client, a window each, and Delete. A payment's category is its invoice's, so Set Category on a payment sets its invoice's category, as before. A payment's client is its invoice's, so Set Client leaves payments alone, and payments are deleted from their invoice, not here. - The export's Type column says Invoice payment or Other income (it was Payment or Revenue), and Source is now What for, with new Invoice and Paid Online columns. - In Transactions, a payment's date opens its quick look instead of the invoice. The quick look has Open Invoice. +- Accounts, transactions and transfers: + - The Transfers page is now the Transfers pill of Transactions. The Transfers link stays in the menu and opens it for every account. An old link to `transfers.php` lands there with its account and dates. The old page could filter by the account a transfer came from and the one it went to together. Now pick one account, and its Transfers pill shows the transfers into and out of it. + - The New Transfer window no longer has the list of recent payments to copy into the notes. + - A transfer takes the currency of the account it leaves. It used to take the company's currency whatever the accounts. + - An account can only be archived once it holds nothing and no payment provider pays into it, and only an account that was never used can be deleted. The Accounts page used to hide Archive otherwise, but the link itself archived any account, and Delete removed an account with its history still pointing at it. Archive one you no longer use instead. + - The Transactions export's Type column says Invoice payment, Other income, Transfer in, Expense, Transfer out or Reimbursement (it was Payment, Revenue, Transfer In, Expense, Transfer Out or Reimbursement). Money in and out are now separate In and Out columns, with Account, Who or What and Client added. The signed Amount column is still there, but off unless ticked. + - Links to `transactions.php?type=Payment` and the other old type names still work. - Notifications now follow access too. An agent only sees notifications for clients and areas they can open, so someone without sales access no longer sees invoice and quote notifications. - Drop-downs: Tom Select is gone, replaced by ITFlow's own picker. It works the way you are used to: type to search, tick several in a multi-select, and type a new value in fields that allow it, such as ticket watchers. See Developer Updates if you have custom pages that used Tom Select. - Several small windows are gone because the change is now made where the value is shown: @@ -619,6 +628,22 @@ If you are coming from 26.08 or earlier, the database update still has to be run - The payers are remembered for next time - see or forget them from the Import Deposits page. - A payment or other income brought in this way says so on its quick look. - Payments need write access to Sales, as adding one on an invoice does. Without it, deposits can still come in as other income and transfers. +- **Accounts and transactions** (rebuilt) + - Each account has a kind - checking, savings, credit card, cash, payment processor or other - with its own icon wherever the account is picked. A credit card's balance runs below zero as it is spent on, and shows as what is owed on it ("$1,240.00 owed") on the Accounts page, its ledger, the dashboard and every account picker. + - Along the top of the Accounts page: what the accounts hold (cards aside), what is owed on cards, and the money in and out this month against the same days of last month. Money moved between your own accounts doesn't count as in or out. + - Each account shows its description and the defaults it carries (payments go in, expenses paid from, transfers from or to, or a payment provider's payments), its kind and currency, what it holds with its opening balance, what came in and went out this month, and its latest line with how many there are. Its menu has Transactions, Income into it, Expenses from it, Edit, Pay the Card for a card or Transfer from it otherwise, and Archive once it holds nothing. The Archived pill lists archived accounts, each with Restore, and Delete for one never used. + - New Account and Edit are one form with an icon on every field: the name (unique among open accounts), the kind, the currency, the opening balance, a description and notes. The opening balance can be changed later, and the change is logged. The currency is locked once money is recorded on the account. For a card, an opening balance below zero is what was owed on it. + - Transactions is each account's ledger. Pick an account at the top of the filters, or Every account: + - The cards show what it holds now (or owes, for a card), the money in and out over the dates picked, and the difference, with what it held at the end of those dates. + - Every line has its date (opening its quick look), what it was - the invoice paid, the other income, the vendor, the transfer's other account or the agent paid back - with its kind, how it was paid, the reference and a mark when it came from a bank statement, then the category, the client, In, Out and the balance after it. The balance shows for one account sorted by date. With Every account, an Account column takes its place. + - Pills for Money in, Money out and Transfers, each with a count. Filter by the date, category and client, and under More filters by the kind of line, how it was paid and an amount range. Search finds what it was, the description, the reference, the category, the client, a transfer's other account, how it was paid, or an amount. The totals in and out sit under the list. + - Export takes the same filters and pills, for one account or every account. + - Transfers keep their place in the menu, which opens the Transfers pill for every account. Each transfer shows once there, from the account it left, and the cards show what was moved, what paid off cards and what payment processors paid out. + - New Transfer, Pay the Card and Edit are one form: where it comes from and where it goes, each with what it holds now, a Swap button to turn it round, the amount in the currency of the account it leaves, the date, how it was moved and notes. Every field is checked when it is saved: two different open accounts, an amount, a real date and one of your payment methods. Editing moves both sides together. + - A transfer into a credit card is a card payment, and says so on the ledger and in its quick look. Clicking a transfer opens its quick look: what came out of one account and went into the other, with what each holds now and a link to its ledger, how it was moved, who entered it, notes, and Edit and Delete. A transfer's expense opens it too. + - The dashboard's accounts open their ledgers, and a card shows what is owed on it. + - Adding and changing accounts and transfers needs write access to Financial, and deleting full access, as before. Restoring an archived account needs write access. + - The demo data adds a Business Visa for software, hosting, supplies, meals and the other small costs, paid off from Operating Checking each month. ### Bug Fixes @@ -676,6 +701,10 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Income on the dashboard and in the reports counted money moved between your own accounts as income when the transfer had been given a category. - Income: the Invoice payments and Other revenue figures on the Income Summary report opened the whole Income list, as the list didn't understand their links. Sorting the Income list by a column it doesn't have gave a database error. - Income: the revenue form saved whatever was typed, such as no account, no category or an amount of nothing. A revenue's client couldn't be set, so it never counted for the client. +- Accounts: the Accounts page, the dashboard and the account pickers counted archived payments, income and expenses in a balance, while Transactions left them out, so the same account could show two balances. +- Accounts: New Account saved whatever was typed, such as no name, the name of another account or a balance of words. Edit could only change the name and notes. +- Transfers: a transfer could be saved from an account to itself, for nothing, or with no date. Editing one took which expense and income to change from the form, so a changed form could rewrite any expense or income. Neither side recorded who entered it. +- Transactions: opening it for an account that no longer exists wrote PHP warnings. It now says so and shows every account's. - Client Portal: a ticket rating could be saved as any value. Only Good and Bad, the two buttons the portal shows, are accepted now. The demo data rated tickets Great and Ok, which the portal never offers, and now uses Good and Bad. - Notifications that linked to a page did not open correctly from Admin pages. - Messages showed a backslash before apostrophes and quotes in names, or showed `&` instead of `&`. @@ -688,7 +717,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run ### Developer Updates -- Database 2.8.0 to 3.0.5, one file per step under `admin/database_updates/`, with `db.sql` matching 3.0.5: +- Database 2.8.0 to 3.0.7, one file per step under `admin/database_updates/`, with `db.sql` matching 3.0.7: - 2.8.0: `racks.rack_units_descending`, `rack_power_capacity` and `rack_photo_rear`. On `rack_units`: `unit_face`, `unit_full_depth`, `unit_type`, `unit_power_watts`, `unit_notes` and a `unit_asset_id` key. Unlinked items move from asset id 0 to NULL. - 2.8.1: `location_documents` and `location_files`. - 2.8.2: `vendor_tags` (tag type 6), and `vendor_contact_phone_country_code`, `vendor_contact_phone` and `vendor_contact_email`. @@ -728,6 +757,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run - 3.0.4: `trips.trip_own_vehicle` (0), `trip_expense_id` (0, keyed), `trip_billable` (0), `trip_invoice_id` (0, keyed) and `trip_invoice_item_id` (0). `settings.config_mileage_unit` ('mi' or 'km'), `config_mileage_rate` (`decimal(10,4)`, 0), `config_mileage_category_id` (set to an Expense category named Mileage, else Travel), `config_mileage_vendor_id`, `config_trip_charge` (`decimal(15,2)`), `config_trip_bill_rate` (`decimal(10,4)`), `config_trip_included` (`decimal(10,1)`) and `config_trip_item_name` ('Travel'). - 3.0.5: `revenues.revenue_description` becomes `text`, with `revenue_created_by` (0, backfilled from the audit log's Revenue Create entries) and a `revenue_date` key. `payments` gets a `payment_date` key. - 3.0.6: `payments.payment_import_key` and `revenues.revenue_import_key` (keyed), and `income_import_rules` (`import_rule_match` unique, `import_rule_action` - payment, income or transfer - `import_rule_client_id`, `import_rule_category_id`, `import_rule_account_id`, `import_rule_used_count`). + - 3.0.7: no schema change. `accounts.account_type` is set from the name for every account without a kind from 1 to 6 (1 checking, 2 savings, 3 credit card, 4 cash, 5 payment processor, 6 other), the same way as `accountTypeGuess()`, with the 2023 liability types (20 to 29) as 3. Setup and `scripts/setup_cli.php` create the Cash account as type 4. - New shared function files, loaded by `functions.php`: `functions/asset.php`, `asset_interface.php`, `certificate.php`, `client_overview.php`, `contact.php`, `credential.php`, `dashboard.php`, `links.php`, `location.php`, `notifications.php`, `project.php`, `rack.php`, `search.php`, `service.php`, `software.php`, `tags.php`, `ticket.php`, `ticket_fields.php`, `user.php` and `vendor.php`. They hold the queries and rules, so the pages, the API and cron share one definition. - Removed post models: `agent/post/{asset_interface,certificate,credential,domain,event,location,network_ip,network,recurring_ticket,vendor}_model.php`, `admin/post/user_model.php` and `api/v1/certificates/certificate_model.php`. Their handlers call the shared functions above. Interface handlers moved to `agent/post/asset_interface.php`. - New scripts: `js/itflow_select.js`, `quick_pick.js`, `tag_picker.js`, `global_search.js`, `notifications.js`, `dashboard.js`, `client_overview.js`, `client_switcher.js`, `credentials.js` and `autosubmit.js`, and `agent/js/calendar.js`, `calendar_event_modal.js`, `files.js`, `interface_form.js`, `network_form.js`, `project_modal.js` and `ticket_schedule_modal.js`. @@ -954,6 +984,18 @@ If you are coming from 26.08 or earlier, the database update still has to be run - New page `agent/income_import.php` (`?step=columns` and `?step=review`, the statement in `$_SESSION['income_import']`) and `js/income_import.js` (`itflowIncomeImport()`, with each client's open invoices). Handlers in the new `agent/post/income_import.php`: `income_import_upload`, `income_import_columns`, `income_import_save` (POST `picks`, one JSON list of `n`, `take`, `action`, `client`, `invoices`, `category`, `account`, and `receipts`), GET `income_import_discard`, `income_import_rule_delete`, and `income_import_from_statement` and `expense_import_from_statement`, which copy the statement in the session from one side to the other. New window `agent/modals/income/income_import_rules.php`. - The columns step is now `agent/includes/statement_columns.php` (`$columns_form_id`, `$columns_submit`, `$columns_side`), shared by `agent/expense_import.php` and `agent/income_import.php`. `expenseImportGuess()` takes the sign from the words on the lines when they are clear. - CSS: `.itflow-import-details` and `.itflow-import-invoices`. +- Accounts, in the new `functions/accounts.php` (loaded after `income_import.php`): + - `accountTypes()` (id => `label` and `icon`), `accountTypeGuess($name)` and `accountType($row)`. + - `accountBalanceSql($column, $to_date = null)` is the one balance rule: the opening balance plus payments and revenues, less expenses, leaving out archived rows, up to a date when given. `accountBalance($id, $to_date)`, `accountsList($archived = false, $keep_id = 0)` (false open, true archived, null both; each row with `type`, `balance`, `month_in`, `month_out`, `last_date`, `line_count` and `defaults`), `accountRow($id)`, `accountDefaults()` (the settings defaults and active payment providers' accounts), `accountBalanceText($account)` and `accountsStats($accounts)`. `paymentAccounts()` returns `accountsList(false, $keep_id)`, and `dashboardAccountBalances()` uses it too. + - `accountInUse($id)` (payments, revenues, expenses, recurring expenses, payment providers and reimbursements), `accountSave($in, $id, &$error)` (`name`, `type`, `currency_code`, `opening_balance`, `description`, `notes`), `accountArchive()`, `accountRestore()` and `accountDelete()`, which clears the settings defaults that pointed at it. + - `accountLedgerSql($account_id = 0)` is every line as one row set, `txn_kind` one of `accountLedgerKinds()` (`payment`, `revenue`, `transfer_in`, `expense`, `transfer_out`, `reimbursement`), with `txn_id`, `txn_transfer_id`, `txn_account_id`, `txn_date`, `txn_created_at`, `txn_amount` (signed), `txn_what`, `txn_description`, `txn_category_id`, `txn_client_id`, `txn_other_account_id`, `txn_other_name`, `txn_method`, `txn_reference`, `txn_invoice_id`, `txn_currency`, `txn_imported`, the account's and category's names, `client_name`, `other_account_name`, `other_account_type` (a transfer's other account's kind) and `txn_balance`, the running balance per account. Select from it `AS ledger`. The page and export share `accountLedgerFiltersSql($src, &$summary, $with_show, $all_accounts)` (q, kind or the old type names through `accountLedgerTypeNames()`, category, client, method or payment_method, amount_min, amount_max, show, dtf, dtt) and `accountLedgerShows()`. With every account, the `transfers` pill is the out side only. `accountLedgerModal($row)` is the quick look of a line. + - Transfers: `transferRow($id)`, `transferLabel($row)`, `transferWhat($row)` ('Card payment' into a type 3 account), `transferSave($in, $id, &$error)` (`date`, `amount`, `account_from`, `account_to`, `transfer_method`, `notes`; updates both legs on edit, sets `expense_created_by` and `revenue_created_by` on insert) and `transferDelete($id, &$error)`. + - `agent/transactions.php` is rebuilt on these, taking `?account=`, `?show=` (`in`, `out`, `transfers`), `?kind=` or `?type=`, `?category=`, `?client=`, `?method=` or `?payment_method=`, `?amount_min=`, `?amount_max=`, `?q=` and the date filter, and sorting on a whitelist only. `agent/transfers.php` redirects to `transactions.php?show=transfers`, mapping `account_from` or `account_to` to `account`. `agent/accounts.php` takes `?show=archived` and `?q=`. + - Handlers: `agent/post/account.php` - `add_account`, `edit_account`, GET `archive_account` and `restore_account` (Financial at 2) and GET `delete_account` (at 3). `agent/post/transfer.php` - `add_transfer` and `edit_transfer` (with `transfer_id`; the form's `expense_id` and `revenue_id` are no longer read) at 2, GET `delete_transfer` at 3. `export_transactions` in `agent/post/transaction.php` reads the ledger with the page's filters; the `transactions` export columns add `transaction_account`, `transaction_what`, `transaction_client`, `transaction_in` and `transaction_out`, with `transaction_amount` off by default. + - New windows: `agent/modals/account/account_form.php` (shared by `account_add.php`, which takes `?type=`, and `account_edit.php`), `agent/modals/transfer/transfer.php?id=` (quick look) and `transfer_form.php` (shared by `transfer_add.php`, which takes `?from=` and `?to=`, and `transfer_edit.php`). `transaction_export.php` takes the ledger's filters. + - Seeds: `demoDataAccounts()` adds Business Visa, and accounts take `accountTypeGuess()`. `demoBuildCompanyFinancials()` puts the card's categories on it and pays it off from Operating Checking each month with `demoTransfer()`. Company costs paid from the bank are ACH. + - CSS: `.itflow-account-name` and `.itflow-ledger-what`. + - Removed: `agent/post/transfer_model.php`. - The billing helpers (`billingTimeInvoice()`, `quoteConvert()`, `recurringInvoiceGenerate()` and the rest) take the agent's name as plain text. Their handlers now pass `stripslashes($session_name)`, so a name with an apostrophe no longer shows a backslash in history and the audit log. - Sidebar brand: `agent/css/sidebar_brand_fix.css` and `agent/js/sidebar_brand_fix.js` from 26.10 are replaced by CSS in `css/itflow_custom.css`. `includes/load_company_settings.php` now sets `$session_company_logo`. - `alertMessageHtml()` no longer double-encodes, and save messages built from `escapeSql()` values use `stripslashes()` at the flash. diff --git a/admin/database_updates/3.0.7.php b/admin/database_updates/3.0.7.php new file mode 100644 index 000000000..d4d5b3753 --- /dev/null +++ b/admin/database_updates/3.0.7.php @@ -0,0 +1,23 @@ + $account[1], 'opening_balance' => number_format($account[2], 2, '.', ''), 'account_currency_code' => $currency, + 'account_type' => accountTypeGuess($account[0]), 'account_created_at' => demoMonthDateTime(24, 1, 9, 0), ]); } @@ -3244,7 +3246,7 @@ function demoBuildBilling($mysqli, $profile, $index, $client_id, $vendor_id, $mo 'expense_currency_code' => $currency, 'expense_date' => demoMonthDate($month, 14), 'expense_reference' => 'INV-' . demoSerial($profile['abbreviation'] . $expense[0] . $month, 8), - 'expense_payment_method' => 'Credit Card', + 'expense_payment_method' => 'ACH', 'expense_vendor_id' => $context['org_vendors'][$expense[3]] ?? 0, 'expense_client_id' => $client_id, 'expense_category_id' => demoCategoryId($mysqli, $expense[2], 'Expense'), @@ -3348,6 +3350,12 @@ function demoBuildCompanyFinancials($mysqli, $context, &$counts) { ['Conference and travel', 1450.00, 'Travel', 12, 18, 'Alcott and Reyes CPA'], ]; + // The smaller, recurring costs go on the company card, paid off from the + // bank early the next month - so the card shows what is owed on it + $card_id = $context['accounts']['Business Visa'] ?? 0; + $card_categories = ['Software', 'Cloud and Hosting', 'Office Supplies', 'Meals', 'Shipping and Postage', 'Dues and Subscriptions', 'Telecom and Internet']; + $card_spent = []; + // Which purchases put hardware on the shelf, and what they bring in $stock_lines = [ 'UniFi shelf stock for the workshop' => ['Ubiquiti U6-Pro Access Point', 4], @@ -3363,6 +3371,10 @@ function demoBuildCompanyFinancials($mysqli, $context, &$counts) { // Payroll and rent creep up over two years rather than sitting flat $drift = 1 + ((24 - $month) * 0.004); + $on_card = $card_id && in_array($cost[2], $card_categories, true); + if ($on_card) { + $card_spent[$month] = ($card_spent[$month] ?? 0) + round($cost[1] * $drift, 2); + } $expense_id = starterInsert($mysqli, 'expenses', [ 'expense_description' => $cost[0], @@ -3370,11 +3382,12 @@ function demoBuildCompanyFinancials($mysqli, $context, &$counts) { 'expense_currency_code' => $currency, 'expense_date' => demoMonthDate($month, $cost[4]), 'expense_reference' => 'BILL-' . demoSerial('company' . $cost[0] . $month, 8), - 'expense_payment_method' => $cost[0] === 'Payroll' ? 'ACH' : 'Credit Card', + // Payroll and the bills from the bank go by ACH, the card's own costs by card + 'expense_payment_method' => $cost[0] !== 'Payroll' && ($on_card || !$card_id) ? 'Credit Card' : 'ACH', 'expense_vendor_id' => $vendors[$cost[5]] ?? 0, 'expense_client_id' => 0, 'expense_category_id' => demoCategoryId($mysqli, $cost[2], 'Expense'), - 'expense_account_id' => $operating_id, + 'expense_account_id' => $on_card ? $card_id : $operating_id, 'expense_created_at' => demoMonthDateTime($month, $cost[4], 17, 0), ]); $counts['company']++; @@ -3508,6 +3521,13 @@ function demoBuildCompanyFinancials($mysqli, $context, &$counts) { } } + // The card's statement paid in full from the bank on the 2nd of the next month + foreach ($card_spent as $month => $spent) { + if ($month > 0 && $spent > 0) { + demoTransfer($mysqli, $operating_id, $card_id, $spent, $currency, 'Bank Transfer', 'Business Visa statement paid in full', $month - 1, 2, $counts); + } + } + // Card settlements landing in the bank, and the quarterly tax reserve $cash_id = $context['cash_account_id']; for ($month = 24; $month >= 0; $month--) { diff --git a/agent/accounts.php b/agent/accounts.php index 5a97cf3de..e1993c6ad 100644 --- a/agent/accounts.php +++ b/agent/accounts.php @@ -1,129 +1,254 @@ = 2; +$can_delete = lookupUserPermission('module_financial') >= 3; +$show = ($_GET['show'] ?? '') === 'archived' ? 'archived' : ''; +$open_accounts = accountsList(false); +$archived_accounts = accountsList(true); +$stats = accountsStats($open_accounts); +$types = accountTypes(); +$accounts = $show === 'archived' ? $archived_accounts : $open_accounts; +$q_text = trim((string) ($_GET['q'] ?? '')); +if ($q_text !== '') { + $accounts = array_filter($accounts, function ($account) use ($q_text) { + return mb_stripos($account['account_name'] . ' ' . $account['account_description'] . ' ' . $account['account_notes'], $q_text) !== false; + }); +} +$cards = array_filter($open_accounts, function ($account) { return $account['type'] === 3; }); +$owing_cards = array_filter($cards, function ($account) { return $account['balance'] < 0; }); +$money = function ($amount, $currency = '') { + return escapeHtml(billingMoney($amount, $currency)); +}; +$change = function ($now, $before) { + if ($before <= 0) { + return null; + } + return round(($now - $before) / $before * 100); +}; +$in_change = $change($stats['in'], $stats['last_in']); +$out_change = $change($stats['out'], $stats['last_out']); +$month_url = 'canned_date=custom&dtf=' . $stats['month_start'] . '&dtt=' . date('Y-m-t'); ?> -
-
-

Accounts

-
- + + + +
+
+

Accounts

+ -
-
-
- - -
-
-
-
- - "> - - - - - - - - - - - - - - - - - - - - -
- - Name - - - - Currency - - BalanceAction
- - - - - -
-
-
- +
+
+ +
+ + +
+
+ +
+
+ +
+ + + + + + + + + + + + + $account) { + $type = $types[$account['type']]; + $card = $account['type'] === 3; + $currency = $account['account_currency_code']; + $balance = $account['balance']; + $in_use = $account['line_count'] > 0; ?> + + + + + + + + + + + + + + + + + + + +
AccountKindHoldsThis monthLatest
+ + +
opened with
+ +
+ + +
+
+ + +
-
+ + + - + +
+ +
line
+ + Nothing yet + +
+ +
What you have, less what is owed on cards
+
+ +
+ +
+
+ +
+ + 'text-danger-emphasis', 'warning' => 'text-warning-emp diff --git a/agent/includes/side_nav.php b/agent/includes/side_nav.php index 92268b11c..d0fca73d5 100644 --- a/agent/includes/side_nav.php +++ b/agent/includes/side_nav.php @@ -237,13 +237,13 @@