Files
itflow/agent/modals/ticket/ticket_quote_add.php
johnnyq 1e5bffff3c Rates, net terms and SLAs come from the client's contract, else the instance defaults: database 3.5.0 adds a default after-hours rate (Settings > Defaults, now Rates and terms), gives each client whose own rate, net terms or SLAs differ from the defaults an active Standard terms contract setting just those (12-month term renewing itself, starting the day the client was added or the day before its earliest contract, archived with an archived client), deletes per-client SLA assignments and drops clients.client_rate and client_net_terms; time bills at the contract's rate, its after-hours rate or the default after-hours rate, else the default rate, new invoices are due on the contract's net terms else the default, and new tickets get the contract's SLA else the default; a client's own terms still apply until the update runs; the client add and edit windows lose the rate, terms and SLA fields for a note saying what the client is billed on and where it comes from, the Clients list loses Set Hourly Rate and Set Net Terms and shows the contract's rate, the overview's Terms shows the after-hours rate and whether terms are a contract's or the defaults, a contract's page and document show what it leaves empty from another contract in force or the default so signed documents stay unchanged, contract windows show the defaults as placeholders, contract names take a proper possessive; the client import and API create make a Standard terms contract for a rate or terms that aren't the defaults (returning contract_id), API update reports them in not_kept; the client export gives the rates, terms and SLAs as billed; demo data puts each demo client's terms on a contract; changelog updated
2026-10-27 22:22:34 -04:00

215 lines
9.2 KiB
PHP

<?php
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_sales', 2);
// The ticket, after the module and client checks - or the modal's error
$ticket = ticketForModal($_GET['ticket_id'] ?? 0, 2);
$ticket_id = intval($ticket['ticket_id']);
if (!intval($ticket['ticket_client_id'])) {
echo json_encode(['error' => 'Move the ticket to a client before quoting it.']);
exit;
}
$ticket_sql = mysqli_query(
$mysqli,
"SELECT asset_id, asset_name, asset_type, category_name, client_id, contact_id,
contact_name, ticket_assigned_to, ticket_billable, ticket_category, ticket_created_at,
ticket_created_by, ticket_first_response_at, ticket_number, ticket_onsite, ticket_prefix,
ticket_priority, ticket_resolved_at, ticket_subject, user_name 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
" . clientScopeSql('ticket_client_id') . "
LIMIT 1"
);
$row = mysqli_fetch_assoc($ticket_sql);
$client_id = intval($row['client_id']);
// The contract in force's rate, else the default
$client_rate_info = contractClientRate($client_id);
$client_rate = $client_rate_info['rate'];
$ticket_prefix = escapeHtml($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_category = intval($row['ticket_category']);
$ticket_category_display = escapeHtml($row['category_name']);
$ticket_subject = escapeHtml($row['ticket_subject']);
$ticket_priority = escapeHtml($row['ticket_priority']);
$ticket_billable = intval($row['ticket_billable']);
$ticket_onsite = intval($row['ticket_onsite']);
$ticket_created_at = escapeHtml($row['ticket_created_at']);
$ticket_created_by = intval($row['ticket_created_by']);
$ticket_date = date('Y-m-d g:i A', strtotime($ticket_created_at));
$ticket_first_response_at = escapeHtml($row['ticket_first_response_at']);
if ($ticket_first_response_at) {
$ticket_first_response_date_time = date('Y-m-d g:i A', strtotime($ticket_first_response_at));
} else {
$ticket_first_response_date_time = '';
}
$ticket_resolved_at = escapeHtml($row['ticket_resolved_at']);
if ($ticket_resolved_at) {
$ticket_resolved_date = date('Y-m-d g:i A', strtotime($ticket_resolved_at));
} else {
$ticket_resolved_date = '';
}
$ticket_assigned_to = intval($row['ticket_assigned_to']);
if ($ticket_assigned_to) {
$ticket_assigned_agent = escapeHtml($row['user_name']);
} else {
$ticket_assigned_agent = '';
}
$contact_id = intval($row['contact_id']);
$contact_name = escapeHtml($row['contact_name']);
$asset_id = intval($row['asset_id']);
$asset_name = escapeHtml($row['asset_name']);
$asset_type = escapeHtml($row['asset_type']);
if ($client_id) {
enforceClientAccess();
}
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-comment-dollar me-2"></i>Create quote from ticket</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="ticket_id" value="<?= $ticket_id ?>">
<div class="modal-body">
<!-- Row 1 -->
<div class="row">
<div class="col-sm-6">
<div class="mb-3">
<label>Scope</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-comment"></i></span>
<input type="text" class="form-control" name="scope" placeholder="Quick description" maxlength="255">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label>Quote Category <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
<select class="form-select select2" name="category" required>
<option value="">- Select a Category -</option>
<?= categoryOptionsHtml('Income') ?>
</select>
<?= categoryAddButtonHtml('Income', 'btn btn-secondary') ?>
</div>
</div>
</div>
</div>
<!-- End row 1 -->
<!-- Row 2 -->
<div class="row">
<div class="col-sm-6">
<div class="mb-3">
<label>Quote Date <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="date" max="2999-12-31" value="<?= date("Y-m-d") ?>">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label>Expire <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="expire" min="<?= date("Y-m-d") ?>" max="2999-12-31" value="<?= date("Y-m-d", strtotime("+30 days")) ?>" required>
</div>
</div>
</div>
</div>
<!-- End row 2 -->
<div class="mb-3">
<label>Item <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-box"></i></span>
<input type="text" class="form-control" name="item_name" placeholder="Item" maxlength="200" required>
</div>
</div>
<?php $description = "Ticket: {$ticket_prefix}{$ticket_number} - $ticket_subject\n"; ?>
<div class="mb-3">
<label>Item Description</label>
<div class="input-group">
<textarea class="form-control" rows="10" name="item_description"><?= trim($description) ?></textarea>
</div>
</div>
<div class="row g-2">
<div class="col">
<div class="mb-3">
<label>QTY <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-balance-scale"></i></span>
<input type="text" class="form-control" inputmode="decimal" pattern="-?[0-9]*\.?[0-9]{0,2}" name="qty" required>
</div>
</div>
</div>
<div class="col">
<div class="mb-3">
<label>Price <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
<input type="text" class="form-control" inputmode="decimal" pattern="-?[0-9]*\.?[0-9]{0,2}" name="price" value="<?= number_format($client_rate, 2, '.', '') ?>" required>
</div>
<small class="form-text text-muted">
<?= escapeHtml(ucfirst($client_rate_info['note'])) ?> is <strong><?= numfmt_format_currency($currency_format, $client_rate, $session_company_currency); ?></strong> an hour
</small>
</div>
</div>
</div>
<div class="mb-3">
<label>Tax <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
<select class="form-select select2" name="tax_id" required>
<?= taxSelectOptions(taxDefaultFor($client_id, 'time')) ?>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_quote_from_ticket" class="btn btn-primary text-bold">
<i class="fa fa-check me-2"></i>Quote
</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
<i class="fa fa-times me-2"></i>Cancel
</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';