Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cefdf3e35c | |||
| f34ee749be |
@@ -4,7 +4,7 @@
|
|||||||
ViewData["Title"] = "Edit Bill";
|
ViewData["Title"] = "Edit Bill";
|
||||||
ViewData["PageIcon"] = "bi-pencil-square";
|
ViewData["PageIcon"] = "bi-pencil-square";
|
||||||
ViewData["PageHelpTitle"] = "Edit Bill";
|
ViewData["PageHelpTitle"] = "Edit Bill";
|
||||||
ViewData["PageHelpContent"] = "Bills can only be edited while in Draft status. Once marked Open, they are locked — Void the bill and recreate it if corrections are needed after confirmation.";
|
ViewData["PageHelpContent"] = "Bills can only be edited while in Draft status. Once marked Open, they are locked — Void the bill and recreate it if corrections are needed after confirmation.";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="d-flex justify-content-start mb-4">
|
<div class="d-flex justify-content-start mb-4">
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Bill Details"
|
data-bs-title="Bill Details"
|
||||||
data-bs-content="Vendor: who you're paying. AP Account: the liability account this bill posts to (e.g. Accounts Payable). Bill Date: date on the vendor's invoice. Due Date: when payment is due — drives overdue status. Vendor Invoice #: the vendor's own reference number for reconciliation.">
|
data-bs-content="Vendor: who you're paying. AP Account: the liability account this bill posts to (e.g. Accounts Payable). Bill Date: date on the vendor's invoice. Due Date: when payment is due — drives overdue status. Vendor Invoice #: the vendor's own reference number for reconciliation.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
<label asp-for="VendorId" class="form-label fw-medium">Vendor <span class="text-danger">*</span></label>
|
<label asp-for="VendorId" class="form-label fw-medium">Vendor <span class="text-danger">*</span></label>
|
||||||
<select asp-for="VendorId" asp-items="ViewBag.Vendors" class="form-select"
|
<select asp-for="VendorId" asp-items="ViewBag.Vendors" class="form-select"
|
||||||
data-quick-add-url="/Vendors/Create" data-quick-add-title="Add New Vendor">
|
data-quick-add-url="/Vendors/Create" data-quick-add-title="Add New Vendor">
|
||||||
<option value="">— Select Vendor —</option>
|
<option value="">— Select Vendor —</option>
|
||||||
<option value="__new__">+ Add New Vendor…</option>
|
<option value="__new__">+ Add New Vendor…</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
<input type="file" name="receiptFile" id="receiptFile" class="form-control"
|
<input type="file" name="receiptFile" id="receiptFile" class="form-control"
|
||||||
accept=".jpg,.jpeg,.png,.gif,.webp,.pdf" />
|
accept=".jpg,.jpeg,.png,.gif,.webp,.pdf" />
|
||||||
<div class="form-text">JPG, PNG, GIF, WebP, or PDF — up to 10 MB.</div>
|
<div class="form-text">JPG, PNG, GIF, WebP, or PDF — up to 10 MB.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Line Items"
|
data-bs-title="Line Items"
|
||||||
data-bs-content="Each line maps to an expense account (e.g. Supplies, Materials, Subcontractors). Optionally link a line to a Job to track costs against specific work orders. Qty × Unit Price = Amount. Use multiple lines to split one bill across different expense categories.">
|
data-bs-content="Each line maps to an expense account (e.g. Supplies, Materials, Subcontractors). Optionally link a line to a Job to track costs against specific work orders. Qty × Unit Price = Amount. Use multiple lines to split one bill across different expense categories.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="left" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="left" data-bs-trigger="focus"
|
||||||
data-bs-title="Bill Summary"
|
data-bs-title="Bill Summary"
|
||||||
data-bs-content="Tax % is applied to the line-item subtotal. The resulting Total is the full amount owed to the vendor. Partial payments are allowed — each payment recorded reduces the balance due until the bill is fully paid.">
|
data-bs-content="Tax % is applied to the line-item subtotal. The resulting Total is the full amount owed to the vendor. Partial payments are allowed — each payment recorded reduces the balance due until the bill is fully paid.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
<tr class="line-item-row">
|
<tr class="line-item-row">
|
||||||
<td>
|
<td>
|
||||||
<select class="form-select form-select-sm account-select" name="LineItems[INDEX].AccountId" required>
|
<select class="form-select form-select-sm account-select" name="LineItems[INDEX].AccountId" required>
|
||||||
<option value="">— Account —</option>
|
<option value="">— Account —</option>
|
||||||
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.ExpenseAccounts)
|
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.ExpenseAccounts)
|
||||||
{
|
{
|
||||||
<option value="@item.Value">@item.Text</option>
|
<option value="@item.Value">@item.Text</option>
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
<td><input type="text" class="form-control form-control-sm" name="LineItems[INDEX].Description" placeholder="Description" /></td>
|
<td><input type="text" class="form-control form-control-sm" name="LineItems[INDEX].Description" placeholder="Description" /></td>
|
||||||
<td>
|
<td>
|
||||||
<select class="form-select form-select-sm" name="LineItems[INDEX].JobId">
|
<select class="form-select form-select-sm" name="LineItems[INDEX].JobId">
|
||||||
<option value="">—</option>
|
<option value="">—</option>
|
||||||
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.Jobs)
|
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.Jobs)
|
||||||
{
|
{
|
||||||
<option value="@item.Value">@item.Text</option>
|
<option value="@item.Value">@item.Text</option>
|
||||||
|
|||||||
@@ -168,6 +168,23 @@
|
|||||||
}
|
}
|
||||||
.reason-pill.selected { border-color: var(--purple); background: #f3effe; color: var(--purple); font-weight: 600; }
|
.reason-pill.selected { border-color: var(--purple); background: #f3effe; color: var(--purple); font-weight: 600; }
|
||||||
|
|
||||||
|
/* ── Input mode toggle ───────────────────────── */
|
||||||
|
.mode-toggle { display: flex; border: 1.5px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 18px; }
|
||||||
|
.mode-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 8px;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
transition: background .15s, color .15s;
|
||||||
|
}
|
||||||
|
.mode-btn.active { background: var(--purple); color: #fff; }
|
||||||
|
.mode-btn:first-child { border-right: 1.5px solid var(--border); }
|
||||||
|
|
||||||
/* ── Submit / Cancel ─────────────────────────── */
|
/* ── Submit / Cancel ─────────────────────────── */
|
||||||
.btn-submit {
|
.btn-submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -309,12 +326,28 @@
|
|||||||
|
|
||||||
<div class="form-card">
|
<div class="form-card">
|
||||||
<h2>2. Enter Quantity</h2>
|
<h2>2. Enter Quantity</h2>
|
||||||
<div class="field">
|
|
||||||
|
<div class="mode-toggle">
|
||||||
|
<button type="button" class="mode-btn active" id="modeUsed" onclick="setMode('used')">Amount Used</button>
|
||||||
|
<button type="button" class="mode-btn" id="modeRemaining" onclick="setMode('remaining')">Remaining Weight</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- amount-used mode -->
|
||||||
|
<div id="usedField" class="field">
|
||||||
<label for="quantityInput">Amount Used (@item.UnitOfMeasure) <span class="req">*</span></label>
|
<label for="quantityInput">Amount Used (@item.UnitOfMeasure) <span class="req">*</span></label>
|
||||||
<input type="number" id="quantityInput" name="quantity"
|
<input type="number" id="quantityInput" name="quantity"
|
||||||
min="0" step="any" required placeholder="0" inputmode="decimal" />
|
min="0" step="any" placeholder="0" inputmode="decimal"
|
||||||
|
oninvalid="this.setCustomValidity('')" />
|
||||||
<div class="hint" id="balanceHint"></div>
|
<div class="hint" id="balanceHint"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- remaining-weight mode -->
|
||||||
|
<div id="remainingField" class="field" style="display:none">
|
||||||
|
<label for="remainingInput">Weight Remaining (@item.UnitOfMeasure) <span class="req">*</span></label>
|
||||||
|
<input type="number" id="remainingInput" min="0" step="any"
|
||||||
|
placeholder="0" inputmode="decimal" />
|
||||||
|
<div class="hint" id="remainingHint"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-card">
|
<div class="form-card">
|
||||||
@@ -346,6 +379,21 @@
|
|||||||
<script>
|
<script>
|
||||||
var currentQty = @item.QuantityOnHand;
|
var currentQty = @item.QuantityOnHand;
|
||||||
var uom = '@item.UnitOfMeasure';
|
var uom = '@item.UnitOfMeasure';
|
||||||
|
var inputMode = 'used'; // 'used' | 'remaining'
|
||||||
|
|
||||||
|
// ── Input mode toggle ────────────────────────────
|
||||||
|
function setMode(mode) {
|
||||||
|
inputMode = mode;
|
||||||
|
document.getElementById('modeUsed').classList.toggle('active', mode === 'used');
|
||||||
|
document.getElementById('modeRemaining').classList.toggle('active', mode === 'remaining');
|
||||||
|
document.getElementById('usedField').style.display = mode === 'used' ? '' : 'none';
|
||||||
|
document.getElementById('remainingField').style.display = mode === 'remaining' ? '' : 'none';
|
||||||
|
document.getElementById('balanceHint').textContent = '';
|
||||||
|
document.getElementById('remainingHint').textContent = '';
|
||||||
|
// clear both inputs when switching
|
||||||
|
document.getElementById('quantityInput').value = '';
|
||||||
|
document.getElementById('remainingInput').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
// ── Job selection ────────────────────────────────
|
// ── Job selection ────────────────────────────────
|
||||||
function showTab(tab) {
|
function showTab(tab) {
|
||||||
@@ -384,7 +432,7 @@
|
|||||||
document.getElementById('transactionTypeInput').value = el.dataset.val;
|
document.getElementById('transactionTypeInput').value = el.dataset.val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Balance hint ─────────────────────────────────
|
// ── Balance hint (amount-used mode) ─────────────
|
||||||
document.getElementById('quantityInput').addEventListener('input', function() {
|
document.getElementById('quantityInput').addEventListener('input', function() {
|
||||||
var qty = parseFloat(this.value) || 0;
|
var qty = parseFloat(this.value) || 0;
|
||||||
if (!this.value) { document.getElementById('balanceHint').textContent = ''; return; }
|
if (!this.value) { document.getElementById('balanceHint').textContent = ''; return; }
|
||||||
@@ -394,6 +442,24 @@
|
|||||||
'New balance: <strong style="color:' + col + '">' + newBal.toFixed(2) + ' ' + uom + '</strong>';
|
'New balance: <strong style="color:' + col + '">' + newBal.toFixed(2) + ' ' + uom + '</strong>';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Remaining-weight hint ────────────────────────
|
||||||
|
document.getElementById('remainingInput').addEventListener('input', function() {
|
||||||
|
var hint = document.getElementById('remainingHint');
|
||||||
|
if (!this.value) { hint.textContent = ''; return; }
|
||||||
|
var remaining = parseFloat(this.value);
|
||||||
|
if (isNaN(remaining) || remaining < 0) { hint.innerHTML = '<span style="color:var(--danger)">Enter a valid weight.</span>'; return; }
|
||||||
|
if (remaining > currentQty) {
|
||||||
|
hint.innerHTML = '<span style="color:var(--danger)">Remaining cannot exceed current stock (' + currentQty.toFixed(2) + ' ' + uom + ').</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var used = currentQty - remaining;
|
||||||
|
if (used <= 0) {
|
||||||
|
hint.innerHTML = '<span style="color:var(--danger)">No usage to log — remaining equals current stock.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hint.innerHTML = 'Will log <strong>' + used.toFixed(2) + ' ' + uom + '</strong> as used — new balance: <strong style="color:' + (remaining === 0 ? '#343a40' : 'var(--success)') + '">' + remaining.toFixed(2) + ' ' + uom + '</strong>';
|
||||||
|
});
|
||||||
|
|
||||||
// ── Preselect job if coming from success page ────
|
// ── Preselect job if coming from success page ────
|
||||||
@if (preselectedJobId.HasValue)
|
@if (preselectedJobId.HasValue)
|
||||||
{
|
{
|
||||||
@@ -406,8 +472,37 @@
|
|||||||
</text>
|
</text>
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Submit spinner ───────────────────────────────
|
// ── Submit: resolve quantity from whichever mode is active ──
|
||||||
document.getElementById('usageForm').addEventListener('submit', function() {
|
document.getElementById('usageForm').addEventListener('submit', function(e) {
|
||||||
|
if (inputMode === 'remaining') {
|
||||||
|
var remaining = parseFloat(document.getElementById('remainingInput').value);
|
||||||
|
if (isNaN(remaining) || remaining < 0 || remaining > currentQty) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('remainingHint').innerHTML =
|
||||||
|
'<span style="color:var(--danger)">Please enter a valid remaining weight.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var used = currentQty - remaining;
|
||||||
|
if (used <= 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('remainingHint').innerHTML =
|
||||||
|
'<span style="color:var(--danger)">No usage to log — remaining equals current stock.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('quantityInput').value = used.toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate amount-used mode
|
||||||
|
if (inputMode === 'used') {
|
||||||
|
var qty = parseFloat(document.getElementById('quantityInput').value);
|
||||||
|
if (isNaN(qty) || qty <= 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('balanceHint').innerHTML =
|
||||||
|
'<span style="color:var(--danger)">Please enter a quantity greater than zero.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var btn = document.getElementById('submitBtn');
|
var btn = document.getElementById('submitBtn');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = 'Saving…';
|
btn.textContent = 'Saving…';
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Invoice Details"
|
data-bs-title="Invoice Details"
|
||||||
data-bs-content="Invoice Date is the date of issue and the reference for payment terms. Due Date drives overdue status and A/R aging. Payment Terms prints on the invoice — changing it here only affects this invoice. Draft, Sent, and Overdue invoices can be edited; Paid and Partially Paid invoices are locked.">
|
data-bs-content="Invoice Date is the date of issue and the reference for payment terms. Due Date drives overdue status and A/R aging. Payment Terms prints on the invoice — changing it here only affects this invoice. Draft, Sent, and Overdue invoices can be edited; Paid and Partially Paid invoices are locked.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Line Items"
|
data-bs-title="Line Items"
|
||||||
data-bs-content="Each row is a billable line on the invoice. Qty × Unit Price = Total per line; you can also override Total directly. Color is optional and appears under the description when printed. Add manual lines for charges not in the original job (e.g., rush fee, pickup charge).">
|
data-bs-content="Each row is a billable line on the invoice. Qty × Unit Price = Total per line; you can also override Total directly. Color is optional and appears under the description when printed. Add manual lines for charges not in the original job (e.g., rush fee, pickup charge).">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Notes"
|
data-bs-title="Notes"
|
||||||
data-bs-content="Customer Notes appear on the printed and emailed invoice — use these for payment instructions, thank-you messages, or job-specific reminders. Internal Notes are only visible to staff in the app and are never sent to the customer.">
|
data-bs-content="Customer Notes appear on the printed and emailed invoice — use these for payment instructions, thank-you messages, or job-specific reminders. Internal Notes are only visible to staff in the app and are never sent to the customer.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<i class="bi bi-layout-text-window-reverse fs-5"></i>
|
<i class="bi bi-layout-text-window-reverse fs-5"></i>
|
||||||
<div>
|
<div>
|
||||||
Pre-filled from template <strong>@ViewBag.TemplateName</strong>.
|
Pre-filled from template <strong>@ViewBag.TemplateName</strong>.
|
||||||
Items and coatings have been loaded — review and adjust before saving.
|
Items and coatings have been loaded — review and adjust before saving.
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Details"
|
data-bs-title="Job Details"
|
||||||
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and affect how work is sorted. Customer PO is the customer's own reference number for their purchase order — include it so it appears on invoices. Special Instructions go directly to the shop floor worker.">
|
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and affect how work is sorted. Customer PO is the customer's own reference number for their purchase order — include it so it appears on invoices. Special Instructions go directly to the shop floor worker.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Priority"
|
data-bs-title="Job Priority"
|
||||||
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
|
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Due Date"
|
data-bs-title="Due Date"
|
||||||
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
|
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Special Instructions"
|
data-bs-title="Special Instructions"
|
||||||
data-bs-content="Free-text notes visible to the shop floor worker on the work order. Use this for masking requirements, handling notes, customer preferences, or anything that doesn't fit in the item-level notes — e.g., 'Keep brackets separated, customer allergic to zinc primer'.">
|
data-bs-content="Free-text notes visible to the shop floor worker on the work order. Use this for masking requirements, handling notes, customer preferences, or anything that doesn't fit in the item-level notes — e.g., 'Keep brackets separated, customer allergic to zinc primer'.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Items"
|
data-bs-title="Job Items"
|
||||||
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
|
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Details"
|
data-bs-title="Job Details"
|
||||||
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and job list. Customer PO is the customer's own reference number — it appears on invoices. Special Instructions go directly to the shop floor worker on the work order.">
|
data-bs-content="Core job information. Priority and due date are visible on the shop floor board and job list. Customer PO is the customer's own reference number — it appears on invoices. Special Instructions go directly to the shop floor worker on the work order.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Priority"
|
data-bs-title="Job Priority"
|
||||||
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
|
data-bs-content="Controls sort order on the shop floor board and job list. Rush and Urgent jobs are highlighted in red/orange. Normal is the default. Raise priority only when the customer has an actual deadline constraint — overuse of Rush dilutes its meaning for the shop floor team.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</label>
|
</label>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Due Date"
|
data-bs-title="Due Date"
|
||||||
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
|
data-bs-content="The customer's deadline — when the work must be ready for pickup or delivery. Overdue jobs (past due date and not yet completed) are highlighted in red on the job list.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</label>
|
</label>
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Job Items"
|
data-bs-title="Job Items"
|
||||||
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
|
data-bs-content="Each item represents a physical piece being coated. Use the wizard to pick from the catalog, enter custom dimensions, or upload a photo for AI analysis. Each item gets its own coating specification — color, powder, finish, and cure details. You can add multiple coating passes per item for multi-color or primer+topcoat work.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@using PowderCoating.Core.Entities
|
@using PowderCoating.Core.Entities
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = $"Edit Items — {Model.JobNumber}";
|
ViewData["Title"] = $"Edit Items — {Model.JobNumber}";
|
||||||
ViewData["PageIcon"] = "bi-list-check";
|
ViewData["PageIcon"] = "bi-list-check";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Personal Information"
|
data-bs-title="Personal Information"
|
||||||
data-bs-content="First Name, Last Name, and Phone are editable and saved when you click Save Profile. Email is shown here for reference — change it on the Security tab. Department, Position, Role, and Employee Number are set by an administrator and cannot be changed here.">
|
data-bs-content="First Name, Last Name, and Phone are editable and saved when you click Save Profile. Email is shown here for reference â€" change it on the Security tab. Department, Position, Role, and Employee Number are set by an administrator and cannot be changed here.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||||
data-bs-title="Appearance Settings"
|
data-bs-title="Appearance Settings"
|
||||||
data-bs-content="Theme switches the app between Light and Dark mode. Sidebar Color changes the navigation panel background — click a swatch to preview it instantly. Date Format controls how dates display throughout the app. Timezone is used to localize timestamps. Click Save Appearance to persist your choices.">
|
data-bs-content="Theme switches the app between Light and Dark mode. Sidebar Color changes the navigation panel background â€" click a swatch to preview it instantly. Date Format controls how dates display throughout the app. Timezone is used to localize timestamps. Click Save Appearance to persist your choices.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,39 +364,39 @@
|
|||||||
<label class="form-label fw-semibold">Timezone</label>
|
<label class="form-label fw-semibold">Timezone</label>
|
||||||
<select class="form-select" id="timezoneInput" name="TimeZone" style="max-width:350px;">
|
<select class="form-select" id="timezoneInput" name="TimeZone" style="max-width:350px;">
|
||||||
<optgroup label="United States">
|
<optgroup label="United States">
|
||||||
<option value="America/New_York" selected="@(Model.TimeZone == "America/New_York" ? "selected" : null)">Eastern (ET) — New York</option>
|
<option value="America/New_York" selected="@(Model.TimeZone == "America/New_York" ? "selected" : null)">Eastern (ET) — New York</option>
|
||||||
<option value="America/Chicago" selected="@(Model.TimeZone == "America/Chicago" ? "selected" : null)">Central (CT) — Chicago</option>
|
<option value="America/Chicago" selected="@(Model.TimeZone == "America/Chicago" ? "selected" : null)">Central (CT) — Chicago</option>
|
||||||
<option value="America/Denver" selected="@(Model.TimeZone == "America/Denver" ? "selected" : null)">Mountain (MT) — Denver</option>
|
<option value="America/Denver" selected="@(Model.TimeZone == "America/Denver" ? "selected" : null)">Mountain (MT) — Denver</option>
|
||||||
<option value="America/Phoenix" selected="@(Model.TimeZone == "America/Phoenix" ? "selected" : null)">Mountain no-DST — Phoenix</option>
|
<option value="America/Phoenix" selected="@(Model.TimeZone == "America/Phoenix" ? "selected" : null)">Mountain no-DST — Phoenix</option>
|
||||||
<option value="America/Los_Angeles" selected="@(Model.TimeZone == "America/Los_Angeles" ? "selected" : null)">Pacific (PT) — Los Angeles</option>
|
<option value="America/Los_Angeles" selected="@(Model.TimeZone == "America/Los_Angeles" ? "selected" : null)">Pacific (PT) — Los Angeles</option>
|
||||||
<option value="America/Anchorage" selected="@(Model.TimeZone == "America/Anchorage" ? "selected" : null)">Alaska (AKT) — Anchorage</option>
|
<option value="America/Anchorage" selected="@(Model.TimeZone == "America/Anchorage" ? "selected" : null)">Alaska (AKT) — Anchorage</option>
|
||||||
<option value="Pacific/Honolulu" selected="@(Model.TimeZone == "Pacific/Honolulu" ? "selected" : null)">Hawaii (HT) — Honolulu</option>
|
<option value="Pacific/Honolulu" selected="@(Model.TimeZone == "Pacific/Honolulu" ? "selected" : null)">Hawaii (HT) — Honolulu</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="Canada">
|
<optgroup label="Canada">
|
||||||
<option value="America/Halifax" selected="@(Model.TimeZone == "America/Halifax" ? "selected" : null)">Atlantic (AT) — Halifax</option>
|
<option value="America/Halifax" selected="@(Model.TimeZone == "America/Halifax" ? "selected" : null)">Atlantic (AT) — Halifax</option>
|
||||||
<option value="America/Toronto" selected="@(Model.TimeZone == "America/Toronto" ? "selected" : null)">Eastern — Toronto</option>
|
<option value="America/Toronto" selected="@(Model.TimeZone == "America/Toronto" ? "selected" : null)">Eastern — Toronto</option>
|
||||||
<option value="America/Winnipeg" selected="@(Model.TimeZone == "America/Winnipeg" ? "selected" : null)">Central — Winnipeg</option>
|
<option value="America/Winnipeg" selected="@(Model.TimeZone == "America/Winnipeg" ? "selected" : null)">Central — Winnipeg</option>
|
||||||
<option value="America/Edmonton" selected="@(Model.TimeZone == "America/Edmonton" ? "selected" : null)">Mountain — Edmonton</option>
|
<option value="America/Edmonton" selected="@(Model.TimeZone == "America/Edmonton" ? "selected" : null)">Mountain — Edmonton</option>
|
||||||
<option value="America/Vancouver" selected="@(Model.TimeZone == "America/Vancouver" ? "selected" : null)">Pacific — Vancouver</option>
|
<option value="America/Vancouver" selected="@(Model.TimeZone == "America/Vancouver" ? "selected" : null)">Pacific — Vancouver</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="Europe">
|
<optgroup label="Europe">
|
||||||
<option value="Europe/London" selected="@(Model.TimeZone == "Europe/London" ? "selected" : null)">GMT/BST — London</option>
|
<option value="Europe/London" selected="@(Model.TimeZone == "Europe/London" ? "selected" : null)">GMT/BST — London</option>
|
||||||
<option value="Europe/Paris" selected="@(Model.TimeZone == "Europe/Paris" ? "selected" : null)">CET — Paris / Berlin</option>
|
<option value="Europe/Paris" selected="@(Model.TimeZone == "Europe/Paris" ? "selected" : null)">CET — Paris / Berlin</option>
|
||||||
<option value="Europe/Helsinki" selected="@(Model.TimeZone == "Europe/Helsinki" ? "selected" : null)">EET — Helsinki</option>
|
<option value="Europe/Helsinki" selected="@(Model.TimeZone == "Europe/Helsinki" ? "selected" : null)">EET — Helsinki</option>
|
||||||
<option value="Europe/Moscow" selected="@(Model.TimeZone == "Europe/Moscow" ? "selected" : null)">MSK — Moscow</option>
|
<option value="Europe/Moscow" selected="@(Model.TimeZone == "Europe/Moscow" ? "selected" : null)">MSK — Moscow</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="Asia / Pacific">
|
<optgroup label="Asia / Pacific">
|
||||||
<option value="Asia/Dubai" selected="@(Model.TimeZone == "Asia/Dubai" ? "selected" : null)">GST — Dubai</option>
|
<option value="Asia/Dubai" selected="@(Model.TimeZone == "Asia/Dubai" ? "selected" : null)">GST — Dubai</option>
|
||||||
<option value="Asia/Kolkata" selected="@(Model.TimeZone == "Asia/Kolkata" ? "selected" : null)">IST — India</option>
|
<option value="Asia/Kolkata" selected="@(Model.TimeZone == "Asia/Kolkata" ? "selected" : null)">IST — India</option>
|
||||||
<option value="Asia/Bangkok" selected="@(Model.TimeZone == "Asia/Bangkok" ? "selected" : null)">ICT — Bangkok</option>
|
<option value="Asia/Bangkok" selected="@(Model.TimeZone == "Asia/Bangkok" ? "selected" : null)">ICT — Bangkok</option>
|
||||||
<option value="Asia/Shanghai" selected="@(Model.TimeZone == "Asia/Shanghai" ? "selected" : null)">CST — Beijing / Shanghai</option>
|
<option value="Asia/Shanghai" selected="@(Model.TimeZone == "Asia/Shanghai" ? "selected" : null)">CST — Beijing / Shanghai</option>
|
||||||
<option value="Asia/Tokyo" selected="@(Model.TimeZone == "Asia/Tokyo" ? "selected" : null)">JST — Tokyo</option>
|
<option value="Asia/Tokyo" selected="@(Model.TimeZone == "Asia/Tokyo" ? "selected" : null)">JST — Tokyo</option>
|
||||||
<option value="Australia/Sydney" selected="@(Model.TimeZone == "Australia/Sydney" ? "selected" : null)">AEST — Sydney</option>
|
<option value="Australia/Sydney" selected="@(Model.TimeZone == "Australia/Sydney" ? "selected" : null)">AEST — Sydney</option>
|
||||||
<option value="Pacific/Auckland" selected="@(Model.TimeZone == "Pacific/Auckland" ? "selected" : null)">NZST — Auckland</option>
|
<option value="Pacific/Auckland" selected="@(Model.TimeZone == "Pacific/Auckland" ? "selected" : null)">NZST — Auckland</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="South America">
|
<optgroup label="South America">
|
||||||
<option value="America/Sao_Paulo" selected="@(Model.TimeZone == "America/Sao_Paulo" ? "selected" : null)">BRT — São Paulo</option>
|
<option value="America/Sao_Paulo" selected="@(Model.TimeZone == "America/Sao_Paulo" ? "selected" : null)">BRT — São Paulo</option>
|
||||||
<option value="America/Buenos_Aires" selected="@(Model.TimeZone == "America/Buenos_Aires" ? "selected" : null)">ART — Buenos Aires</option>
|
<option value="America/Buenos_Aires" selected="@(Model.TimeZone == "America/Buenos_Aires" ? "selected" : null)">ART — Buenos Aires</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="UTC">
|
<optgroup label="UTC">
|
||||||
<option value="UTC" selected="@(Model.TimeZone == "UTC" ? "selected" : null)">UTC</option>
|
<option value="UTC" selected="@(Model.TimeZone == "UTC" ? "selected" : null)">UTC</option>
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme radio — map light/dark → paper/ink surface system
|
// Theme radio â€" map light/dark → paper/ink surface system
|
||||||
document.querySelectorAll('input[name="theme"]').forEach(radio => {
|
document.querySelectorAll('input[name="theme"]').forEach(radio => {
|
||||||
radio.addEventListener('change', function () {
|
radio.addEventListener('change', function () {
|
||||||
var surface = this.value === 'dark' ? 'ink' : 'paper';
|
var surface = this.value === 'dark' ? 'ink' : 'paper';
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Customer vs Prospect/Walk-In"
|
data-bs-title="Customer vs Prospect/Walk-In"
|
||||||
data-bs-content="Choose <strong>Existing Customer</strong> if this person is already in your system. Choose <strong>New Prospect/Walk-In</strong> if they haven't committed yet — their details stay on the quote. When they approve, you can convert them to a full customer record with one click.<br><br><a href='/Help/Quotes#prospect-conversion' target='_blank'>Learn more →</a>">
|
data-bs-content="Choose <strong>Existing Customer</strong> if this person is already in your system. Choose <strong>New Prospect/Walk-In</strong> if they haven't committed yet — their details stay on the quote. When they approve, you can convert them to a full customer record with one click.<br><br><a href='/Help/Quotes#prospect-conversion' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Quote Information"
|
data-bs-title="Quote Information"
|
||||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Oven & Batch Pricing"
|
data-bs-title="Oven & Batch Pricing"
|
||||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Quote Item Types"
|
data-bs-title="Quote Item Types"
|
||||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -314,7 +314,7 @@
|
|||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
||||||
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
||||||
Hide discount from customer — PDFs and approval portal show final price only
|
Hide discount from customer — PDFs and approval portal show final price only
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
<a tabindex="0" class="help-icon text-white" role="button"
|
<a tabindex="0" class="help-icon text-white" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="left"
|
data-bs-toggle="popover" data-bs-placement="left"
|
||||||
data-bs-title="Pricing Summary"
|
data-bs-title="Pricing Summary"
|
||||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||||
@@ -379,7 +379,7 @@
|
|||||||
<div class="row g-3" id="stagedPhotoGrid"></div>
|
<div class="row g-3" id="stagedPhotoGrid"></div>
|
||||||
<div id="stagedPhotoUploadProgress" class="d-none mt-2">
|
<div id="stagedPhotoUploadProgress" class="d-none mt-2">
|
||||||
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
||||||
<small class="text-muted">Uploading…</small>
|
<small class="text-muted">Uploading…</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -560,7 +560,7 @@
|
|||||||
function applyMode(mode) {
|
function applyMode(mode) {
|
||||||
if (mode === 'simple') {
|
if (mode === 'simple') {
|
||||||
form.classList.add('quote-simple-mode');
|
form.classList.add('quote-simple-mode');
|
||||||
hint.textContent = 'Advanced fields are hidden — switch to Full Quote to see them.';
|
hint.textContent = 'Advanced fields are hidden — switch to Full Quote to see them.';
|
||||||
} else {
|
} else {
|
||||||
form.classList.remove('quote-simple-mode');
|
form.classList.remove('quote-simple-mode');
|
||||||
hint.textContent = '';
|
hint.textContent = '';
|
||||||
@@ -628,8 +628,8 @@
|
|||||||
smsNote.style.display = 'inline';
|
smsNote.style.display = 'inline';
|
||||||
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
||||||
smsNote.innerHTML = hasSms
|
smsNote.innerHTML = hasSms
|
||||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||||
} else {
|
} else {
|
||||||
smsNote.style.display = 'none';
|
smsNote.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Quote Information"
|
data-bs-title="Quote Information"
|
||||||
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
data-bs-content="Set the quote date, expiration, and any internal notes. The <strong>Expiration Date</strong> is shown to the customer — once it passes the quote is flagged Expired and can no longer be approved without editing. The <strong>Customer PO</strong> field is optional — use it if the customer provides their own purchase order number.<br><br><a href='/Help/Quotes#quote-statuses' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Oven & Batch Pricing"
|
data-bs-title="Oven & Batch Pricing"
|
||||||
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
data-bs-content="The oven cost is charged once per batch at the quote level, not per item. Estimate how many oven loads the full job will fill — for example, if you have 20 small parts and your oven fits 10, that's 2 batches. Cycle time is how long each batch runs. The cost is calculated from your oven's hourly rate in Settings.">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
<a tabindex="0" class="help-icon" role="button"
|
<a tabindex="0" class="help-icon" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="right"
|
data-bs-toggle="popover" data-bs-placement="right"
|
||||||
data-bs-title="Quote Item Types"
|
data-bs-title="Quote Item Types"
|
||||||
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
data-bs-content="<strong>Calculated</strong> — you enter surface area (sq ft) and the system prices it using your rates for materials, labour, and overhead.<br><strong>Custom Work</strong> — you enter a description and a manual price. Use this for flat-rate jobs or work that doesn't fit the formula.<br><strong>AI Photo</strong> — upload photos and let the AI estimate surface area and complexity for you.<br><br><a href='/Help/Quotes#quote-items' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -277,7 +277,7 @@
|
|||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
<input asp-for="HideDiscountFromCustomer" class="form-check-input" type="checkbox" id="hideDiscountFromCustomer" />
|
||||||
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
<label class="form-check-label small" for="hideDiscountFromCustomer">
|
||||||
Hide discount from customer — PDFs and approval portal show final price only
|
Hide discount from customer — PDFs and approval portal show final price only
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
<a tabindex="0" class="help-icon text-white" role="button"
|
<a tabindex="0" class="help-icon text-white" role="button"
|
||||||
data-bs-toggle="popover" data-bs-placement="left"
|
data-bs-toggle="popover" data-bs-placement="left"
|
||||||
data-bs-title="Pricing Summary"
|
data-bs-title="Pricing Summary"
|
||||||
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
data-bs-content="The total is built up from materials, labour, equipment time, overhead, and profit margin — all based on the rates in Settings. A <strong>Tier Discount</strong> appears automatically if the customer has a pricing tier assigned. A <strong>Rush Fee</strong> is added when Rush Job is checked.<br><br><a href='/Help/Quotes#pricing-breakdown' target='_blank'>Learn more →</a>">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
<p class="mb-1 text-muted small" id="pricingPlaceholder">Pricing will update automatically as you add items.</p>
|
||||||
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
<p class="mb-1 d-none" id="itemsSubtotalRow">Items Subtotal: <strong id="itemsSubtotalDisplay">$0.00</strong></p>
|
||||||
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
<p class="mb-1 d-none" id="ovenBatchCostRow">
|
||||||
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
<i class="bi bi-fire me-1"></i>Oven (<span id="ovenBatchesDisplay">1</span> batch × <span id="ovenCycleMinDisplay">45</span> min):
|
||||||
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
<strong id="ovenBatchCostDisplay">$0.00</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
<p class="mb-1 text-success d-none" id="pricingTierDiscountRow">
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="editPhotoUploadProgress" class="d-none mt-2">
|
<div id="editPhotoUploadProgress" class="d-none mt-2">
|
||||||
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" style="width:100%"></div></div>
|
||||||
<small class="text-muted">Uploading…</small>
|
<small class="text-muted">Uploading…</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,11 +435,11 @@
|
|||||||
<span id="smsNotifyNote" class="badge @(editHasSms ? "bg-info text-white" : "bg-warning text-dark")">
|
<span id="smsNotifyNote" class="badge @(editHasSms ? "bg-info text-white" : "bg-warning text-dark")">
|
||||||
@if (editHasSms)
|
@if (editHasSms)
|
||||||
{
|
{
|
||||||
<i class="bi bi-phone me-1"></i><text>No email — send via SMS from quote details</text>
|
<i class="bi bi-phone me-1"></i><text>No email — send via SMS from quote details</text>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<i class="bi bi-phone-slash me-1"></i><text>No email — SMS consent required</text>
|
<i class="bi bi-phone-slash me-1"></i><text>No email — SMS consent required</text>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@
|
|||||||
@Html.Raw(Json.Serialize(ViewBag.BlastSetups ?? new List<object>()))
|
@Html.Raw(Json.Serialize(ViewBag.BlastSetups ?? new List<object>()))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Existing items — always populated on Edit -->
|
<!-- Existing items — always populated on Edit -->
|
||||||
<script id="existingItemsData" type="application/json">
|
<script id="existingItemsData" type="application/json">
|
||||||
@Html.Raw(System.Text.Json.JsonSerializer.Serialize((Model.QuoteItems ?? new List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemDto>()).Select((item, i) => new {
|
@Html.Raw(System.Text.Json.JsonSerializer.Serialize((Model.QuoteItems ?? new List<PowderCoating.Application.DTOs.Quote.CreateQuoteItemDto>()).Select((item, i) => new {
|
||||||
description = item.Description,
|
description = item.Description,
|
||||||
@@ -613,8 +613,8 @@
|
|||||||
smsNote.style.display = 'inline';
|
smsNote.style.display = 'inline';
|
||||||
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
||||||
smsNote.innerHTML = hasSms
|
smsNote.innerHTML = hasSms
|
||||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||||
} else {
|
} else {
|
||||||
smsNote.style.display = 'none';
|
smsNote.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -639,7 +639,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quote photo direct upload (Edit page — quoteId is known)
|
// Quote photo direct upload (Edit page — quoteId is known)
|
||||||
(function () {
|
(function () {
|
||||||
const quoteId = @Model.Id;
|
const quoteId = @Model.Id;
|
||||||
const uploadUrl = '@Url.Action("UploadQuotePhoto", "Quotes")';
|
const uploadUrl = '@Url.Action("UploadQuotePhoto", "Quotes")';
|
||||||
|
|||||||
Reference in New Issue
Block a user