diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f093f1a..58177b5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ 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.3.9) 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.4.0) in the same pass. Nothing else is required. This is a big release, with sixty database steps in one go, so take a backup before you update. @@ -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). +Ticket statuses keep their names, colours, order and SLA settings, and every one of them still sends a ticket back to Open when the client replies - until you set one of your own to leave it where it is (see New Features, Ticket Statuses). The five built-in statuses are given a line on what each is for, which you can change. + 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). No account is reconciled after the update. The first statement you reconcile on each can start from the bank's balance on the day it starts, bringing everything before it forward as reconciled, or from the account's opening balance (see New Features, Accounts and transactions). Transfers already made out of a cash account count as having taken everything in it up to then to the bank, so the first deposit you make after the update only offers the checks and cash taken in since the last one. Taxes keep their names and rates, now kept to four decimal places. No client has a rate of its own or is tax exempt, and there is no default rate, so new lines start untaxed as before until you pick one in Admin > Taxes. Billed time, trips and expenses stay untaxed until you switch them on there. Discounts now come off before tax on invoices and quotes that are still open, the next time they are edited (see Breaking Changes, Taxes). No expense has tax recorded on it until you add it, so Tax Collected shows no tax paid on purchases for the months before. @@ -1111,6 +1113,15 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Uploads: files and size by folder, biggest first. - Copy for support puts the whole report on the clipboard as plain text, for a forum post or a support email - versions, what needs looking at and the size of things, without host names, paths, addresses or keys. The report is on the page too, to read before posting. - The page opens quickly on a big install, and still opens when the database is behind the code - which is when it is wanted most. +- **Ticket Statuses** (rebuilt) + - Admin > Ticket Statuses shows each status as its badge, with what it is for, how many tickets are in it (a link to them), whether the SLA's resolution clock runs or pauses in it, and what a client's reply does to a ticket in it. Tabs for the ones in use and the ones switched off; search by name or description. + - Drag them into the order the status pickers and the ticket board show them in, or use Move up and Move down. The Order number is gone. + - New Status and Edit are one window with a preview of the badge, colour swatches (or any colour), and a description that shows when the status is hovered - in the pickers, on the board, and on tickets everywhere. + - When the client replies: each of your own statuses can send the ticket back to Open, as every reply always has, or leave it where it is - for a status like Scheduled, where a "thanks, see you then" shouldn't put the ticket back in the queue. By email and in the portal; whoever it is assigned to is told either way. + - Switch off a status to take it out of the pickers and off the board, keeping it - its tickets can be moved to another status first, or left where they are. Switch it on again from the Switched off tab. + - Delete one of your own and its tickets are moved to the status you pick first - each says so in its history, and its SLA clock follows. Canned responses that set it leave the status as it is instead. + - ITFlow's five statuses (New, Open, On Hold, Resolved, Closed) are marked Built in. They keep their names, stay switched on, and their SLA clock and reply behaviour are fixed - the window shows what each does and when ITFlow sets it. Their colour and description can change. + - Status badges have dark or white text, whichever reads on their colour - a yellow status is readable now. ### Bug Fixes @@ -1304,6 +1315,10 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Admin > Debug counted every row of every table and every file in uploads on each visit, which could take minutes on a big install. Rows are estimated unless you ask for an exact count, and counting files stops after a few seconds. - Admin > Debug checked the certificate of whatever host the browser asked for rather than ITFlow's own, said HTTPS was off behind a proxy that handles it, and counted shell_exec being on as a pass. - Admin > Debug flagged every whole-number column as a different type on MySQL 8, which no longer shows their display widths. +- Ticket statuses: deleting one left the tickets in it pointing at a status that no longer existed. +- Ticket statuses: a hand-made request could rename one of the five built-in statuses or switch it off, and saving a built-in one said "Custom Ticket Status edited". +- Ticket statuses: badges always had white text, unreadable on a light colour. +- Tickets: the status filter dropped a switched-off status as soon as the filter was changed. ### Developer Updates @@ -1898,6 +1913,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Ticket emails: `emailHtml()` takes a `history` block (a heading and items of who, organisation, action, when and plain text, or a `gap` count) drawn after the button. `emailPlainText($html, $limit)` (`functions/email.php`) is HTML as plain text, keeping line breaks, dropping pictures and the reply line, and cut at a word. `ticketEmailHistory($row, $opts)` (`functions/ticket.php`) builds the history: public and client replies newest first, never internal, archived or the portal's "Ticket ... by X." lines, then the request. `skip` leaves out the reply the email shows and anything newer, `request` and `replies` choose the parts, and `limit` (4) caps the replies. `ticketEmailExcerpt()` takes off the parser's "Email from" line. `ticketEmailContent()` and `ticketAgentEmailContent()` add it per kind and set the preview line through `ticketEmailPreview()`. `ticketMailColleaguesNew()` shows the details unless `ticket_source` is Email. `cron/ticket_email_parser.php` also cuts a reply at a plain-text reply line, quoted or not. - Custom links: new `functions/custom_link.php` holds the link helpers. `customLinkLocations()` lists the menus (1 agent menu, 2 top bar, 3 client portal, 4 admin menu, 5 reports menu). `customLinkUriProblem()` and `customLinkHref()` check addresses: escapeUrl()'s schemes plus mailto and tel, relative paths allowed, anything else drawn as `#`. `customLinksFor($location)` is a menu's current links, read once a page, with `href` and `icon` added; `customLinkNavItemHtml()` draws a side menu line. Also `customLinkFromInput()`, `customLinkProblem()`, `customLinkCreate()`, `customLinkUpdate()` (returns the from-to changes for the audit log), `customLinkCopy()`, `customLinkReorder()`, `customLinkMenuIds()` and `customLinkIconChoices()`. `admin/includes/side_nav.php`, `agent/includes/side_nav.php`, `agent/reports/includes/reports_side_nav.php`, `includes/top_nav.php` and `portalNav()` read `customLinksFor()`; the portal's nav items take a `title`. `admin/post/custom_link.php` adds `copy_custom_link`, `archive_custom_link`, `restore_custom_link`, `move_custom_link` and `reorder_custom_links` (POST, JSON, like `reorder_categories`). The add and edit windows share `admin/modals/custom_link/custom_link_form.php` with `js/custom_link_form.js`. `custom_link_description` is now used; `custom_link_order` is set by position. No database change. - Debug: new `functions/system_health.php`. `healthChecks()` returns the sections in order (`key`, `title`, `icon`, `items`), each item from `healthItem($label, $level, $value, $fix, $url, $action, $private)` - level `ok`, `info`, `warning` or `danger`, `fix` as HTML, `private` leaving the value out of the report. `healthSummary($sections)` counts them by level and lists the warnings and problems, problems first. `healthDatabaseTables($exact)` reads sizes and InnoDB's row estimates from `information_schema.TABLES` in one query, or counts each table with `$exact` (`debug.php?exact=1`); `healthStructureDifferences()` compares `information_schema.COLUMNS` with `db.sql`, ignoring integer display widths (`healthColumnType()`); `healthUploads($seconds)` counts uploads by top-level folder and stops at the time given, marked `partial`. `healthReport()` is the plain-text report. Every read goes through `healthQuery()` / `healthRow()`, which answer null instead of throwing, so a column the database doesn't have yet costs a line, not the page. The certificate is read from `$config_base_url`'s host (`healthBaseHost()`, `healthCertificate()`), never `HTTP_HOST`; HTTPS counts `X-Forwarded-Proto`; the required extensions come from `setupRequiredExtensions()` in `setup/setup_checks.php` so setup and Debug agree; mail uses `mailQueueHealth()` and `mailQueueSummary()`; updates use `shellCommandsAvailable()` and `commandPath('git')`. `admin/debug.php` no longer closes the database before the footer, uses `reportStats()` for the figures along the top, and the copy button is the shared `.clipboardjs` one, targeting the report's textarea. New CSS: `.itflow-debug-section-icon`, `.itflow-debug-item`, `.itflow-debug-fix`, `.itflow-debug-attention`, `.itflow-debug-bar`, `.itflow-debug-stats`. No database change. +- Ticket statuses: database 3.4.0 adds `ticket_statuses.ticket_status_description` varchar(255) and `ticket_status_client_reply` tinyint(1) (1, the default, sends the ticket back to Open when the client replies; 0 leaves it), gives the five built-in statuses their descriptions where they have none, and numbers `ticket_status_order` 1, 2, 3... in the order they already showed in. New `functions/ticket_status.php`: `ticketStatusBuiltIns()` (ids 1-5 with icon, meaning and what sets them), `ticketStatusIsBuiltIn()`, `ticketStatusRow()`, `ticketStatusesAll()` (once a page, in order), `ticketStatusAbout()`, `ticketStatusColor()`, `ticketStatusBadgeHtml()` (text colour from `tagTextColor()`, the description as its title), `ticketStatusOptions($keep)`, `ticketStatusTicketCounts()`, `ticketStatusMoveTargets()`, `ticketStatusFromInput()` (a built-in keeps its name, stays on and takes `getTicketStatusSlaLock()` and client reply 1 whatever is posted), `ticketStatusProblem()` (a name, not taken), `ticketStatusCreate()`, `ticketStatusUpdate()` (returns the from-to changes, syncs its tickets' SLA clocks when pausing changes), `ticketStatusMoveTickets()`, `ticketStatusSetActive()`, `ticketStatusDelete()`, `ticketStatusReorder()` and `ticketStatusAfterClientReply($status_id)`, which `cron/ticket_email_parser.php` and `client/post.php` now use instead of setting status 2. `ticketStatusColumnsReady()` keeps all of it working before 3.4.0 has run. `ticketStatusHtml()` draws through `ticketStatusBadgeHtml()`, and the dashboard, client overview, ticket page, Ticket Aging and Client Review reports use it instead of their own badges. `admin/post/ticket_status.php` adds `move_ticket_status`, `reorder_ticket_statuses` (POST, JSON), `switch_on_ticket_status`, `switch_off_ticket_status` (POST, `move_to`) and `delete_ticket_status` as POST with `move_to` - the GET delete is gone. The add and edit windows share `admin/modals/ticket_status/ticket_status_form.php` with `js/ticket_status_form.js`; `ticket_status_remove.php` is the switch-off and delete window. Quick picker options take a `title` (`js/quick_pick.js`), which the status picker sets to the description. New CSS: `.itflow-ticket-status`. ### Library Updates diff --git a/admin/database_updates/3.4.0.php b/admin/database_updates/3.4.0.php new file mode 100644 index 000000000..fe1d28978 --- /dev/null +++ b/admin/database_updates/3.4.0.php @@ -0,0 +1,48 @@ + 0; + }; + + // What a ticket status is for - shown when it is hovered, in the pickers and on the board + if (!$itflow_column('ticket_statuses', 'ticket_status_description')) { + mysqli_query($mysqli, "ALTER TABLE `ticket_statuses` ADD `ticket_status_description` varchar(255) DEFAULT NULL AFTER `ticket_status_color`"); + } + + // What a client's reply does to a ticket in the status - 1 moves it back to Open (as every reply always has), 0 leaves it there + if (!$itflow_column('ticket_statuses', 'ticket_status_client_reply')) { + mysqli_query($mysqli, "ALTER TABLE `ticket_statuses` ADD `ticket_status_client_reply` tinyint(1) NOT NULL DEFAULT 1 AFTER `ticket_status_pauses_sla`"); + } + + // The five built-in statuses say what they are for - only where nothing is written yet + $itflow_descriptions = [ + 1 => 'Just come in - nobody has picked it up yet.', + 2 => 'Being worked on.', + 3 => 'Waiting on someone else - the client, a vendor or a part.', + 4 => 'Fixed, and waiting to be closed - a reply from the client reopens it.', + 5 => 'Finished - it can\'t be replied to.', + ]; + foreach ($itflow_descriptions as $itflow_status_id => $itflow_description) { + mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_description = '" . mysqli_real_escape_string($mysqli, $itflow_description) . "' + WHERE ticket_status_id = $itflow_status_id AND (ticket_status_description IS NULL OR ticket_status_description = '')"); + } + + // Number the statuses 1, 2, 3... in the order they already show in (most are all 0, and ties go by id), + // so they can be dragged into another + $itflow_position = 0; + $itflow_statuses = mysqli_query($mysqli, "SELECT ticket_status_id FROM ticket_statuses ORDER BY ticket_status_order ASC, ticket_status_id ASC"); + while ($itflow_status = mysqli_fetch_row($itflow_statuses)) { + $itflow_position++; + mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_order = $itflow_position WHERE ticket_status_id = " . intval($itflow_status[0])); + } + + unset($itflow_column, $itflow_descriptions, $itflow_status_id, $itflow_description, $itflow_position, $itflow_statuses, $itflow_status); diff --git a/admin/modals/ticket_status/ticket_status_add.php b/admin/modals/ticket_status/ticket_status_add.php index 9f22cdbb8..d0cf40616 100644 --- a/admin/modals/ticket_status/ticket_status_add.php +++ b/admin/modals/ticket_status/ticket_status_add.php @@ -1,5 +1,15 @@ @@ -11,34 +21,7 @@ ob_start();