271 lines
8.2 KiB
JavaScript
271 lines
8.2 KiB
JavaScript
import { nextTick } from "vue";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const getOverviewMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
objects: {
|
|
department_daily_reports: {
|
|
functions: {
|
|
getOverview: getOverviewMock,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const flushAll = async () => {
|
|
await nextTick();
|
|
await Promise.resolve();
|
|
await nextTick();
|
|
await Promise.resolve();
|
|
};
|
|
|
|
const deferred = () => {
|
|
let resolve;
|
|
let reject;
|
|
const promise = new Promise((nextResolve, nextReject) => {
|
|
resolve = nextResolve;
|
|
reject = nextReject;
|
|
});
|
|
|
|
return { promise, resolve, reject };
|
|
};
|
|
|
|
const makeOverview = ({
|
|
transactions = 0,
|
|
productsSold = 0,
|
|
revenue = 0,
|
|
washes = 0,
|
|
waterUsage = 0,
|
|
bookings = 0,
|
|
bookingsOutOf = 0,
|
|
complaints = 0,
|
|
complaintsState = "ready",
|
|
complaintsMessage = null,
|
|
nightWashesState = "ready",
|
|
nightWashes = 0,
|
|
nightWashesBySource = { orders: 0, xlvask: 0, selfserve: 0 },
|
|
nightWashesMissingDepartmentIds = [],
|
|
overtimeState = "ready",
|
|
overtime = 0,
|
|
} = {}) => ({
|
|
data: {
|
|
data: {
|
|
metrics: {
|
|
transactions: { state: "ready", value: transactions, out_of: null, message: null },
|
|
products_sold: { state: "ready", value: productsSold, out_of: null, message: null },
|
|
revenue: { state: "ready", value: revenue, out_of: null, message: null },
|
|
washes: { state: "ready", value: washes, out_of: null, message: null },
|
|
water_usage: { state: "ready", value: waterUsage, out_of: null, message: null },
|
|
bookings: { state: "ready", value: bookings, out_of: bookingsOutOf, message: null },
|
|
complaints: { state: complaintsState, value: complaints, out_of: null, message: complaintsMessage },
|
|
night_washes: {
|
|
state: nightWashesState,
|
|
value: nightWashes,
|
|
out_of: null,
|
|
message: null,
|
|
by_source: nightWashesBySource,
|
|
has_missing_opening_hours: nightWashesMissingDepartmentIds.length > 0,
|
|
missing_department_ids: nightWashesMissingDepartmentIds,
|
|
},
|
|
overtime: {
|
|
state: overtimeState,
|
|
value: overtime,
|
|
out_of: null,
|
|
message: overtimeState === "ready" ? null : "Overarbejde kræver Workfeed-kobling.",
|
|
},
|
|
},
|
|
products: [
|
|
{
|
|
product_id: 24,
|
|
slug: "spot-free-lastbil",
|
|
title: "Spot Free (Lastbil)",
|
|
state: "ready",
|
|
value: 3,
|
|
out_of: 9,
|
|
target_percentage: "56.5",
|
|
target_department_id: "2",
|
|
},
|
|
{
|
|
product_id: 25,
|
|
slug: "faelg-flex",
|
|
title: "Fælg flex pr. enhed",
|
|
state: "ready",
|
|
value: 0,
|
|
out_of: 9,
|
|
target_percentage: 0,
|
|
target_department_id: 2,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
describe("DepartmentDailyReportObject behavior", () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
getOverviewMock.mockReset();
|
|
});
|
|
|
|
it("fetches once per filter change and lets the latest overview response win", async () => {
|
|
const firstRequest = deferred();
|
|
const secondRequest = deferred();
|
|
getOverviewMock.mockReturnValueOnce(firstRequest.promise).mockReturnValueOnce(secondRequest.promise);
|
|
|
|
const module = await import(
|
|
"@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue"
|
|
);
|
|
|
|
module.initializeDailyReportFilters({
|
|
department_ids: [1],
|
|
date: "2026-03-23",
|
|
date_to: "2026-03-23",
|
|
});
|
|
await flushAll();
|
|
|
|
expect(getOverviewMock).toHaveBeenCalledTimes(1);
|
|
expect(getOverviewMock).toHaveBeenLastCalledWith([1], "2026-03-23", "2026-03-23");
|
|
|
|
module.selectDate("2026-03-23", "2026-03-24");
|
|
await flushAll();
|
|
|
|
expect(getOverviewMock).toHaveBeenCalledTimes(2);
|
|
expect(getOverviewMock).toHaveBeenLastCalledWith([1], "2026-03-23", "2026-03-24");
|
|
|
|
secondRequest.resolve(
|
|
makeOverview({
|
|
transactions: 9,
|
|
productsSold: 18,
|
|
revenue: 4200,
|
|
washes: 11,
|
|
waterUsage: 34,
|
|
bookings: 5,
|
|
bookingsOutOf: 7,
|
|
complaints: 2,
|
|
nightWashes: 2,
|
|
nightWashesBySource: { orders: 1, xlvask: 0, selfserve: 1 },
|
|
nightWashesMissingDepartmentIds: [7],
|
|
overtime: 1.25,
|
|
})
|
|
);
|
|
await flushAll();
|
|
|
|
firstRequest.resolve(
|
|
makeOverview({
|
|
transactions: 1,
|
|
productsSold: 2,
|
|
revenue: 300,
|
|
washes: 1,
|
|
waterUsage: 3,
|
|
bookings: 1,
|
|
bookingsOutOf: 1,
|
|
nightWashes: 0,
|
|
nightWashesBySource: { orders: 0, xlvask: 0, selfserve: 0 },
|
|
overtime: 0.25,
|
|
})
|
|
);
|
|
await flushAll();
|
|
|
|
expect(module.count_transactions.value).toBe(9);
|
|
expect(module.count_products_sold.value).toBe(18);
|
|
expect(module.count_earnings.value).toBe(4200);
|
|
expect(module.count_washes.value).toBe(11);
|
|
expect(module.count_water_usage.value).toBe(34);
|
|
expect(module.count_bookings.value).toBe(5);
|
|
expect(module.count_bookings_out_of.value).toBe(7);
|
|
expect(module.count_complaints.value).toBe(2);
|
|
expect(module.count_night_washes.value).toBe(2);
|
|
expect(module.night_washes_by_source.value).toEqual({ orders: 1, xlvask: 0, selfserve: 1 });
|
|
expect(module.night_washes_has_missing_opening_hours.value).toBe(true);
|
|
expect(module.night_washes_missing_department_ids.value).toEqual([7]);
|
|
expect(module.count_overtime.value).toBe(1.25);
|
|
});
|
|
|
|
it("keeps unavailable complaint and overtime tiles out of fake zero state", async () => {
|
|
getOverviewMock.mockResolvedValue(
|
|
makeOverview({
|
|
transactions: 4,
|
|
productsSold: 12,
|
|
revenue: 1800,
|
|
washes: 6,
|
|
waterUsage: 22,
|
|
bookings: 3,
|
|
bookingsOutOf: 4,
|
|
complaintsState: "unavailable",
|
|
complaintsMessage: "Kundeklager er ikke tilgængelig endnu.",
|
|
nightWashesState: "ready",
|
|
nightWashes: 1,
|
|
nightWashesBySource: { orders: 0, xlvask: 1, selfserve: 0 },
|
|
overtimeState: "unavailable",
|
|
overtime: 0,
|
|
})
|
|
);
|
|
|
|
const module = await import(
|
|
"@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue"
|
|
);
|
|
|
|
module.initializeDailyReportFilters({
|
|
department_ids: [2],
|
|
date: "2026-03-25",
|
|
date_to: "2026-03-25",
|
|
});
|
|
await flushAll();
|
|
|
|
expect(module.complaints_metric_state.value.state).toBe("unavailable");
|
|
expect(module.complaints_metric_state.value.message).toContain("ikke tilgængelig");
|
|
expect(module.count_complaints.value).toBe(0);
|
|
expect(module.overtime_metric_state.value.state).toBe("unavailable");
|
|
expect(module.count_overtime.value).toBeNull();
|
|
expect(module.count_night_washes.value).toBe(1);
|
|
expect(module.night_washes_by_source.value).toEqual({ orders: 0, xlvask: 1, selfserve: 0 });
|
|
expect(module.night_washes_has_missing_opening_hours.value).toBe(false);
|
|
expect(module.night_washes_missing_department_ids.value).toEqual([]);
|
|
expect(module.daily_report_products.value[24]).toMatchObject({
|
|
slug: "spot-free-lastbil",
|
|
value: 3,
|
|
out_of: 9,
|
|
target_percentage: 56.5,
|
|
target_department_id: 2,
|
|
});
|
|
expect(module.daily_report_products.value[25]).toMatchObject({
|
|
slug: "faelg-flex",
|
|
value: 0,
|
|
out_of: 9,
|
|
target_percentage: 0,
|
|
target_department_id: 2,
|
|
});
|
|
});
|
|
|
|
it("exposes an explicit overview refresh helper", async () => {
|
|
getOverviewMock.mockResolvedValue(
|
|
makeOverview({
|
|
transactions: 7,
|
|
complaints: 3,
|
|
})
|
|
);
|
|
|
|
const module = await import(
|
|
"@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue"
|
|
);
|
|
|
|
module.initializeDailyReportFilters({
|
|
department_ids: [3],
|
|
date: "2026-03-26",
|
|
date_to: "2026-03-26",
|
|
});
|
|
await flushAll();
|
|
|
|
getOverviewMock.mockClear();
|
|
|
|
await module.refreshOverview();
|
|
await flushAll();
|
|
|
|
expect(getOverviewMock).toHaveBeenCalledTimes(1);
|
|
expect(getOverviewMock).toHaveBeenCalledWith([3], "2026-03-26", "2026-03-26");
|
|
expect(module.count_complaints.value).toBe(3);
|
|
});
|
|
});
|