$name created"); redirect(); } // Kept for anything still posting the old New from Template form - the vendor form now does both if (isset($_POST['add_vendor_from_template'])) { validateCSRFToken(); $client_id = intval($_POST['client_id'] ?? 0); vendorEnforce($client_id, 2); $template = vendorTemplateRow($_POST['vendor_template_id'] ?? 0); if (!$template) { flashAlert('That template no longer exists.', 'error'); redirect(); } $vendor_id = vendorCreate(vendorTemplateFormFields($template), $client_id); logAudit("Vendor", "Create", "$session_name created vendor " . escapeSql($template['vendor_template_name']) . " from a template", $client_id, $vendor_id); flashAlert("Vendor {$template['vendor_template_name']} created from template"); redirect(); } /* * One template, added as a vendor to several clients at once - a new standard * vendor rolled out everywhere. A client that already has a vendor made from * this template is left alone. */ if (isset($_POST['add_vendor_template_to_clients'])) { validateCSRFToken(); enforceUserPermission('module_client', 2); $template = vendorTemplateRow($_POST['vendor_template_id'] ?? 0); if (!$template) { flashAlert('That template no longer exists.', 'error'); redirect(); } $template_id = intval($template['vendor_template_id']); $fields = vendorTemplateFormFields($template); $added = $already = 0; foreach (array_unique(array_filter(array_map('intval', (array) ($_POST['client_ids'] ?? [])))) as $target_client_id) { if (!hasClientAccess($target_client_id) || !getFieldById('clients', $target_client_id, 'client_name')) { continue; } if (mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM vendors WHERE vendor_client_id = $target_client_id AND vendor_template_id = $template_id AND vendor_archived_at IS NULL LIMIT 1"))) { $already++; continue; } $vendor_id = vendorCreate($fields, $target_client_id); logAudit("Vendor", "Create", "$session_name created vendor " . escapeSql($template['vendor_template_name']) . " from a template", $target_client_id, $vendor_id); $added++; } flashAlert("Added {$template['vendor_template_name']} to $added client" . ($added === 1 ? '' : 's') . ($already ? " - $already already had it" : '')); redirect(); } /* The vendors a request names that this agent may change - vendor id => row. */ function vendorsForRequest(array $vendor_ids) { $vendors = []; foreach (array_unique(array_filter(array_map('intval', array_filter($vendor_ids, 'is_scalar')))) as $vendor_id) { $row = vendorRow($vendor_id); if ($row && vendorCan($row['vendor_client_id'], 2)) { $vendors[$vendor_id] = $row; } } return $vendors; } /* * Vendors made from a template brought in line with it - the fields picked, * where they differ (vendorTemplateBringInLine()). Their account number, PIN * and account manager are never touched. From the template's page, the * vendor's page, or the window either opens. */ if (isset($_POST['update_vendors_from_template'])) { validateCSRFToken(); $template = vendorTemplateInfo($_POST['vendor_template_id'] ?? 0); if (!$template) { flashAlert('That template no longer exists.', 'error'); redirect(); } $vendors = vendorsForRequest((array) ($_POST['vendor_ids'] ?? [])); $keys = array_map('strval', array_filter((array) ($_POST['fields'] ?? []), 'is_scalar')); if (!$vendors || !$keys) { flashAlert(!$vendors ? 'Pick the vendors to bring in line.' : 'Tick the fields to take from the template.', 'error'); redirect(); } $changed = vendorTemplateBringInLine($template['vendor_template_id'], array_keys($vendors), $keys); foreach ($changed as $vendor_id => $vendor) { logAudit("Vendor", "Edit", "$session_name brought vendor " . escapeSql($vendor['name']) . " in line with template " . escapeSql($template['vendor_template_name']) . " - " . escapeSql(mb_strtolower(implode(', ', $vendor['fields']))), $vendor['client_id'], $vendor_id); } flashAlert($changed ? "Brought " . count($changed) . " vendor" . (count($changed) === 1 ? '' : 's') . " in line with " . escapeHtml($template['vendor_template_name']) . "" : 'Nothing to change - they already say what the template says.', $changed ? 'success' : 'warning'); redirect(); } /* * Vendors made by hand linked to a template, as if made from it - so they * count as made from it, and are kept in step with it. With bring, every * field they differ in is taken from it too. */ if (isset($_POST['link_vendors_to_template'])) { validateCSRFToken(); $template = vendorTemplateInfo($_POST['vendor_template_id'] ?? 0); if (!$template || $template['vendor_template_archived_at']) { flashAlert($template ? 'That template is archived - restore it to link vendors to it.' : 'That template no longer exists.', 'error'); redirect(); } $vendors = vendorsForRequest((array) ($_POST['vendor_ids'] ?? [])); $linked = vendorTemplateLink($template['vendor_template_id'], array_keys($vendors)); foreach ($linked as $vendor_id => $vendor) { logAudit("Vendor", "Edit", "$session_name linked vendor " . escapeSql($vendor['name']) . " to template " . escapeSql($template['vendor_template_name']), $vendor['client_id'], $vendor_id); } $changed = !empty($_POST['bring']) && $linked ? vendorTemplateBringInLine($template['vendor_template_id'], array_keys($linked), VENDOR_TEMPLATE_SHARED) : []; foreach ($changed as $vendor_id => $vendor) { logAudit("Vendor", "Edit", "$session_name brought vendor " . escapeSql($vendor['name']) . " in line with template " . escapeSql($template['vendor_template_name']) . " - " . escapeSql(mb_strtolower(implode(', ', $vendor['fields']))), $vendor['client_id'], $vendor_id); } flashAlert($linked ? "Linked " . count($linked) . " vendor" . (count($linked) === 1 ? '' : 's') . " to " . escapeHtml($template['vendor_template_name']) . "" . ($changed ? ", bringing " . count($changed) . " in line" : '') : 'Nothing to link - they are made from a template already.', $linked ? 'success' : 'warning'); redirect(); } /* A vendor no longer made from its template - it stays as it is, and is no longer kept in step. */ if (isset($_GET['unlink_vendor_template'])) { validateCSRFToken(); $row = vendorForRequest($_GET['unlink_vendor_template'], 2); $vendor_id = intval($row['vendor_id']); if (intval($row['vendor_template_id'])) { mysqli_query($mysqli, "UPDATE vendors SET vendor_template_id = 0 WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Edit", "$session_name unlinked vendor " . escapeSql($row['vendor_name']) . " from template " . escapeSql((string) $row['vendor_template_name']), $client_id, $vendor_id); flashAlert("" . escapeHtml($row['vendor_name']) . " is no longer linked to " . escapeHtml((string) $row['vendor_template_name']) . ""); } redirect(); } if (isset($_POST['edit_vendor'])) { validateCSRFToken(); $row = vendorForRequest($_POST['vendor_id'] ?? 0, 2); $vendor_id = intval($row['vendor_id']); $set = vendorFormSet($_POST); if ($set === null) { flashAlert('A vendor needs a name.', 'error'); redirect(); } mysqli_query($mysqli, "UPDATE vendors SET $set WHERE vendor_id = $vendor_id"); // Only a form that showed the tags changes them if (isset($_POST['tags_form'])) { vendorSetTags($vendor_id, $_POST['tags'] ?? []); } $name = trim($_POST['name']); logAudit("Vendor", "Edit", "$session_name edited vendor " . escapeSql($name), $client_id, $vendor_id); flashAlert("Vendor $name updated"); redirect(); } if (isset($_GET['favorite_vendor']) || isset($_GET['unfavorite_vendor'])) { validateCSRFToken(); $favorite = isset($_GET['favorite_vendor']) ? 1 : 0; $row = vendorForRequest($favorite ? $_GET['favorite_vendor'] : $_GET['unfavorite_vendor'], 2); $vendor_id = intval($row['vendor_id']); mysqli_query($mysqli, "UPDATE vendors SET vendor_favorite = $favorite WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Edit", "$session_name " . ($favorite ? 'starred' : 'unstarred') . " vendor " . escapeSql($row['vendor_name']), $client_id, $vendor_id); redirect(); } if (isset($_GET['archive_vendor'])) { validateCSRFToken(); $row = vendorForRequest($_GET['archive_vendor'], 2); $vendor_id = intval($row['vendor_id']); mysqli_query($mysqli, "UPDATE vendors SET vendor_archived_at = NOW() WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Archive", "$session_name archived vendor " . escapeSql($row['vendor_name']), $client_id, $vendor_id); flashAlert("Vendor {$row['vendor_name']} archived", 'error'); redirect(); } if (isset($_GET['restore_vendor'])) { validateCSRFToken(); $row = vendorForRequest($_GET['restore_vendor'], 2); $vendor_id = intval($row['vendor_id']); mysqli_query($mysqli, "UPDATE vendors SET vendor_archived_at = NULL WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Restore", "$session_name restored vendor " . escapeSql($row['vendor_name']), $client_id, $vendor_id); flashAlert("Vendor {$row['vendor_name']} restored"); redirect(); } if (isset($_GET['delete_vendor'])) { validateCSRFToken(); $row = vendorForRequest($_GET['delete_vendor'], 3); $vendor_id = intval($row['vendor_id']); $blocker = vendorDeleteBlocker($vendor_id); if ($blocker) { flashAlert("{$row['vendor_name']} was not deleted - $blocker.", 'error'); redirect(); } // What came from them stays, from no vendor; tags and links go with it vendorClearReferences($vendor_id); mysqli_query($mysqli, "DELETE FROM vendors WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Delete", "$session_name deleted vendor " . escapeSql($row['vendor_name']), $client_id); flashAlert("Vendor {$row['vendor_name']} deleted", 'error'); vendorRedirectAfterDelete($client_id); } if (isset($_POST['bulk_archive_vendors'])) { validateCSRFToken(); $count = 0; foreach (array_unique(array_map('intval', (array) ($_POST['vendor_ids'] ?? []))) as $vendor_id) { $row = vendorRow($vendor_id); if (!$row || $row['vendor_archived_at']) { continue; } $client_id = intval($row['vendor_client_id']); vendorEnforce($client_id, 2); mysqli_query($mysqli, "UPDATE vendors SET vendor_archived_at = NOW() WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Archive", "$session_name archived vendor " . escapeSql($row['vendor_name']), $client_id, $vendor_id); $count++; } flashAlert("Archived $count vendor" . ($count === 1 ? '' : 's'), 'error'); redirect(); } if (isset($_POST['bulk_restore_vendors'])) { validateCSRFToken(); $count = 0; foreach (array_unique(array_map('intval', (array) ($_POST['vendor_ids'] ?? []))) as $vendor_id) { $row = vendorRow($vendor_id); if (!$row || !$row['vendor_archived_at']) { continue; } $client_id = intval($row['vendor_client_id']); vendorEnforce($client_id, 2); mysqli_query($mysqli, "UPDATE vendors SET vendor_archived_at = NULL WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Restore", "$session_name restored vendor " . escapeSql($row['vendor_name']), $client_id, $vendor_id); $count++; } flashAlert("Restored $count vendor" . ($count === 1 ? '' : 's')); redirect(); } if (isset($_POST['bulk_delete_vendors'])) { validateCSRFToken(); $count = 0; $kept = []; foreach (array_unique(array_map('intval', (array) ($_POST['vendor_ids'] ?? []))) as $vendor_id) { $row = vendorRow($vendor_id); if (!$row) { continue; } $client_id = intval($row['vendor_client_id']); vendorEnforce($client_id, 3); if (vendorDeleteBlocker($vendor_id)) { $kept[] = $row['vendor_name']; continue; } vendorClearReferences($vendor_id); mysqli_query($mysqli, "DELETE FROM vendors WHERE vendor_id = $vendor_id"); logAudit("Vendor", "Delete", "$session_name deleted vendor " . escapeSql($row['vendor_name']), $client_id); $count++; } flashAlert("Deleted $count vendor" . ($count === 1 ? '' : 's') . ($kept ? ' - kept ' . implode(', ', $kept) . ', which have expenses booked to them' : ''), 'error'); redirect(); } if (isExportRequest('export_vendors')) { validateCSRFToken(); $format = resolveExportFormat($_POST['export_vendors']); // Filters inherited from the vendors page - mirrors agent/vendors.php $filter_summary = []; // Archived Filter $archived = (isset($_POST['archived']) && $_POST['archived'] == 1); if ($archived) { $filter_summary['Archived'] = 'Archived only'; } $archive_query = $archived ? "vendor_archived_at IS NOT NULL" : "vendor_archived_at IS NULL"; if (!empty($_POST['client_id'])) { // One client's vendors $client_id = intval($_POST['client_id']); enforceUserPermission('module_client'); enforceClientAccess(); $client_query = "AND vendor_client_id = $client_id"; $client_name = getFieldById('clients', $client_id, 'client_name'); $file_name_prepend = "$client_name-"; $filter_summary['Client'] = $client_name; } elseif (($_POST['view'] ?? '') === 'clients') { // Every client's vendors this agent may see enforceUserPermission('module_client'); $client_id = 0; // for Logging $client_query = "AND vendor_client_id > 0"; $file_name_prepend = "$session_company_name-Client-"; $filter_summary['Vendors'] = 'Client vendors'; if (!empty($_POST['client'])) { $filter_client_id = intval($_POST['client']); $client_query = "AND vendor_client_id = $filter_client_id"; $filter_summary['Client'] = exportClientName($filter_client_id); } $archive_query = $archived ? "(client_archived_at IS NOT NULL OR vendor_archived_at IS NOT NULL)" : "(client_archived_at IS NULL AND vendor_archived_at IS NULL)"; } else { // Ours, same as the vendors page $client_id = 0; // for Logging $client_query = "AND vendor_client_id = 0"; $file_name_prepend = "$session_company_name-"; } // Tags Filter - a vendor with any of them $tag_query = ''; if (isset($_POST['tags']) && is_array($_POST['tags']) && !empty($_POST['tags'])) { $tag_filter = implode(",", array_map('intval', $_POST['tags'])); $tag_query = "AND vendors.vendor_id IN (SELECT vendor_id FROM vendor_tags WHERE tag_id IN ($tag_filter))"; $tag_names = []; $sql_tags = mysqli_query($mysqli, "SELECT tag_name FROM tags WHERE tag_id IN ($tag_filter) ORDER BY tag_name ASC"); while ($tag_row = mysqli_fetch_assoc($sql_tags)) { $tag_names[] = $tag_row['tag_name']; } $filter_summary['Tags'] = implode(', ', $tag_names); } // Search Filter $q = escapeSql($_POST['q'] ?? ''); if (!empty($q)) { $filter_summary['Search'] = $_POST['q']; } $sql = mysqli_query( $mysqli, "SELECT vendors.*, client_name, (SELECT GROUP_CONCAT(tag_name ORDER BY tag_name SEPARATOR ', ') FROM vendor_tags JOIN tags ON tags.tag_id = vendor_tags.tag_id WHERE vendor_tags.vendor_id = vendors.vendor_id) AS vendor_tags, (SELECT COUNT(*) FROM tickets WHERE ticket_client_id = vendor_client_id AND ticket_vendor_id = vendors.vendor_id AND ticket_resolved_at IS NULL AND ticket_closed_at IS NULL) AS open_case_count FROM vendors LEFT JOIN clients ON client_id = vendor_client_id WHERE $archive_query AND (vendor_name LIKE '%$q%' OR vendor_description LIKE '%$q%' OR vendor_account_number LIKE '%$q%' OR vendor_website LIKE '%$q%' OR vendor_status_page LIKE '%$q%' OR vendor_portal LIKE '%$q%' OR vendor_contact_name LIKE '%$q%' OR vendor_email LIKE '%$q%' OR vendor_contact_email LIKE '%$q%' OR vendor_phone LIKE '%$q%' OR vendor_notes LIKE '%$q%') $client_query $tag_query " . clientScopeSql('vendor_client_id') . " ORDER BY client_name ASC, vendor_name ASC" ); $num_rows = mysqli_num_rows($sql); guardExportHasRows($num_rows, 'vendors'); if ($num_rows > 0) { guardExportPdfRowCount($format, $num_rows); $export = beginExport('vendors', $format, $file_name_prepend . 'Vendors', 'Vendors', summarizeExportFilters($filter_summary)); while ($row = mysqli_fetch_assoc($sql)) { addExportRow($export, $row); } finishExport($export); } logAudit("Vendor", "Export", "$session_name exported $num_rows vendor(s) to a " . strtoupper($format) . " file", $client_id); exit; }