Makes XL Vask accept/reject/ignore buttons always visible when review is enabled (not gated on AI autopilot suggestion). Keeps the Fakturer nu button visible when a customer has multiple red flags. Includes vitest tests for the manual-review flow. Required for tomorrow's manual review + accepted order workflow. Co-authored-by: Cleanup Agent <agent@truckwash.io>
204 lines
6.0 KiB
JavaScript
204 lines
6.0 KiB
JavaScript
// @vitest-environment jsdom
|
|
|
|
import { flushPromises, shallowMount } from "@vue/test-utils";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { ref } from "vue";
|
|
import { createI18n } from "vue-i18n";
|
|
|
|
const { request, loadList, fire } = vi.hoisted(() => ({
|
|
request: vi.fn(),
|
|
loadList: vi.fn(),
|
|
fire: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("sweetalert2", () => ({ default: { fire } }));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
request,
|
|
functions: {
|
|
currency: { toLocal: (value) => String(value) },
|
|
parseErrorMessage: (error) => String(error),
|
|
},
|
|
objects: {
|
|
global: {
|
|
language: {
|
|
completed: "Completed",
|
|
generated: "Generated",
|
|
possible_duplicates: "Possible duplicates",
|
|
price_match: "Price match",
|
|
price_unmatch: "Price mismatch",
|
|
},
|
|
},
|
|
orders: { meta: { title: "Orders" } },
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/components/pagination/paginatedList.vue", () => ({
|
|
usePaginatedListInstance: () => ({ loadList }),
|
|
}));
|
|
|
|
vi.mock("@/components/pagination/departmentTabs.vue", () => ({
|
|
departments: ref([{ id: 1, name: "Hall 1" }]),
|
|
getDepartments: vi.fn(),
|
|
getDepartmentName: () => "Hall 1",
|
|
}));
|
|
|
|
vi.mock("@/components/displays/department/pos/sync/xlvaskUsageAmountCache.js", () => ({
|
|
clearCachedXlvaskUsageAmount: vi.fn(),
|
|
getCachedXlvaskUsageAmount: vi.fn(() => null),
|
|
setCachedXlvaskUsageAmount: vi.fn(),
|
|
}));
|
|
|
|
import XlvaskUsageOrdersTable from "@/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue";
|
|
|
|
const reviewEligibleRow = (overrides = {}) => ({
|
|
id: 9101,
|
|
reg_1: "ZZ99001",
|
|
created_at: new Date().toISOString(),
|
|
customer_name: "Test customer",
|
|
department_id: 1,
|
|
lane: 1,
|
|
total_net_amount: 250,
|
|
import_state: "new",
|
|
resolution_state: "needs_review",
|
|
certainty: "uncertain",
|
|
planned_action: "none",
|
|
// No AI suggestion yet — automation is empty (the autopilot hasn't run).
|
|
automation: {
|
|
status: "none",
|
|
review_eligible: true,
|
|
},
|
|
...overrides,
|
|
});
|
|
|
|
const mountTable = (props = {}) =>
|
|
shallowMount(XlvaskUsageOrdersTable, {
|
|
props: {
|
|
objects: [reviewEligibleRow()],
|
|
allowReviewActions: true,
|
|
allowSelectMultiple: true,
|
|
...props,
|
|
},
|
|
global: {
|
|
plugins: [
|
|
createI18n({ legacy: false, locale: "en", missingWarn: false, fallbackWarn: false, messages: { en: {} } }),
|
|
],
|
|
stubs: {
|
|
WhiteBoxCard: {
|
|
template: "<section><slot name='header'/><slot name='content'/></section>",
|
|
},
|
|
OrderItemsTable: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
describe("XL-Vask manual review buttons", () => {
|
|
beforeEach(() => {
|
|
request.mockReset();
|
|
loadList.mockReset();
|
|
fire.mockReset();
|
|
});
|
|
|
|
it("exposes accept / reject / ignore buttons even when the autopilot has no suggestion", () => {
|
|
const wrapper = mountTable();
|
|
|
|
expect(wrapper.find("[data-testid='xlvask-accept-9101']").exists()).toBe(true);
|
|
expect(wrapper.find("[data-testid='xlvask-reject-9101']").exists()).toBe(true);
|
|
expect(wrapper.find("[data-testid='xlvask-ignore-9101']").exists()).toBe(true);
|
|
});
|
|
|
|
it("sends force_manual=true when accepting without an AI suggestion", async () => {
|
|
const wrapper = mountTable();
|
|
|
|
request.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
preview: { id: "preview-id", selection_hash: "abc12345".repeat(8) },
|
|
},
|
|
},
|
|
});
|
|
|
|
await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click");
|
|
await flushPromises();
|
|
|
|
const previewCall = request.mock.calls.find(
|
|
([endpoint, method]) =>
|
|
endpoint === "/modules/xlvask/services/usage/automation/decisions/preview" && method === "POST"
|
|
);
|
|
expect(previewCall).toBeDefined();
|
|
const payload = previewCall[2];
|
|
expect(payload).toMatchObject({
|
|
usage_log_ids: [9101],
|
|
action: "create_order",
|
|
force_manual: true,
|
|
});
|
|
});
|
|
|
|
it("does not send force_manual when the AI already has a matching suggestion", async () => {
|
|
const row = reviewEligibleRow({
|
|
automation: {
|
|
id: 5001,
|
|
status: "suggested",
|
|
action: "create_order",
|
|
can_accept: true,
|
|
can_deny: true,
|
|
can_ignore: true,
|
|
can_attach_order: true,
|
|
can_create_order: true,
|
|
review_eligible: true,
|
|
},
|
|
});
|
|
const wrapper = mountTable({ objects: [row] });
|
|
|
|
request.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
preview: { id: "preview-id-2", selection_hash: "def67890".repeat(8) },
|
|
},
|
|
},
|
|
});
|
|
|
|
await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click");
|
|
await flushPromises();
|
|
|
|
const previewCall = request.mock.calls.find(
|
|
([endpoint, method]) =>
|
|
endpoint === "/modules/xlvask/services/usage/automation/decisions/preview" && method === "POST"
|
|
);
|
|
expect(previewCall).toBeDefined();
|
|
const payload = previewCall[2];
|
|
expect(payload.action).toBe("create_order");
|
|
expect(payload).not.toHaveProperty("force_manual");
|
|
});
|
|
|
|
it("hides the buttons once the row is already resolved (no double-action)", () => {
|
|
const row = reviewEligibleRow({
|
|
resolution_state: "auto_linked",
|
|
automation: { status: "accepted", action: "attach_order", review_eligible: false },
|
|
});
|
|
const wrapper = mountTable({ objects: [row] });
|
|
|
|
expect(wrapper.find("[data-testid='xlvask-accept-9101']").exists()).toBe(false);
|
|
expect(wrapper.find("[data-testid='xlvask-reject-9101']").exists()).toBe(false);
|
|
expect(wrapper.find("[data-testid='xlvask-ignore-9101']").exists()).toBe(false);
|
|
});
|
|
|
|
it("surfaces the backend error when the manual review is rejected", async () => {
|
|
const wrapper = mountTable();
|
|
|
|
request.mockRejectedValue(new Error("Wash-id uniqueness activation is blocked"));
|
|
|
|
await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(fire).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
icon: "error",
|
|
title: "invoicing_period.xlvask_autopilot.preview.error_title",
|
|
})
|
|
);
|
|
});
|
|
});
|