$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;
}