diff --git a/src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue b/src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue index 2e4459c9..0d213db8 100644 --- a/src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue +++ b/src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue @@ -274,10 +274,15 @@ const actions = { }, approval: { accept: (object) => { - return runReviewDecision(object, "create_order"); + // Manual fallback: when the AI has no suggestion yet, force the backend + // to materialise a deterministic proposal from the wash log itself. + return runReviewDecision(object, "create_order", { forceManual: !canRunReviewAction(object, 'create_order') }); }, reject: (object) => { - return requestReviewReason(object, "deny"); + return requestReviewReason(object, "deny", { forceManual: !canRunReviewAction(object, 'deny') }); + }, + ignore: (object) => { + return requestReviewReason(object, "ignore", { forceManual: !canRunReviewAction(object, 'ignore') }); }, history: (object) => { object.showAutopilotHistory = !object.showAutopilotHistory; @@ -347,6 +352,18 @@ const canCreateAutomation = (object) => props.allowReviewActions && isXlvaskReviewEligible(object) && object?.automation?.can_create_order === true; +// Manual review eligibility: review actions are allowed when review is enabled +// AND the row is review-eligible (i.e. still has a chance of accepting / denying). +// This unlocks Accept / Reject / Ignore buttons even when the AI autopilot has +// not produced a suggestion yet, so operators can process the import queue +// during the AI autopilot readiness window. +const canRunManualReviewAction = (object) => props.allowReviewActions + && isXlvaskReviewEligible(object) + && object?.automation?.status !== 'accepted' + && object?.automation?.status !== 'denied' + && object?.automation?.status !== 'auto_accepted' + && !isObjectAttachedToOrder(object); + const canCompareAutomation = (object) => props.allowReviewActions && isXlvaskReviewEligible(object) && doesObjectHaveDuplicates(object); @@ -642,8 +659,13 @@ const applyDecisionPreview = async (objectOrObjects, preview, expectedVersions = return true; }; -const runReviewDecision = async (object, action, { orderId = null, reason = "" } = {}) => { - if (!canRunReviewAction(object, action) || !object?.id || object.automationLoading) return; +const runReviewDecision = async (object, action, { orderId = null, reason = "", forceManual = false } = {}) => { + // Allow either an AI-driven suggestion or a manual operator decision. + const hasAiCapability = canRunReviewAction(object, action); + const hasManualCapability = canRunManualReviewAction(object) && ( + action === 'create_order' || action === 'attach_order' || action === 'deny' || action === 'ignore' + ); + if ((!hasAiCapability && !hasManualCapability) || !object?.id || object.automationLoading) return; object.automationLoading = true; try { const response = await SessionUser.request( @@ -655,6 +677,7 @@ const runReviewDecision = async (object, action, { orderId = null, reason = "" } suggestion_id: object?.automation?.id ?? null, ...(orderId ? { order_id: Number(orderId) } : {}), ...(reason ? { reason } : {}), + ...(forceManual || (!hasAiCapability && hasManualCapability) ? { force_manual: true } : {}), expected_versions: getExpectedVersions(object), }, ); @@ -674,7 +697,7 @@ const runReviewDecision = async (object, action, { orderId = null, reason = "" } } }; -const requestReviewReason = async (object, action) => { +const requestReviewReason = async (object, action, options = {}) => { const result = await Swal.fire({ title: t(`invoicing_period.xlvask_autopilot.actions.${action}`), input: "textarea", @@ -687,7 +710,10 @@ const requestReviewReason = async (object, action) => { : t("invoicing_period.xlvask_autopilot.preview.reason_required"), }); if (result.isConfirmed) { - await runReviewDecision(object, action, { reason: String(result.value).trim() }); + await runReviewDecision(object, action, { + reason: String(result.value).trim(), + forceManual: options.forceManual === true || !canRunReviewAction(object, action), + }); } }; @@ -1181,20 +1207,47 @@ const filteredObjects = computed(() => {