From 1fd45bcffafeb1d0eda85ab1dc203dd52abffa78 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 26 Oct 2026 12:19:53 -0400 Subject: [PATCH] Get started: the standard SLAs set a response time only - Urgent 1 business hour, High 2, Medium 4, Low 8 - with no resolution time, reuse an existing SLA only when it has the same response time and none, and the Support step shows Answered within alone, with Put right within only when an SLA already given has one --- CHANGELOG.md | 4 ++-- admin/getting_started/support.php | 24 ++++++++++++++++-------- functions/getting_started.php | 28 ++++++++++++++++------------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ab0caab..a6421c28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1194,7 +1194,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Email: the SMTP server ITFlow sends through and the address and name it sends as, checked as Admin > Mail checks them, with Save and send me a test. Optionally a support mailbox, read into tickets, whose address ticket emails then come from when they had none. Microsoft 365 and Google Workspace are set up in Admin > Mail, and the step keeps them. - Backups: the backup key, to show and copy, with a tick to say a copy is kept somewhere else before nightly backups are turned on - when they are taken, the database or everything, and how many to keep and for how long. - Billing: the hourly rate and net terms a new client starts with, a tax rate for new lines when there is no default (made and made the default), invoice and quote numbering, and - if you like - Stripe, with its keys put to Stripe before they are saved. - - Support: business days and hours, the standard response times for each priority as the default SLAs (Urgent answered in 1 business hour and put right in 4, High 2 and 8, Medium 4 and 16, Low 8 and 40 - one with the same hours is used when there is one, and only priorities with none get them), ticket numbering and the client portal on or off. Open tickets are stamped afresh with the hours and response times saved. + - Support: business days and hours, the standard response times for each priority as the default SLAs (Urgent answered in 1 business hour, High 2, Medium 4, Low 8, with no time to resolve by - one can be added in Admin > SLAs; an SLA with the same response time and none to resolve by is used when there is one, and only priorities with none get them), ticket numbering and the client portal on or off. Open tickets are stamped afresh with the hours and response times saved. - Your team: everyone who signs in, and Add someone - a name, an email and a role. ITFlow makes their password: they are emailed their sign-in details, as Admin > Users does, or, with no email set up yet, it is shown to you once to give them. They change it once signed in. - Starter content: the packs from Maintenance > Starter Content, each with how much it adds, ticked to add - and Pick which under each, as on that page (see Starter Content, Pick which). Your first client: a name, website and contact, made as the Clients page makes one - or the demo data, to look around first. - The dashboard shows Finish setting up, with how far it has got, a chip for each step and Carry on, to admins until every step is done or skipped. Its × stops it showing there. Admin > Get started opens it any time, with how many steps are done or skipped beside it, and a summary at the end lists every step with where it stands. @@ -2008,7 +2008,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Contract prepaid hours: database 3.4.4 adds to `contract_templates` and `contracts` `{prefix}_monthly_hours` and `{prefix}_hours_alert` decimal(8,2) (0.00), and `contracts.contract_hours_alerted_at` datetime, plus two tables. `contract_blocks`: `contract_block_contract_id` (ON DELETE CASCADE), `_hours`, `_price` (NULL for none), `_bought`, `_expires` (NULL for never), `_month` (char(7), for a month's included hours - UNIQUE with the contract, so a month is made once), `_invoice_id`, `_note`, `_created_by`, `_created_at`. `contract_block_uses`: `contract_block_use_block_id` (ON DELETE CASCADE), `_ticket_id`, `_hours`, `_full` (on the use that covered the rest of a ticket), `_created_by`, `_created_at`; a use of 0 hours keeps a ticket that was put back out of the draw. New `functions/contract_hours.php`: `contractHoursReady()`, `contractHoursText()`, `contractHoursWords()`, `contractBlocks()`, `contractHoursSummary()`, `contractHoursLeftFor()`, `contractHasHours()`, `contractClientHours()`, `contractHoursEnsureMonthly()`, `contractHoursSync()` (after a save, Make active, Renew or a countersignature that makes a draft active), `contractHoursTicketUses()`, `contractHoursUses()`, `contractHoursDraw()` (one at a time per client, under `GET_LOCK`), `contractHoursDoneOn()`, `contractHoursRecheck()`, `contractHoursPutBack()`, `contractHoursAlertReset()`, `contractHoursAlertCheck()`, `contractHoursAlertSend()`, `contractHoursFromInput()`, `contractHoursAdd()` (the invoice through `billingInvoiceCreate()` and `billingItemSave()`), `contractHoursBlockDelete()`, `contractBlockRow()` and `contractHoursNightly()`. `billingUnbilledSql()` leaves out a ticket with a full use; `billingUnbilledTickets()` adds `worked` (the hours the time logged makes) and `prepaid`, and `hours` is now what is left after them; `billingUnbilledByClient()` adds `prepaid`. `contractFromInput()` takes `monthly_hours` and `hours_alert`, `contractTermsSetSql()` writes them once 3.4.4 has run, and `contractUpdate()` logs them and clears `contract_hours_alerted_at` when the level changes. `contractsNightly()` returns `drawn` too, logged by `cron/nightly_tasks.php`. `ticketDeleteForGood()` deletes the ticket's uses. `CONTRACT_PLACEHOLDERS` adds Monthly Hours, and `contractDocumentHtml()` adds an Included hours row only when there are some, so documents without them hash as before. `agent/post/contract.php` adds `add_contract_hours` and `draw_contract_hours` (POST, with a `contract_id`, a `client_id` or a `ticket_id`) and `delete_contract_block` and `put_back_contract_hours` (GET with the token), all with Sales Modify. New `agent/modals/contract/contract_hours_add.php`. `emailPreviewContractCatalog()` adds `contract_hours_low`. New CSS: `.itflow-contract-hours-left` and `.itflow-hours-bar` (a ``, as the portal's CSP allows no inline styles). - Contract billing and SLAs: database 3.4.5 adds `contracts.contract_recurring_invoice_id` int (0, keyed) and `contract_recurring_stopped_at` datetime, set when the contract pauses its recurring invoice so it does so once, and sets a rate of 0.00 on contracts and templates to NULL (`contractFromInput()` saves 0 as NULL too). New `functions/contract_billing.php`: `contractBillingReady()`, `contractTermsContracts()` (a client's contracts that can be in force, kept for the request), `contractInForceOn()`, `contractTermsOn()` (the one started last that sets a rate, net terms or a priority's SLA), `contractsInForce()`, `contractSlaFor()`, `contractAfterHours()` (from `getSlaSettings()` and `getBusinessHolidays()`), `contractTicketRate()` (source after_hours, contract, client or default, and a note), `contractClientRate()`, `contractNetTerms()`, `contractNetTermsToday()`, `contractRecurringRow()`, `contractForRecurring()`, `contractRecurringChoices()`, `contractRecurringShouldRun()`, `contractRecurringAmountText()`, `contractRecurringStart()` (skips missed runs with `recurringInvoiceCatchUp()`), `contractRecurringStop()`, `contractRecurringFollow()`, `contractRecurringLink()`, `contractRecurringUnlink()`, `contractRecurringFromInput()`, `contractRecurringFirstDate()`, `contractRecurringCreate()` and `contractRecurringNightly()`. `getTicketSlaId()` asks `contractSlaFor()` first, and takes `$with_contract` (false for the client's own - `contractSlaRows()` uses it). `billingUnbilledTickets()` prices each ticket with `contractTicketRate()` and adds `rate_source` and `rate_note`; `billingUnbilledByClient()` adds `rates`, `rate_note` and `rate_source`; `billingTimeInvoice()` with no price bills each ticket at its own rate, and `combine` makes one line per rate. `billingInvoiceCreate()`, `quoteConvert()`, `recurringInvoiceGenerate()`, New Invoice and Copy in `agent/post/invoice.php` take `contractNetTerms()`. `contractsNightly()` returns `paused` too, logged by `cron/nightly_tasks.php`. `agent/post/contract.php` adds `make_contract_recurring` and `link_contract_recurring` (POST) and `unlink_contract_recurring` (GET with the token), all Sales Modify, and Make active, Renew, Cancel and a countersignature that makes a draft active call `contractRecurringFollow()`; deleting a recurring invoice unlinks its contract. New `agent/modals/contract/contract_recurring.php`. The prepaid hours handlers pass the agent's name unescaped, as the history and logs escape it. - Client portal invites and sign-ups: database 3.4.6 adds `portal_invites` - `invite_kind` (invite or signup), `invite_email`, `invite_name`, `invite_billing`, `invite_technical`, `invite_token_hash` (the SHA-256 of the link's token), `invite_expires_at`, `invite_sent_at`, `invite_sent_count`, `invite_created_by` (an agent), `invite_created_by_contact_id` (a primary contact), `invite_created_at`, `invite_accepted_at`, `invite_contact_id`, `invite_cancelled_at` and `invite_client_id` (ON DELETE CASCADE), keyed on the client and address and on the contact - plus `clients.client_portal_signup` and `settings.config_client_portal_signup` tinyint(1) (0). New `functions/portal_invite.php`: `PORTAL_SIGNUP_LINK_HOURS` (24), `PORTAL_INVITE_MAX` (50), `PORTAL_SIGNUP_IP_LIMIT` (20), `portalInvitesReady()` (keeps every page and the nightly job as they were before 3.4.6 has run), `portalInviteParse()`, `portalInviteWho()` (new, contact, member or elsewhere), `portalInviteRow()`, `portalInviteBy()`, `portalInviteState()`, `portalInviteUrl()`, `portalInviteNewToken()`, `portalInviteByLink()` (hash_equals), `portalInviteMail()`, `portalInviteSend()`, `portalInviteResend()`, `portalInviteCancel()`, `portalInvitesWaiting()`, `portalInviteStateLabel()`, `portalContactJoined()`, `portalInviteAccept()` (spends the link with one guarded UPDATE first, so a second press or a race does nothing), `portalMailSignupJoined()`, `portalMailInviteAccepted()`, `portalSignupOn()`, `portalSignupDomains()`, `portalSignupClients()`, `portalSignupOpen()`, `portalSignupClientFor()`, `portalSignupRequest()` (logged as `Client Login` Sign Up Requested, Sign Up Sent and Sign Up Refused), `portalMailSignupExists()`, `portalSignupSet()` and `portalInvitesNightly()`, which `cron/nightly_tasks.php` runs and logs. `portalContactAccess()` and `portalSignInMethods()` move from `client/functions.php` to `functions/portal.php`, as cron and the agent side use them now. `agent/post/contact.php` adds `send_portal_invites` (POST) and `resend_portal_invite` and `cancel_portal_invite` (GET with the token), and `agent/post/client.php` `set_client_portal_signup` (POST), all Client Modify. `agent/modals/contact/contact_invite.php`, an unused stub (Welcome Letter, Big Wig) with no handler, is replaced by the Invite people window. `agent/contacts.php` adds `role=invites`. `client/post.php` adds `portal_invites_send`, `portal_invite_resend` and `portal_invite_cancel` (the primary contact's). New `client/invite.php`, `client/join.php` (`?invite=&token=`, Referrer-Policy no-referrer) and `client/signup.php` (a form token in the session and a hidden field robots fill in). `edit_client_portal_settings` saves `config_client_portal_signup` once 3.4.6 has run. `emailPreviewInviteCatalog()` adds `portal_join_invite`, `portal_join_invite_colleague`, `portal_join_accepted`, `portal_signup`, `portal_signup_exists` and `portal_signup_joined`. `auditLogActionStyle()` adds Invite and the Sign Up actions. New CSS: `.itflow-join-box`. -- Get started: database 3.4.7 adds `settings.config_getting_started` tinyint(1) (1 in db.sql, so a new install has it; the update sets it to 0 for one already running), `config_getting_started_skipped` varchar(255) (the steps skipped, comma separated) and `config_getting_started_opened_at` datetime (when it was first opened - it opens by itself on signing in only until then). New `functions/getting_started.php`: `GETTING_STARTED_STEPS`, `GETTING_STARTED_SLAS`, `gettingStartedReady()` (nothing shows before 3.4.7 has run), `gettingStartedState()`, `gettingStartedUrl()`, `gettingStartedCronCommand()`, `gettingStartedBackupJob()`, `gettingStartedSteps()` (each step's done, skipped and status, from the settings), `gettingStartedProgress()`, `gettingStartedAfter()`, `gettingStartedSkip()`, `gettingStartedOpened()`, `gettingStartedDismiss()`, `gettingStartedFirstVisit()` (login.php takes the first admin there, after MFA enforcement and not instead of a `last_visited` page), `gettingStartedCard()` (the dashboard's card), `gettingStartedBackupSave()` (the backup job's row in `cron_jobs`, seeded from the registry when the dispatcher hasn't yet), `gettingStartedApplySlas()` and `gettingStartedDaysText()`. New `admin/getting_started.php` (a page of its own, in the setup wizard's frame; `?step=`, and `?cron_status=1` answers in JSON), its steps in `admin/getting_started/` and `admin/post/getting_started.php`: `getting_started_cron_on`, `getting_started_mail` (continue or test), `getting_started_backups`, `getting_started_billing`, `getting_started_support`, `getting_started_team`, `getting_started_content`, `getting_started_skip` and `getting_started_dismiss`. The first client posts `add_client` to `agent/post.php` and the demo data `load_demo_data`, both of which return to the step. Admin > Mail's check of a sending or receiving server moves from a closure in `admin/post/settings_mail.php` to `settingsMailServerCheck($server, $before, $post)` in `functions/settings.php`, which both use. In `admin/post/starter_content_model.php`, `starterContentLoad()` and each `starterLoad*()` take `$only`, the items to load by `starterItemKey()` (null for the whole pack, as Maintenance > Starter Content loads it), and new `starterContentRows()`, `starterItemKey()` (a category's or tag's type with its name), `starterItemName()`, `starterItemGroup()`, `starterOnly()` and `starterContentItems()` (what a pack would add, judged here as each loader judges it); `getting_started_content` and `load_starter_content` (for one pack) take `picked[pack]` and `items[pack][]`; `starterContentItems()` gives each item's library row. New `admin/includes/starter_content_pick.php` (both pages): `starterItemAbout()`, `starterItemLook()` (through `tagPill()`, `categoryChip()` and `productBilledText()`), `starterPreviewPacks()`, `starterItemPreview()`, `starterPickHtml($pack, $items, $form)` (the list, tied by `form=` to a table row's Add) and `starterPreviewModalHtml()`; new `admin/starter_content_preview.php` answers Preview in JSON (`?pack=&item=`, admins only, 404 for anything else); new `js/starter_content.js` (`data-starter-pick`, `data-starter-item`, `data-starter-pack-check`, `data-starter-count`, `data-starter-add`, `data-starter-preview`) takes over from the picking in `js/getting_started.js`; new CSS `.itflow-starter-*`. New `js/getting_started.js` (`data-gs-reveal`, `data-gs-show`, `data-gs-cron-poll`), and `js/app.js` only starts DataTables where it is loaded, so setup and Get started, which load app.js on its own, no longer log an error to the console. New CSS: `.itflow-gs-*`. +- Get started: database 3.4.7 adds `settings.config_getting_started` tinyint(1) (1 in db.sql, so a new install has it; the update sets it to 0 for one already running), `config_getting_started_skipped` varchar(255) (the steps skipped, comma separated) and `config_getting_started_opened_at` datetime (when it was first opened - it opens by itself on signing in only until then). New `functions/getting_started.php`: `GETTING_STARTED_STEPS`, `GETTING_STARTED_SLAS` (priority => business minutes to respond; the SLAs `gettingStartedApplySlas()` makes have `sla_resolution_minutes` NULL, and it reuses one only with the same response time and none), `gettingStartedReady()` (nothing shows before 3.4.7 has run), `gettingStartedState()`, `gettingStartedUrl()`, `gettingStartedCronCommand()`, `gettingStartedBackupJob()`, `gettingStartedSteps()` (each step's done, skipped and status, from the settings), `gettingStartedProgress()`, `gettingStartedAfter()`, `gettingStartedSkip()`, `gettingStartedOpened()`, `gettingStartedDismiss()`, `gettingStartedFirstVisit()` (login.php takes the first admin there, after MFA enforcement and not instead of a `last_visited` page), `gettingStartedCard()` (the dashboard's card), `gettingStartedBackupSave()` (the backup job's row in `cron_jobs`, seeded from the registry when the dispatcher hasn't yet), `gettingStartedApplySlas()` and `gettingStartedDaysText()`. New `admin/getting_started.php` (a page of its own, in the setup wizard's frame; `?step=`, and `?cron_status=1` answers in JSON), its steps in `admin/getting_started/` and `admin/post/getting_started.php`: `getting_started_cron_on`, `getting_started_mail` (continue or test), `getting_started_backups`, `getting_started_billing`, `getting_started_support`, `getting_started_team`, `getting_started_content`, `getting_started_skip` and `getting_started_dismiss`. The first client posts `add_client` to `agent/post.php` and the demo data `load_demo_data`, both of which return to the step. Admin > Mail's check of a sending or receiving server moves from a closure in `admin/post/settings_mail.php` to `settingsMailServerCheck($server, $before, $post)` in `functions/settings.php`, which both use. In `admin/post/starter_content_model.php`, `starterContentLoad()` and each `starterLoad*()` take `$only`, the items to load by `starterItemKey()` (null for the whole pack, as Maintenance > Starter Content loads it), and new `starterContentRows()`, `starterItemKey()` (a category's or tag's type with its name), `starterItemName()`, `starterItemGroup()`, `starterOnly()` and `starterContentItems()` (what a pack would add, judged here as each loader judges it); `getting_started_content` and `load_starter_content` (for one pack) take `picked[pack]` and `items[pack][]`; `starterContentItems()` gives each item's library row. New `admin/includes/starter_content_pick.php` (both pages): `starterItemAbout()`, `starterItemLook()` (through `tagPill()`, `categoryChip()` and `productBilledText()`), `starterPreviewPacks()`, `starterItemPreview()`, `starterPickHtml($pack, $items, $form)` (the list, tied by `form=` to a table row's Add) and `starterPreviewModalHtml()`; new `admin/starter_content_preview.php` answers Preview in JSON (`?pack=&item=`, admins only, 404 for anything else); new `js/starter_content.js` (`data-starter-pick`, `data-starter-item`, `data-starter-pack-check`, `data-starter-count`, `data-starter-add`, `data-starter-preview`) takes over from the picking in `js/getting_started.js`; new CSS `.itflow-starter-*`. New `js/getting_started.js` (`data-gs-reveal`, `data-gs-show`, `data-gs-cron-poll`), and `js/app.js` only starts DataTables where it is loaded, so setup and Get started, which load app.js on its own, no longer log an error to the console. New CSS: `.itflow-gs-*`. - Tags' look: `.itflow-tag` draws `--itflow-tag-color` as a `color-mix()` wash and edge with the icon in it and the name in `--bs-body-color` (`--itflow-tag-ink`, 75% or 55% under `[data-bs-theme="dark"]`, is how much of the color the icon keeps), with neutral fallbacks before each `color-mix()`; `--itflow-tag-text` is no longer used by pills. `.itflow-search-tag-icon` and the new `.itflow-select-badge.is-soft` follow it. New `tagOptionLook($color, $icon)` in `functions/tags.php` (`selectOptionColor()` plus `data-soft="1"`) for a tag's `