[Feature] Updated Invoice Ticket to include more details in the description, predefined along with optional Invoice Title, helper texts below qty and price to show how it gets its information, fixed ticket number not showing in ticket reply when adding ticket to an existing invoice. Add to Existing Invoice is default if open invoices exist, migrated many more ticket related modals to use ajax-modal

This commit is contained in:
johnnyq 2025-11-04 17:48:21 -05:00
parent 43a7b7faa5
commit 9fcaf9f5cc
19 changed files with 1014 additions and 913 deletions

View File

@ -36,7 +36,4 @@ if (isset($session_is_admin) && $session_is_admin) {
require_once "../post/logout.php"; require_once "../post/logout.php";
// TODO: Find a home for these // TODO: Find a home for these
require_once "../post/ai.php";
require_once "../post/misc.php"; require_once "../post/misc.php";

View File

@ -698,4 +698,248 @@ if (isset($_GET['client_duplicate_check'])) {
} }
echo json_encode($response); echo json_encode($response);
} }
if (isset($_GET['ai_reword'])) {
header('Content-Type: application/json');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$promptText = $row['ai_model_prompt'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
// Collecting the input data from the AJAX request.
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE); // Convert JSON into array.
$userText = $input['text'];
// Preparing the data for the OpenAI Chat API request.
$data = [
"model" => "$model_name", // Specify the model
"messages" => [
["role" => "system", "content" => $promptText],
["role" => "user", "content" => $userText],
],
"temperature" => 0.5
];
// Initialize cURL session to the OpenAI Chat API.
$ch = curl_init("$url");
// Set cURL options for the request.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key,
]);
// Execute the cURL session and capture the response.
$response = curl_exec($ch);
curl_close($ch);
// Decode the JSON response.
$responseData = json_decode($response, true);
// Check if the response contains the expected data and return it.
if (isset($responseData['choices'][0]['message']['content'])) {
// Get the response content.
$content = $responseData['choices'][0]['message']['content'];
// Clean any leading "html" word or other unwanted text at the beginning.
$content = preg_replace('/^html/i', '', $content); // Remove any occurrence of 'html' at the start
// Clean the response content to remove backticks or code block markers.
$cleanedContent = str_replace('```', '', $content); // Remove backticks if they exist.
// Trim any leading/trailing whitespace.
$cleanedContent = trim($cleanedContent);
// Return the cleaned response.
echo json_encode(['rewordedText' => $cleanedContent]);
} else {
// Handle errors or unexpected response structure.
echo json_encode(['rewordedText' => 'Failed to get a response from the AI API.']);
}
}
if (isset($_GET['ai_create_document_template'])) {
// get_ai_document_template.php
header('Content-Type: text/html; charset=UTF-8');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
$prompt = $_POST['prompt'] ?? '';
// Basic validation
if(empty($prompt)){
echo "No prompt provided.";
exit;
}
// Prepare prompt
$system_message = "You are a helpful IT documentation assistant. You will create a well-structured HTML template for IT documentation based on a given prompt. Include headings, subheadings, bullet points, and possibly tables for clarity. No Lorem Ipsum, use realistic placeholders and professional language.";
$user_message = "Create an HTML formatted IT documentation template based on the following request:\n\n\"$prompt\"\n\nThe template should be structured, professional, and useful for IT staff. Include relevant sections, instructions, prerequisites, and best practices.";
$post_data = [
"model" => "$model_name",
"messages" => [
["role" => "system", "content" => $system_message],
["role" => "user", "content" => $user_message]
],
"temperature" => 0.5
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "Error: " . curl_error($ch);
exit;
}
curl_close($ch);
$response_data = json_decode($response, true);
$template = $response_data['choices'][0]['message']['content'] ?? "<p>No content returned from AI.</p>";
// Print the generated HTML template directly
echo $template;
}
if (isset($_GET['ai_ticket_summary'])) {
header('Content-Type: text/html; charset=UTF-8');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
// Retrieve the ticket_id from POST
$ticket_id = intval($_POST['ticket_id']);
// Query the database for ticket details
$sql = mysqli_query($mysqli, "
SELECT ticket_subject, ticket_details, ticket_source, ticket_priority, ticket_status_name, category_name
FROM tickets
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
LEFT JOIN categories ON ticket_category = category_id
WHERE ticket_id = $ticket_id
LIMIT 1
");
$row = mysqli_fetch_assoc($sql);
$ticket_subject = $row['ticket_subject'];
$ticket_details = strip_tags($row['ticket_details']); // strip HTML for cleaner prompt
$ticket_status = $row['ticket_status_name'];
$ticket_category = $row['category_name'];
$ticket_source = $row['ticket_source'];
$ticket_priority = $row['ticket_priority'];
// Get ticket replies
$sql_replies = mysqli_query($mysqli, "
SELECT ticket_reply, ticket_reply_type, user_name
FROM ticket_replies
LEFT JOIN users ON ticket_reply_by = user_id
WHERE ticket_reply_ticket_id = $ticket_id
AND ticket_reply_archived_at IS NULL
ORDER BY ticket_reply_id ASC
");
$all_replies_text = "";
while ($reply = mysqli_fetch_assoc($sql_replies)) {
$reply_type = $reply['ticket_reply_type'];
$reply_text = strip_tags($reply['ticket_reply']);
$reply_by = $reply['user_name'];
$all_replies_text .= "\nReply Type: $reply_type Reply By: $reply_by: Reply Text: $reply_text";
}
$prompt = "
Summarize the following IT support ticket and its responses in a concise, clear, and professional manner.
The summary should include:
1. Main Issue: What was the problem reported by the user?
2. Actions Taken: What steps were taken to address the issue?
3. Resolution or Next Steps: Was the issue resolved or is it ongoing?
Please ensure:
- If there are multiple issues, summarize each separately.
- Urgency: If the ticket or replies express urgency or escalation, highlight it.
- Attachments: If mentioned in the ticket, note any relevant attachments or files.
- Avoid extra explanations or unnecessary information.
Ticket Data:
- Ticket Source: $ticket_source
- Current Ticket Status: $ticket_status
- Ticket Priority: $ticket_priority
- Ticket Category: $ticket_category
- Ticket Subject: $ticket_subject
- Ticket Details: $ticket_details
- Replies:
$all_replies_text
Formatting instructions:
- Use valid HTML tags only.
- Use <h3> for section headers (Main Issue, Actions Taken, Resolution).
- Use <ul><li> for bullet points under each section.
- Do NOT wrap the output in ```html or any other code fences.
- Do NOT include <html>, <head>, or <body>.
- Output only the summary content in pure HTML.
If any part of the ticket or replies is unclear or ambiguous, mention it in the summary and suggest if further clarification is needed.
";
// Prepare the POST data
$post_data = [
"model" => "$model_name",
"messages" => [
["role" => "system", "content" => "Your task is to summarize IT support tickets with clear, concise details."],
["role" => "user", "content" => $prompt]
],
"temperature" => 0.3
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "Error: " . curl_error($ch);
exit;
}
curl_close($ch);
$response_data = json_decode($response, true);
$summary = $response_data['choices'][0]['message']['content'] ?? "No summary available.";
echo $summary; // nl2br to convert newlines to <br>, htmlspecialchars to prevent XSS
}

View File

@ -33,7 +33,7 @@ if (isset($_GET['calendar_id'])) {
<div class="card-header py-2"> <div class="card-header py-2">
<h3 class="card-title mt-1">Calendars</h3> <h3 class="card-title mt-1">Calendars</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-dark btn-sm" data-toggle="modal" data-target="#addCalendarModal"><i class="fas fa-plus"></i></button> <button type="button" class="btn btn-dark btn-sm ajax-modal" data-modal-url="modals/calendar/calendar_add.php"><i class="fas fa-plus"></i></button>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
@ -82,8 +82,6 @@ if (isset($_GET['calendar_id'])) {
<?php <?php
require_once "modals/calendar/calendar_event_add.php"; require_once "modals/calendar/calendar_event_add.php";
require_once "modals/calendar/calendar_add.php";
//loop through IDs and create a modal for each //loop through IDs and create a modal for each
$sql = mysqli_query($mysqli, "SELECT * FROM calendar_events LEFT JOIN calendars ON event_calendar_id = calendar_id $client_event_query"); $sql = mysqli_query($mysqli, "SELECT * FROM calendar_events LEFT JOIN calendars ON event_calendar_id = calendar_id $client_event_query");

View File

@ -1,94 +1,55 @@
/* $(document).ready(function() {
* LISTENERS
*/
// Modal loaded listener - populate client select // Function to load contacts for a given client
const changeClientModalLoad = document.getElementById('clientChangeTicketModalLoad'); function loadContacts(clientId) {
changeClientModalLoad.addEventListener('click', function() { if (!clientId) return;
populateChangeClientModal_Clients();
})
// Client selected listener - populate contact select var $contactSelect = $('#contact_select');
// We seem to have to use jQuery to listen for events, as the client input is a select2 component? $contactSelect.html('<option value="">Loading...</option>');
const clientSelectDropdown = document.getElementById("changeClientSelect");
$(clientSelectDropdown).on('select2:select', function (e) {
let client_id = $(this).find(':selected').val();
populateChangeClientModal_Contacts(client_id);
});
$.ajax({
/* url: 'ajax.php',
* FUNCTIONS type: 'GET',
*/ dataType: 'json',
data: {
// Populate client list function get_client_contacts: 1,
function populateChangeClientModal_Clients() { client_id: clientId
},
// Get current client ID success: function(response) {
let current_client_id = document.getElementById("client_id").value; $contactSelect.empty();
if (response.contacts && response.contacts.length > 0) {
// Send a GET request to ajax.php as ajax.php?get_active_clients=true $contactSelect.append('<option value="">Select a contact</option>');
jQuery.get( $.each(response.contacts, function(i, contact) {
"ajax.php", $contactSelect.append(
{get_active_clients: 'true'}, $('<option>', {
function(data) { value: contact.contact_id,
text: contact.contact_name
// If we get a response from ajax.php, parse it as JSON })
const response = JSON.parse(data); );
});
// Access the data for clients (multiple) } else {
const clients = response.clients; $contactSelect.append('<option value="">No contacts found</option>');
// Client dropdown already defined in listeners as clientSelectDropdown
// Clear dropdown
let i, L = clientSelectDropdown.options.length - 1;
for (i = L; i >= 0; i--) {
clientSelectDropdown.remove(i);
}
clientSelectDropdown[clientSelectDropdown.length] = new Option('- Client -', '0');
// Populate dropdown
clients.forEach(client => {
if (parseInt(current_client_id) !== parseInt(client.client_id)) {
// Show clients returned (excluding the current client ID - we can't change a ticket client to itself)
clientSelectDropdown[clientSelectDropdown.length] = new Option(client.client_name, client.client_id);
} }
});
} // Refresh Select2
); if ($.fn.select2) {
} $contactSelect.trigger('change.select2');
}
// Populate client contact function (after a client is selected) },
function populateChangeClientModal_Contacts(client_id) { error: function(xhr, status, error) {
// Send a GET request to ajax.php as ajax.php?get_client_contacts=true&client_id=NUM console.error('AJAX Error:', error);
jQuery.get( $contactSelect.html('<option value="">Failed to load contacts</option>');
"ajax.php",
{get_client_contacts: 'true', client_id: client_id},
function(data) {
// If we get a response from ajax.php, parse it as JSON
const response = JSON.parse(data);
// Access the data for contacts (multiple)
const contacts = response.contacts;
// Contacts dropdown
const contactSelectDropdown = document.getElementById("changeContactSelect");
// Clear Category dropdown
let i, L = contactSelectDropdown.options.length - 1;
for (i = L; i >= 0; i--) {
contactSelectDropdown.remove(i);
} }
contactSelectDropdown[contactSelectDropdown.length] = new Option('- Contact -', '0'); });
}
// Populate dropdown // Load contacts for the currently selected client when modal opens
contacts.forEach(contact => { var initialClientId = $('#client_select').val();
contactSelectDropdown[contactSelectDropdown.length] = new Option(contact.contact_name, contact.contact_id); loadContacts(initialClientId);
});
} // Load contacts when client changes
); $('#client_select').on('change', function() {
} var clientId = $(this).val();
loadContacts(clientId);
});
});

View File

@ -1,41 +1,45 @@
<div class="modal" id="addCalendarModal" tabindex="-1"> <?php
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-calendar-plus mr-2"></i>New Calendar</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
<div class="form-group"> require_once '../../../includes/modal_header.php';
<label>Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Name your calendar" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group"> ob_start();
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" required>
</div>
</div>
</div> ?>
<div class="modal-footer"> <div class="modal-header bg-dark">
<button type="submit" name="add_calendar" class="btn btn-primary"><i class="fa fa-check mr-2"></i>Create</button> <h5 class="modal-title"><i class="fa fa-fw fa-calendar-plus mr-2"></i>New Calendar</h5>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> <button type="button" class="close text-white" data-dismiss="modal">
</div> <span>&times;</span>
</form> </button>
</div>
</div>
</div> </div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
<div class="form-group">
<label>Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Name your calendar" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-paint-brush"></i></span>
</div>
<input type="color" class="form-control col-3" name="color" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_calendar" class="btn btn-primary"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -1,153 +1,160 @@
<div class="modal" id="addRackModal" tabindex="-1"> <?php
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-server mr-2"></i>New Rack</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off"> require_once '../../../includes/modal_header.php';
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>"> $client_id = intval($_GET['client_id'] ?? 0);
<div class="modal-body"> ob_start();
<ul class="nav nav-pills nav-justified mb-3"> ?>
<li class="nav-item"> <div class="modal-header bg-dark">
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a> <h5 class="modal-title"><i class="fa fa-fw fa-server mr-2"></i>New Rack</h5>
</li> <button type="button" class="close text-white" data-dismiss="modal">
<li class="nav-item"> <span>&times;</span>
<a class="nav-link" data-toggle="pill" href="#pills-notes">Notes</a> </button>
</li> </div>
</ul>
<hr> <form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<div class="tab-content"> <input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<div class="tab-pane fade show active" id="pills-details"> <div class="modal-body">
<div class="form-group"> <ul class="nav nav-pills nav-justified mb-3">
<label>Name <strong class="text-danger">*</strong></label> <li class="nav-item">
<div class="input-group"> <a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
<div class="input-group-prepend"> </li>
<span class="input-group-text"><i class="fa fa-fw fa-server"></i></span> <li class="nav-item">
</div> <a class="nav-link" data-toggle="pill" href="#pills-notes">Notes</a>
<input type="text" class="form-control" name="name" placeholder="Rack name" maxlength="200" required autofocus> </li>
</div> </ul>
</div>
<div class="form-group"> <hr>
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description of the rack">
</div>
</div>
<div class="form-group"> <div class="tab-content">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-server"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Type -</option>
<?php foreach($rack_type_select_array as $rack_type) { ?>
<option><?php echo $rack_type; ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group"> <div class="tab-pane fade show active" id="pills-details">
<label>Model</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="make" placeholder="ex StarTech 12U Open Frame" maxlength="200">
</div>
</div>
<div class="form-group">
<label>Depth</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-ruler"></i></span>
</div>
<input type="text" class="form-control" name="depth" placeholder="Rack Depth eg 800 mm or 31.5 Inches" maxlength="50">
</div>
</div>
<div class="form-group">
<label>Number of Units <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-up-alt"></i></span>
</div>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="70" required>
</div>
</div>
<div class="form-group">
<label>Physical Location</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
</div>
<input type="text" class="form-control" name="physical_location" placeholder="Physical location eg. Floor 2, Closet B" maxlength="200">
</div>
</div>
<div class="form-group">
<label>Location</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
</div>
<select class="form-control select2" name="location">
<option value="">- Location -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM locations WHERE location_archived_at IS NULL AND location_client_id = $client_id ORDER BY location_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$location_id = intval($row['location_id']);
$location_name = nullable_htmlentities($row['location_name']);
?>
<option value="<?php echo $location_id; ?>"><?php echo $location_name; ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-server"></i></span>
</div> </div>
<input type="text" class="form-control" name="name" placeholder="Rack name" maxlength="200" required autofocus>
<div class="tab-pane fade" id="pills-notes">
<div class="form-group">
<label>Upload Photo</label>
<input type="file" class="form-control-file" name="file" accept="image/*">
</div>
<div class="form-group">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"></textarea>
</div>
</div>
</div> </div>
</div> </div>
<div class="modal-footer">
<button type="submit" name="add_rack" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button> <div class="form-group">
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> <label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Description of the rack">
</div>
</div> </div>
</form>
<div class="form-group">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-server"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Type -</option>
<?php foreach($rack_type_select_array as $rack_type) { ?>
<option><?php echo $rack_type; ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>Model</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
</div>
<input type="text" class="form-control" name="make" placeholder="ex StarTech 12U Open Frame" maxlength="200">
</div>
</div>
<div class="form-group">
<label>Depth</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-ruler"></i></span>
</div>
<input type="text" class="form-control" name="depth" placeholder="Rack Depth eg 800 mm or 31.5 Inches" maxlength="50">
</div>
</div>
<div class="form-group">
<label>Number of Units <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-up-alt"></i></span>
</div>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="70" required>
</div>
</div>
<div class="form-group">
<label>Physical Location</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
</div>
<input type="text" class="form-control" name="physical_location" placeholder="Physical location eg. Floor 2, Closet B" maxlength="200">
</div>
</div>
<div class="form-group">
<label>Location</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
</div>
<select class="form-control select2" name="location">
<option value="">- Location -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM locations WHERE location_archived_at IS NULL AND location_client_id = $client_id ORDER BY location_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$location_id = intval($row['location_id']);
$location_name = nullable_htmlentities($row['location_name']);
?>
<option value="<?php echo $location_id; ?>"><?php echo $location_name; ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-notes">
<div class="form-group">
<label>Upload Photo</label>
<input type="file" class="form-control-file" name="file" accept="image/*">
</div>
<div class="form-group">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"></textarea>
</div>
</div>
</div> </div>
</div> </div>
</div> <div class="modal-footer">
<button type="submit" name="add_rack" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -1,64 +1,68 @@
<div class="modal" id="addTicketWatcherModal" tabindex="-1"> <?php
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-eye mr-2"></i>Adding a ticket Watcher: <strong><?php echo "$ticket_prefix$ticket_number"; ?></strong> - <?php echo $client_name; ?></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<input type="hidden" name="ticket_number" value="<?php echo "$ticket_prefix$ticket_number"; ?>">
<input type="hidden" name="watcher_notify" value="0"> <!-- Default 0 -->
<div class="modal-body">
<div class="form-group"> require_once '../../../includes/modal_header.php';
<label>Watcher Email</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<select class="form-control select2" data-tags="true" name="watcher_email">
<option value="">- Select a contact or enter an email(s) -</option>
<?php
$sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' ORDER BY contact_name ASC"); $ticket_id = intval($_GET['ticket_id'] ?? 0);
while ($row = mysqli_fetch_array($sql_client_contacts_select)) { $client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id') ?? 0);
$contact_id_select = intval($row['contact_id']);
$contact_name_select = nullable_htmlentities($row['contact_name']);
$contact_email_select = nullable_htmlentities($row['contact_email']);
?>
<option value="<?php echo $contact_email_select; ?>"><?php echo "$contact_name_select - $contact_email_select"; ?></option>
<?php ob_start();
}
?>
</select>
</div>
</div>
<?php if (!empty($config_smtp_host)) { ?> ?>
<div class="form-group"> <div class="modal-header bg-dark">
<div class="form-check"> <h5 class="modal-title"><i class="fa fa-fw fa-eye mr-2"></i>Adding a ticket Watcher</h5>
<input class="form-check-input" type="checkbox" name="watcher_notify" value="1" id="checkNotifyWatcher"> <button type="button" class="close text-white" data-dismiss="modal">
<label class="form-check-label" for="checkNotifyWatcher"> <span>&times;</span>
Send email notification </button>
</label>
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_watcher" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Add</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
</div>
</div>
</div> </div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<div class="modal-body">
<div class="form-group">
<label>Watcher Email</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<select class="form-control select2" data-tags="true" name="watcher_email">
<option value="">- Select a contact or enter an email(s) -</option>
<?php
$sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' ORDER BY contact_name ASC");
while ($row = mysqli_fetch_array($sql_client_contacts_select)) {
$contact_id_select = intval($row['contact_id']);
$contact_name_select = nullable_htmlentities($row['contact_name']);
$contact_email_select = nullable_htmlentities($row['contact_email']);
?>
<option value="<?php echo $contact_email_select; ?>"><?php echo "$contact_name_select - $contact_email_select"; ?></option>
<?php
}
?>
</select>
</div>
</div>
<?php if (!empty($config_smtp_host)) { ?>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="watcher_notify" value="1" id="checkNotifyWatcher">
<label class="form-check-label" for="checkNotifyWatcher">
Send email notification
</label>
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_watcher" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Add</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -1,49 +1,70 @@
<div class="modal" id="clientChangeTicketModal" tabindex="-1"> <?php
<div class="modal-dialog modal-md"> require_once '../../../includes/modal_header.php';
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-people-carry mr-2"></i>Change <?php echo "$ticket_prefix$ticket_number"; ?> to another client</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<div class="modal-body">
<div class="form-group"> $ticket_id = intval($_GET['ticket_id']);
<label>New Client <strong class="text-danger">*</strong></label> $current_client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id'));
<div class="input-group"> $ticket_prefix = nullable_htmlentities(getFieldById('tickets', $ticket_id, 'ticket_prefix'));
<div class="input-group-prepend"> $ticket_number = nullable_htmlentities(getFieldById('tickets', $ticket_id, 'ticket_number'));
<span class="input-group-text"><i class="fa fa-fw fa-users"></i></span>
</div>
<select class="form-control select2" name="new_client_id" id="changeClientSelect" required>
</select>
</div>
</div>
<div class="form-group"> ob_start();
<label>New Contact</label> ?>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<select class="form-control select2" name="new_contact_id" id="changeContactSelect">
</select>
</div>
</div>
</div> <div class="modal-header bg-dark">
<div class="modal-footer"> <h5 class="modal-title">
<button type="submit" name="change_client_ticket" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Change</button> <i class="fa fa-fw fa-people-carry mr-2"></i>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> Change <?php echo "$ticket_prefix$ticket_number"; ?> to another client
</div> </h5>
</form> <button type="button" class="close text-white" data-dismiss="modal"><span>&times;</span></button>
</div>
</div>
</div> </div>
<!-- Ticket Change Client JS --> <form action="post.php" method="post" autocomplete="off">
<link rel="stylesheet" href="../plugins/jquery-ui/jquery-ui.min.css"> <input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<script src="../plugins/jquery-ui/jquery-ui.min.js"></script>
<script src="js/ticket_change_client.js"></script> <div class="modal-body">
<div class="form-group">
<label>New Client <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-users"></i></span>
</div>
<select class="form-control select2" name="new_client_id" id="client_select" required>
<?php
$sql_clients = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients WHERE client_lead = 0 AND client_archived_at IS NULL ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql_clients)) {
$client_id_select = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
?>
<option value="<?= $client_id_select ?>" <?php if ($current_client_id == $client_id_select) echo 'selected'; ?>>
<?= $client_name ?>
</option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>New Contact</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<select class="form-control select2" name="new_contact_id" id="contact_select">
<option value="">- Select a contact -</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="change_client_ticket" class="btn btn-primary text-bold">
<i class="fa fa-check mr-2"></i>Change
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fa fa-times mr-2"></i>Cancel
</button>
</div>
</form>
<script src="/agent/js/ticket_change_client.js"></script>
<?php require_once '../../../includes/modal_footer.php'; ?>

View File

@ -1,49 +1,57 @@
<div class="modal" id="editTicketVendorModal<?php echo $ticket_id; ?>" tabindex="-1"> <?php
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-building mr-2"></i>Editing ticket Vendor: <strong><?php echo "$ticket_prefix$ticket_number"; ?></strong> - <?php echo $client_name; ?></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<div class="modal-body">
<div class="form-group"> require_once '../../../includes/modal_header.php';
<label>Vendor</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<select class="form-control select2" name="vendor">
<option value="0">- None -</option>
<?php
$sql_vendors = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = $client_id AND vendor_archived_at IS NULL ORDER BY vendor_name ASC"); $ticket_id = intval($_GET['ticket_id'] ?? 0);
while ($row = mysqli_fetch_array($sql_vendors)) {
$vendor_id_select = intval($row['vendor_id']);
$vendor_name_select = nullable_htmlentities($row['vendor_name']);
?>
<option <?php if ($vendor_id == $vendor_id_select) { echo "selected"; } ?> value="<?php echo $vendor_id_select; ?>"><?php echo $vendor_name_select; ?></option>
<?php $client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id') ?? 0);
} $vendor_id = intval(getFieldById('tickets', $ticket_id, 'ticket_vendor_id') ?? 0);
?>
</select>
</div>
</div>
</div> ob_start();
<div class="modal-footer"> ?>
<button type="submit" name="edit_ticket_vendor" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button> <div class="modal-header bg-dark">
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> <h5 class="modal-title"><i class="fa fa-fw fa-building mr-2"></i>Editing ticket Vendor</h5>
</div> <button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</form> </button>
</div>
</div>
</div> </div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?= $ticket_id ?>">
<div class="modal-body">
<div class="form-group">
<label>Vendor</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
</div>
<select class="form-control select2" name="vendor">
<option value="0">- None -</option>
<?php
$sql_vendors = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = $client_id AND vendor_archived_at IS NULL ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_array($sql_vendors)) {
$vendor_id_select = intval($row['vendor_id']);
$vendor_name = nullable_htmlentities($row['vendor_name']);
?>
<option <?php if ($vendor_id == $vendor_id_select) { echo "selected"; } ?> value="<?= $vendor_id_select ?>"><?= $vendor_name ?></option>
<?php
}
?>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_vendor" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -1,210 +1,322 @@
<?php <?php
require_once '../../../includes/modal_header.php';
$ticket_id = intval($_GET['ticket_id']);
$ticket_sql = mysqli_query(
$mysqli,
"SELECT * FROM tickets
LEFT JOIN clients ON ticket_client_id = client_id
LEFT JOIN contacts ON ticket_contact_id = contact_id
LEFT JOIN users ON ticket_assigned_to = user_id
LEFT JOIN locations ON ticket_location_id = location_id
LEFT JOIN assets ON ticket_asset_id = asset_id
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
LEFT JOIN categories ON ticket_category = category_id
WHERE ticket_id = $ticket_id
$access_permission_query
LIMIT 1"
);
$row = mysqli_fetch_array($ticket_sql);
$client_id = intval($row['client_id']);
$client_rate = floatval($row['client_rate']);
$ticket_prefix = nullable_htmlentities($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_category = intval($row['ticket_category']);
$ticket_category_display = nullable_htmlentities($row['category_name']);
$ticket_subject = nullable_htmlentities($row['ticket_subject']);
$ticket_priority = nullable_htmlentities($row['ticket_priority']);
$ticket_billable = intval($row['ticket_billable']);
$ticket_onsite = intval($row['ticket_onsite']);
$ticket_created_at = nullable_htmlentities($row['ticket_created_at']);
$ticket_created_by = intval($row['ticket_created_by']);
$ticket_date = date('Y-m-d', strtotime($ticket_created_at));
$ticket_first_response_at = nullable_htmlentities($row['ticket_first_response_at']);
if ($ticket_first_response_at) {
$ticket_first_response_date_time = date('Y-m-d H:i', strtotime($ticket_first_response_at));
} else {
$ticket_first_response_date_time = '';
}
$ticket_resolved_at = nullable_htmlentities($row['ticket_resolved_at']);
if ($ticket_resolved_at) {
$ticket_resolved_date = date('Y-m-d', strtotime($ticket_resolved_at));
} else {
$ticket_resolved_date = '';
}
$ticket_assigned_to = intval($row['ticket_assigned_to']);
if ($ticket_assigned_to) {
$ticket_assigned_agent = nullable_htmlentities($row['user_name']);
} else {
$ticket_assigned_agent = '';
}
$contact_id = intval($row['contact_id']);
$contact_name = nullable_htmlentities($row['contact_name']);
$asset_id = intval($row['asset_id']);
$asset_name = nullable_htmlentities($row['asset_name']);
$asset_type = nullable_htmlentities($row['asset_type']);
$location_id = intval($row['location_id']);
$location_name = nullable_htmlentities($row['location_name']);
$location_address = nullable_htmlentities($row['location_address']);
$location_city = nullable_htmlentities($row['location_city']);
$location_state = nullable_htmlentities($row['location_state']);
$location_zip = nullable_htmlentities($row['location_zip']);
$location_phone = formatPhoneNumber($row['location_phone']);
//Get Total Ticket Time
$ticket_total_reply_time = mysqli_query($mysqli, "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ticket_reply_time_worked))) AS ticket_total_reply_time FROM ticket_replies WHERE ticket_reply_archived_at IS NULL AND ticket_reply_ticket_id = $ticket_id");
$row = mysqli_fetch_array($ticket_total_reply_time);
$ticket_total_reply_time = nullable_htmlentities($row['ticket_total_reply_time']);
$sql_invoices = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_status LIKE 'Draft' AND invoice_client_id = $client_id ORDER BY invoice_number ASC"); $sql_invoices = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_status LIKE 'Draft' AND invoice_client_id = $client_id ORDER BY invoice_number ASC");
ob_start();
?> ?>
<div class="modal" id="addInvoiceFromTicketModal" tabindex="-1"> <div class="modal-header bg-dark">
<div class="modal-dialog modal-lg"> <h5 class="modal-title"><i class="fa fa-fw fa-file-invoice-dollar mr-2"></i>Invoice ticket</h5>
<div class="modal-content"> <button type="button" class="close text-white" data-dismiss="modal">
<div class="modal-header bg-dark"> <span>&times;</span>
<h5 class="modal-title"><i class="fa fa-fw fa-file-invoice-dollar mr-2"></i>Invoice ticket</h5> </button>
<button type="button" class="close text-white" data-dismiss="modal"> </div>
<span>&times;</span> <form action="post.php" method="post" autocomplete="off">
</button> <input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
</div> <div class="modal-body">
<form action="post.php" method="post" autocomplete="off"> <?php if (mysqli_num_rows($sql_invoices) > 0) { ?>
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>">
<div class="modal-body">
<?php if (mysqli_num_rows($sql_invoices) > 0) { ?>
<ul class="nav nav-pills nav-justified mb-3"> <ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-create-invoice"><i class="fa fa-fw fa-check mr-2"></i>Create New Invoice</a> <a class="nav-link active" data-toggle="pill" href="#pills-add-to-invoice"><i class="fa fa-fw fa-plus mr-2"></i>Add to Existing Invoice</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-add-to-invoice"><i class="fa fa-fw fa-plus mr-2"></i>Add to Existing Invoice</a> <a class="nav-link" data-toggle="pill" href="#pills-create-invoice"><i class="fa fa-fw fa-check mr-2"></i>Create New Invoice</a>
</li> </li>
<?php } ?>
</ul> </ul>
<hr> <hr>
<div class="tab-content"> <?php } ?>
<div class="tab-pane fade show active" id="pills-create-invoice"> <div class="tab-content">
<div class="row"> <?php
<div class="col-sm-6">
if (mysqli_num_rows($sql_invoices) > 0) { ?>
<div class="form-group">
<label>Invoice Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control" name="date" max="2999-12-31" value="<?php echo date("Y-m-d"); ?>">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label>Invoice Category <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="category">
<option value="">- Category -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Income' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$category_id = intval($row['category_id']);
$category_name = nullable_htmlentities($row['category_name']);
?>
<option value="<?php echo $category_id; ?>"><?php echo $category_name; ?></option>
<?php
}
?>
</select>
<div class="input-group-append">
<button type="button" class="btn btn-secondary" data-toggle="modal" data-target="#addQuickCategoryIncomeModal"><i class="fas fa-fw fa-plus"></i></button>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>Scope</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-comment"></i></span>
</div>
<input type="text" class="form-control" name="scope" placeholder="Quick description" value="Ticket <?php echo "$ticket_prefix$ticket_number - $ticket_subject"; ?>">
</div>
</div>
</div>
<?php
if (mysqli_num_rows($sql_invoices) > 0) { ?>
<div class="tab-pane fade" id="pills-add-to-invoice">
<div class="form-group">
<label>Invoice</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div>
<select class="form-control" name="invoice_id">
<option value="0">- Invoice -</option>
<?php
while ($row = mysqli_fetch_array($sql_invoices)) {
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
$invoice_scope = nullable_htmlentities($row['invoice_scope']);
$invoice_status = nullable_htmlentities($row['invoice_status']);
$invoice_date = nullable_htmlentities($row['invoice_date']);
$invoice_due = nullable_htmlentities($row['invoice_due']);
$invoice_amount = floatval($row['invoice_amount']);
?>
<option value="<?php echo $invoice_id; ?>"><?php echo "$invoice_prefix$invoice_number | $invoice_scope"; ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<?php } ?>
</div>
<hr>
<div class="tab-pane fade <?php if (mysqli_num_rows($sql_invoices) > 0) { echo "active show"; } ?>" id="pills-add-to-invoice">
<div class="form-group"> <div class="form-group">
<label>Item <strong class="text-danger">*</strong></label> <label>Existing Invoice</label>
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-box"></i></span> <span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div> </div>
<input type="text" class="form-control" name="item_name" placeholder="Item" value="Support [Hourly]" required> <select class="form-control" name="invoice_id">
</div> <option value="0">- Select an Existing Invoice -</option>
</div>
<div class="form-group">
<label>Item Description</label>
<div class="input-group">
<textarea class="form-control" rows="5" name="item_description"><?php echo "# $contact_name - $asset_name - $ticket_date\nTicket $ticket_prefix$ticket_number\n$ticket_subject\nTT: $ticket_total_reply_time"; ?></textarea>
</div>
</div>
<div class="form-row">
<div class="col">
<div class="form-group">
<label>QTY <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-balance-scale"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="-?[0-9]*\.?[0-9]{0,2}" name="qty" value="<?php echo roundToNearest15($ticket_total_reply_time); ?>" required>
</div>
</div>
</div>
<div class="col">
<div class="form-group">
<label>Price <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="-?[0-9]*\.?[0-9]{0,2}" name="price" value="<?php echo number_format($client_rate, 2, '.', ''); ?>" required>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>Tax <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
</div>
<select class="form-control select2" name="tax_id" required>
<option value="0">None</option>
<?php <?php
$taxes_sql = mysqli_query($mysqli, "SELECT * FROM taxes WHERE tax_archived_at IS NULL ORDER BY tax_name ASC"); while ($row = mysqli_fetch_array($sql_invoices)) {
while ($row = mysqli_fetch_array($taxes_sql)) { $invoice_id = intval($row['invoice_id']);
$tax_id_select = intval($row['tax_id']); $invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$tax_name = nullable_htmlentities($row['tax_name']); $invoice_number = intval($row['invoice_number']);
$tax_percent = floatval($row['tax_percent']); $invoice_scope = nullable_htmlentities($row['invoice_scope']);
$invoice_status = nullable_htmlentities($row['invoice_status']);
$invoice_date = nullable_htmlentities($row['invoice_date']);
$invoice_due = nullable_htmlentities($row['invoice_due']);
$invoice_amount = floatval($row['invoice_amount']);
?> ?>
<option value="<?php echo $tax_id_select; ?>"><?php echo "$tax_name $tax_percent%"; ?></option> <option value="<?php echo $invoice_id; ?>"><?php echo "$invoice_prefix$invoice_number | $invoice_scope"; ?></option>
<?php } ?> <?php } ?>
</select>
</select>
</div> </div>
</div> </div>
</div>
<?php } ?>
<div class="tab-pane fade <?php if (mysqli_num_rows($sql_invoices) == 0) { echo "active show"; } ?>" id="pills-create-invoice">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label>Invoice Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control" name="date" max="2999-12-31" value="<?php echo date("Y-m-d"); ?>">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label>Invoice Category <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="category">
<option value="">- Select a Category -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Income' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$category_id = intval($row['category_id']);
$category_name = nullable_htmlentities($row['category_name']);
?>
<option value="<?php echo $category_id; ?>"><?php echo $category_name; ?></option>
<?php
}
?>
</select>
<div class="input-group-append">
<button type="button" class="btn btn-secondary ajax-modal" data-modal-url="/admin/modals/category/category_add.php?category=Expense"><i class="fas fa-fw fa-plus"></i></button>
</div>
</div>
</div>
</div>
</div> </div>
<div class="modal-footer">
<button type="submit" name="add_invoice_from_ticket" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Invoice</button> <div class="form-group">
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> <label>Invoice Title</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-comment"></i></span>
</div>
<select class="form-control select2" name="scope" data-tags="true" data-placeholder="- Enter or Select an Invoice Title -">
<option value=""></option>
<option><?= date('F Y'); ?> Tickets</option>
<option><?= "Ticket $ticket_prefix$ticket_number - $ticket_subject" ?></option>
</select>
</div>
</div> </div>
</form>
</div>
</div> </div>
<div class="form-group">
<label>Item <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-box"></i></span>
</div>
<input type="text" class="form-control" name="item_name" placeholder="Item" value="Support [Hourly]" required>
</div>
</div>
<div class="form-group">
<label>Item Description</label>
<div class="input-group">
<textarea class="form-control" rows="10" name="item_description"><?php
// Build description text cleanly in PHP, not mixed with HTML
$description = "#Ticket: {$ticket_prefix}{$ticket_number} - $ticket_subject\n";
$description .= "Priority: {$ticket_priority}\n";
$description .= "Opened at: {$ticket_date}\n";
if ($ticket_first_response_date_time) {
$description .= "Initial Response: {$ticket_first_response_date_time}\n";
}
if ($ticket_resolved_date) {
$description .= "Resolved at: {$ticket_resolved_date}\n";
}
if ($ticket_assigned_agent) {
$description .= "Agent: {$ticket_assigned_agent}\n";
}
if ($location_id) {
$description .= "Location: {$location_name}\n";
}
if ($contact_id) {
$description .= "Contact: {$contact_name}\n";
}
if ($asset_id) {
$description .= "Asset: {$asset_name}\n";
}
if ($ticket_total_reply_time) {
$description .= "Agent Time Spent: {$ticket_total_reply_time}";
}
echo trim($description); // Trim any leading/trailing spaces/newlines
?></textarea>
</div>
</div>
<div class="form-row">
<div class="col">
<div class="form-group">
<label>QTY <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-balance-scale"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="-?[0-9]*\.?[0-9]{0,2}" name="qty" value="<?php echo roundToNearest15($ticket_total_reply_time); ?>" required>
</div>
<small class="form-text text-muted">
Based off Ticket time spent <strong><?= $ticket_total_reply_time ?></strong> in 15 Min Increments rounded up.
</small>
</div>
</div>
<div class="col">
<div class="form-group">
<label>Price <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="-?[0-9]*\.?[0-9]{0,2}" name="price" value="<?php echo number_format($client_rate, 2, '.', ''); ?>" required>
</div>
<small class="form-text text-muted">
Based off Hourly Client rate of <strong><?= numfmt_format_currency($currency_format, $client_rate, $session_company_currency); ?></strong>
</small>
</div>
</div>
</div>
<div class="form-group">
<label>Tax <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
</div>
<select class="form-control select2" name="tax_id" required>
<option value="0">None</option>
<?php
$taxes_sql = mysqli_query($mysqli, "SELECT * FROM taxes WHERE tax_archived_at IS NULL ORDER BY tax_name ASC");
while ($row = mysqli_fetch_array($taxes_sql)) {
$tax_id_select = intval($row['tax_id']);
$tax_name = nullable_htmlentities($row['tax_name']);
$tax_percent = floatval($row['tax_percent']);
?>
<option value="<?php echo $tax_id_select; ?>"><?php echo "$tax_name $tax_percent%"; ?></option>
<?php } ?>
</select>
</div>
</div>
</div> </div>
</div> <div class="modal-footer">
<button type="submit" name="add_invoice_from_ticket" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Invoice</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -0,0 +1,40 @@
<?php
require_once '../../../includes/modal_header.php';
$ticket_id = intval($_GET['ticket_id']);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title" id="summaryModalTitle">Ticket Summary</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div id="summaryContent">
<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Generating summary...</div>
</div>
</div>
<script>
$(function() {
$.ajax({
url: 'ajax.php?ai_ticket_summary',
method: 'POST',
data: { ticket_id: <?php echo $ticket_id; ?> },
success: function(response) {
$('#summaryContent').html(response);
},
error: function() {
$('#summaryContent').html('Error generating summary.');
}
});
});
</script>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -59,7 +59,7 @@ ob_start();
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-arrow-right"></i></span> <span class="input-group-text"><i class="fa fa-fw fa-arrow-right"></i></span>
</div> </div>
<select class="form-control select2" name="destination" data-tags="true" data-placeholder="- Select / Input Destination -" required> <select class="form-control select2" name="destination" data-tags="true" data-placeholder="- Select or Enter a Destination -" required>
<option value=""></option> <option value=""></option>
<?php <?php
if ($client_id) { if ($client_id) {

View File

@ -40,6 +40,4 @@ foreach (glob("post/*.php") as $user_module) {
require_once "../post/logout.php"; require_once "../post/logout.php";
// TODO: Find a home for these // TODO: Find a home for these
require_once "../post/ai.php";
require_once "../post/misc.php"; require_once "../post/misc.php";

View File

@ -444,9 +444,8 @@ if (isset($_POST['add_ticket_watcher'])) {
$ticket_id = intval($_POST['ticket_id']); $ticket_id = intval($_POST['ticket_id']);
$client_id = intval($_POST['client_id']); $client_id = intval($_POST['client_id']);
$ticket_number = sanitizeInput($_POST['ticket_number']);
$watcher_emails = preg_split("/,| |;/", $_POST['watcher_email']); // Split on comma, semicolon or space, we sanitize later $watcher_emails = preg_split("/,| |;/", $_POST['watcher_email']); // Split on comma, semicolon or space, we sanitize later
$notify = intval($_POST['watcher_notify']); $notify = intval($_POST['watcher_notify'] ?? 0);
// Process each watcher in list // Process each watcher in list
foreach ($watcher_emails as $watcher_email) { foreach ($watcher_emails as $watcher_email) {
@ -505,7 +504,7 @@ if (isset($_POST['add_ticket_watcher'])) {
addToMailQueue($data); addToMailQueue($data);
} }
logAction("Ticket", "Edit", "$session_name added $watcher_email as a watcher for ticket $config_ticket_prefix$ticket_number", $client_id, $ticket_id); logAction("Ticket", "Edit", "$session_name added $watcher_email as a watcher for ticket $ticket_prefix$ticket_number", $client_id, $ticket_id);
} }
} }
@ -2147,6 +2146,7 @@ if (isset($_POST['add_invoice_from_ticket'])) {
//Get the last Invoice Number and add 1 for the new invoice number //Get the last Invoice Number and add 1 for the new invoice number
$invoice_number = $config_invoice_next_number; $invoice_number = $config_invoice_next_number;
$invoice_prefix = sanitizeInput($config_invoice_prefix);
$new_config_invoice_next_number = $config_invoice_next_number + 1; $new_config_invoice_next_number = $config_invoice_next_number + 1;
mysqli_query($mysqli, "UPDATE settings SET config_invoice_next_number = $new_config_invoice_next_number WHERE company_id = 1"); mysqli_query($mysqli, "UPDATE settings SET config_invoice_next_number = $new_config_invoice_next_number WHERE company_id = 1");
@ -2155,6 +2155,11 @@ if (isset($_POST['add_invoice_from_ticket'])) {
mysqli_query($mysqli, "INSERT INTO invoices SET invoice_prefix = '$config_invoice_prefix', invoice_number = $invoice_number, invoice_scope = '$scope', invoice_date = '$date', invoice_due = DATE_ADD('$date', INTERVAL $client_net_terms day), invoice_currency_code = '$session_company_currency', invoice_category_id = $category, invoice_status = 'Draft', invoice_url_key = '$url_key', invoice_client_id = $client_id"); mysqli_query($mysqli, "INSERT INTO invoices SET invoice_prefix = '$config_invoice_prefix', invoice_number = $invoice_number, invoice_scope = '$scope', invoice_date = '$date', invoice_due = DATE_ADD('$date', INTERVAL $client_net_terms day), invoice_currency_code = '$session_company_currency', invoice_category_id = $category, invoice_status = 'Draft', invoice_url_key = '$url_key', invoice_client_id = $client_id");
$invoice_id = mysqli_insert_id($mysqli); $invoice_id = mysqli_insert_id($mysqli);
} else {
$sql_invoice = mysqli_query($mysqli, "SELECT invoice_prefix, invoice_number FROM invoices WHERE invoice_id = $invoice_id");
$row = mysqli_fetch_array($sql_invoice);
$invoice_prefix = sanitizeInput($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
} }
//Add Item //Add Item
@ -2195,9 +2200,9 @@ if (isset($_POST['add_invoice_from_ticket'])) {
mysqli_query($mysqli, "UPDATE tickets SET ticket_invoice_id = $invoice_id WHERE ticket_id = $ticket_id"); mysqli_query($mysqli, "UPDATE tickets SET ticket_invoice_id = $invoice_id WHERE ticket_id = $ticket_id");
logAction("Invoice", "Create", "$session_name created invoice $config_invoice_prefix$invoice_number from Ticket $ticket_prefix$ticket_number", $client_id, $invoice_id); logAction("Invoice", "Create", "$session_name created invoice $invoice_prefix$invoice_number from Ticket $ticket_prefix$ticket_number", $client_id, $invoice_id);
flash_alert("Invoice $config_invoice_prefix$invoice_number created from ticket"); flash_alert("Invoice $invoice_prefix$invoice_number created from ticket");
redirect("invoice.php?invoice_id=$invoice_id"); redirect("invoice.php?invoice_id=$invoice_id");

View File

@ -27,7 +27,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="card-header py-2"> <div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-server mr-2"></i>Network Racks</h3> <h3 class="card-title mt-2"><i class="fas fa-fw fa-server mr-2"></i>Network Racks</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addRackModal"> <button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/rack/rack_add.php?client_id=<?= $client_id ?>">
<i class="fas fa-plus mr-2"></i>New Rack <i class="fas fa-plus mr-2"></i>New Rack
</button> </button>
</div> </div>
@ -342,6 +342,4 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div> </div>
<?php <?php
require_once "modals/rack/rack_add.php";
require_once "../includes/footer.php"; require_once "../includes/footer.php";

View File

@ -381,7 +381,7 @@ if (isset($_GET['ticket_id'])) {
<div class="btn-toolbar"> <div class="btn-toolbar">
<?php if ($config_module_enable_accounting && $ticket_billable == 1 && empty($invoice_id) && lookupUserPermission("module_sales") >= 2) { ?> <?php if ($config_module_enable_accounting && $ticket_billable == 1 && empty($invoice_id) && lookupUserPermission("module_sales") >= 2) { ?>
<a href="#" class="btn btn-light btn-sm ml-3" href="#" data-toggle="modal" data-target="#addInvoiceFromTicketModal"> <a href="#" class="btn btn-light btn-sm ml-3 ajax-modal" href="#" data-modal-url="modals/ticket/ticket_invoice_add.php?ticket_id=<?= $ticket_id ?>" data-modal-size="lg">
<i class="fas fa-fw fa-file-invoice mr-2"></i>Invoice <i class="fas fa-fw fa-file-invoice mr-2"></i>Invoice
</a> </a>
<?php } <?php }
@ -416,7 +416,7 @@ if (isset($_GET['ticket_id'])) {
data-modal-url="modals/ticket/ticket_edit.php?id=<?= $ticket_id ?>"> data-modal-url="modals/ticket/ticket_edit.php?id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit <i class="fas fa-fw fa-edit mr-2"></i>Edit
</a> </a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#summaryModal"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_summary.php?ticket_id=<?= $ticket_id ?>" data-modal-size="lg">
<i class="fas fa-fw fa-lightbulb mr-2"></i>Summarize <i class="fas fa-fw fa-lightbulb mr-2"></i>Summarize
</a> </a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#mergeTicketModal<?php echo $ticket_id; ?>"> <a class="dropdown-item" href="#" data-toggle="modal" data-target="#mergeTicketModal<?php echo $ticket_id; ?>">
@ -431,15 +431,15 @@ if (isset($_GET['ticket_id'])) {
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-desktop mr-2"></i>Add Asset <i class="fas fa-fw fa-desktop mr-2"></i>Add Asset
</a> </a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#editTicketVendorModal<?php echo $ticket_id; ?>"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_vendor.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-building mr-2"></i>Add Vendor <i class="fas fa-fw fa-building mr-2"></i>Add Vendor
</a> </a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#addTicketWatcherModal"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_add_watcher.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-users mr-2"></i>Add Watcher <i class="fas fa-fw fa-users mr-2"></i>Add Watcher
</a> </a>
<?php } ?> <?php } ?>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" id="clientChangeTicketModalLoad" data-target="#clientChangeTicketModal"> <a class="dropdown-item ajax-modal" href="#" id="clientChangeTicketModalLoad" data-modal-url="modals/ticket/ticket_change_client.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-people-carry mr-2"></i>Change Client <i class="fas fa-fw fa-people-carry mr-2"></i>Change Client
</a> </a>
<?php if (lookupUserPermission("module_support") == 3) { ?> <?php if (lookupUserPermission("module_support") == 3) { ?>
@ -1077,8 +1077,8 @@ if (isset($_GET['ticket_id'])) {
<h5 class="card-title"><i class="fas fa-fw fa-eye mr-2 mt-2"></i>Watchers</h5> <h5 class="card-title"><i class="fas fa-fw fa-eye mr-2 mt-2"></i>Watchers</h5>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<a class="btn btn-light text-secondary btn-sm" href="#" data-toggle="modal" data-target="#addTicketWatcherModal"> <a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_add_watcher.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-edit"></i> <i class="fas fa-fw fa-plus"></i>
</a> </a>
<?php } ?> <?php } ?>
</div> </div>
@ -1114,7 +1114,7 @@ if (isset($_GET['ticket_id'])) {
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>"> <a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>">
<i class="fas fa-edit"></i> <i class="fas fa-fw fa-edit"></i>
</a> </a>
<?php } ?> <?php } ?>
</div> </div>
@ -1161,8 +1161,8 @@ if (isset($_GET['ticket_id'])) {
<h5 class="card-title"><i class="fas fa-fw fa-building mr-2 mt-2"></i>Vendor</h5> <h5 class="card-title"><i class="fas fa-fw fa-building mr-2 mt-2"></i>Vendor</h5>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<a class="btn btn-light text-secondary btn-sm" href="#" data-toggle="modal" data-target="#editTicketVendorModal<?php echo $ticket_id; ?>"> <a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_vendor.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-edit"></i> <i class="fas fa-fw fa-edit"></i>
</a> </a>
<?php } ?> <?php } ?>
</div> </div>
@ -1244,16 +1244,9 @@ if (isset($_GET['ticket_id'])) {
<?php <?php
if (lookupUserPermission("module_support") >= 2 && empty($ticket_closed_at)) { if (lookupUserPermission("module_support") >= 2 && empty($ticket_closed_at)) {
require_once "modals/ticket/ticket_edit_vendor.php";
require_once "modals/ticket/ticket_add_watcher.php";
require_once "modals/ticket/ticket_change_client.php";
require_once "modals/ticket/ticket_edit_schedule.php"; require_once "modals/ticket/ticket_edit_schedule.php";
require_once "modals/ticket/ticket_merge.php"; require_once "modals/ticket/ticket_merge.php";
} }
if (lookupUserPermission("module_support") >= 2 && lookupUserPermission("module_sales") >= 2 && $config_module_enable_accounting) {
require_once "modals/ticket/ticket_invoice_add.php";
}
} }
} }
@ -1261,25 +1254,6 @@ require_once "../includes/footer.php";
?> ?>
<!-- Summary Modal -->
<div class="modal fade" id="summaryModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title" id="summaryModalTitle">Ticket Summary</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div id="summaryContent">
<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Generating summary...</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/show_modals.js"></script> <script src="/js/show_modals.js"></script>
<?php if (empty($ticket_closed_at)) { ?> <?php if (empty($ticket_closed_at)) { ?>
@ -1297,23 +1271,6 @@ require_once "../includes/footer.php";
<script src="/js/pretty_content.js"></script> <script src="/js/pretty_content.js"></script>
<script>
$('#summaryModal').on('shown.bs.modal', function (e) {
// Perform AJAX request to get the summary
$.ajax({
url: 'post.php?ai_ticket_summary',
method: 'POST',
data: { ticket_id: <?php echo $ticket_id; ?> },
success: function(response) {
$('#summaryContent').html(response);
},
error: function() {
$('#summaryContent').html('Error generating summary.');
}
});
});
</script>
<script src="/plugins/SortableJS/Sortable.min.js"></script> <script src="/plugins/SortableJS/Sortable.min.js"></script>
<script> <script>
new Sortable(document.querySelector('table#tasks tbody'), { new Sortable(document.querySelector('table#tasks tbody'), {

View File

@ -40,7 +40,5 @@ foreach (glob("post/*.php") as $user_module) {
require_once "../../post/logout.php"; require_once "../../post/logout.php";
// TODO: Find a home for these // TODO: Find a home for these
require_once "../../post/ai.php";
require_once "../../post/misc.php"; require_once "../../post/misc.php";

View File

@ -131,7 +131,7 @@ $(document).ready(function() {
// Show the progress indicator // Show the progress indicator
editor.setProgressState(true); editor.setProgressState(true);
fetch('post.php?ai_reword', { fetch('ajax.php?ai_reword', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -236,7 +236,7 @@ $(document).ready(function() {
rewordButtonApi.setEnabled(false); rewordButtonApi.setEnabled(false);
editor.setProgressState(true); editor.setProgressState(true);
fetch('post.php?ai_reword', { fetch('ajax.php?ai_reword', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: content }), body: JSON.stringify({ text: content }),

View File

@ -1,251 +0,0 @@
<?php
/*
* ITFlow - GET/POST request handler for AI Functions
*/
// TODO: Should this be moved to AJAX?
if (isset($_GET['ai_reword'])) {
header('Content-Type: application/json');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$promptText = $row['ai_model_prompt'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
// Collecting the input data from the AJAX request.
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE); // Convert JSON into array.
$userText = $input['text'];
// Preparing the data for the OpenAI Chat API request.
$data = [
"model" => "$model_name", // Specify the model
"messages" => [
["role" => "system", "content" => $promptText],
["role" => "user", "content" => $userText],
],
"temperature" => 0.5
];
// Initialize cURL session to the OpenAI Chat API.
$ch = curl_init("$url");
// Set cURL options for the request.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key,
]);
// Execute the cURL session and capture the response.
$response = curl_exec($ch);
curl_close($ch);
// Decode the JSON response.
$responseData = json_decode($response, true);
// Check if the response contains the expected data and return it.
if (isset($responseData['choices'][0]['message']['content'])) {
// Get the response content.
$content = $responseData['choices'][0]['message']['content'];
// Clean any leading "html" word or other unwanted text at the beginning.
$content = preg_replace('/^html/i', '', $content); // Remove any occurrence of 'html' at the start
// Clean the response content to remove backticks or code block markers.
$cleanedContent = str_replace('```', '', $content); // Remove backticks if they exist.
// Trim any leading/trailing whitespace.
$cleanedContent = trim($cleanedContent);
// Return the cleaned response.
echo json_encode(['rewordedText' => $cleanedContent]);
} else {
// Handle errors or unexpected response structure.
echo json_encode(['rewordedText' => 'Failed to get a response from the AI API.']);
}
}
if (isset($_GET['ai_ticket_summary'])) {
header('Content-Type: text/html; charset=UTF-8');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
// Retrieve the ticket_id from POST
$ticket_id = intval($_POST['ticket_id']);
// Query the database for ticket details
$sql = mysqli_query($mysqli, "
SELECT ticket_subject, ticket_details, ticket_source, ticket_priority, ticket_status_name, category_name
FROM tickets
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
LEFT JOIN categories ON ticket_category = category_id
WHERE ticket_id = $ticket_id
LIMIT 1
");
$row = mysqli_fetch_assoc($sql);
$ticket_subject = $row['ticket_subject'];
$ticket_details = strip_tags($row['ticket_details']); // strip HTML for cleaner prompt
$ticket_status = $row['ticket_status_name'];
$ticket_category = $row['category_name'];
$ticket_source = $row['ticket_source'];
$ticket_priority = $row['ticket_priority'];
// Get ticket replies
$sql_replies = mysqli_query($mysqli, "
SELECT ticket_reply, ticket_reply_type, user_name
FROM ticket_replies
LEFT JOIN users ON ticket_reply_by = user_id
WHERE ticket_reply_ticket_id = $ticket_id
AND ticket_reply_archived_at IS NULL
ORDER BY ticket_reply_id ASC
");
$all_replies_text = "";
while ($reply = mysqli_fetch_assoc($sql_replies)) {
$reply_type = $reply['ticket_reply_type'];
$reply_text = strip_tags($reply['ticket_reply']);
$reply_by = $reply['user_name'];
$all_replies_text .= "\nReply Type: $reply_type Reply By: $reply_by: Reply Text: $reply_text";
}
$prompt = "
Summarize the following IT support ticket and its responses in a concise, clear, and professional manner.
The summary should include:
1. Main Issue: What was the problem reported by the user?
2. Actions Taken: What steps were taken to address the issue?
3. Resolution or Next Steps: Was the issue resolved or is it ongoing?
Please ensure:
- If there are multiple issues, summarize each separately.
- Urgency: If the ticket or replies express urgency or escalation, highlight it.
- Attachments: If mentioned in the ticket, note any relevant attachments or files.
- Avoid extra explanations or unnecessary information.
Ticket Data:
- Ticket Source: $ticket_source
- Current Ticket Status: $ticket_status
- Ticket Priority: $ticket_priority
- Ticket Category: $ticket_category
- Ticket Subject: $ticket_subject
- Ticket Details: $ticket_details
- Replies:
$all_replies_text
Formatting instructions:
- Use valid HTML tags only.
- Use <h3> for section headers (Main Issue, Actions Taken, Resolution).
- Use <ul><li> for bullet points under each section.
- Do NOT wrap the output in ```html or any other code fences.
- Do NOT include <html>, <head>, or <body>.
- Output only the summary content in pure HTML.
If any part of the ticket or replies is unclear or ambiguous, mention it in the summary and suggest if further clarification is needed.
";
// Prepare the POST data
$post_data = [
"model" => "$model_name",
"messages" => [
["role" => "system", "content" => "Your task is to summarize IT support tickets with clear, concise details."],
["role" => "user", "content" => $prompt]
],
"temperature" => 0.3
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "Error: " . curl_error($ch);
exit;
}
curl_close($ch);
$response_data = json_decode($response, true);
$summary = $response_data['choices'][0]['message']['content'] ?? "No summary available.";
echo $summary; // nl2br to convert newlines to <br>, htmlspecialchars to prevent XSS
}
if (isset($_GET['ai_create_document_template'])) {
// get_ai_document_template.php
header('Content-Type: text/html; charset=UTF-8');
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
$prompt = $_POST['prompt'] ?? '';
// Basic validation
if(empty($prompt)){
echo "No prompt provided.";
exit;
}
// Prepare prompt
$system_message = "You are a helpful IT documentation assistant. You will create a well-structured HTML template for IT documentation based on a given prompt. Include headings, subheadings, bullet points, and possibly tables for clarity. No Lorem Ipsum, use realistic placeholders and professional language.";
$user_message = "Create an HTML formatted IT documentation template based on the following request:\n\n\"$prompt\"\n\nThe template should be structured, professional, and useful for IT staff. Include relevant sections, instructions, prerequisites, and best practices.";
$post_data = [
"model" => "$model_name",
"messages" => [
["role" => "system", "content" => $system_message],
["role" => "user", "content" => $user_message]
],
"temperature" => 0.5
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "Error: " . curl_error($ch);
exit;
}
curl_close($ch);
$response_data = json_decode($response, true);
$template = $response_data['choices'][0]['message']['content'] ?? "<p>No content returned from AI.</p>";
// Print the generated HTML template directly
echo $template;
}