Merge dev into master: QR scan URL fixes and http scheme failsafe
This commit is contained in:
@@ -946,7 +946,10 @@ public class InventoryController : Controller
|
|||||||
if (!string.IsNullOrWhiteSpace(urlMfr))
|
if (!string.IsNullOrWhiteSpace(urlMfr))
|
||||||
{
|
{
|
||||||
aiResult = await _aiLookupService.LookupAsync(urlMfr, urlColor, null, urlPart);
|
aiResult = await _aiLookupService.LookupAsync(urlMfr, urlColor, null, urlPart);
|
||||||
if (aiResult.Success && aiResult.SpecPageUrl == null)
|
// The scanned QR URL is always the authoritative product page link — it came
|
||||||
|
// directly from the manufacturer's bag and is always fully-qualified. Overwrite
|
||||||
|
// whatever LookupAsync returned (which may be a scheme-less path from the template).
|
||||||
|
if (aiResult.Success)
|
||||||
aiResult.SpecPageUrl = qrUrl;
|
aiResult.SpecPageUrl = qrUrl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
ViewData["PageIcon"] = "bi-box-seam";
|
ViewData["PageIcon"] = "bi-box-seam";
|
||||||
ViewData["PageHelpTitle"] = "Inventory Item";
|
ViewData["PageHelpTitle"] = "Inventory Item";
|
||||||
ViewData["PageHelpContent"] = "Full detail for this inventory item. Stock Information shows current quantity and reorder thresholds — a Low Stock banner appears when quantity is at or below the Reorder Point. Pricing shows Unit Cost (what you paid), Average Cost (weighted average across purchases), and Total Stock Value. Use the Actions panel to edit, view jobs using this powder, or delete the item.";
|
ViewData["PageHelpContent"] = "Full detail for this inventory item. Stock Information shows current quantity and reorder thresholds — a Low Stock banner appears when quantity is at or below the Reorder Point. Pricing shows Unit Cost (what you paid), Average Cost (weighted average across purchases), and Total Stock Value. Use the Actions panel to edit, view jobs using this powder, or delete the item.";
|
||||||
|
|
||||||
|
string SafeUrl(string? url) =>
|
||||||
|
string.IsNullOrEmpty(url) ? "#"
|
||||||
|
: (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
? url : "http://" + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Styles {
|
@section Styles {
|
||||||
@@ -184,7 +189,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label class="text-muted small mb-1">Product URL</label>
|
<label class="text-muted small mb-1">Product URL</label>
|
||||||
<p class="mb-0">
|
<p class="mb-0">
|
||||||
<a href="@Model.SpecPageUrl" target="_blank" class="text-decoration-none">
|
<a href="@SafeUrl(Model.SpecPageUrl)" target="_blank" class="text-decoration-none">
|
||||||
<i class="bi bi-box-arrow-up-right me-1"></i>View on Manufacturer's Web Site
|
<i class="bi bi-box-arrow-up-right me-1"></i>View on Manufacturer's Web Site
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -197,13 +202,13 @@
|
|||||||
<div class="d-flex gap-2 flex-wrap">
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
@if (!string.IsNullOrEmpty(Model.SdsUrl))
|
@if (!string.IsNullOrEmpty(Model.SdsUrl))
|
||||||
{
|
{
|
||||||
<a href="@Model.SdsUrl" target="_blank" class="btn btn-sm btn-outline-secondary">
|
<a href="@SafeUrl(Model.SdsUrl)" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="bi bi-file-earmark-pdf me-1"></i>Safety Data Sheet
|
<i class="bi bi-file-earmark-pdf me-1"></i>Safety Data Sheet
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrEmpty(Model.TdsUrl))
|
@if (!string.IsNullOrEmpty(Model.TdsUrl))
|
||||||
{
|
{
|
||||||
<a href="@Model.TdsUrl" target="_blank" class="btn btn-sm btn-outline-secondary">
|
<a href="@SafeUrl(Model.TdsUrl)" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="bi bi-file-earmark-text me-1"></i>Technical Data Sheet
|
<i class="bi bi-file-earmark-text me-1"></i>Technical Data Sheet
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,10 +413,15 @@
|
|||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function ensureAbsoluteUrl(url) {
|
||||||
|
if (!url) return url;
|
||||||
|
return /^https?:\/\//i.test(url) ? url : 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
function syncLinkButton(inputId, linkId, url) {
|
function syncLinkButton(inputId, linkId, url) {
|
||||||
const link = document.getElementById(linkId);
|
const link = document.getElementById(linkId);
|
||||||
if (!link) return;
|
if (!link) return;
|
||||||
if (url) { link.href = url; link.classList.remove('d-none'); }
|
if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); }
|
||||||
else { link.classList.add('d-none'); }
|
else { link.classList.add('d-none'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -550,10 +550,15 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureAbsoluteUrl(url) {
|
||||||
|
if (!url) return url;
|
||||||
|
return /^https?:\/\//i.test(url) ? url : 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
function syncLink(inputId, linkId, url) {
|
function syncLink(inputId, linkId, url) {
|
||||||
const link = document.getElementById(linkId);
|
const link = document.getElementById(linkId);
|
||||||
if (!link) return;
|
if (!link) return;
|
||||||
if (url) { link.href = url; link.classList.remove('d-none'); }
|
if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); }
|
||||||
else { link.classList.add('d-none'); }
|
else { link.classList.add('d-none'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user