fix(pleno-vue): simplify XLVask Selvvask surface by removing AI autopilot pipeline (#294)
Removes the now-removed XL Vask Selvvask AI autopilot and MiniMax UI from the Superuser → Fakturaer → Periode → Selvvask surface. The view is now an operator-only surface with Accept / Reject / Ignore actions on usage entries, period-scoped server pagination, and a summary refresh after operator review. Aligns unit tests with the cleaned surface, restores behavior compatibility for the residual summary normalizer used by the period right rail, and adds data-testid hooks on the operator action buttons so the contract tests can address them directly.
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
export const XLVASK_IMPORT_STATES = ["new", "updated", "unchanged", "invalid"];
|
||||
export const XLVASK_RESOLUTION_STATES = [
|
||||
/**
|
||||
* Compatibility shim for the now-removed XL Vask autopilot UI helpers.
|
||||
*
|
||||
* The original module exposed several helpers that paginated views and the
|
||||
* Superuser → Fakturaer → Periode → Selvvask surface consumed; the autopilot
|
||||
* surface itself was deprecated. We keep only the lightweight summary
|
||||
* normaliser that the period-side right rail still reads from the
|
||||
* `/modules/xlvask/services/usage/orders/summary` endpoint.
|
||||
*
|
||||
* Any caller that previously imported removed helpers (e.g.
|
||||
* `normalizeXlvaskAutopilotRun`, `isXlvaskAutopilotRunActive`) should drop
|
||||
* those usages – they no longer exist.
|
||||
*/
|
||||
|
||||
const XLVASK_RESOLUTION_STATES = [
|
||||
"already_linked",
|
||||
"auto_linked",
|
||||
"auto_created",
|
||||
@@ -8,240 +21,29 @@ export const XLVASK_RESOLUTION_STATES = [
|
||||
"ignored",
|
||||
"failed",
|
||||
];
|
||||
export const XLVASK_CERTAINTY_STATES = ["certain", "uncertain", "none"];
|
||||
export const XLVASK_PLANNED_ACTIONS = [
|
||||
"attach_order",
|
||||
"create_order",
|
||||
"resolve_mapping",
|
||||
"recheck",
|
||||
"ignore",
|
||||
"none",
|
||||
];
|
||||
|
||||
export const emptyXlvaskAutopilotSummary = () => ({
|
||||
total: 0,
|
||||
new: 0,
|
||||
updated: 0,
|
||||
unchanged: 0,
|
||||
invalid: 0,
|
||||
already_linked: 0,
|
||||
auto_linked: 0,
|
||||
auto_created: 0,
|
||||
needs_review: 0,
|
||||
blocked: 0,
|
||||
ignored: 0,
|
||||
failed: 0,
|
||||
certain: 0,
|
||||
uncertain: 0,
|
||||
none: 0,
|
||||
});
|
||||
const XLVASK_IMPORT_STATES = ["new", "updated", "unchanged", "invalid"];
|
||||
const XLVASK_CERTAINTY_STATES = ["certain", "uncertain", "none"];
|
||||
|
||||
const nonNegativeInteger = (value) => {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
||||
};
|
||||
|
||||
export const normalizeXlvaskAutopilotSummary = (summary) => {
|
||||
export const emptyXlvaskAutopilotSummary = () => ({
|
||||
total: 0,
|
||||
...Object.fromEntries(
|
||||
[...XLVASK_IMPORT_STATES, ...XLVASK_RESOLUTION_STATES, ...XLVASK_CERTAINTY_STATES].map((key) => [key, 0]),
|
||||
),
|
||||
});
|
||||
|
||||
export const normalizeXlvaskAutopilotSummary = (rawSummary) => {
|
||||
const normalized = emptyXlvaskAutopilotSummary();
|
||||
if (!rawSummary || typeof rawSummary !== "object") {
|
||||
return normalized;
|
||||
}
|
||||
Object.keys(normalized).forEach((key) => {
|
||||
normalized[key] = nonNegativeInteger(summary?.[key]);
|
||||
normalized[key] = nonNegativeInteger(rawSummary[key]);
|
||||
});
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const normalizeXlvaskAutopilotRun = (run) => {
|
||||
if (!run || typeof run !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...run,
|
||||
id: run.id ?? null,
|
||||
status: String(run.status || "queued"),
|
||||
phase: String(run.phase || run.status || "queued"),
|
||||
processed: nonNegativeInteger(run.processed),
|
||||
total: nonNegativeInteger(run.total),
|
||||
summary: normalizeXlvaskAutopilotSummary(run.summary),
|
||||
};
|
||||
};
|
||||
|
||||
const stringList = (value) => Array.isArray(value)
|
||||
? value.map((entry) => String(entry || "").trim()).filter(Boolean)
|
||||
: [];
|
||||
|
||||
const booleanCapability = (source, key) => source?.[key] === true;
|
||||
|
||||
export const emptyXlvaskAutomationCapabilities = () => ({
|
||||
can_view: false,
|
||||
can_review: false,
|
||||
can_dry_run: false,
|
||||
can_execute: false,
|
||||
can_manage_policy: false,
|
||||
can_halt: false,
|
||||
effective_stage: "off",
|
||||
allowed_modes: [],
|
||||
blocked_reasons: [],
|
||||
effective_action_sources: [],
|
||||
});
|
||||
|
||||
export const normalizeXlvaskAutomationCapabilities = (value) => {
|
||||
const source = value?.capabilities && typeof value.capabilities === "object"
|
||||
? value.capabilities
|
||||
: value;
|
||||
const normalized = emptyXlvaskAutomationCapabilities();
|
||||
if (!source || typeof source !== "object") return normalized;
|
||||
|
||||
Object.keys(normalized).forEach((key) => {
|
||||
if (key.startsWith("can_")) normalized[key] = booleanCapability(source, key);
|
||||
});
|
||||
normalized.effective_stage = String(source.effective_stage || source.policy_stage || "off").toLowerCase();
|
||||
normalized.allowed_modes = stringList(source.allowed_modes).map((mode) => mode.toLowerCase());
|
||||
normalized.blocked_reasons = stringList(source.blocked_reasons);
|
||||
normalized.effective_action_sources = stringList(source.effective_action_sources);
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const emptyXlvaskAutomationReadiness = () => ({
|
||||
ready: false,
|
||||
effective_stage: "off",
|
||||
policy_version: "",
|
||||
model: "",
|
||||
worker_healthy: false,
|
||||
blocked_reasons: [],
|
||||
budgets: {
|
||||
attach_order: { remaining_global: 0, remaining_hall: 0 },
|
||||
create_order: { remaining_global: 0, remaining_hall: 0 },
|
||||
},
|
||||
review_progress: {
|
||||
attach_order: { reviewed: 0, target: 200 },
|
||||
create_order: { reviewed: 0, target: 50 },
|
||||
},
|
||||
eligible_counts: { attach_order: 0, create_order: 0, total: 0 },
|
||||
calibrations: {},
|
||||
});
|
||||
|
||||
const normalizeProgress = (value, defaultTarget) => ({
|
||||
reviewed: nonNegativeInteger(value?.reviewed ?? value?.completed),
|
||||
target: nonNegativeInteger(value?.target) || defaultTarget,
|
||||
});
|
||||
|
||||
const normalizeBudget = (value) => ({
|
||||
remaining_global: nonNegativeInteger(value?.remaining_global ?? value?.global_remaining),
|
||||
remaining_hall: nonNegativeInteger(value?.remaining_hall ?? value?.hall_remaining),
|
||||
});
|
||||
|
||||
export const normalizeXlvaskAutomationReadiness = (value) => {
|
||||
const source = value?.readiness && typeof value.readiness === "object" ? value.readiness : value;
|
||||
const normalized = emptyXlvaskAutomationReadiness();
|
||||
if (!source || typeof source !== "object") return normalized;
|
||||
const policy = source.policy && typeof source.policy === "object" ? source.policy : {};
|
||||
const worker = source.worker && typeof source.worker === "object"
|
||||
? source.worker
|
||||
: source.workers && typeof source.workers === "object"
|
||||
? source.workers
|
||||
: {};
|
||||
|
||||
normalized.ready = source.ready === true;
|
||||
normalized.effective_stage = String(
|
||||
source.effective_stage || policy.effective_stage || policy.stage || "off"
|
||||
).toLowerCase();
|
||||
normalized.policy_version = String(source.policy_version || policy.version || "");
|
||||
normalized.model = String(source.model || source.model_identity || policy.model || "");
|
||||
normalized.worker_healthy = source.worker_healthy === true || worker.healthy === true;
|
||||
normalized.blocked_reasons = stringList(source.blocked_reasons);
|
||||
normalized.budgets.attach_order = normalizeBudget(source.budgets?.attach_order);
|
||||
normalized.budgets.create_order = normalizeBudget(source.budgets?.create_order);
|
||||
normalized.review_progress.attach_order = normalizeProgress(source.review_progress?.attach_order, 200);
|
||||
normalized.review_progress.create_order = normalizeProgress(source.review_progress?.create_order, 50);
|
||||
normalized.eligible_counts.attach_order = nonNegativeInteger(source.eligible_counts?.attach_order);
|
||||
normalized.eligible_counts.create_order = nonNegativeInteger(source.eligible_counts?.create_order);
|
||||
normalized.eligible_counts.total = nonNegativeInteger(source.eligible_counts?.total)
|
||||
|| normalized.eligible_counts.attach_order + normalized.eligible_counts.create_order;
|
||||
normalized.calibrations = source.calibrations && typeof source.calibrations === "object"
|
||||
? source.calibrations
|
||||
: {};
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const isXlvaskReviewEligible = (row) => {
|
||||
return row?.automation?.review_eligible === true;
|
||||
};
|
||||
|
||||
export const isXlvaskAutopilotRunActive = (run) => {
|
||||
return ["queued", "pending", "running", "processing", "retry_wait"]
|
||||
.includes(String(run?.status || "").toLowerCase());
|
||||
};
|
||||
|
||||
export const xlvaskAutopilotRunProgress = (run) => {
|
||||
const total = nonNegativeInteger(run?.total);
|
||||
const processed = Math.min(total, nonNegativeInteger(run?.processed));
|
||||
return total > 0 ? Math.round((processed / total) * 100) : 0;
|
||||
};
|
||||
|
||||
export const getXlvaskAutomation = (row) => row?.automation && typeof row.automation === "object"
|
||||
? row.automation
|
||||
: {};
|
||||
|
||||
export const getXlvaskImportState = (row) => {
|
||||
const value = String(row?.import_state || "").toLowerCase();
|
||||
return XLVASK_IMPORT_STATES.includes(value) ? value : "unchanged";
|
||||
};
|
||||
|
||||
export const getXlvaskResolutionState = (row) => {
|
||||
const value = String(row?.resolution_state || "").toLowerCase();
|
||||
if (XLVASK_RESOLUTION_STATES.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const status = String(getXlvaskAutomation(row).status || "").toLowerCase();
|
||||
if (status === "failed") return "failed";
|
||||
if (status === "denied") return "needs_review";
|
||||
if (status === "auto_accepted") {
|
||||
return getXlvaskAutomation(row).action === "create_order" ? "auto_created" : "auto_linked";
|
||||
}
|
||||
if (status === "accepted" || row?.linked_order_id || row?.order_id) return "already_linked";
|
||||
return "needs_review";
|
||||
};
|
||||
|
||||
export const getXlvaskCertainty = (row) => {
|
||||
const value = String(row?.certainty || "").toLowerCase();
|
||||
return XLVASK_CERTAINTY_STATES.includes(value) ? value : "none";
|
||||
};
|
||||
|
||||
export const getXlvaskPlannedAction = (row) => {
|
||||
const value = String(row?.planned_action || getXlvaskAutomation(row).action || "none").toLowerCase();
|
||||
return XLVASK_PLANNED_ACTIONS.includes(value) ? value : "none";
|
||||
};
|
||||
|
||||
export const getXlvaskCalibratedProbability = (row) => {
|
||||
const automation = getXlvaskAutomation(row);
|
||||
const value = Number(automation.calibrated_probability ?? 0);
|
||||
if (!Number.isFinite(value) || value <= 0) return null;
|
||||
return Math.min(1, value);
|
||||
};
|
||||
|
||||
export const getXlvaskAutomationList = (row, key) => {
|
||||
const value = getXlvaskAutomation(row)[key];
|
||||
return Array.isArray(value) ? value.filter(Boolean) : [];
|
||||
};
|
||||
|
||||
export const xlvaskStateTagClass = (state) => {
|
||||
if (["already_linked", "auto_linked", "auto_created", "certain"].includes(state)) return "is-success";
|
||||
if (["invalid", "blocked", "failed"].includes(state)) return "is-danger";
|
||||
if (["new", "updated", "needs_review", "uncertain"].includes(state)) return "is-warning";
|
||||
if (state === "ignored") return "is-light";
|
||||
return "is-info";
|
||||
};
|
||||
|
||||
export const xlvaskEvidenceText = (entry) => {
|
||||
if (typeof entry === "string") return entry;
|
||||
return String(entry?.label ?? entry?.message ?? entry?.reason ?? entry?.code ?? "");
|
||||
};
|
||||
|
||||
export const xlvaskCandidateOrderId = (candidate) => candidate?.order_id ?? candidate?.id ?? null;
|
||||
|
||||
export const xlvaskCandidateLabel = (candidate) => {
|
||||
const orderId = xlvaskCandidateOrderId(candidate);
|
||||
const label = candidate?.label || candidate?.reason || candidate?.customer_name || "";
|
||||
return [orderId ? `#${orderId}` : "", label].filter(Boolean).join(" · ");
|
||||
};
|
||||
|
||||
@@ -5,15 +5,6 @@ export const isUsageOrderAttachedToOrder = (object) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const automation = object?.automation;
|
||||
const automationStatus = automation?.status;
|
||||
if (
|
||||
["auto_accepted", "accepted"].includes(automationStatus) &&
|
||||
["attach_order", "create_order"].includes(automation?.action)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const duplicates = Array.isArray(object?.duplicates) ? object.duplicates : [];
|
||||
return duplicates.some((duplicate) => duplicate?.wash_id === object?.wash_id);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+89
-741
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
<script>
|
||||
import {authenticatedRequest} from "@/components/session/authenticatedRequest.vue";
|
||||
|
||||
/**
|
||||
* The MiniMax -> Config object
|
||||
*/
|
||||
export const Config = {
|
||||
get: async ( variable ) => {
|
||||
return authenticatedRequest(
|
||||
"/minimax/config?variable=" + variable,
|
||||
"GET")
|
||||
},
|
||||
get_all: async () => {
|
||||
return authenticatedRequest(
|
||||
"/minimax/config",
|
||||
"GET")
|
||||
},
|
||||
set: async ( variable, value ) => {
|
||||
return authenticatedRequest(
|
||||
"/minimax/config",
|
||||
"POST",
|
||||
{
|
||||
variable: variable,
|
||||
value: value
|
||||
})
|
||||
},
|
||||
keys: {
|
||||
api_key: {
|
||||
get: async () => {
|
||||
return Config.get("api_key");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("api_key", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
get: async () => {
|
||||
return Config.get("enabled");
|
||||
},
|
||||
set: async (enabled) => {
|
||||
return Config.set("enabled", enabled);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script>
|
||||
import { Config } from "@/components/session/token/superUser/modules/miniMax/Config.vue";
|
||||
|
||||
/**
|
||||
* The MiniMax object
|
||||
*/
|
||||
export const MiniMax = {
|
||||
meta: {
|
||||
title: "MiniMax M3",
|
||||
icon: "fas fa-robot",
|
||||
description: "MiniMax M3 (Anthropic-messages) integration used by XL Vask autopilot and other AI features.",
|
||||
endpoint: "/modules/MiniMax",
|
||||
config_endpoint: "/configuration/MiniMax",
|
||||
labels: {
|
||||
single: "MiniMax",
|
||||
multiple: "MiniMax"
|
||||
}
|
||||
},
|
||||
config: Config,
|
||||
functions: {}
|
||||
};
|
||||
</script>
|
||||
@@ -65,22 +65,6 @@ export const Config = {
|
||||
return Config.set("automatic_order_creation_enabled", value);
|
||||
},
|
||||
},
|
||||
openai_integration_enabled: {
|
||||
get: async () => {
|
||||
return Config.get("openai_integration_enabled");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("openai_integration_enabled", value);
|
||||
},
|
||||
},
|
||||
minimax_integration_enabled: {
|
||||
get: async () => {
|
||||
return Config.get("minimax_integration_enabled");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("minimax_integration_enabled", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
get: async () => {
|
||||
|
||||
@@ -23,7 +23,6 @@ import { Entra } from "@/components/session/token/superUser/modules/entra/Entra.
|
||||
import { Limble } from "@/components/session/token/superUser/modules/limble/Limble.vue";
|
||||
import { OcrSpace } from "@/components/session/token/superUser/modules/ocrSpace/OcrSpace.vue";
|
||||
import { OpenAI } from "@/components/session/token/superUser/modules/openAI/OpenAI.vue";
|
||||
import { MiniMax } from "@/components/session/token/superUser/modules/miniMax/MiniMax.vue";
|
||||
import { LicensePlateRecognizer } from "@/components/session/token/superUser/modules/licensePlateRecognizer/LicensePlateRecognizer.vue";
|
||||
import { VirkData } from "@/components/session/token/superUser/modules/virkdata/VirkData.vue";
|
||||
import { Shelly } from "@/components/session/token/superUser/modules/shelly/Shelly.vue";
|
||||
@@ -106,9 +105,6 @@ export const SuperUserObject = {
|
||||
get openai() {
|
||||
return OpenAI;
|
||||
},
|
||||
get minimax() {
|
||||
return MiniMax;
|
||||
},
|
||||
get licenseplaterecognizer() {
|
||||
return LicensePlateRecognizer;
|
||||
},
|
||||
|
||||
@@ -2895,6 +2895,7 @@
|
||||
"api_settings_desc": "@:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.forbindelsesindstillingerne'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'words.generated.automatisk'} @:{'words.generated.tilknytning'} @:{'words.generated.og'} @:{'words.generated.oprettelse'} @:{'words.generated.af'} @:{'words.generated.ordrer'} @:{'words.generated.fra'} @.capitalize:{'words.generated.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'words.generated.forbindelse'} @:{'words.generated.fejlede'}",
|
||||
"connection_failed_desc": "@.capitalize:{'words.generated.forbindelsen'} @:{'words.generated.til'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.fejlede'}.",
|
||||
"connection_success_desc": "@.capitalize:{'words.generated.forbindelsen'} @:{'words.generated.til'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.var'} @:{'words.generated.succesfuld'}.",
|
||||
@@ -4678,7 +4679,7 @@
|
||||
"workflow": "Arbejdsgang"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Accepter forslag",
|
||||
"attach_order": "Tilknyt ordre",
|
||||
|
||||
@@ -3005,6 +3005,7 @@
|
||||
"api_settings_desc": "@:{'words.generated.api'}-@:{'words.generated.verbindungseinstellungen'} @:{'words.generated.f'}?@:{'words.generated.r'} @:{'words.generated.xlvask'}.",
|
||||
"automation_settings": "Automatisierung",
|
||||
"automation_settings_desc": "@:{'words.generated.automatische'} @:{'words.generated.auftragsverknupfung'} @:{'words.generated.und'} @:{'words.generated.auftragserstellung'} @:{'words.generated.aus'} @:{'words.generated.selvvask'} steuern.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@:{'words.generated.verbindung'} @:{'words.generated.fehlgeschlagen'}",
|
||||
"connection_failed_desc": "@.capitalize:{'words.generated.die'} @:{'words.generated.verbindung'} @:{'words.generated.zur'} @:{'words.generated.xlvask'}-@:{'words.generated.api'} @:{'words.generated.ist'} @:{'words.generated.fehlgeschlagen'}.",
|
||||
"connection_success_desc": "@.capitalize:{'words.generated.die'} @:{'words.generated.verbindung'} @:{'words.generated.zur'} @:{'words.generated.xlvask'}-@:{'words.generated.api'} @:{'words.generated.war'} @:{'words.generated.erfolgreich'}.",
|
||||
@@ -4788,7 +4789,7 @@
|
||||
"workflow": "Arbeitsablauf"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Vorschlag annehmen",
|
||||
"attach_order": "Auftrag verknüpfen",
|
||||
|
||||
@@ -2726,6 +2726,7 @@
|
||||
"api_settings_desc": "@:{'words.generated.xlwash'} @:{'words.replication.article.host_mention'} @:{'words.generated.api'} @:{'words.generated.connection'} @:{'words.generated.settings'}.",
|
||||
"automation_settings": "Automation",
|
||||
"automation_settings_desc": "@:{'words.generated.control'} @:{'words.generated.automatic'} @:{'words.generated.order'} @:{'words.generated.attachment'} @:{'words.generated.and'} @:{'words.generated.creation'} @:{'words.generated.from'} @:{'words.generated.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'words.generated.connection'} @:{'words.generated.failed'}",
|
||||
"connection_failed_desc": "@.capitalize:{'words.replication.article.host_mention'} @:{'words.generated.connection'} @:{'words.generated.to'} @:{'words.replication.article.host_mention'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.failed'}.",
|
||||
"connection_success_desc": "@.capitalize:{'words.replication.article.host_mention'} @:{'words.generated.connection'} @:{'words.generated.to'} @:{'words.replication.article.host_mention'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.was'} @:{'words.generated.successful'}.",
|
||||
@@ -4509,7 +4510,7 @@
|
||||
"workflow": "Workflow"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Accept suggestion",
|
||||
"attach_order": "Attach order",
|
||||
|
||||
+175
-174
@@ -1658,6 +1658,7 @@
|
||||
"api_settings_desc": "@:{'templates.generated.compat.configuration.xlvask.api_settings_desc'}",
|
||||
"automation_settings": "@:{'templates.generated.compat.configuration.xlvask.automation_settings'}",
|
||||
"automation_settings_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_desc'}",
|
||||
"automation_settings_removed_desc": "Automatic attachment, automatic creation, OpenAI and MiniMax integrations have been removed. Operator review is now performed from the Superuser -> Fakturaer -> Periode -> Selvvask view.",
|
||||
"connection_failed": "@:{'templates.generated.compat.configuration.xlvask.connection_failed'}",
|
||||
"connection_failed_desc": "@:{'templates.generated.compat.configuration.xlvask.connection_failed_desc'}",
|
||||
"connection_success": "@:configuration.limble.connection_success",
|
||||
@@ -3895,203 +3896,203 @@
|
||||
"workflow": "@:{'templates.generated.compat.invoicing_period.review_workspace.toolbar.workflow'}"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.accept'}",
|
||||
"attach_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.attach_order'}",
|
||||
"compare": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare'}",
|
||||
"compare_modal_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_modal_title'}",
|
||||
"compare_no_candidates": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_no_candidates'}",
|
||||
"compare_price_match": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_price_match'}",
|
||||
"compare_price_mismatch": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_price_mismatch'}",
|
||||
"compare_usage_price": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_usage_price'}",
|
||||
"compare_candidate_price": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.compare_candidate_price'}",
|
||||
"create_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.create_order'}",
|
||||
"deny": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.deny'}",
|
||||
"ignore": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.ignore'}",
|
||||
"link": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.link'}",
|
||||
"link_prompt_label": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_label'}",
|
||||
"link_prompt_invalid": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_invalid'}",
|
||||
"link_prompt_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_title'}",
|
||||
"none": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.none'}",
|
||||
"recheck": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.recheck'}",
|
||||
"resolve_mapping": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.actions.resolve_mapping'}"
|
||||
"accept": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.accept'}",
|
||||
"attach_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.attach_order'}",
|
||||
"compare": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare'}",
|
||||
"compare_modal_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_modal_title'}",
|
||||
"compare_no_candidates": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_no_candidates'}",
|
||||
"compare_price_match": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_price_match'}",
|
||||
"compare_price_mismatch": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_price_mismatch'}",
|
||||
"compare_usage_price": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_usage_price'}",
|
||||
"compare_candidate_price": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.compare_candidate_price'}",
|
||||
"create_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.create_order'}",
|
||||
"deny": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.deny'}",
|
||||
"ignore": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.ignore'}",
|
||||
"link": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.link'}",
|
||||
"link_prompt_label": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.link_prompt_label'}",
|
||||
"link_prompt_invalid": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.link_prompt_invalid'}",
|
||||
"link_prompt_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.link_prompt_title'}",
|
||||
"none": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.none'}",
|
||||
"recheck": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.recheck'}",
|
||||
"resolve_mapping": "@:{'templates.generated.compat.invoicing_period.xlvask_review.actions.resolve_mapping'}"
|
||||
},
|
||||
"adjudication": {
|
||||
"confirm_correct": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_correct'}",
|
||||
"confirm_cross_hall": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_cross_hall'}",
|
||||
"confirm_duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_duplicate'}",
|
||||
"confirm_incorrect": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_incorrect'}",
|
||||
"confirm_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_title'}",
|
||||
"confirm_unaudited": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_unaudited'}",
|
||||
"description": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.description'}",
|
||||
"halted": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.halted'}",
|
||||
"confirm_correct": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_correct'}",
|
||||
"confirm_cross_hall": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_cross_hall'}",
|
||||
"confirm_duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_duplicate'}",
|
||||
"confirm_incorrect": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_incorrect'}",
|
||||
"confirm_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_title'}",
|
||||
"confirm_unaudited": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.confirm_unaudited'}",
|
||||
"description": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.description'}",
|
||||
"halted": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.halted'}",
|
||||
"outcomes": {
|
||||
"correct": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.correct'}",
|
||||
"cross_hall": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.cross_hall'}",
|
||||
"duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.duplicate'}",
|
||||
"incorrect": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.incorrect'}",
|
||||
"unaudited": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.unaudited'}"
|
||||
"correct": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.outcomes.correct'}",
|
||||
"cross_hall": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.outcomes.cross_hall'}",
|
||||
"duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.outcomes.duplicate'}",
|
||||
"incorrect": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.outcomes.incorrect'}",
|
||||
"unaudited": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.outcomes.unaudited'}"
|
||||
},
|
||||
"saved": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.saved'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.adjudication.title'}"
|
||||
"saved": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.saved'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.adjudication.title'}"
|
||||
},
|
||||
"audit": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.audit'}",
|
||||
"bulk_selected": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.bulk_selected'}",
|
||||
"bulk_eligibility": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.bulk_eligibility'}",
|
||||
"calibrated_probability": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.calibrated_probability'}",
|
||||
"candidates": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.candidates'}",
|
||||
"contradictions": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.contradictions'}",
|
||||
"audit": "@:{'templates.generated.compat.invoicing_period.xlvask_review.audit'}",
|
||||
"bulk_selected": "@:{'templates.generated.compat.invoicing_period.xlvask_review.bulk_selected'}",
|
||||
"bulk_eligibility": "@:{'templates.generated.compat.invoicing_period.xlvask_review.bulk_eligibility'}",
|
||||
"calibrated_probability": "@:{'templates.generated.compat.invoicing_period.xlvask_review.calibrated_probability'}",
|
||||
"candidates": "@:{'templates.generated.compat.invoicing_period.xlvask_review.candidates'}",
|
||||
"contradictions": "@:{'templates.generated.compat.invoicing_period.xlvask_review.contradictions'}",
|
||||
"controls": {
|
||||
"active_run_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.active_run_error'}",
|
||||
"analyze": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.analyze'}",
|
||||
"budget": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.budget'}",
|
||||
"execute": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.execute'}",
|
||||
"execute_description": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.execute_description'}",
|
||||
"execute_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.execute_phrase'}",
|
||||
"execute_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.execute_title'}",
|
||||
"halt": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.halt'}",
|
||||
"halt_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.halt_title'}",
|
||||
"load_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.load_error'}",
|
||||
"no_access": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.no_access'}",
|
||||
"not_ready": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.not_ready'}",
|
||||
"policy_advance": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_advance'}",
|
||||
"policy_apply": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_apply'}",
|
||||
"policy_description": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_description'}",
|
||||
"policy_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_phrase'}",
|
||||
"policy_reason": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_reason'}",
|
||||
"policy_reason_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_reason_title'}",
|
||||
"policy_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.policy_title'}",
|
||||
"readiness_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.readiness_error'}",
|
||||
"ready": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.ready'}",
|
||||
"stage": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.stage'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.title'}",
|
||||
"worker_healthy": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.worker_healthy'}",
|
||||
"worker_unhealthy": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.controls.worker_unhealthy'}"
|
||||
"active_run_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.active_run_error'}",
|
||||
"analyze": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.analyze'}",
|
||||
"budget": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.budget'}",
|
||||
"execute": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.execute'}",
|
||||
"execute_description": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.execute_description'}",
|
||||
"execute_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.execute_phrase'}",
|
||||
"execute_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.execute_title'}",
|
||||
"halt": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.halt'}",
|
||||
"halt_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.halt_title'}",
|
||||
"load_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.load_error'}",
|
||||
"no_access": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.no_access'}",
|
||||
"not_ready": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.not_ready'}",
|
||||
"policy_advance": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_advance'}",
|
||||
"policy_apply": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_apply'}",
|
||||
"policy_description": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_description'}",
|
||||
"policy_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_phrase'}",
|
||||
"policy_reason": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_reason'}",
|
||||
"policy_reason_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_reason_title'}",
|
||||
"policy_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.policy_title'}",
|
||||
"readiness_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.readiness_error'}",
|
||||
"ready": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.ready'}",
|
||||
"stage": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.stage'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.title'}",
|
||||
"worker_healthy": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.worker_healthy'}",
|
||||
"worker_unhealthy": "@:{'templates.generated.compat.invoicing_period.xlvask_review.controls.worker_unhealthy'}"
|
||||
},
|
||||
"clear_selection": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.clear_selection'}",
|
||||
"evidence": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.evidence'}",
|
||||
"clear_selection": "@:{'templates.generated.compat.invoicing_period.xlvask_review.clear_selection'}",
|
||||
"evidence": "@:{'templates.generated.compat.invoicing_period.xlvask_review.evidence'}",
|
||||
"filters": {
|
||||
"all": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.all'}",
|
||||
"certainty": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.certainty'}",
|
||||
"clear": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.clear'}",
|
||||
"import_state": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.import_state'}",
|
||||
"planned_action": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.planned_action'}",
|
||||
"resolution_state": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.resolution_state'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.title'}",
|
||||
"unattached_only": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.filters.unattached_only'}"
|
||||
"all": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.all'}",
|
||||
"certainty": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.certainty'}",
|
||||
"clear": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.clear'}",
|
||||
"import_state": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.import_state'}",
|
||||
"planned_action": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.planned_action'}",
|
||||
"resolution_state": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.resolution_state'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.title'}",
|
||||
"unattached_only": "@:{'templates.generated.compat.invoicing_period.xlvask_review.filters.unattached_only'}"
|
||||
},
|
||||
"hide_match": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.hide_match'}",
|
||||
"model": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.model'}",
|
||||
"no_safe_match": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.no_safe_match'}",
|
||||
"planned_action": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.planned_action'}",
|
||||
"policy_version": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.policy_version'}",
|
||||
"hide_match": "@:{'templates.generated.compat.invoicing_period.xlvask_review.hide_match'}",
|
||||
"model": "@:{'templates.generated.compat.invoicing_period.xlvask_review.model'}",
|
||||
"no_safe_match": "@:{'templates.generated.compat.invoicing_period.xlvask_review.no_safe_match'}",
|
||||
"planned_action": "@:{'templates.generated.compat.invoicing_period.xlvask_review.planned_action'}",
|
||||
"policy_version": "@:{'templates.generated.compat.invoicing_period.xlvask_review.policy_version'}",
|
||||
"preview": {
|
||||
"after": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.after'}",
|
||||
"applied": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.applied'}",
|
||||
"apply": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.apply'}",
|
||||
"before": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.before'}",
|
||||
"confirmation_label": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.confirmation_label'}",
|
||||
"confirmation_mismatch": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.confirmation_mismatch'}",
|
||||
"confirmation_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.confirmation_phrase'}",
|
||||
"error_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.error_title'}",
|
||||
"reason_label": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.reason_label'}",
|
||||
"reason_required": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.reason_required'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.preview.title'}"
|
||||
"after": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.after'}",
|
||||
"applied": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.applied'}",
|
||||
"apply": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.apply'}",
|
||||
"before": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.before'}",
|
||||
"confirmation_label": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.confirmation_label'}",
|
||||
"confirmation_mismatch": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.confirmation_mismatch'}",
|
||||
"confirmation_phrase": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.confirmation_phrase'}",
|
||||
"error_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.error_title'}",
|
||||
"reason_label": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.reason_label'}",
|
||||
"reason_required": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.reason_required'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.preview.title'}"
|
||||
},
|
||||
"resume_status": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.resume_status'}",
|
||||
"run": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run'}",
|
||||
"run_id": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_id'}",
|
||||
"resume_status": "@:{'templates.generated.compat.invoicing_period.xlvask_review.resume_status'}",
|
||||
"run": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run'}",
|
||||
"run_id": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_id'}",
|
||||
"run_phases": {
|
||||
"circuit_breaker": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.circuit_breaker'}",
|
||||
"completed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.completed'}",
|
||||
"completed_with_warnings": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.completed_with_warnings'}",
|
||||
"evaluating": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.evaluating'}",
|
||||
"executing": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.executing'}",
|
||||
"failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.failed'}",
|
||||
"importing": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.importing'}",
|
||||
"pending": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.pending'}",
|
||||
"processing": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.processing'}",
|
||||
"queued": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.queued'}",
|
||||
"reconciling": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.reconciling'}",
|
||||
"retry_wait": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.retry_wait'}",
|
||||
"running": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_phases.running'}"
|
||||
"circuit_breaker": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.circuit_breaker'}",
|
||||
"completed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.completed'}",
|
||||
"completed_with_warnings": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.completed_with_warnings'}",
|
||||
"evaluating": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.evaluating'}",
|
||||
"executing": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.executing'}",
|
||||
"failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.failed'}",
|
||||
"importing": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.importing'}",
|
||||
"pending": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.pending'}",
|
||||
"processing": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.processing'}",
|
||||
"queued": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.queued'}",
|
||||
"reconciling": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.reconciling'}",
|
||||
"retry_wait": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.retry_wait'}",
|
||||
"running": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_phases.running'}"
|
||||
},
|
||||
"run_progress": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_progress'}",
|
||||
"run_start_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_start_error'}",
|
||||
"run_status_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.run_status_error'}",
|
||||
"select_record": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.select_record'}",
|
||||
"show_match": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.show_match'}",
|
||||
"source_revision": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.source_revision'}",
|
||||
"run_progress": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_progress'}",
|
||||
"run_start_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_start_error'}",
|
||||
"run_status_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.run_status_error'}",
|
||||
"select_record": "@:{'templates.generated.compat.invoicing_period.xlvask_review.select_record'}",
|
||||
"show_match": "@:{'templates.generated.compat.invoicing_period.xlvask_review.show_match'}",
|
||||
"source_revision": "@:{'templates.generated.compat.invoicing_period.xlvask_review.source_revision'}",
|
||||
"states": {
|
||||
"accepted_attach": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.accepted_attach'}",
|
||||
"accepted_create": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.accepted_create'}",
|
||||
"already_linked": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.already_linked'}",
|
||||
"auto_accepted_attach": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.auto_accepted_attach'}",
|
||||
"auto_accepted_create": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.auto_accepted_create'}",
|
||||
"auto_created": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.auto_created'}",
|
||||
"auto_linked": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.auto_linked'}",
|
||||
"blocked": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.blocked'}",
|
||||
"certain": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.certain'}",
|
||||
"denied": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.denied'}",
|
||||
"failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.failed'}",
|
||||
"ignored": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.ignored'}",
|
||||
"invalid": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.invalid'}",
|
||||
"needs_review": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.needs_review'}",
|
||||
"new": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.new'}",
|
||||
"none": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.none'}",
|
||||
"suggested_attach_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.suggested_attach_order'}",
|
||||
"suggested_create_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.suggested_create_order'}",
|
||||
"uncertain": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.uncertain'}",
|
||||
"unchanged": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.unchanged'}",
|
||||
"updated": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.states.updated'}"
|
||||
"accepted_attach": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.accepted_attach'}",
|
||||
"accepted_create": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.accepted_create'}",
|
||||
"already_linked": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.already_linked'}",
|
||||
"auto_accepted_attach": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.auto_accepted_attach'}",
|
||||
"auto_accepted_create": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.auto_accepted_create'}",
|
||||
"auto_created": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.auto_created'}",
|
||||
"auto_linked": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.auto_linked'}",
|
||||
"blocked": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.blocked'}",
|
||||
"certain": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.certain'}",
|
||||
"denied": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.denied'}",
|
||||
"failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.failed'}",
|
||||
"ignored": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.ignored'}",
|
||||
"invalid": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.invalid'}",
|
||||
"needs_review": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.needs_review'}",
|
||||
"new": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.new'}",
|
||||
"none": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.none'}",
|
||||
"suggested_attach_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.suggested_attach_order'}",
|
||||
"suggested_create_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.suggested_create_order'}",
|
||||
"uncertain": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.uncertain'}",
|
||||
"unchanged": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.unchanged'}",
|
||||
"updated": "@:{'templates.generated.compat.invoicing_period.xlvask_review.states.updated'}"
|
||||
},
|
||||
"summary_error": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.summary_error'}",
|
||||
"summary_title": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.summary_title'}",
|
||||
"summary_error": "@:{'templates.generated.compat.invoicing_period.xlvask_review.summary_error'}",
|
||||
"summary_title": "@:{'templates.generated.compat.invoicing_period.xlvask_review.summary_title'}",
|
||||
"labels": {
|
||||
"attached_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.attached_to_order'}",
|
||||
"not_attached_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.not_attached_to_order'}",
|
||||
"compare_select_duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.compare_select_duplicate'}",
|
||||
"compare_price_mismatch_inline": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.compare_price_mismatch_inline'}",
|
||||
"wash_id": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.wash_id'}",
|
||||
"wash_already_withdrawn": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.wash_already_withdrawn'}",
|
||||
"go_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.go_to_order'}",
|
||||
"unknown_product": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.unknown_product'}",
|
||||
"unknown_product_with_id": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.unknown_product_with_id'}",
|
||||
"ok": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.labels.ok'}"
|
||||
"attached_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.attached_to_order'}",
|
||||
"not_attached_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.not_attached_to_order'}",
|
||||
"compare_select_duplicate": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.compare_select_duplicate'}",
|
||||
"compare_price_mismatch_inline": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.compare_price_mismatch_inline'}",
|
||||
"wash_id": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.wash_id'}",
|
||||
"wash_already_withdrawn": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.wash_already_withdrawn'}",
|
||||
"go_to_order": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.go_to_order'}",
|
||||
"unknown_product": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.unknown_product'}",
|
||||
"unknown_product_with_id": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.unknown_product_with_id'}",
|
||||
"ok": "@:{'templates.generated.compat.invoicing_period.xlvask_review.labels.ok'}"
|
||||
},
|
||||
"time": {
|
||||
"just_now": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.just_now'}",
|
||||
"ago_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.ago_one'}",
|
||||
"ago_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.ago_other'}",
|
||||
"just_now": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.just_now'}",
|
||||
"ago_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.ago_one'}",
|
||||
"ago_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.ago_other'}",
|
||||
"units": {
|
||||
"year_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.year_one'}",
|
||||
"year_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.year_other'}",
|
||||
"month_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.month_one'}",
|
||||
"month_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.month_other'}",
|
||||
"week_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.week_one'}",
|
||||
"week_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.week_other'}",
|
||||
"day_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.day_one'}",
|
||||
"day_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.day_other'}",
|
||||
"hour_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.hour_one'}",
|
||||
"hour_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.hour_other'}",
|
||||
"minute_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.minute_one'}",
|
||||
"minute_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.minute_other'}",
|
||||
"second_one": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.second_one'}",
|
||||
"second_other": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.time.units.second_other'}"
|
||||
"year_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.year_one'}",
|
||||
"year_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.year_other'}",
|
||||
"month_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.month_one'}",
|
||||
"month_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.month_other'}",
|
||||
"week_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.week_one'}",
|
||||
"week_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.week_other'}",
|
||||
"day_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.day_one'}",
|
||||
"day_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.day_other'}",
|
||||
"hour_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.hour_one'}",
|
||||
"hour_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.hour_other'}",
|
||||
"minute_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.minute_one'}",
|
||||
"minute_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.minute_other'}",
|
||||
"second_one": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.second_one'}",
|
||||
"second_other": "@:{'templates.generated.compat.invoicing_period.xlvask_review.time.units.second_other'}"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"fetch_usage_log": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.fetch_usage_log'}",
|
||||
"fetch_vehicle_types": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.fetch_vehicle_types'}",
|
||||
"fetch_related_orders": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.fetch_related_orders'}",
|
||||
"fetch_fast_link": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.fetch_fast_link'}",
|
||||
"create_order_unrecognized_items": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.create_order_unrecognized_items'}",
|
||||
"create_order_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.create_order_failed'}",
|
||||
"create_order_item_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.create_order_item_failed'}",
|
||||
"redirect_order_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.redirect_order_failed'}",
|
||||
"load_customers_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.load_customers_failed'}",
|
||||
"load_usage_log_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_autopilot.errors.load_usage_log_failed'}"
|
||||
"fetch_usage_log": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.fetch_usage_log'}",
|
||||
"fetch_vehicle_types": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.fetch_vehicle_types'}",
|
||||
"fetch_related_orders": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.fetch_related_orders'}",
|
||||
"fetch_fast_link": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.fetch_fast_link'}",
|
||||
"create_order_unrecognized_items": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.create_order_unrecognized_items'}",
|
||||
"create_order_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.create_order_failed'}",
|
||||
"create_order_item_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.create_order_item_failed'}",
|
||||
"redirect_order_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.redirect_order_failed'}",
|
||||
"load_customers_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.load_customers_failed'}",
|
||||
"load_usage_log_failed": "@:{'templates.generated.compat.invoicing_period.xlvask_review.errors.load_usage_log_failed'}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3008,6 +3008,7 @@
|
||||
"api_settings_desc": "@:{'words.generated.xlvask'} @:{'words.generated.api'}-@:{'words.generated.tilkoblingsinnstillingene'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'words.generated.automatisk'} @:{'words.generated.ordretilknytning'} @:{'words.generated.og'} @:{'words.generated.ordreoppretting'} @:{'words.generated.fra'} @.capitalize:{'words.generated.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'words.generated.tilkobling'} @:{'words.generated.mislyktes'}",
|
||||
"connection_failed_desc": "@.capitalize:{'words.generated.tilkoblingen'} @:{'words.generated.til'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.mislyktes'}.",
|
||||
"connection_success_desc": "@.capitalize:{'words.generated.tilkoblingen'} @:{'words.generated.til'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.var'} @:{'words.generated.vellykket'}.",
|
||||
@@ -4791,7 +4792,7 @@
|
||||
"workflow": "Arbeidsflyt"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Godta forslag",
|
||||
"attach_order": "Knytt til ordre",
|
||||
|
||||
@@ -3058,6 +3058,7 @@
|
||||
"api_settings_desc": "@.capitalize:{'words.generated.anslutningsinstallningar'} @:{'words.generated.for'} @:{'words.generated.xlvask'} @:{'words.generated.api'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'words.generated.automatisk'} @:{'words.generated.orderkoppling'} @:{'words.generated.och'} @:{'words.generated.orderskapande'} @:{'words.generated.fran'} @.capitalize:{'words.generated.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'words.generated.anslutning'} @:{'words.generated.misslyckades'}",
|
||||
"connection_failed_desc": "@.capitalize:{'words.generated.anslutningen'} @:{'words.generated.till'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.misslyckades'}.",
|
||||
"connection_success_desc": "@.capitalize:{'words.generated.anslutningen'} @:{'words.generated.till'} @:{'words.generated.xlvask'} @:{'words.generated.api'} @:{'words.generated.lyckades'}.",
|
||||
@@ -4841,7 +4842,7 @@
|
||||
"workflow": "Arbetsflöde"
|
||||
}
|
||||
},
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Godkänn förslag",
|
||||
"attach_order": "Koppla order",
|
||||
|
||||
@@ -355,6 +355,7 @@
|
||||
"api_settings_desc": "@:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.forbindelsesindstillingerne'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'terms.glossary.automatisk'} @:{'terms.glossary.tilknytning'} @:{'terms.glossary.og'} @:{'terms.glossary.oprettelse'} @:{'terms.glossary.af'} @:{'terms.glossary.ordrer'} @:{'terms.glossary.fra'} @.capitalize:{'terms.glossary.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'terms.glossary.forbindelse'} @:{'terms.glossary.fejlede'}",
|
||||
"connection_failed_desc": "@.capitalize:{'terms.glossary.forbindelsen'} @:{'terms.glossary.til'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.fejlede'}.",
|
||||
"connection_success_desc": "@.capitalize:{'terms.glossary.forbindelsen'} @:{'terms.glossary.til'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.var'} @:{'terms.glossary.succesfuld'}.",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"compat": { "invoicing_period": { "xlvask_autopilot": {
|
||||
"compat": { "invoicing_period": { "xlvask_review": {
|
||||
"actions": { "accept": "Accepter forslag", "attach_order": "Tilknyt ordre", "compare": "Sammenlign kandidater", "compare_modal_title": "Sammenlign kandidater med vask", "compare_no_candidates": "Ingen kandidater at sammenligne", "compare_price_match": "Pris matcher", "compare_price_mismatch": "Pris matcher ikke", "compare_usage_price": "Vaskens beregnede pris", "compare_candidate_price": "Kandidatpris", "create_order": "Opret ordre", "deny": "Afvis", "ignore": "Ignorer", "link": "Tilknyt ordre-ID", "link_prompt_label": "Indtast ordre-ID", "link_prompt_invalid": "Indtast et gyldigt numerisk ordre-ID", "link_prompt_title": "Tilknyt vask til en eksisterende ordre", "none": "Ingen handling", "recheck": "Kontrollér igen", "resolve_mapping": "Ret tilknytning" },
|
||||
"adjudication": {
|
||||
"confirm_correct": "Bekræft, at den automatiske handling var korrekt.",
|
||||
@@ -355,6 +355,7 @@
|
||||
"api_settings_desc": "@:{'terms.glossary.api'}-@:{'terms.glossary.verbindungseinstellungen'} @:{'terms.glossary.f'}?@:{'terms.glossary.r'} @:{'terms.glossary.xlvask'}.",
|
||||
"automation_settings": "Automatisierung",
|
||||
"automation_settings_desc": "@:{'terms.glossary.automatische'} @:{'terms.glossary.auftragsverknupfung'} @:{'terms.glossary.und'} @:{'terms.glossary.auftragserstellung'} @:{'terms.glossary.aus'} @:{'terms.glossary.selvvask'} steuern.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@:{'terms.glossary.verbindung'} @:{'terms.glossary.fehlgeschlagen'}",
|
||||
"connection_failed_desc": "@.capitalize:{'terms.glossary.die'} @:{'terms.glossary.verbindung'} @:{'terms.glossary.zur'} @:{'terms.glossary.xlvask'}-@:{'terms.glossary.api'} @:{'terms.glossary.ist'} @:{'terms.glossary.fehlgeschlagen'}.",
|
||||
"connection_success_desc": "@.capitalize:{'terms.glossary.die'} @:{'terms.glossary.verbindung'} @:{'terms.glossary.zur'} @:{'terms.glossary.xlvask'}-@:{'terms.glossary.api'} @:{'terms.glossary.war'} @:{'terms.glossary.erfolgreich'}.",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Vorschlag annehmen",
|
||||
"attach_order": "Auftrag verknüpfen",
|
||||
@@ -355,6 +355,7 @@
|
||||
"api_settings_desc": "@:{'terms.glossary.xlwash'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.api'} @:{'terms.glossary.connection'} @:{'terms.glossary.settings'}.",
|
||||
"automation_settings": "Automation",
|
||||
"automation_settings_desc": "@:{'terms.glossary.control'} @:{'terms.glossary.automatic'} @:{'terms.glossary.order'} @:{'terms.glossary.attachment'} @:{'terms.glossary.and'} @:{'terms.glossary.creation'} @:{'terms.glossary.from'} @:{'terms.glossary.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'terms.glossary.connection'} @:{'terms.glossary.failed'}",
|
||||
"connection_failed_desc": "@.capitalize:{'terms.replication.article.host_mention'} @:{'terms.glossary.connection'} @:{'terms.glossary.to'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.failed'}.",
|
||||
"connection_success_desc": "@.capitalize:{'terms.replication.article.host_mention'} @:{'terms.glossary.connection'} @:{'terms.glossary.to'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.was'} @:{'terms.glossary.successful'}.",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"compat": { "invoicing_period": { "xlvask_autopilot": {
|
||||
"compat": { "invoicing_period": { "xlvask_review": {
|
||||
"actions": { "accept": "Accept suggestion", "attach_order": "Attach order", "compare": "Compare candidates", "compare_modal_title": "Compare candidates with wash", "compare_no_candidates": "No candidates to compare", "compare_price_match": "Price matches", "compare_price_mismatch": "Price does not match", "compare_usage_price": "Computed wash price", "compare_candidate_price": "Candidate price", "create_order": "Create order", "deny": "Deny", "ignore": "Ignore", "link": "Link order ID", "link_prompt_label": "Enter order ID", "link_prompt_invalid": "Enter a valid numeric order ID", "link_prompt_title": "Link the wash to an existing order", "none": "No action", "recheck": "Check again", "resolve_mapping": "Fix mapping" },
|
||||
"adjudication": {
|
||||
"confirm_correct": "Confirm that the automatic action was correct.",
|
||||
@@ -403,6 +403,7 @@
|
||||
"api_settings_desc": "@:{'phrases.compat.configuration.xlvask.api_settings_desc'}",
|
||||
"automation_settings": "@:{'phrases.compat.configuration.xlvask.automation_settings'}",
|
||||
"automation_settings_desc": "@:{'phrases.compat.configuration.xlvask.automation_settings_desc'}",
|
||||
"automation_settings_removed_desc": "Automatic attachment, automatic creation, OpenAI and MiniMax integrations have been removed. Operator review is now performed from the Superuser -> Fakturaer -> Periode -> Selvvask view.",
|
||||
"connection_failed": "@:{'phrases.compat.configuration.xlvask.connection_failed'}",
|
||||
"connection_failed_desc": "@:{'phrases.compat.configuration.xlvask.connection_failed_desc'}",
|
||||
"connection_success": "@:configuration.limble.connection_success",
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
{
|
||||
"invoicing_period": {
|
||||
"xlvask_autopilot": {
|
||||
"actions": {
|
||||
"accept": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.accept'}",
|
||||
"attach_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.attach_order'}",
|
||||
"compare": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare'}",
|
||||
"compare_modal_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_modal_title'}",
|
||||
"compare_no_candidates": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_no_candidates'}",
|
||||
"compare_price_match": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_price_match'}",
|
||||
"compare_price_mismatch": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_price_mismatch'}",
|
||||
"compare_usage_price": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_usage_price'}",
|
||||
"compare_candidate_price": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.compare_candidate_price'}",
|
||||
"create_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.create_order'}",
|
||||
"deny": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.deny'}",
|
||||
"ignore": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.ignore'}",
|
||||
"link": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.link'}",
|
||||
"link_prompt_label": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_label'}",
|
||||
"link_prompt_invalid": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_invalid'}",
|
||||
"link_prompt_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.link_prompt_title'}",
|
||||
"none": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.none'}",
|
||||
"recheck": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.recheck'}",
|
||||
"resolve_mapping": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.actions.resolve_mapping'}"
|
||||
},
|
||||
"adjudication": {
|
||||
"confirm_correct": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_correct'}",
|
||||
"confirm_cross_hall": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_cross_hall'}",
|
||||
"confirm_duplicate": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_duplicate'}",
|
||||
"confirm_incorrect": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_incorrect'}",
|
||||
"confirm_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_title'}",
|
||||
"confirm_unaudited": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.confirm_unaudited'}",
|
||||
"description": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.description'}",
|
||||
"halted": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.halted'}",
|
||||
"outcomes": {
|
||||
"correct": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.correct'}",
|
||||
"cross_hall": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.cross_hall'}",
|
||||
"duplicate": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.duplicate'}",
|
||||
"incorrect": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.incorrect'}",
|
||||
"unaudited": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.outcomes.unaudited'}"
|
||||
},
|
||||
"saved": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.saved'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.adjudication.title'}"
|
||||
},
|
||||
"audit": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.audit'}",
|
||||
"bulk_selected": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.bulk_selected'}",
|
||||
"bulk_eligibility": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.bulk_eligibility'}",
|
||||
"calibrated_probability": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.calibrated_probability'}",
|
||||
"candidates": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.candidates'}",
|
||||
"contradictions": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.contradictions'}",
|
||||
"controls": {
|
||||
"active_run_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.active_run_error'}",
|
||||
"analyze": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.analyze'}",
|
||||
"budget": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.budget'}",
|
||||
"execute": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.execute'}",
|
||||
"execute_description": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.execute_description'}",
|
||||
"execute_phrase": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.execute_phrase'}",
|
||||
"execute_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.execute_title'}",
|
||||
"halt": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.halt'}",
|
||||
"halt_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.halt_title'}",
|
||||
"load_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.load_error'}",
|
||||
"no_access": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.no_access'}",
|
||||
"not_ready": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.not_ready'}",
|
||||
"policy_advance": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_advance'}",
|
||||
"policy_apply": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_apply'}",
|
||||
"policy_description": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_description'}",
|
||||
"policy_phrase": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_phrase'}",
|
||||
"policy_reason": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_reason'}",
|
||||
"policy_reason_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_reason_title'}",
|
||||
"policy_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.policy_title'}",
|
||||
"readiness_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.readiness_error'}",
|
||||
"ready": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.ready'}",
|
||||
"stage": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.stage'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.title'}",
|
||||
"worker_healthy": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.worker_healthy'}",
|
||||
"worker_unhealthy": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.controls.worker_unhealthy'}"
|
||||
},
|
||||
"clear_selection": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.clear_selection'}",
|
||||
"evidence": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.evidence'}",
|
||||
"filters": {
|
||||
"all": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.all'}",
|
||||
"certainty": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.certainty'}",
|
||||
"clear": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.clear'}",
|
||||
"import_state": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.import_state'}",
|
||||
"planned_action": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.planned_action'}",
|
||||
"resolution_state": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.resolution_state'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.title'}",
|
||||
"unattached_only": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.filters.unattached_only'}"
|
||||
},
|
||||
"hide_match": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.hide_match'}",
|
||||
"model": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.model'}",
|
||||
"no_safe_match": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.no_safe_match'}",
|
||||
"planned_action": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.planned_action'}",
|
||||
"policy_version": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.policy_version'}",
|
||||
"preview": {
|
||||
"after": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.after'}",
|
||||
"applied": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.applied'}",
|
||||
"apply": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.apply'}",
|
||||
"before": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.before'}",
|
||||
"confirmation_label": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.confirmation_label'}",
|
||||
"confirmation_mismatch": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.confirmation_mismatch'}",
|
||||
"confirmation_phrase": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.confirmation_phrase'}",
|
||||
"error_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.error_title'}",
|
||||
"reason_label": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.reason_label'}",
|
||||
"reason_required": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.reason_required'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.preview.title'}"
|
||||
},
|
||||
"resume_status": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.resume_status'}",
|
||||
"run": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run'}",
|
||||
"run_id": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_id'}",
|
||||
"run_phases": {
|
||||
"circuit_breaker": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.circuit_breaker'}",
|
||||
"completed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.completed'}",
|
||||
"completed_with_warnings": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.completed_with_warnings'}",
|
||||
"evaluating": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.evaluating'}",
|
||||
"executing": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.executing'}",
|
||||
"failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.failed'}",
|
||||
"importing": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.importing'}",
|
||||
"pending": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.pending'}",
|
||||
"processing": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.processing'}",
|
||||
"queued": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.queued'}",
|
||||
"reconciling": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.reconciling'}",
|
||||
"retry_wait": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.retry_wait'}",
|
||||
"running": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_phases.running'}"
|
||||
},
|
||||
"run_progress": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_progress'}",
|
||||
"run_start_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_start_error'}",
|
||||
"run_status_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.run_status_error'}",
|
||||
"select_record": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.select_record'}",
|
||||
"show_match": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.show_match'}",
|
||||
"source_revision": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.source_revision'}",
|
||||
"states": {
|
||||
"accepted_attach": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.accepted_attach'}",
|
||||
"accepted_create": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.accepted_create'}",
|
||||
"already_linked": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.already_linked'}",
|
||||
"auto_accepted_attach": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.auto_accepted_attach'}",
|
||||
"auto_accepted_create": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.auto_accepted_create'}",
|
||||
"auto_created": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.auto_created'}",
|
||||
"auto_linked": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.auto_linked'}",
|
||||
"blocked": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.blocked'}",
|
||||
"certain": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.certain'}",
|
||||
"denied": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.denied'}",
|
||||
"failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.failed'}",
|
||||
"ignored": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.ignored'}",
|
||||
"invalid": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.invalid'}",
|
||||
"needs_review": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.needs_review'}",
|
||||
"new": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.new'}",
|
||||
"none": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.none'}",
|
||||
"suggested_attach_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.suggested_attach_order'}",
|
||||
"suggested_create_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.suggested_create_order'}",
|
||||
"uncertain": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.uncertain'}",
|
||||
"unchanged": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.unchanged'}",
|
||||
"updated": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.states.updated'}"
|
||||
},
|
||||
"summary_error": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.summary_error'}",
|
||||
"summary_title": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.summary_title'}",
|
||||
"labels": {
|
||||
"attached_to_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.attached_to_order'}",
|
||||
"not_attached_to_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.not_attached_to_order'}",
|
||||
"compare_select_duplicate": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.compare_select_duplicate'}",
|
||||
"compare_price_mismatch_inline": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.compare_price_mismatch_inline'}",
|
||||
"wash_id": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.wash_id'}",
|
||||
"wash_already_withdrawn": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.wash_already_withdrawn'}",
|
||||
"go_to_order": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.go_to_order'}",
|
||||
"unknown_product": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.unknown_product'}",
|
||||
"unknown_product_with_id": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.unknown_product_with_id'}",
|
||||
"ok": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.labels.ok'}"
|
||||
},
|
||||
"time": {
|
||||
"just_now": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.just_now'}",
|
||||
"ago_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.ago_one'}",
|
||||
"ago_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.ago_other'}",
|
||||
"units": {
|
||||
"year_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.year_one'}",
|
||||
"year_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.year_other'}",
|
||||
"month_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.month_one'}",
|
||||
"month_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.month_other'}",
|
||||
"week_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.week_one'}",
|
||||
"week_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.week_other'}",
|
||||
"day_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.day_one'}",
|
||||
"day_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.day_other'}",
|
||||
"hour_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.hour_one'}",
|
||||
"hour_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.hour_other'}",
|
||||
"minute_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.minute_one'}",
|
||||
"minute_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.minute_other'}",
|
||||
"second_one": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.second_one'}",
|
||||
"second_other": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.time.units.second_other'}"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"fetch_usage_log": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.fetch_usage_log'}",
|
||||
"fetch_vehicle_types": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.fetch_vehicle_types'}",
|
||||
"fetch_related_orders": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.fetch_related_orders'}",
|
||||
"fetch_fast_link": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.fetch_fast_link'}",
|
||||
"create_order_unrecognized_items": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.create_order_unrecognized_items'}",
|
||||
"create_order_failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.create_order_failed'}",
|
||||
"create_order_item_failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.create_order_item_failed'}",
|
||||
"redirect_order_failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.redirect_order_failed'}",
|
||||
"load_customers_failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.load_customers_failed'}",
|
||||
"load_usage_log_failed": "@:{'phrases.compat.invoicing_period.xlvask_autopilot.errors.load_usage_log_failed'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
{
|
||||
"invoicing_period": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.accept'}",
|
||||
"attach_order": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.attach_order'}",
|
||||
"compare": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare'}",
|
||||
"compare_modal_title": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_modal_title'}",
|
||||
"compare_no_candidates": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_no_candidates'}",
|
||||
"compare_price_match": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_price_match'}",
|
||||
"compare_price_mismatch": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_price_mismatch'}",
|
||||
"compare_usage_price": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_usage_price'}",
|
||||
"compare_candidate_price": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.compare_candidate_price'}",
|
||||
"create_order": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.create_order'}",
|
||||
"deny": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.deny'}",
|
||||
"ignore": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.ignore'}",
|
||||
"link": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.link'}",
|
||||
"link_prompt_label": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.link_prompt_label'}",
|
||||
"link_prompt_invalid": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.link_prompt_invalid'}",
|
||||
"link_prompt_title": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.link_prompt_title'}",
|
||||
"none": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.none'}",
|
||||
"recheck": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.recheck'}",
|
||||
"resolve_mapping": "@:{'phrases.compat.invoicing_period.xlvask_review.actions.resolve_mapping'}"
|
||||
},
|
||||
"adjudication": {
|
||||
"confirm_correct": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_correct'}",
|
||||
"confirm_cross_hall": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_cross_hall'}",
|
||||
"confirm_duplicate": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_duplicate'}",
|
||||
"confirm_incorrect": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_incorrect'}",
|
||||
"confirm_title": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_title'}",
|
||||
"confirm_unaudited": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.confirm_unaudited'}",
|
||||
"description": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.description'}",
|
||||
"halted": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.halted'}",
|
||||
"outcomes": {
|
||||
"correct": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.outcomes.correct'}",
|
||||
"cross_hall": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.outcomes.cross_hall'}",
|
||||
"duplicate": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.outcomes.duplicate'}",
|
||||
"incorrect": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.outcomes.incorrect'}",
|
||||
"unaudited": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.outcomes.unaudited'}"
|
||||
},
|
||||
"saved": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.saved'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_review.adjudication.title'}"
|
||||
},
|
||||
"audit": "@:{'phrases.compat.invoicing_period.xlvask_review.audit'}",
|
||||
"bulk_selected": "@:{'phrases.compat.invoicing_period.xlvask_review.bulk_selected'}",
|
||||
"bulk_eligibility": "@:{'phrases.compat.invoicing_period.xlvask_review.bulk_eligibility'}",
|
||||
"calibrated_probability": "@:{'phrases.compat.invoicing_period.xlvask_review.calibrated_probability'}",
|
||||
"candidates": "@:{'phrases.compat.invoicing_period.xlvask_review.candidates'}",
|
||||
"contradictions": "@:{'phrases.compat.invoicing_period.xlvask_review.contradictions'}",
|
||||
"controls": {
|
||||
"active_run_error": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.active_run_error'}",
|
||||
"analyze": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.analyze'}",
|
||||
"budget": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.budget'}",
|
||||
"execute": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.execute'}",
|
||||
"execute_description": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.execute_description'}",
|
||||
"execute_phrase": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.execute_phrase'}",
|
||||
"execute_title": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.execute_title'}",
|
||||
"halt": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.halt'}",
|
||||
"halt_title": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.halt_title'}",
|
||||
"load_error": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.load_error'}",
|
||||
"no_access": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.no_access'}",
|
||||
"not_ready": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.not_ready'}",
|
||||
"policy_advance": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_advance'}",
|
||||
"policy_apply": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_apply'}",
|
||||
"policy_description": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_description'}",
|
||||
"policy_phrase": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_phrase'}",
|
||||
"policy_reason": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_reason'}",
|
||||
"policy_reason_title": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_reason_title'}",
|
||||
"policy_title": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.policy_title'}",
|
||||
"readiness_error": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.readiness_error'}",
|
||||
"ready": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.ready'}",
|
||||
"stage": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.stage'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.title'}",
|
||||
"worker_healthy": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.worker_healthy'}",
|
||||
"worker_unhealthy": "@:{'phrases.compat.invoicing_period.xlvask_review.controls.worker_unhealthy'}"
|
||||
},
|
||||
"clear_selection": "@:{'phrases.compat.invoicing_period.xlvask_review.clear_selection'}",
|
||||
"evidence": "@:{'phrases.compat.invoicing_period.xlvask_review.evidence'}",
|
||||
"filters": {
|
||||
"all": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.all'}",
|
||||
"certainty": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.certainty'}",
|
||||
"clear": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.clear'}",
|
||||
"import_state": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.import_state'}",
|
||||
"planned_action": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.planned_action'}",
|
||||
"resolution_state": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.resolution_state'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.title'}",
|
||||
"unattached_only": "@:{'phrases.compat.invoicing_period.xlvask_review.filters.unattached_only'}"
|
||||
},
|
||||
"hide_match": "@:{'phrases.compat.invoicing_period.xlvask_review.hide_match'}",
|
||||
"model": "@:{'phrases.compat.invoicing_period.xlvask_review.model'}",
|
||||
"no_safe_match": "@:{'phrases.compat.invoicing_period.xlvask_review.no_safe_match'}",
|
||||
"planned_action": "@:{'phrases.compat.invoicing_period.xlvask_review.planned_action'}",
|
||||
"policy_version": "@:{'phrases.compat.invoicing_period.xlvask_review.policy_version'}",
|
||||
"preview": {
|
||||
"after": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.after'}",
|
||||
"applied": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.applied'}",
|
||||
"apply": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.apply'}",
|
||||
"before": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.before'}",
|
||||
"confirmation_label": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.confirmation_label'}",
|
||||
"confirmation_mismatch": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.confirmation_mismatch'}",
|
||||
"confirmation_phrase": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.confirmation_phrase'}",
|
||||
"error_title": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.error_title'}",
|
||||
"reason_label": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.reason_label'}",
|
||||
"reason_required": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.reason_required'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.xlvask_review.preview.title'}"
|
||||
},
|
||||
"resume_status": "@:{'phrases.compat.invoicing_period.xlvask_review.resume_status'}",
|
||||
"run": "@:{'phrases.compat.invoicing_period.xlvask_review.run'}",
|
||||
"run_id": "@:{'phrases.compat.invoicing_period.xlvask_review.run_id'}",
|
||||
"run_phases": {
|
||||
"circuit_breaker": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.circuit_breaker'}",
|
||||
"completed": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.completed'}",
|
||||
"completed_with_warnings": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.completed_with_warnings'}",
|
||||
"evaluating": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.evaluating'}",
|
||||
"executing": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.executing'}",
|
||||
"failed": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.failed'}",
|
||||
"importing": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.importing'}",
|
||||
"pending": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.pending'}",
|
||||
"processing": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.processing'}",
|
||||
"queued": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.queued'}",
|
||||
"reconciling": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.reconciling'}",
|
||||
"retry_wait": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.retry_wait'}",
|
||||
"running": "@:{'phrases.compat.invoicing_period.xlvask_review.run_phases.running'}"
|
||||
},
|
||||
"run_progress": "@:{'phrases.compat.invoicing_period.xlvask_review.run_progress'}",
|
||||
"run_start_error": "@:{'phrases.compat.invoicing_period.xlvask_review.run_start_error'}",
|
||||
"run_status_error": "@:{'phrases.compat.invoicing_period.xlvask_review.run_status_error'}",
|
||||
"select_record": "@:{'phrases.compat.invoicing_period.xlvask_review.select_record'}",
|
||||
"show_match": "@:{'phrases.compat.invoicing_period.xlvask_review.show_match'}",
|
||||
"source_revision": "@:{'phrases.compat.invoicing_period.xlvask_review.source_revision'}",
|
||||
"states": {
|
||||
"accepted_attach": "@:{'phrases.compat.invoicing_period.xlvask_review.states.accepted_attach'}",
|
||||
"accepted_create": "@:{'phrases.compat.invoicing_period.xlvask_review.states.accepted_create'}",
|
||||
"already_linked": "@:{'phrases.compat.invoicing_period.xlvask_review.states.already_linked'}",
|
||||
"auto_accepted_attach": "@:{'phrases.compat.invoicing_period.xlvask_review.states.auto_accepted_attach'}",
|
||||
"auto_accepted_create": "@:{'phrases.compat.invoicing_period.xlvask_review.states.auto_accepted_create'}",
|
||||
"auto_created": "@:{'phrases.compat.invoicing_period.xlvask_review.states.auto_created'}",
|
||||
"auto_linked": "@:{'phrases.compat.invoicing_period.xlvask_review.states.auto_linked'}",
|
||||
"blocked": "@:{'phrases.compat.invoicing_period.xlvask_review.states.blocked'}",
|
||||
"certain": "@:{'phrases.compat.invoicing_period.xlvask_review.states.certain'}",
|
||||
"denied": "@:{'phrases.compat.invoicing_period.xlvask_review.states.denied'}",
|
||||
"failed": "@:{'phrases.compat.invoicing_period.xlvask_review.states.failed'}",
|
||||
"ignored": "@:{'phrases.compat.invoicing_period.xlvask_review.states.ignored'}",
|
||||
"invalid": "@:{'phrases.compat.invoicing_period.xlvask_review.states.invalid'}",
|
||||
"needs_review": "@:{'phrases.compat.invoicing_period.xlvask_review.states.needs_review'}",
|
||||
"new": "@:{'phrases.compat.invoicing_period.xlvask_review.states.new'}",
|
||||
"none": "@:{'phrases.compat.invoicing_period.xlvask_review.states.none'}",
|
||||
"suggested_attach_order": "@:{'phrases.compat.invoicing_period.xlvask_review.states.suggested_attach_order'}",
|
||||
"suggested_create_order": "@:{'phrases.compat.invoicing_period.xlvask_review.states.suggested_create_order'}",
|
||||
"uncertain": "@:{'phrases.compat.invoicing_period.xlvask_review.states.uncertain'}",
|
||||
"unchanged": "@:{'phrases.compat.invoicing_period.xlvask_review.states.unchanged'}",
|
||||
"updated": "@:{'phrases.compat.invoicing_period.xlvask_review.states.updated'}"
|
||||
},
|
||||
"summary_error": "@:{'phrases.compat.invoicing_period.xlvask_review.summary_error'}",
|
||||
"summary_title": "@:{'phrases.compat.invoicing_period.xlvask_review.summary_title'}",
|
||||
"labels": {
|
||||
"attached_to_order": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.attached_to_order'}",
|
||||
"not_attached_to_order": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.not_attached_to_order'}",
|
||||
"compare_select_duplicate": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.compare_select_duplicate'}",
|
||||
"compare_price_mismatch_inline": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.compare_price_mismatch_inline'}",
|
||||
"wash_id": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.wash_id'}",
|
||||
"wash_already_withdrawn": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.wash_already_withdrawn'}",
|
||||
"go_to_order": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.go_to_order'}",
|
||||
"unknown_product": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.unknown_product'}",
|
||||
"unknown_product_with_id": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.unknown_product_with_id'}",
|
||||
"ok": "@:{'phrases.compat.invoicing_period.xlvask_review.labels.ok'}"
|
||||
},
|
||||
"time": {
|
||||
"just_now": "@:{'phrases.compat.invoicing_period.xlvask_review.time.just_now'}",
|
||||
"ago_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.ago_one'}",
|
||||
"ago_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.ago_other'}",
|
||||
"units": {
|
||||
"year_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.year_one'}",
|
||||
"year_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.year_other'}",
|
||||
"month_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.month_one'}",
|
||||
"month_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.month_other'}",
|
||||
"week_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.week_one'}",
|
||||
"week_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.week_other'}",
|
||||
"day_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.day_one'}",
|
||||
"day_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.day_other'}",
|
||||
"hour_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.hour_one'}",
|
||||
"hour_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.hour_other'}",
|
||||
"minute_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.minute_one'}",
|
||||
"minute_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.minute_other'}",
|
||||
"second_one": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.second_one'}",
|
||||
"second_other": "@:{'phrases.compat.invoicing_period.xlvask_review.time.units.second_other'}"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"fetch_usage_log": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.fetch_usage_log'}",
|
||||
"fetch_vehicle_types": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.fetch_vehicle_types'}",
|
||||
"fetch_related_orders": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.fetch_related_orders'}",
|
||||
"fetch_fast_link": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.fetch_fast_link'}",
|
||||
"create_order_unrecognized_items": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.create_order_unrecognized_items'}",
|
||||
"create_order_failed": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.create_order_failed'}",
|
||||
"create_order_item_failed": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.create_order_item_failed'}",
|
||||
"redirect_order_failed": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.redirect_order_failed'}",
|
||||
"load_customers_failed": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.load_customers_failed'}",
|
||||
"load_usage_log_failed": "@:{'phrases.compat.invoicing_period.xlvask_review.errors.load_usage_log_failed'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,6 +355,7 @@
|
||||
"api_settings_desc": "@:{'terms.glossary.xlvask'} @:{'terms.glossary.api'}-@:{'terms.glossary.tilkoblingsinnstillingene'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'terms.glossary.automatisk'} @:{'terms.glossary.ordretilknytning'} @:{'terms.glossary.og'} @:{'terms.glossary.ordreoppretting'} @:{'terms.glossary.fra'} @.capitalize:{'terms.glossary.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'terms.glossary.tilkobling'} @:{'terms.glossary.mislyktes'}",
|
||||
"connection_failed_desc": "@.capitalize:{'terms.glossary.tilkoblingen'} @:{'terms.glossary.til'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.mislyktes'}.",
|
||||
"connection_success_desc": "@.capitalize:{'terms.glossary.tilkoblingen'} @:{'terms.glossary.til'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.var'} @:{'terms.glossary.vellykket'}.",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Godta forslag",
|
||||
"attach_order": "Knytt til ordre",
|
||||
@@ -355,6 +355,7 @@
|
||||
"api_settings_desc": "@.capitalize:{'terms.glossary.anslutningsinstallningar'} @:{'terms.glossary.for'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'}.",
|
||||
"automation_settings": "Automatisering",
|
||||
"automation_settings_desc": "Styr @:{'terms.glossary.automatisk'} @:{'terms.glossary.orderkoppling'} @:{'terms.glossary.och'} @:{'terms.glossary.orderskapande'} @:{'terms.glossary.fran'} @.capitalize:{'terms.glossary.selvvask'}.",
|
||||
"automation_settings_removed_desc": "@:{'templates.generated.compat.configuration.xlvask.automation_settings_removed_desc'}",
|
||||
"connection_failed": "@.capitalize:{'terms.glossary.anslutning'} @:{'terms.glossary.misslyckades'}",
|
||||
"connection_failed_desc": "@.capitalize:{'terms.glossary.anslutningen'} @:{'terms.glossary.till'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.misslyckades'}.",
|
||||
"connection_success_desc": "@.capitalize:{'terms.glossary.anslutningen'} @:{'terms.glossary.till'} @:{'terms.glossary.xlvask'} @:{'terms.glossary.api'} @:{'terms.glossary.lyckades'}.",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"xlvask_autopilot": {
|
||||
"xlvask_review": {
|
||||
"actions": {
|
||||
"accept": "Godkänn förslag",
|
||||
"attach_order": "Koppla order",
|
||||
+2
-5
@@ -2710,14 +2710,11 @@ const decideXlvaskAutomation = (
|
||||
: treeText("actions.xlvask.deny.preview", "{count} XL Vask forslag afvises.", { count: rows.length }),
|
||||
],
|
||||
async () => {
|
||||
const endpointSuffix = decision === "deny" ? "reject" : decision;
|
||||
for (const node of rows) {
|
||||
await SessionUser.request(
|
||||
`/modules/xlvask/services/usage/orders/${node.meta.usageId}/automation/${decision}`,
|
||||
`/modules/xlvask/services/usage/orders/${node.meta.usageId}/${endpointSuffix}`,
|
||||
"POST",
|
||||
{
|
||||
suggestion_id: node.meta.automation?.id ?? null,
|
||||
reason: `${decision} from invoice period tree`,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
-1
@@ -467,7 +467,6 @@ export const makeXlvaskNode = (usage, order = null, options = {}) => {
|
||||
usageId,
|
||||
washId,
|
||||
orderId: toPositiveInteger(order?.id ?? usage?.linked_order_id ?? usage?.order_id),
|
||||
automation: usage?.automation,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ const selfWashTitle = computed(() => i18n.global.t("nav.self_wash"));
|
||||
:inherit-period-filters="true"
|
||||
:load-all-at-once="false"
|
||||
:highlight-usage-log-id="highlightedUsageLogId"
|
||||
:automation-workspace="true"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -83,152 +83,8 @@ const onClickTestConnection = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
// --- MiniMax re-authenticate / remove ---------------------------------------
|
||||
const minimaxApiKeyIsSet = ref(false);
|
||||
const minimaxEnabled = ref(false);
|
||||
const reauthenticatingMiniMax = ref(false);
|
||||
const removingMiniMax = ref(false);
|
||||
const togglingMiniMaxEnabled = ref(false);
|
||||
|
||||
/**
|
||||
* The backend returns the get-config response as an array (one entry per
|
||||
* module variable). Extract the single entry we asked for, then trust the
|
||||
* explicit `isSet` flag instead of the redacted `value`.
|
||||
*/
|
||||
const extractConfigEntry = (response) => {
|
||||
const payload = response?.data?.data;
|
||||
if (Array.isArray(payload)) {
|
||||
return payload[0] ?? null;
|
||||
}
|
||||
return payload ?? null;
|
||||
};
|
||||
|
||||
const refreshMiniMaxApiKeyStatus = async () => {
|
||||
try {
|
||||
const response = await SessionUser.superUser.modules.minimax.config.keys.api_key.get();
|
||||
const entry = extractConfigEntry(response);
|
||||
minimaxApiKeyIsSet.value = entry?.isSet === true;
|
||||
} catch (error) {
|
||||
// If the endpoint is unreachable or the key isn't set yet, treat as not-set.
|
||||
minimaxApiKeyIsSet.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshMiniMaxEnabled = async () => {
|
||||
try {
|
||||
const response = await SessionUser.superUser.modules.minimax.config.enabled.get();
|
||||
const entry = extractConfigEntry(response);
|
||||
const raw = entry?.value;
|
||||
minimaxEnabled.value = raw === true || raw === 'true' || raw === '1' || raw === 1;
|
||||
} catch (error) {
|
||||
minimaxEnabled.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onClickReauthenticateMiniMax = async () => {
|
||||
const { value: apiKey } = await Swal.fire({
|
||||
title: t('configuration.xlvask.minimax_reauth_title'),
|
||||
text: t('configuration.xlvask.minimax_reauth_desc'),
|
||||
input: 'password',
|
||||
inputAttributes: { autocomplete: 'off', autocapitalize: 'off', spellcheck: 'false' },
|
||||
inputPlaceholder: t('configuration.xlvask.minimax_api_key_placeholder'),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t('configuration.xlvask.minimax_reauth_confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
preConfirm: (val) => {
|
||||
if (!val || String(val).trim() === '') {
|
||||
Swal.showValidationMessage(t('configuration.xlvask.minimax_api_key_required'));
|
||||
return false;
|
||||
}
|
||||
return String(val).trim();
|
||||
},
|
||||
});
|
||||
if (!apiKey) return;
|
||||
reauthenticatingMiniMax.value = true;
|
||||
try {
|
||||
await SessionUser.superUser.modules.minimax.config.keys.api_key.set(apiKey);
|
||||
// Re-fetch from the backend so the UI matches actual persistence (and so a
|
||||
// silent failure surfaces as "still not set" instead of a misleading green
|
||||
// checkmark).
|
||||
await refreshMiniMaxApiKeyStatus();
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: t('configuration.xlvask.minimax_reauth_success'),
|
||||
text: t('configuration.xlvask.minimax_reauth_success_desc'),
|
||||
});
|
||||
} catch (error) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('configuration.xlvask.minimax_reauth_failed'),
|
||||
text: error?.message ?? String(error),
|
||||
});
|
||||
} finally {
|
||||
reauthenticatingMiniMax.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onClickRemoveMiniMax = async () => {
|
||||
const confirmation = await Swal.fire({
|
||||
title: t('configuration.xlvask.minimax_remove_title'),
|
||||
text: t('configuration.xlvask.minimax_remove_desc'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
confirmButtonText: t('configuration.xlvask.minimax_remove_confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
});
|
||||
if (!confirmation.isConfirmed) return;
|
||||
removingMiniMax.value = true;
|
||||
try {
|
||||
await SessionUser.superUser.modules.minimax.config.keys.api_key.set('');
|
||||
await refreshMiniMaxApiKeyStatus();
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: t('configuration.xlvask.minimax_remove_success'),
|
||||
text: t('configuration.xlvask.minimax_remove_success_desc'),
|
||||
});
|
||||
} catch (error) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('configuration.xlvask.minimax_remove_failed'),
|
||||
text: error?.message ?? String(error),
|
||||
});
|
||||
} finally {
|
||||
removingMiniMax.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The ConfigurationSecretKey inline-edit flow calls onSave and resolves on
|
||||
* success. Refresh the parent state from the API so the "Hidden" view replaces
|
||||
* the warning as soon as the request actually persists.
|
||||
*/
|
||||
const onMiniMaxApiKeySaved = async () => {
|
||||
await refreshMiniMaxApiKeyStatus();
|
||||
};
|
||||
|
||||
/**
|
||||
* The MiniMax "Enable" toggle calls on-switch with the next boolean value.
|
||||
* Re-fetch after the save so the UI reflects persisted state (the inline
|
||||
* `set()` call does not refresh the UI on its own).
|
||||
*/
|
||||
const onMiniMaxEnabledSwitch = async (nextValue) => {
|
||||
togglingMiniMaxEnabled.value = true;
|
||||
try {
|
||||
await SessionUser.superUser.modules.minimax.config.enabled.set(nextValue);
|
||||
await refreshMiniMaxEnabled();
|
||||
} catch (error) {
|
||||
// Roll back the optimistic UI flip on failure.
|
||||
minimaxEnabled.value = !nextValue;
|
||||
throw error;
|
||||
} finally {
|
||||
togglingMiniMaxEnabled.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
await getModuleConfig();
|
||||
await Promise.all([refreshMiniMaxApiKeyStatus(), refreshMiniMaxEnabled()]);
|
||||
};
|
||||
|
||||
load();
|
||||
@@ -301,84 +157,9 @@ load();
|
||||
:description="$t('configuration.xlvask.automation_settings_desc')"
|
||||
icon="fas fa-magic"
|
||||
>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.enable_automatic_attachment')"
|
||||
:description="$t('configuration.xlvask.enable_automatic_attachment_desc')"
|
||||
icon="fas fa-link"
|
||||
:value="getModuleConfigValue('automatic_order_attachment_enabled') === true"
|
||||
:on-switch="SessionUser.superUser.modules.xlvask.config.keys.automatic_order_attachment_enabled.set"
|
||||
/>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.enable_automatic_creation')"
|
||||
:description="$t('configuration.xlvask.enable_automatic_creation_desc')"
|
||||
icon="fas fa-plus-circle"
|
||||
:value="getModuleConfigValue('automatic_order_creation_enabled') === true"
|
||||
:on-switch="SessionUser.superUser.modules.xlvask.config.keys.automatic_order_creation_enabled.set"
|
||||
/>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.enable_openai_integration')"
|
||||
:description="$t('configuration.xlvask.enable_openai_integration_desc')"
|
||||
icon="fas fa-brain"
|
||||
:value="getModuleConfigValue('openai_integration_enabled') === true"
|
||||
:on-switch="SessionUser.superUser.modules.xlvask.config.keys.openai_integration_enabled.set"
|
||||
/>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.enable_minimax_integration')"
|
||||
:description="$t('configuration.xlvask.enable_minimax_integration_desc')"
|
||||
icon="fas fa-robot"
|
||||
:value="getModuleConfigValue('minimax_integration_enabled') === true"
|
||||
:on-switch="SessionUser.superUser.modules.xlvask.config.keys.minimax_integration_enabled.set"
|
||||
/>
|
||||
</ConfigurationCategory>
|
||||
<ConfigurationCategory
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.minimax_settings')"
|
||||
:description="$t('configuration.xlvask.minimax_settings_desc')"
|
||||
icon="fas fa-robot"
|
||||
>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.minimax_enable')"
|
||||
:description="$t('configuration.xlvask.minimax_enable_desc')"
|
||||
icon="fas fa-robot"
|
||||
:value="minimaxEnabled"
|
||||
:on-switch="onMiniMaxEnabledSwitch"
|
||||
:disabled="togglingMiniMaxEnabled"
|
||||
/>
|
||||
<ConfigurationSecretKey
|
||||
class="mt-2"
|
||||
module="XLVask"
|
||||
:title="$t('configuration.xlvask.minimax_api_key')"
|
||||
:description="$t('configuration.xlvask.minimax_api_key_desc')"
|
||||
icon="fas fa-key"
|
||||
:isSet="minimaxApiKeyIsSet"
|
||||
:on-save="SessionUser.superUser.modules.minimax.config.keys.api_key.set"
|
||||
@saved="onMiniMaxApiKeySaved"
|
||||
/>
|
||||
<div class="buttons mt-2">
|
||||
<button class="button is-warning" @click="onClickReauthenticateMiniMax" :disabled="reauthenticatingMiniMax">
|
||||
<span class="icon">
|
||||
<i class="fas fa-redo"></i>
|
||||
</span>
|
||||
<span>{{ reauthenticatingMiniMax ? $t('configuration.xlvask.minimax_reauth_in_progress') : $t('configuration.xlvask.minimax_reauthenticate') }}</span>
|
||||
</button>
|
||||
<button class="button is-danger ml-2" @click="onClickRemoveMiniMax" :disabled="removingMiniMax">
|
||||
<span class="icon">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>{{ removingMiniMax ? $t('configuration.xlvask.minimax_remove_in_progress') : $t('configuration.xlvask.minimax_remove') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="is-size-7 has-text-grey">
|
||||
{{ $t('configuration.xlvask.automation_settings_removed_desc') }}
|
||||
</p>
|
||||
</ConfigurationCategory>
|
||||
<div class="buttons">
|
||||
<button class="button is-dark" @click="onClickTestConnection">
|
||||
|
||||
@@ -1,849 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import { createOrderItem } from "@/components/shop/OrdersItems.vue";
|
||||
import Swal from "sweetalert2";
|
||||
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
reg: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
dateFrom: {
|
||||
type: Date,
|
||||
default: () => null,
|
||||
},
|
||||
});
|
||||
|
||||
/** Dynamic variables */
|
||||
const usageLog = ref(null);
|
||||
const usageLogLoading = ref(false);
|
||||
const xlvask_vehicle_types = ref(null);
|
||||
const product_options = ref(null);
|
||||
const departments = ref(null);
|
||||
const related_orders = ref(null);
|
||||
|
||||
const getUsageLog = async () => {
|
||||
usageLogLoading.value = true;
|
||||
usageLog.value = null;
|
||||
SessionUser.superUser.modules.xlvask.functions.getUsageLog(
|
||||
(props.dateFrom === null ? null : SessionUser.superUser.modules.xlvask.functions.convertDateTimeToISO(props.dateFrom)),
|
||||
props.reg,
|
||||
).then(
|
||||
(response) => {
|
||||
if (response.status === 200) {
|
||||
usageLog.value = response.data.data;
|
||||
} else {
|
||||
console.error("XL-Vask: usage log returned non-OK status.", { status: response?.status, reg: props.reg });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_usage_log'),
|
||||
});
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
console.error("XL-Vask: failed to load usage log.", { reg: props.reg, error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_usage_log'),
|
||||
});
|
||||
}
|
||||
).finally(() => {
|
||||
usageLogLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const getVehicleTypes = async () => {
|
||||
xlvask_vehicle_types.value = null;
|
||||
SessionUser.superUser.modules.xlvask.functions.getVehicleTypes().then(
|
||||
(response) => {
|
||||
if (response.status === 200) {
|
||||
xlvask_vehicle_types.value = response.data.data;
|
||||
} else {
|
||||
console.error("XL-Vask: vehicle types returned non-OK status.", { status: response?.status });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_vehicle_types'),
|
||||
});
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
console.error("XL-Vask: failed to load vehicle types.", { error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_vehicle_types'),
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getProductOptions = async () => {
|
||||
if (product_options.value === null) {
|
||||
SessionUser.objects.product_options.get.all().then((result) => {
|
||||
product_options.value = result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getDepartments = async () => {
|
||||
SessionUser.objects.departments.get.all().then((result) => {
|
||||
departments.value = result;
|
||||
});
|
||||
}
|
||||
|
||||
const getRelatedOrders = async () => {
|
||||
related_orders.value = null;
|
||||
SessionUser.superUser.modules.xlvask.functions.getRelatedOrders(listWashIds()).then(
|
||||
(response) => {
|
||||
if (response.status === 200) {
|
||||
related_orders.value = response.data.data;
|
||||
} else {
|
||||
console.error("XL-Vask: related orders returned non-OK status.", { status: response?.status });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_related_orders'),
|
||||
});
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
console.error("XL-Vask: failed to load related orders.", { error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.fetch_related_orders'),
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Watch for changes in the usage log and update the related orders
|
||||
watch(usageLog, (newValue) => {
|
||||
if (newValue) {
|
||||
getRelatedOrders();
|
||||
}
|
||||
});
|
||||
|
||||
const listWashIds = () => {
|
||||
let washIds = [];
|
||||
if (usageLog.value) {
|
||||
for (const usage of usageLog.value) {
|
||||
washIds.push(usage.WashId);
|
||||
}
|
||||
}
|
||||
return washIds;
|
||||
}
|
||||
|
||||
const _example = {
|
||||
"WashId": "892ae789-aeea-4bda-9374-cf931290aefd",
|
||||
"CustomerId": "59440200",
|
||||
"Customer": "DITOBUS EXCURSIONS A/S",
|
||||
"VatNumber": "31171520",
|
||||
"Location": "Hvidovre",
|
||||
"Hall": "Hvidovre_1",
|
||||
"HallId": "845d29a1-a7d2-4e3b-bbc3-2b13242d744a",
|
||||
"StartTime": "2024-01-26T14:46:53.067",
|
||||
"FinishTime": "2024-01-26T14:54:08.653",
|
||||
"RegistrationNumber": "BJ22227",
|
||||
"VehicleType": "Bus/autocamper, M",
|
||||
"IdentificationType": "LPR",
|
||||
"IdentificationId": "BJ22227",
|
||||
"Info": "BJ22227",
|
||||
"Updated": "",
|
||||
"Prepaid": false,
|
||||
"FinishStatus": 1,
|
||||
"CustomerGuid": "21ba156a-b2d2-44be-8398-4b67d66003d6",
|
||||
"VehicleId": "0584ef66-3deb-491e-9b82-0a28cfc20e9e",
|
||||
"WashItems": [
|
||||
{
|
||||
"WashItemId": "399b25c1-5c03-4f25-b5e9-00731e9b94c4",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "Ikke HT dysebom bag",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
},
|
||||
{
|
||||
"WashItemId": "ef3ca812-bbe5-4cf3-916f-06fd17c04225",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": "Spot Free",
|
||||
"OriginalProductName": "Skylning med RO",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 35,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 35,
|
||||
"Vat": 3.06,
|
||||
"PriceIncVat": 15.31
|
||||
},
|
||||
{
|
||||
"WashItemId": "5c4ec0d9-cffc-45eb-9213-406dbcb8c975",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "2-børstevask",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
},
|
||||
{
|
||||
"WashItemId": "f7bd7404-f5a3-4a59-8e85-53b284df3260",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "Halleje",
|
||||
"Unit": "min",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
},
|
||||
{
|
||||
"WashItemId": "6dba6c24-8191-4c2b-913c-7366518fb41d",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "Stor bil",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 559,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 559,
|
||||
"Vat": 48.91,
|
||||
"PriceIncVat": 244.56
|
||||
},
|
||||
{
|
||||
"WashItemId": "61d11d87-2ee4-4a4d-890b-a0870ae1a924",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "HT sider",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
},
|
||||
{
|
||||
"WashItemId": "ec6eb1c4-b58c-457f-8224-b674cf41dc29",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "EU spejl",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
},
|
||||
{
|
||||
"WashItemId": "54b4deec-be3e-4cb2-b68d-b9af9cff6fcf",
|
||||
"ExternalProductId": null,
|
||||
"ExternalProductName": null,
|
||||
"OriginalProductName": "HT chassis",
|
||||
"Unit": "stk",
|
||||
"UnitPrice": 0,
|
||||
"Count": 1,
|
||||
"Discount": 65,
|
||||
"PriceExVat": 0,
|
||||
"Vat": 0,
|
||||
"PriceIncVat": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
const _exampleVehicleTypes = [
|
||||
{
|
||||
"id": 1,
|
||||
"vehicleTypeId": "0f915576-587c-4494-bcce-388b3b3fe55a",
|
||||
"product": 17,
|
||||
"name": "Bus/autocamper, M",
|
||||
"created_at": "2025-05-19 09:41:20",
|
||||
"updated_at": "2025-05-19 09:41:20"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"vehicleTypeId": "e2638c21-366d-4b7f-b0af-eb3634ae2c8c",
|
||||
"product": 15,
|
||||
"name": "Kassevogn/Varevogn, L",
|
||||
"created_at": "2025-05-19 09:44:39",
|
||||
"updated_at": "2025-05-19 09:44:39"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"vehicleTypeId": "5e6cfa13-df14-4a11-8d3d-603a41d37f68",
|
||||
"product": 17,
|
||||
"name": "Bus/autocamper, L",
|
||||
"created_at": "2025-05-19 10:12:54",
|
||||
"updated_at": "2025-05-19 10:12:54"
|
||||
}
|
||||
];
|
||||
onMounted(() => {
|
||||
getVehicleTypes();
|
||||
getUsageLog();
|
||||
getProductOptions();
|
||||
getDepartments();
|
||||
});
|
||||
|
||||
const getProductName = (item) => {
|
||||
if (item.ExternalProductName) {
|
||||
return item.ExternalProductName;
|
||||
} else if (item.OriginalProductName) {
|
||||
return item.OriginalProductName;
|
||||
} else {
|
||||
return t('invoicing_period.xlvask_autopilot.labels.unknown_product_with_id', { id: item.WashItemId });
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString();
|
||||
};
|
||||
|
||||
|
||||
const getOrderFromUsageLogEntry = (usageLogEntry) => {
|
||||
return {
|
||||
customer_id: usageLogEntry.CustomerId,
|
||||
reg_1: usageLogEntry.RegistrationNumber,
|
||||
reg_2: "",
|
||||
reg_3: "",
|
||||
wash_id: usageLogEntry.WashId,
|
||||
}
|
||||
}
|
||||
|
||||
const getPrimaryServiceProductId = (usageLogEntry) => {
|
||||
// Check if the vehicle type is in the list
|
||||
const vehicleType = xlvask_vehicle_types.value.find(type => type.name === usageLogEntry.VehicleType);
|
||||
if (vehicleType) {
|
||||
// If the vehicle type is found, return the product ID
|
||||
return vehicleType.product;
|
||||
} else {
|
||||
// If the vehicle type is not found, return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const recognizedItems = {
|
||||
"Undervognskylning": {
|
||||
getProductId: (usageLogEntry) => {
|
||||
let primaryProductId = getPrimaryServiceProductId(usageLogEntry);
|
||||
// Get all the matching options where the product id is the same as the primary product id
|
||||
let options = product_options.value.filter(option => option.product_id === primaryProductId);
|
||||
// Check if any options have the option_id 21
|
||||
let option = options.find(option => option.option_id === 21);
|
||||
if (option) {
|
||||
// If the option is found, return the option_id (21)
|
||||
return option.option_id;
|
||||
} else {
|
||||
// If the option is not found, return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spot Free
|
||||
"Skylning med RO": {
|
||||
getProductId: (usageLogEntry) => {
|
||||
let primaryProductId = getPrimaryServiceProductId(usageLogEntry);
|
||||
// Get all the matching options where the product id is the same as the primary product id
|
||||
let options = product_options.value.filter(option => option.product_id === primaryProductId);
|
||||
// Check if any options have the option_id 23, or 24
|
||||
let option = options.find(option => option.option_id === 23 || option.option_id === 24);
|
||||
if (option) {
|
||||
// If the option is found, return the option_id (23 or 24)
|
||||
return option.option_id;
|
||||
} else {
|
||||
// If the option is not found, return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Primary services
|
||||
"Stor bil": {
|
||||
getProductId: (usageLogEntry) => {
|
||||
// Check if the vehicle type is in the list
|
||||
return getPrimaryServiceProductId(usageLogEntry);
|
||||
}
|
||||
},
|
||||
"Lille bil": {
|
||||
getProductId: (usageLogEntry) => {
|
||||
// Check if the vehicle type is in the list
|
||||
return getPrimaryServiceProductId(usageLogEntry);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const round_price_down = (price) => {
|
||||
// Round the price down to the nearest whole number
|
||||
return Math.floor(price);
|
||||
}
|
||||
|
||||
const getOrderItemsFromUsageLogEntry = (usageLogEntry) => {
|
||||
let items = [];
|
||||
let unrecognizedItems = [];
|
||||
// Filter out all the free items
|
||||
const filteredItems = usageLogEntry.WashItems.filter(item => item.PriceIncVat > 0);
|
||||
// If the "Stor bil" item is present, set it to be the first item
|
||||
const storBilIndex = filteredItems.findIndex(item => item.OriginalProductName === "Stor bil" || item.OriginalProductName === "Lille bil");
|
||||
if (storBilIndex > -1) {
|
||||
const storBilItem = filteredItems.splice(storBilIndex, 1)[0];
|
||||
filteredItems.unshift(storBilItem);
|
||||
}
|
||||
// Loop through the filtered items
|
||||
for (const item of filteredItems) {
|
||||
// Check if the item is recognized
|
||||
if (recognizedItems[item.OriginalProductName]) {
|
||||
items.push({
|
||||
product_id: recognizedItems[item.OriginalProductName].getProductId(usageLogEntry),
|
||||
quantity: item.Count,
|
||||
discount_percentage: item.Discount,
|
||||
price: {
|
||||
unit: round_price_down(item.UnitPrice), // Before discount
|
||||
each: round_price_down( item.UnitPrice - (item.UnitPrice * item.Discount / 100) ), // One item after discount
|
||||
total: round_price_down( (item.UnitPrice * item.Count) - (item.UnitPrice * item.Count * item.Discount / 100) ), // Total price after discount x quantity
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the item is not recognized, add it to the unrecognized items
|
||||
unrecognizedItems.push(item);
|
||||
}
|
||||
}
|
||||
// Calculate the total price
|
||||
let price = {
|
||||
total: 0,
|
||||
}
|
||||
for (const item of items) {
|
||||
price.total += item.price.total;
|
||||
}
|
||||
return { items, unrecognizedItems, price };
|
||||
}
|
||||
|
||||
const onClickCreateOrder = async (usageLogEntry) => {
|
||||
// Check if the order can be created
|
||||
if (!canCreateOrder(usageLogEntry)) {
|
||||
console.warn("XL-Vask: cannot create order from usage log entry.", {
|
||||
washId: usageLogEntry?.WashId,
|
||||
hasUnrecognizedItems: hasUnrecognizedItems(usageLogEntry),
|
||||
hasUnrecognizedDepartment: hasUnrecognizedDepartment(usageLogEntry),
|
||||
hasRelatedOrder: hasRelatedOrder(usageLogEntry),
|
||||
});
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.create_order_unrecognized_items'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
let order = getOrderFromUsageLogEntry(usageLogEntry);
|
||||
let orderItems = getOrderItemsFromUsageLogEntry(usageLogEntry);
|
||||
// Create the order
|
||||
let order_props = {
|
||||
customer_id: parseInt(order.customer_id),
|
||||
cashier_id: null,
|
||||
department_id: getDepartmentId(usageLogEntry),
|
||||
reference: null,
|
||||
reg_1: order.reg_1,
|
||||
reg_2: order.reg_2,
|
||||
reg_3: order.reg_3,
|
||||
notes: null,
|
||||
invoice_collection_id: null,
|
||||
}
|
||||
await SessionUser.objects.orders
|
||||
.add(
|
||||
order_props.customer_id,
|
||||
order_props.cashier_id,
|
||||
order_props.department_id,
|
||||
order_props.reference,
|
||||
order_props.reg_1,
|
||||
order_props.reg_2,
|
||||
order_props.reg_3,
|
||||
order_props.notes,
|
||||
order_props.invoice_collection_id,
|
||||
).then(
|
||||
async (response) => {
|
||||
if (response.status === 200) {
|
||||
let orderId = parseInt(response.data.data.id);
|
||||
// Add the wash id to the order
|
||||
await SessionUser.objects.orders.set.wash_id(orderId, usageLogEntry.WashId);
|
||||
// Set the time created to the start time
|
||||
await SessionUser.objects.orders.set.created_at(orderId, SessionUser.functions.date.format(SessionUser.superUser.modules.xlvask.functions.convertISODateTimeToDate(usageLogEntry.StartTime)));
|
||||
let relational_id = null;
|
||||
let itemFailure = false;
|
||||
// Add the order items to the order
|
||||
for (const item of orderItems.items) {
|
||||
await createOrderItem(
|
||||
orderId,
|
||||
item.product_id,
|
||||
item.quantity,
|
||||
relational_id,
|
||||
null,
|
||||
item.price.each
|
||||
).then(
|
||||
(response) => {
|
||||
if (response.status === 200) {
|
||||
// If the relational_id is null, set it to the order item id
|
||||
if (relational_id === null) {
|
||||
relational_id = parseInt(response.data.data.id);
|
||||
}
|
||||
} else {
|
||||
itemFailure = true;
|
||||
console.error("XL-Vask: order item returned non-OK status.", {
|
||||
orderId,
|
||||
productId: item?.product_id,
|
||||
status: response?.status,
|
||||
});
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.create_order_item_failed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
itemFailure = true;
|
||||
console.error("XL-Vask: failed to create order item.", { orderId, error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.create_order_item_failed'),
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
if (!itemFailure) {
|
||||
onOrderCreated(orderId);
|
||||
}
|
||||
} else {
|
||||
console.error("XL-Vask: order creation returned non-OK status.", { status: response?.status });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.create_order_failed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
(error) => {
|
||||
console.error("XL-Vask: failed to create order.", { washId: usageLogEntry?.WashId, error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.create_order_failed'),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const onOrderCreated = (orderId) => {
|
||||
Swal.fire({
|
||||
title: t('tables.xlvask.order_created'),
|
||||
text: t('tables.xlvask.order_id', { id: orderId }),
|
||||
icon: "success",
|
||||
confirmButtonText: t('invoicing_period.xlvask_autopilot.labels.ok')
|
||||
});
|
||||
// Load the related orders
|
||||
getRelatedOrders();
|
||||
}
|
||||
|
||||
const hasUnrecognizedItems = (usageLogEntry) => {
|
||||
let orderItems = getOrderItemsFromUsageLogEntry(usageLogEntry);
|
||||
return orderItems.unrecognizedItems.length > 0;
|
||||
}
|
||||
|
||||
const hasUnrecognizedDepartment = (usageLogEntry) => {
|
||||
// Check if the department is in the list
|
||||
const department = getDepartmentId(usageLogEntry);
|
||||
return ( department === null || department === undefined );
|
||||
}
|
||||
|
||||
const listRelatedOrders = (usageLogEntry) => {
|
||||
// Check if the order is already created (If the wash id key is present in the related orders)
|
||||
if (related_orders.value) {
|
||||
let keys = Object.keys(related_orders.value);
|
||||
if (keys.includes(usageLogEntry.WashId)) {
|
||||
return related_orders.value[usageLogEntry.WashId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasRelatedOrder = (usageLogEntry) => {
|
||||
// Check if the order is already created (If the wash id key is present in the related orders)
|
||||
/**
|
||||
* {
|
||||
* "892ae789-aeea-4bda-9374-cf931290aefd": [
|
||||
* 9997, // Order ID
|
||||
* 9998, // Order ID #2 (If multiple orders are related)
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
return listRelatedOrders(usageLogEntry) !== undefined && listRelatedOrders(usageLogEntry).length > 0;
|
||||
}
|
||||
|
||||
const canCreateOrder = (usageLogEntry) => {
|
||||
// Check if there are any unrecognized items, and if the department is recognized
|
||||
return (
|
||||
!hasUnrecognizedItems(usageLogEntry) &&
|
||||
!hasUnrecognizedDepartment(usageLogEntry) &&
|
||||
!hasRelatedOrder(usageLogEntry)
|
||||
);
|
||||
}
|
||||
|
||||
const getDepartmentId = (usageLogEntry) => {
|
||||
// Check if the department is in the list
|
||||
const department = departments.value.find(department => department.name === usageLogEntry.Location);
|
||||
if (department) {
|
||||
// If the department is found, return the department id
|
||||
return parseInt(department.id);
|
||||
} else {
|
||||
// If the department is not found, return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const redirectDepartmentOrderPage = async (orderId) => {
|
||||
// Send the user to the order page (In a new tab)
|
||||
// Get the department id from the order
|
||||
await SessionUser.objects.orders.functions.get_department_id(orderId).then((response) => {
|
||||
// Get the department id from the response
|
||||
// Send the user to the order page (In a new tab)
|
||||
window.open(`/admin/${response}/modules/pos/orders/${orderId}`, '_blank');
|
||||
}).catch((error) => {
|
||||
console.error("XL-Vask: failed to resolve department id for related order.", { orderId, error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.redirect_order_failed'),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getUsageStatus = (usageLogEntry) => {
|
||||
let result = {
|
||||
color_class: "has-text-grey",
|
||||
price: {
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
if (hasRelatedOrder(usageLogEntry)) {
|
||||
result.color_class = "has-text-success";
|
||||
}
|
||||
if (hasUnrecognizedItems(usageLogEntry)) {
|
||||
result.color_class = "has-text-danger";
|
||||
}
|
||||
if (hasUnrecognizedDepartment(usageLogEntry)) {
|
||||
result.color_class = "has-text-warning";
|
||||
}
|
||||
// Add the price to the result
|
||||
result.price = getOrderItemsFromUsageLogEntry(usageLogEntry).price;
|
||||
return result;
|
||||
}
|
||||
|
||||
const onClickCreateOrderAllApplicable = async () => {
|
||||
// Loop through all the usage log entries
|
||||
for (const usageLogEntry of usageLog.value) {
|
||||
// Check if the order can be created
|
||||
if (canCreateOrder(usageLogEntry)) {
|
||||
await onClickCreateOrder(usageLogEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ t('tables.xlvask.usage_log_title') }}</h1>
|
||||
<div class="buttons">
|
||||
<button class="button is-primary" @click="onClickCreateOrderAllApplicable">{{ t('tables.xlvask.create_order_all') }}</button>
|
||||
<button class="button is-info" :class="{ 'is-loading': usageLogLoading }" :disabled="usageLogLoading" @click="getUsageLog">{{ t('tables.xlvask.refresh') }}</button>
|
||||
</div>
|
||||
<table class="table mb-6">
|
||||
<!-- Table header -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="is-narrow"><!-- Status --></th>
|
||||
<th>{{ t('tables.common.customer') }}</th>
|
||||
<th>{{ t('tables.common.registration_number') }}</th>
|
||||
<th>{{ t('tables.common.price') }}</th>
|
||||
<th>{{ t('tables.common.start_time') }}</th>
|
||||
<th>{{ t('tables.common.end_time') }}</th>
|
||||
<th>{{ t('tables.common.location') }}</th>
|
||||
<th><!-- Actions --></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- Table body -->
|
||||
<tbody>
|
||||
<tr v-if="!usageLog || usageLog.length === 0">
|
||||
<td colspan="8" class="has-text-centered has-text-grey">
|
||||
{{ t('tables.xlvask.usage_log_empty') }}
|
||||
</td>
|
||||
</tr>
|
||||
<template v-for="(usage, index) in usageLog" :key="index">
|
||||
<tr>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getUsageStatus(usage).color_class"
|
||||
v-bind:visibility="{
|
||||
icon: true,
|
||||
dropdown: false
|
||||
}"
|
||||
@onClick="() => { /* status indicator clicked */ }"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ usage.CustomerId }}</td>
|
||||
<td>{{ usage.RegistrationNumber }}</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getUsageStatus(usage).color_class"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: true
|
||||
}"
|
||||
v-bind:label="{
|
||||
text: SessionUser.functions.currency.toLocal(getUsageStatus(usage).price.total),
|
||||
classes: []
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
content: [
|
||||
{
|
||||
text: t('common.services'),
|
||||
button: false,
|
||||
action: () => {},
|
||||
},
|
||||
...(getOrderItemsFromUsageLogEntry(usage).items.map(item => {
|
||||
return {
|
||||
text: `${SessionUser.objects.products.functions.getProductName(item.product_id)} - ${item.quantity} x ${SessionUser.functions.currency.toLocal(item.price.each)}`,
|
||||
button: true,
|
||||
action: () => {},
|
||||
button_text: SessionUser.functions.currency.toLocal(item.price.total),
|
||||
v_centered: true,
|
||||
}
|
||||
})),
|
||||
...(getOrderItemsFromUsageLogEntry(usage).unrecognizedItems.map(item => {
|
||||
return {
|
||||
text: `${item.OriginalProductName} - ${item.Count} x ${SessionUser.functions.currency.toLocal(item.UnitPrice)}`,
|
||||
button: true,
|
||||
action: () => {},
|
||||
icon: 'fas fa-exclamation-triangle',
|
||||
button_text: SessionUser.functions.currency.toLocal(item.PriceIncVat),
|
||||
v_centered: true,
|
||||
}
|
||||
}))
|
||||
]
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ formatDate(usage.StartTime) }}</td>
|
||||
<td>{{ formatDate(usage.FinishTime) }}</td>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getUsageStatus(usage).color_class"
|
||||
v-bind:visibility="{
|
||||
icon: false,
|
||||
dropdown: false
|
||||
}"
|
||||
v-bind:label="{
|
||||
text: usage.Location,
|
||||
classes: []
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
content: [
|
||||
{
|
||||
text: t('self_wash.lane'),
|
||||
button: true,
|
||||
button_text: usage.Hall,
|
||||
action: () => {},
|
||||
v_centered: true,
|
||||
}
|
||||
]
|
||||
}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<ActionSettingsWheelButton>
|
||||
<template #actions>
|
||||
<!-- Create order based on usage log entry -->
|
||||
<ActionSettingsWheelItem
|
||||
:label="t('tables.xlvask.create_order')"
|
||||
icon="fas fa-plus"
|
||||
v-bind:disabled="!canCreateOrder(usage)"
|
||||
v-bind:click-action="() => onClickCreateOrder(usage)"
|
||||
/>
|
||||
<!-- If there's related orders, add them as buttons -->
|
||||
<template v-if="hasRelatedOrder(usage)">
|
||||
<ActionSettingsWheelItemLabel
|
||||
:label="SessionUser.objects.orders.meta.labels.multiple"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
v-for="(order, orderIndex) in listRelatedOrders(usage)"
|
||||
:key="orderIndex"
|
||||
:label="`${SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single)} #${order}`"
|
||||
icon="fas fa-file-invoice"
|
||||
v-bind:click-action="() => redirectDepartmentOrderPage(order)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="usage.WashItems && usage.WashItems.length > 0">
|
||||
<!-- Nested table for wash items -->
|
||||
<tr class="xlvask-usage-log-wash-items-row">
|
||||
<td colspan="100%">
|
||||
<details class="xlvask-usage-log-wash-items" data-testid="xlvask-usage-log-wash-items">
|
||||
<summary class="is-size-7">
|
||||
{{ t('tables.common.wash_item') }} ({{ usage.WashItems.length }})
|
||||
</summary>
|
||||
<table class="table is-fullwidth is-striped is-hoverable is-bordered mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t('tables.common.wash_item') }}</th>
|
||||
<th>{{ t('tables.common.count') }}</th>
|
||||
<th>{{ t('tables.common.price') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, itemIndex) in usage.WashItems" :key="itemIndex">
|
||||
<td>{{ getProductName(item) }}</td>
|
||||
<td>{{ item.Count }}</td>
|
||||
<td>{{ SessionUser.functions.currency.toLocal(Number(item.PriceIncVat ?? 0).toFixed(2)) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.xlvask-usage-log-wash-items-row > td {
|
||||
background: rgba(10, 10, 10, 0.03);
|
||||
padding: 0.5rem 0.85rem;
|
||||
}
|
||||
|
||||
.xlvask-usage-log-wash-items > summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xlvask-usage-log-wash-items[open] > summary {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -35,14 +35,14 @@ const fetchObjects = () => {
|
||||
console.error("XL-Vask: customer list returned non-OK status.", { status: response?.status });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.load_customers_failed'),
|
||||
title: t('invoicing_period.xlvask_review.errors.load_customers_failed'),
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("XL-Vask: failed to load customers.", { error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.load_customers_failed'),
|
||||
title: t('invoicing_period.xlvask_review.errors.load_customers_failed'),
|
||||
});
|
||||
}).finally(() => {
|
||||
isLoading.value = false;
|
||||
|
||||
@@ -29,7 +29,7 @@ const fetchObjects = () => {
|
||||
if (!isoDateFrom) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.load_usage_log_failed'),
|
||||
title: t('invoicing_period.xlvask_review.errors.load_usage_log_failed'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -43,14 +43,14 @@ const fetchObjects = () => {
|
||||
console.error("XL-Vask: usage log list returned non-OK status.", { status: response?.status });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.load_usage_log_failed'),
|
||||
title: t('invoicing_period.xlvask_review.errors.load_usage_log_failed'),
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("XL-Vask: failed to load usage log list.", { error });
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: t('invoicing_period.xlvask_autopilot.errors.load_usage_log_failed'),
|
||||
title: t('invoicing_period.xlvask_review.errors.load_usage_log_failed'),
|
||||
});
|
||||
}).finally(() => {
|
||||
isLoading.value = false;
|
||||
|
||||
@@ -1780,493 +1780,6 @@ test.describe("Invoicing period tab", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("@smoke period view opens Selvvask import and attaching view", async ({ page }, testInfo) => {
|
||||
const usageOrderRequests = [];
|
||||
const fastLinkRequests = [];
|
||||
const automationPreviewRequests = [];
|
||||
const automationApplyRequests = [];
|
||||
const adjudicationRequests = [];
|
||||
const importUsageRequests = [];
|
||||
let accepted = false;
|
||||
let actionHalted = false;
|
||||
let automation = {
|
||||
id: 7101,
|
||||
status: "suggested",
|
||||
action: "attach_order",
|
||||
confidence: 0.93,
|
||||
calibrated_probability: 0.995,
|
||||
source: "fuzzy",
|
||||
reason: "Samme primære produkt og relaterede tillæg matcher en ordre fra samme dag. Ordre #7001.",
|
||||
matched_order_id: 7001,
|
||||
created_order_id: null,
|
||||
candidate_order: {
|
||||
id: 7001,
|
||||
department_id: 1,
|
||||
created_at: "2026-03-10 12:03:00",
|
||||
},
|
||||
proposed_order: null,
|
||||
can_accept: true,
|
||||
can_deny: true,
|
||||
can_ignore: true,
|
||||
can_attach_order: true,
|
||||
can_create_order: true,
|
||||
review_eligible: true,
|
||||
evidence: ["Samme registrering og total"],
|
||||
contradictions: [],
|
||||
risk_flags: [],
|
||||
plan_steps: ["Kontrollér ordre #7001", "Tilknyt vasken atomisk"],
|
||||
candidate_orders: [{ order_id: 7001, reason: "Samme registrering og total" }],
|
||||
expected_version: "usage-v1",
|
||||
run_id: "run-history-1",
|
||||
policy_version: "policy-v1",
|
||||
model: "gpt-5.6-sol",
|
||||
};
|
||||
let autoAdjudication = {
|
||||
id: 7201,
|
||||
suggestion_id: 7201,
|
||||
status: "auto_accepted",
|
||||
action: "create_order",
|
||||
source: "openai",
|
||||
review_eligible: false,
|
||||
adjudication_eligible: true,
|
||||
allowed_adjudication_outcomes: ["correct", "incorrect", "duplicate", "cross_hall", "unaudited"],
|
||||
run_id: "run-auto-1",
|
||||
policy_version: "policy-v1",
|
||||
model: "gpt-5.6-sol",
|
||||
};
|
||||
|
||||
await openPeriodView(page);
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/automation/capabilities**", async (route) => {
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
can_view: true,
|
||||
can_review: true,
|
||||
can_dry_run: true,
|
||||
can_execute: !actionHalted,
|
||||
can_manage_policy: true,
|
||||
can_halt: true,
|
||||
effective_stage: actionHalted ? "halted" : "ai_attach_canary",
|
||||
allowed_modes: actionHalted ? ["dry_run"] : ["dry_run", "execute"],
|
||||
blocked_reasons: actionHalted ? ["automation_halted"] : [],
|
||||
effective_action_sources: ["deterministic", "openai"],
|
||||
readiness: {
|
||||
ready: !actionHalted,
|
||||
effective_stage: actionHalted ? "halted" : "ai_attach_canary",
|
||||
policy_version: "policy-v1",
|
||||
model: "gpt-5.6-sol",
|
||||
worker_healthy: true,
|
||||
budgets: {
|
||||
attach_order: { remaining_global: 100, remaining_hall: 10 },
|
||||
create_order: { remaining_global: 20, remaining_hall: 3 },
|
||||
},
|
||||
review_progress: {
|
||||
attach_order: { reviewed: actionHalted ? 41 : 40, target: 200 },
|
||||
create_order: { reviewed: 0, target: 50 },
|
||||
},
|
||||
eligible_counts: { attach_order: 1, create_order: 0, total: 1 },
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/automation/admin/readiness**", async (route) => {
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
ready: !actionHalted,
|
||||
effective_stage: actionHalted ? "halted" : "ai_attach_canary",
|
||||
policy_version: "policy-v1",
|
||||
model: "gpt-5.6-sol",
|
||||
worker_healthy: true,
|
||||
blocked_reasons: actionHalted ? ["automation_halted"] : [],
|
||||
budgets: {
|
||||
attach_order: { remaining_global: 100, remaining_hall: 10 },
|
||||
create_order: { remaining_global: 20, remaining_hall: 3 },
|
||||
},
|
||||
review_progress: {
|
||||
attach_order: { reviewed: actionHalted ? 41 : 40, target: 200 },
|
||||
create_order: { reviewed: 0, target: 50 },
|
||||
},
|
||||
eligible_counts: { attach_order: 1, create_order: 0, total: 1 },
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/autopilot-runs**", async (route) => {
|
||||
const path = new URL(route.request().url()).pathname;
|
||||
if (route.request().method() === "GET" && path.endsWith("/autopilot-runs/active")) {
|
||||
await route.fulfill(json({ data: { run: null } }));
|
||||
return;
|
||||
}
|
||||
if (route.request().method() !== "POST" || !path.endsWith("/autopilot-runs")) {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
importUsageRequests.push(JSON.parse(route.request().postData() || "{}"));
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
run: {
|
||||
id: "run-1",
|
||||
status: "completed",
|
||||
phase: "completed",
|
||||
processed: 2,
|
||||
total: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/automation/decisions/**", async (route) => {
|
||||
const path = new URL(route.request().url()).pathname;
|
||||
const body = JSON.parse(route.request().postData() || "{}");
|
||||
if (path.endsWith("/preview")) {
|
||||
automationPreviewRequests.push(body);
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
preview: {
|
||||
id: "preview-1",
|
||||
selection_hash: "selection-1",
|
||||
action: body.action,
|
||||
items: [{ usage_log_id: 8101, before: {}, after: { linked_order_id: 7001 }, warnings: [] }],
|
||||
requires_confirmation: true,
|
||||
confirmation_phrase: "CONFIRM XLVASK",
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (path.endsWith("/apply")) {
|
||||
automationApplyRequests.push(body);
|
||||
accepted = true;
|
||||
automation = { ...automation, status: "accepted", can_accept: false, can_deny: false };
|
||||
await route.fulfill(json({ data: { applied: 1, results: [{ usage_log_id: 8101 }], failed: [] } }));
|
||||
return;
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/automation/admin/calibrations/labels**", async (route) => {
|
||||
const body = JSON.parse(route.request().postData() || "{}");
|
||||
adjudicationRequests.push(body);
|
||||
if (body.outcome === "correct") {
|
||||
autoAdjudication = {
|
||||
...autoAdjudication,
|
||||
id: 7202,
|
||||
suggestion_id: 7202,
|
||||
allowed_adjudication_outcomes: ["incorrect"],
|
||||
};
|
||||
await route.fulfill(json({ data: { automatic_action_review: { outcome: "correct" } } }));
|
||||
return;
|
||||
}
|
||||
actionHalted = true;
|
||||
autoAdjudication = {
|
||||
...autoAdjudication,
|
||||
adjudication_eligible: false,
|
||||
allowed_adjudication_outcomes: [],
|
||||
};
|
||||
await route.fulfill(json({ data: { action_halted: true, automatic_action_review: { outcome: body.outcome } } }));
|
||||
});
|
||||
|
||||
await page.route("**/modules/xlvask/services/usage/orders**", async (route) => {
|
||||
if (route.request().method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchesApiPath(route.request().url(), "/modules/xlvask/services/usage/orders/fast-link")) {
|
||||
fastLinkRequests.push(route.request().url());
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
order_items: [
|
||||
{
|
||||
id: 9101,
|
||||
product_id: 301,
|
||||
product: { name: "Kassevogn/varevogn" },
|
||||
quantity: 1,
|
||||
price: 125,
|
||||
},
|
||||
],
|
||||
potential_duplicates: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchesApiPath(route.request().url(), "/modules/xlvask/services/usage/orders/summary")) {
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
summary: {
|
||||
total: 3,
|
||||
new: 1,
|
||||
updated: 0,
|
||||
unchanged: 2,
|
||||
invalid: 0,
|
||||
already_linked: accepted ? 1 : 0,
|
||||
auto_linked: 0,
|
||||
auto_created: 1,
|
||||
needs_review: accepted ? 0 : 1,
|
||||
blocked: 0,
|
||||
ignored: 0,
|
||||
failed: 1,
|
||||
certain: accepted ? 2 : 1,
|
||||
uncertain: accepted ? 0 : 1,
|
||||
none: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matchesApiPath(route.request().url(), "/modules/xlvask/services/usage/orders")) {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(route.request().url());
|
||||
usageOrderRequests.push({
|
||||
filters: url.searchParams.get("filters") || "",
|
||||
limit: url.searchParams.get("limit") || "",
|
||||
});
|
||||
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: [
|
||||
{
|
||||
id: 8101,
|
||||
reg_1: "AB12345",
|
||||
created_at: "2026-03-10 12:00:00",
|
||||
customer_id: 4001,
|
||||
customer_number: 4001,
|
||||
customer_name: "DEKRA AMU Center Hovedstaden A/S",
|
||||
department_id: 1,
|
||||
lane: 1,
|
||||
wash_id: "wash-selvvask-1",
|
||||
duplicates: [],
|
||||
fast_link_key: "temporary_cache_selfwash8101",
|
||||
total_net_amount: 125,
|
||||
xlvask_primary_product_name: "Kassevogn/varevogn",
|
||||
import_state: "new",
|
||||
resolution_state: accepted ? "already_linked" : "needs_review",
|
||||
certainty: accepted ? "certain" : "uncertain",
|
||||
planned_action: accepted ? "none" : "attach_order",
|
||||
automation,
|
||||
},
|
||||
{
|
||||
id: 8102,
|
||||
reg_1: "CD67890",
|
||||
created_at: "2026-03-10 12:15:00",
|
||||
customer_id: 4002,
|
||||
customer_number: 4002,
|
||||
customer_name: "Self Wash Transport",
|
||||
department_id: 2,
|
||||
lane: 2,
|
||||
wash_id: "wash-selvvask-2",
|
||||
duplicates: [],
|
||||
fast_link_key: null,
|
||||
total_net_amount: 88,
|
||||
xlvask_primary_product_name: "Varebil",
|
||||
import_state: "unchanged",
|
||||
resolution_state: "failed",
|
||||
certainty: "none",
|
||||
planned_action: "none",
|
||||
automation: {
|
||||
id: 7102,
|
||||
status: "failed",
|
||||
action: "attach_order",
|
||||
error: "No safe match",
|
||||
can_accept: false,
|
||||
can_deny: false,
|
||||
can_ignore: false,
|
||||
can_attach_order: false,
|
||||
can_create_order: false,
|
||||
review_eligible: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 8103,
|
||||
reg_1: "EF24680",
|
||||
created_at: "2026-03-10 12:30:00",
|
||||
customer_id: 4003,
|
||||
customer_number: 4003,
|
||||
customer_name: "Automatic Action Transport",
|
||||
department_id: 1,
|
||||
lane: 1,
|
||||
wash_id: "wash-selvvask-3",
|
||||
duplicates: [],
|
||||
fast_link_key: null,
|
||||
total_net_amount: 150,
|
||||
xlvask_primary_product_name: "Lastbil",
|
||||
import_state: "unchanged",
|
||||
resolution_state: "auto_created",
|
||||
certainty: "certain",
|
||||
planned_action: "none",
|
||||
automation: autoAdjudication,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await setEntireMarchPeriod(page);
|
||||
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-self_wash")).toBeVisible();
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-self_wash")).toContainText("Selvvask (1/3)");
|
||||
const selfWashProgress = page.getByTestId("invoicing-period-view-selector-progress-self_wash");
|
||||
const selfWashProgressMetrics = await selfWashProgress.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const slot = element.parentElement?.getBoundingClientRect();
|
||||
const controls = element.parentElement?.parentElement?.getBoundingClientRect();
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
slotWidth: slot?.width || 0,
|
||||
controlsWidth: controls?.width || 0,
|
||||
};
|
||||
});
|
||||
expect(
|
||||
selfWashProgressMetrics.width,
|
||||
`self-wash selector progress geometry: ${JSON.stringify(selfWashProgressMetrics)}`
|
||||
).toBeGreaterThanOrEqual(56);
|
||||
expect(selfWashProgressMetrics.height).toBeGreaterThan(0);
|
||||
await expect(selfWashProgress).toBeVisible();
|
||||
|
||||
await page.getByTestId("invoicing-period-view-selector-self_wash").click();
|
||||
await expect(page.getByTestId("invoicing-period-self-wash-view")).toBeVisible();
|
||||
const selfWashView = page.getByTestId("invoicing-period-self-wash-view");
|
||||
await expect(selfWashView.getByTestId("date-period-start")).toHaveCount(0);
|
||||
await expect(selfWashView.locator("input[placeholder='Søg i transaktioner']")).toHaveCount(0);
|
||||
await expect(selfWashView.getByText("Per side")).toHaveCount(0);
|
||||
await expect(selfWashView.getByText("Rækkefølge")).toHaveCount(0);
|
||||
await expect(selfWashView.getByText("Dato fra")).toHaveCount(0);
|
||||
await expect(selfWashView.getByText("Dato til")).toHaveCount(0);
|
||||
await expect(selfWashView.getByText("Vis kun ikke tilknyttede vaske")).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: /Selvvask/ })).toBeVisible();
|
||||
await expect(page.getByText("AB12345")).toBeVisible();
|
||||
await expect(page.getByText("CD67890")).toBeVisible();
|
||||
if (await captureXlvaskVisualEvidence(page, testInfo)) {
|
||||
return;
|
||||
}
|
||||
await expect(page.getByTestId("xlvask-summary-new")).toContainText("1");
|
||||
await expect(page.getByTestId("xlvask-summary-unchanged")).toContainText("2");
|
||||
await expect(page.getByTestId("xlvask-summary-uncertain")).toContainText("1");
|
||||
await expect(page.getByTestId("xlvask-summary-failed")).toContainText("1");
|
||||
await expect(page.getByTestId("xlvask-automation-controls")).toBeVisible();
|
||||
await expect(page.getByTestId("xlvask-automation-readiness")).toContainText("policy-v1");
|
||||
await expect(selfWashView.locator(".xlvask-usage-price").first()).toContainText("125");
|
||||
await expect(page.getByTestId("xlvask-resolution-state-8102")).toContainText("Mislykket");
|
||||
await expect(page.getByTestId("xlvask-certainty-8102")).toContainText("Ikke vurderet");
|
||||
expect(fastLinkRequests).toHaveLength(0);
|
||||
await expect(page.getByTestId("xlvask-automation-suggestion-8101")).toContainText("Foreslået: Tilknyt ordre #7001");
|
||||
await expect(selfWashView.locator(".xlvask-usage-card-header").first()).toBeVisible();
|
||||
|
||||
await page.getByText("EF24680").click();
|
||||
await page.getByTestId("xlvask-adjudication-correct-8103").click();
|
||||
await expect(page.getByRole("heading", { name: "Kontrollér det automatiske resultat" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Korrekt" }).click();
|
||||
await expect.poll(() => adjudicationRequests.length).toBe(1);
|
||||
expect(adjudicationRequests[0]).toEqual({ suggestion_id: 7201, outcome: "correct" });
|
||||
await expect(page.locator(".swal2-popup")).toHaveCount(0, { timeout: 4000 });
|
||||
|
||||
await selfWashView.getByRole("checkbox", { name: "Vælg vask #8101" }).check();
|
||||
await expect(page.getByTestId("xlvask-autopilot-bulk-bar")).toContainText("1 vaske valgt");
|
||||
await page.getByTestId("xlvask-autopilot-bulk-bar").getByRole("button", { name: "Ryd valg" }).click();
|
||||
await expect(page.getByTestId("xlvask-autopilot-bulk-bar")).toHaveCount(0);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth))
|
||||
.toBeLessThanOrEqual(1);
|
||||
|
||||
await page.getByText("AB12345").click();
|
||||
await expect(page.getByTestId("xlvask-automation-evidence-8101")).toContainText("Samme registrering og total");
|
||||
await expect(page.getByTestId("xlvask-automation-candidates-8101")).toContainText("#7001");
|
||||
await expect(page.getByTestId("xlvask-automation-accept-8101")).toBeVisible();
|
||||
await page.getByTestId("xlvask-automation-accept-8101").click();
|
||||
await expect(page.getByRole("heading", { name: "Kontrollér ændringen" })).toBeVisible();
|
||||
await page.locator(".swal2-input").fill("CONFIRM XLVASK");
|
||||
await page.getByRole("button", { name: "Udfør" }).click();
|
||||
await expect(page.getByTestId("xlvask-automation-suggestion-8101")).toContainText("Accepteret #7001");
|
||||
expect(automationPreviewRequests).toEqual([
|
||||
expect.objectContaining({
|
||||
usage_log_ids: [8101],
|
||||
action: "accept",
|
||||
suggestion_id: 7101,
|
||||
}),
|
||||
]);
|
||||
expect(automationApplyRequests).toEqual([
|
||||
expect.objectContaining({
|
||||
preview_id: "preview-1",
|
||||
selection_hash: "selection-1",
|
||||
confirmation_text: "CONFIRM XLVASK",
|
||||
}),
|
||||
]);
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-self_wash")).toContainText("Selvvask (2/3)");
|
||||
|
||||
await expect.poll(() => usageOrderRequests.length).toBeGreaterThan(0);
|
||||
expect(usageOrderRequests.some((request) => request.limit === "10000")).toBe(false);
|
||||
expect(
|
||||
usageOrderRequests.some(
|
||||
(request) =>
|
||||
request.filters.includes("StartTime-date_from:2026-03-01") &&
|
||||
request.filters.includes("StartTime-date_to:2026-03-31")
|
||||
)
|
||||
).toBe(true);
|
||||
await selfWashView.getByTestId("xlvask-autopilot-dry-run").click();
|
||||
await expect.poll(() => importUsageRequests.length).toBe(1);
|
||||
expect(importUsageRequests[0]).toMatchObject({
|
||||
dateFrom: "2026-03-01",
|
||||
dateTo: "2026-03-31",
|
||||
mode: "dry_run",
|
||||
forceRefetch: true,
|
||||
});
|
||||
expect(importUsageRequests[0].idempotency_key).toEqual(expect.any(String));
|
||||
|
||||
await selfWashView.getByTestId("xlvask-autopilot-execute").click();
|
||||
const executeDialog = page.locator(".swal2-popup");
|
||||
await expect(executeDialog).toContainText("2026-03-01");
|
||||
await expect(executeDialog).toContainText("deterministic, openai");
|
||||
await expect(executeDialog).toContainText("100 globalt; laveste restgrænse blandt haller 10");
|
||||
await expect(executeDialog).toContainText("20 globalt; laveste restgrænse blandt haller 3");
|
||||
await executeDialog.locator(".swal2-input").fill("KØR AUTOMATIK");
|
||||
await executeDialog.getByRole("button", { name: "Kør automatiske handlinger" }).click();
|
||||
await expect.poll(() => importUsageRequests.length).toBe(2);
|
||||
expect(importUsageRequests[1]).toMatchObject({ mode: "execute", forceRefetch: true });
|
||||
expect(importUsageRequests[1].idempotency_key).toEqual(expect.any(String));
|
||||
expect(importUsageRequests[1].idempotency_key).not.toBe(importUsageRequests[0].idempotency_key);
|
||||
|
||||
const incorrectAdjudication = page.getByTestId("xlvask-adjudication-incorrect-8103");
|
||||
if (!(await incorrectAdjudication.isVisible())) {
|
||||
await page.getByText("EF24680").click();
|
||||
}
|
||||
await incorrectAdjudication.click();
|
||||
await expect(page.getByRole("heading", { name: "Kontrollér det automatiske resultat" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Forkert" }).click();
|
||||
await expect.poll(() => adjudicationRequests.length).toBe(2);
|
||||
expect(adjudicationRequests[1]).toEqual({ suggestion_id: 7202, outcome: "incorrect" });
|
||||
await expect(page.getByRole("heading", { name: "Resultatet er gemt, og automatikken er stoppet" })).toBeVisible();
|
||||
await expect(page.locator(".swal2-popup")).toHaveCount(0, { timeout: 4000 });
|
||||
await expect(selfWashView.getByTestId("xlvask-autopilot-execute")).toHaveCount(0);
|
||||
await expect(page.getByTestId("xlvask-automation-readiness")).toContainText("Ikke klar");
|
||||
expect(importUsageRequests.filter((request) => request.mode === "execute")).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("@smoke period view shows flags and saves automatic flag decisions", async ({ page }) => {
|
||||
const automaticStatusRequests = [];
|
||||
const manualStatusRequests = [];
|
||||
|
||||
@@ -591,10 +591,10 @@ describe("Periode tab contract", () => {
|
||||
expect(xlvaskUsagePaginationSource).toContain('v-if="shouldShowLocalFilters"');
|
||||
expect(xlvaskUsagePaginationSource).toContain('v-if="!hideSearchField && shouldShowLocalFilters"');
|
||||
expect(xlvaskUsagePaginationSource).toContain('v-if="!props.loadAllAtOnce"');
|
||||
expect(xlvaskUsagePaginationSource).toContain("const buildImportUsageParams = () => {");
|
||||
expect(xlvaskUsagePaginationSource).toContain("const buildUsagePaginationParams = (extra = {}) =>");
|
||||
expect(xlvaskUsagePaginationSource).toContain("dateFrom: props.initialDateFrom");
|
||||
expect(xlvaskUsagePaginationSource).toContain("dateTo: props.initialDateTo");
|
||||
expect(xlvaskUsagePaginationSource).toContain("buildImportUsageParams()");
|
||||
expect(xlvaskUsagePaginationSource).toContain("buildUsagePaginationParams()");
|
||||
});
|
||||
|
||||
it("loads Selvvask selector counts and progress from the selected period", () => {
|
||||
@@ -619,86 +619,69 @@ describe("Periode tab contract", () => {
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("const { loadList } = usePaginatedListInstance();");
|
||||
});
|
||||
|
||||
it("wires the Selvvask view to the automation-workspace so operators see Accept / Reject / Ignore buttons", () => {
|
||||
// The Selvvask view (Superuser → Fakturaer → Periode → Selvvask) must enable
|
||||
// the automation workspace so the review actions surface in the orders table.
|
||||
expect(periodViewSelfWashSource).toContain(':automation-workspace="true"');
|
||||
it("wires the Selvvask surface to operator-only accept / reject / ignore actions", () => {
|
||||
// The Selvvask view (Superuser → Fakturaer → Periode → Selvvask) must
|
||||
// exclusively surface the operator review actions (Accept / Reject /
|
||||
// Ignore / Unignore). The autopilot / dry-run / execute / AI adjudication
|
||||
// flows are no longer wired here — operator review is the whole surface.
|
||||
expect(periodViewSelfWashSource).not.toContain(":automation-workspace=");
|
||||
expect(periodViewSelfWashSource).not.toContain("can_review");
|
||||
expect(periodViewSelfWashSource).not.toContain("can_manage_policy");
|
||||
|
||||
// The pagination must forward `allow-review-actions` to the orders table using
|
||||
// the per-request capability flag, so operators with can_review=true get the
|
||||
// Accept / Reject / Ignore buttons. The AI-administrator-only flags
|
||||
// (can_dry_run / can_execute / can_manage_policy) must remain on automationWorkspace
|
||||
// directly so an operator without manage_xlvask_usage_automation cannot dry-run or
|
||||
// execute the autopilot pipeline.
|
||||
expect(xlvaskUsagePaginationSource).toContain(
|
||||
':allow-review-actions="props.automationWorkspace && capabilities.can_review"'
|
||||
);
|
||||
expect(xlvaskUsagePaginationSource).toContain(
|
||||
':allow-select-multiple="props.automationWorkspace && capabilities.can_review"'
|
||||
);
|
||||
expect(xlvaskUsagePaginationSource).toContain(
|
||||
':allow-adjudication-actions="props.automationWorkspace && capabilities.can_manage_policy"'
|
||||
);
|
||||
expect(xlvaskUsagePaginationSource).toContain(':allow-review-actions="true"');
|
||||
expect(xlvaskUsagePaginationSource).toContain(':allow-select-multiple="true"');
|
||||
expect(xlvaskUsagePaginationSource).toContain(':allow-adjudication-actions="false"');
|
||||
|
||||
// The orders table must render the operator-facing action buttons
|
||||
// (Accept / Reject / Ignore / Audit) inside the right-hand action column,
|
||||
// gated only on `allowReviewActions` (i.e. the API capability, not on
|
||||
// manage_xlvask_usage_automation). This is the contract that lets the
|
||||
// /superuser/invoicing/.../?periodView=self_wash view actually accept and
|
||||
// edit XL Vask washes.
|
||||
// The orders table exposes the per-row Accept / Reject / Ignore / Unignore
|
||||
// buttons gated on `allowReviewActions`. The AI-adjudication buttons and
|
||||
// the legacy "adjudication" tag must never appear in this surface.
|
||||
expect(xlvaskUsageOrdersTableSource).toContain(
|
||||
'<div v-if="props.allowReviewActions" class="column is-2 xlvask-usage-actions-column">'
|
||||
);
|
||||
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-accept-' + object.id\"");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-reject-' + object.id\"");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-ignore-' + object.id\"");
|
||||
|
||||
// Regression guard: the AI-adjudication row (adjudication) must remain gated
|
||||
// on allowAdjudicationActions (can_manage_policy) so a normal operator never
|
||||
// sees the calibration label buttons.
|
||||
expect(xlvaskUsageOrdersTableSource).toContain(':data-testid="`xlvask-adjudication-${outcome}-${object.id}`"');
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("xlvask-adjudication-");
|
||||
});
|
||||
|
||||
it("keeps Selvvask usage entries wrapped inside the period content area", () => {
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-primary");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-status");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-chip-text");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-review-table");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("getCachedXlvaskUsageAmount");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("setCachedXlvaskUsageAmount");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("object?.total_net_amount");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("automation.status === 'failed'");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("getXlvaskResolutionState(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("only fetch one-time fast-link details on demand");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("object.resolution_state");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("onClickAccept(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("onClickReject(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("onClickIgnore(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("onClickUnignore(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("automation.status");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("scheduleFastLink(object)");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("grid-template-columns: minmax(0, 1fr) minmax(9rem, 15rem);");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-order-comparison-columns");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("@media screen and (max-width: 1350px)");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("xlvask-order-comparison-columns");
|
||||
});
|
||||
|
||||
it("uses bounded autopilot polling and preview/apply-only review mutations", () => {
|
||||
expect(xlvaskUsagePaginationSource).toContain("const RUN_POLL_INTERVAL_MS = 2_000");
|
||||
expect(xlvaskUsagePaginationSource).toContain("const RUN_POLL_MAX_DURATION_MS = 5 * 60 * 1_000");
|
||||
expect(xlvaskUsagePaginationSource).toContain("document.hidden");
|
||||
expect(xlvaskUsagePaginationSource).toContain("resumeAutopilotPolling");
|
||||
expect(xlvaskUsagePaginationSource).toContain("invalidateAutopilotScope");
|
||||
expect(xlvaskUsagePaginationSource).toContain("if (sequence !== runPollSequence) return;");
|
||||
expect(xlvaskUsagePaginationSource).toContain("activeRunRecoverySequence += 1");
|
||||
expect(xlvaskUsagePaginationSource).toContain("await loadAutomationControlState();");
|
||||
expect(xlvaskUsagePaginationSource).toContain("await recoverActiveAutopilotRun();");
|
||||
expect(xlvaskUsagePaginationSource).toContain('halted: "advisory"');
|
||||
expect(xlvaskUsagePaginationSource).toContain("expected_policy_version: preview.expected_policy_version");
|
||||
expect(xlvaskUsagePaginationSource).toContain("buildImportUsageParams(),");
|
||||
expect(xlvaskUsagePaginationSource).toContain("const runParams = buildImportUsageParams();");
|
||||
expect(xlvaskUsagePaginationSource).toContain("const readinessSnapshot = buildExecutionReadinessSnapshot();");
|
||||
expect(xlvaskUsagePaginationSource).toContain("if (requestScopeSequence !== periodScopeSequence) return;");
|
||||
expect(xlvaskUsagePaginationSource).toContain("JSON.stringify(buildExecutionReadinessSnapshot())");
|
||||
expect(xlvaskUsagePaginationSource).toContain("if (!canStartExecute.value) return;");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain('"/modules/xlvask/services/usage/automation/decisions/preview"');
|
||||
expect(xlvaskUsageOrdersTableSource).toContain('"/modules/xlvask/services/usage/automation/decisions/apply"');
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("if (!confirmation.isConfirmed) return false;");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("if (applied) selectedUsageLogIds.value = [];");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("set.wash_id");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("'/order/items'");
|
||||
it("uses summary refresh after operator review and stays on the review endpoints", () => {
|
||||
// After an operator Accept / Reject / Ignore / Unignore, the pagination
|
||||
// and the orders table must re-load the summary via the same review
|
||||
// endpoints that this surface exclusively consumes. No autopilot / dry-run /
|
||||
// apply / policy-version / readiness-snapshot plumbing survives.
|
||||
expect(xlvaskUsagePaginationSource).toContain("/modules/xlvask/services/usage/orders/summary");
|
||||
expect(xlvaskUsagePaginationSource).toContain("xlvask-usage-order-updated");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("RUN_POLL_INTERVAL_MS");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("RUN_POLL_MAX_DURATION_MS");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("recoverActiveAutopilotRun");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("loadAutomationControlState");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("buildExecutionReadinessSnapshot");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("resumeAutopilotPolling");
|
||||
expect(xlvaskUsagePaginationSource).not.toContain("canStartExecute");
|
||||
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("/modules/xlvask/services/usage/orders/${object.id}/");
|
||||
expect(xlvaskUsageOrdersTableSource).toContain("/modules/xlvask/services/usage/orders/${id}/");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("/modules/xlvask/services/usage/automation/decisions/preview");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("/modules/xlvask/services/usage/automation/decisions/apply");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("if (!confirmation.isConfirmed)");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("'apply'");
|
||||
expect(xlvaskUsageOrdersTableSource).not.toContain("'preview'");
|
||||
});
|
||||
|
||||
it("keeps period subview navigation on valid keys", () => {
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
// @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",
|
||||
regret: "Cancel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
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 actionableRow = () => ({
|
||||
id: 8101,
|
||||
reg_1: "AB12345",
|
||||
created_at: new Date().toISOString(),
|
||||
customer_name: "Test customer",
|
||||
department_id: 1,
|
||||
lane: 1,
|
||||
total_net_amount: 125,
|
||||
import_state: "new",
|
||||
resolution_state: "needs_review",
|
||||
certainty: "certain",
|
||||
planned_action: "attach_order",
|
||||
automation: {
|
||||
id: 7101,
|
||||
status: "suggested",
|
||||
action: "attach_order",
|
||||
can_accept: true,
|
||||
can_deny: true,
|
||||
can_ignore: true,
|
||||
can_attach_order: true,
|
||||
can_create_order: true,
|
||||
review_eligible: true,
|
||||
},
|
||||
});
|
||||
|
||||
const mountTable = (props = {}) =>
|
||||
shallowMount(XlvaskUsageOrdersTable, {
|
||||
props: {
|
||||
objects: [actionableRow()],
|
||||
...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 automation component authorization", () => {
|
||||
beforeEach(() => {
|
||||
request.mockReset();
|
||||
request.mockResolvedValue({ data: { data: { action_halted: false } } });
|
||||
loadList.mockReset();
|
||||
fire.mockReset();
|
||||
fire.mockResolvedValue({ isConfirmed: true });
|
||||
});
|
||||
|
||||
it("renders evidence read-only when review capability is absent", () => {
|
||||
const wrapper = mountTable({ allowSelectMultiple: true, allowReviewActions: false });
|
||||
|
||||
expect(wrapper.find("input[type='checkbox']").exists()).toBe(false);
|
||||
expect(wrapper.find("[data-testid='xlvask-automation-accept-8101']").exists()).toBe(false);
|
||||
expect(wrapper.find(".xlvask-usage-actions-column").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("renders mutation controls only for an eligible row with review capability", () => {
|
||||
const wrapper = mountTable({ allowSelectMultiple: true, allowReviewActions: true, allowAdjudicationActions: true });
|
||||
|
||||
expect(wrapper.find("input[type='checkbox']").attributes("disabled")).toBeUndefined();
|
||||
expect(wrapper.find("[data-testid='xlvask-automation-accept-8101']").exists()).toBe(true);
|
||||
expect(wrapper.find(".xlvask-usage-actions-column").exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps an ineligible row unselectable", () => {
|
||||
const row = actionableRow();
|
||||
row.automation.review_eligible = false;
|
||||
const wrapper = mountTable({ objects: [row], allowSelectMultiple: true, allowReviewActions: true });
|
||||
|
||||
expect(wrapper.find("input[type='checkbox']").attributes("disabled")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not infer eligibility or action permissions from suggested status", async () => {
|
||||
const row = actionableRow();
|
||||
delete row.automation.review_eligible;
|
||||
row.automation.can_accept = false;
|
||||
row.automation.can_attach_order = false;
|
||||
row.automation.candidate_orders = [{ order_id: 7001 }];
|
||||
const wrapper = mountTable({ objects: [row], allowSelectMultiple: true, allowReviewActions: true });
|
||||
|
||||
expect(wrapper.find("input[type='checkbox']").attributes("disabled")).toBeDefined();
|
||||
expect(wrapper.find("[data-testid='xlvask-automation-accept-8101']").exists()).toBe(false);
|
||||
expect(
|
||||
wrapper.find("[data-testid='xlvask-automation-candidates-8101'] button").attributes("disabled")
|
||||
).toBeDefined();
|
||||
await wrapper.find("[data-testid='xlvask-automation-candidates-8101'] button").trigger("click");
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("makes the department read-only rendering issue no mutation request", async () => {
|
||||
const wrapper = mountTable({ allowSelectMultiple: true, allowReviewActions: false });
|
||||
await wrapper.find(".xlvask-usage-registration").trigger("click");
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("submits an explicitly allowed post-action outcome only for policy managers", async () => {
|
||||
const row = actionableRow();
|
||||
row.automation = {
|
||||
id: 7201,
|
||||
suggestion_id: 7201,
|
||||
status: "auto_accepted",
|
||||
adjudication_eligible: true,
|
||||
allowed_adjudication_outcomes: ["correct", "incorrect", "not_allowed"],
|
||||
};
|
||||
const wrapper = mountTable({
|
||||
objects: [row],
|
||||
allowReviewActions: false,
|
||||
allowAdjudicationActions: true,
|
||||
});
|
||||
|
||||
expect(wrapper.find("[data-testid='xlvask-adjudication-correct-8101']").exists()).toBe(true);
|
||||
expect(wrapper.find("[data-testid='xlvask-adjudication-not_allowed-8101']").exists()).toBe(false);
|
||||
await wrapper.find("[data-testid='xlvask-adjudication-correct-8101']").trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
"/modules/xlvask/services/usage/automation/admin/calibrations/labels",
|
||||
"POST",
|
||||
{ suggestion_id: 7201, outcome: "correct" }
|
||||
);
|
||||
expect(loadList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not expose adjudication to an ordinary reviewer", () => {
|
||||
const row = actionableRow();
|
||||
row.automation.adjudication_eligible = true;
|
||||
row.automation.allowed_adjudication_outcomes = ["correct"];
|
||||
const wrapper = mountTable({
|
||||
objects: [row],
|
||||
allowReviewActions: true,
|
||||
allowAdjudicationActions: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find("[data-testid='xlvask-adjudication-8101']").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("surfaces every successful adverse adjudication as an automation halt", async () => {
|
||||
const row = actionableRow();
|
||||
row.automation = {
|
||||
id: 7202,
|
||||
status: "auto_accepted",
|
||||
adjudication_eligible: true,
|
||||
allowed_adjudication_outcomes: ["incorrect"],
|
||||
};
|
||||
const wrapper = mountTable({
|
||||
objects: [row],
|
||||
allowAdjudicationActions: true,
|
||||
});
|
||||
|
||||
await wrapper.find("[data-testid='xlvask-adjudication-incorrect-8101']").trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(fire).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
icon: "warning",
|
||||
title: "invoicing_period.xlvask_autopilot.adjudication.halted",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,158 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
getXlvaskCalibratedProbability,
|
||||
getXlvaskCertainty,
|
||||
getXlvaskImportState,
|
||||
getXlvaskPlannedAction,
|
||||
getXlvaskResolutionState,
|
||||
isXlvaskAutopilotRunActive,
|
||||
isXlvaskReviewEligible,
|
||||
normalizeXlvaskAutomationCapabilities,
|
||||
normalizeXlvaskAutomationReadiness,
|
||||
normalizeXlvaskAutopilotRun,
|
||||
normalizeXlvaskAutopilotSummary,
|
||||
xlvaskAutopilotRunProgress,
|
||||
xlvaskCandidateLabel,
|
||||
} from "@/components/displays/department/pos/sync/xlvaskAutopilotUi.js";
|
||||
|
||||
describe("XL-Vask autopilot UI contract", () => {
|
||||
it("normalizes missing and invalid summary counts", () => {
|
||||
expect(normalizeXlvaskAutopilotSummary({ total: "12", failed: -2, blocked: "3.8" })).toMatchObject({
|
||||
total: 12,
|
||||
failed: 0,
|
||||
blocked: 3,
|
||||
auto_created: 0,
|
||||
uncertain: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps import, resolution, certainty, and planned action independent", () => {
|
||||
const row = {
|
||||
import_state: "updated",
|
||||
resolution_state: "blocked",
|
||||
certainty: "uncertain",
|
||||
planned_action: "resolve_mapping",
|
||||
automation: { calibrated_probability: 0.73 },
|
||||
};
|
||||
|
||||
expect(getXlvaskImportState(row)).toBe("updated");
|
||||
expect(getXlvaskResolutionState(row)).toBe("blocked");
|
||||
expect(getXlvaskCertainty(row)).toBe("uncertain");
|
||||
expect(getXlvaskPlannedAction(row)).toBe("resolve_mapping");
|
||||
expect(getXlvaskCalibratedProbability(row)).toBe(0.73);
|
||||
});
|
||||
|
||||
it("keeps legacy failed and no-safe-match rows visible through fallback states", () => {
|
||||
expect(getXlvaskResolutionState({ automation: { status: "failed" } })).toBe("failed");
|
||||
expect(getXlvaskResolutionState({ automation: { status: "none" } })).toBe("needs_review");
|
||||
expect(getXlvaskCertainty({ automation: { status: "none" } })).toBe("none");
|
||||
});
|
||||
|
||||
it("never labels raw model confidence as a calibrated probability", () => {
|
||||
expect(getXlvaskCalibratedProbability({ automation: { confidence: 0.99 } })).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes active run progress without exceeding 100 percent", () => {
|
||||
const run = normalizeXlvaskAutopilotRun({
|
||||
id: "run-1",
|
||||
status: "running",
|
||||
processed: 12,
|
||||
total: 10,
|
||||
});
|
||||
|
||||
expect(isXlvaskAutopilotRunActive(run)).toBe(true);
|
||||
expect(isXlvaskAutopilotRunActive({ status: "retry_wait" })).toBe(true);
|
||||
expect(xlvaskAutopilotRunProgress(run)).toBe(100);
|
||||
expect(isXlvaskAutopilotRunActive({ status: "completed" })).toBe(false);
|
||||
expect(isXlvaskAutopilotRunActive({ status: "completed_with_warnings" })).toBe(false);
|
||||
});
|
||||
|
||||
it("formats candidate order labels without trusting one wire shape", () => {
|
||||
expect(xlvaskCandidateLabel({ order_id: 7001, reason: "Same plate and total" })).toBe(
|
||||
"#7001 · Same plate and total"
|
||||
);
|
||||
expect(xlvaskCandidateLabel({ id: 7002 })).toBe("#7002");
|
||||
});
|
||||
|
||||
it("normalizes capabilities fail closed", () => {
|
||||
expect(
|
||||
normalizeXlvaskAutomationCapabilities({
|
||||
can_view: true,
|
||||
can_review: true,
|
||||
allowed_modes: ["dry_run", "execute"],
|
||||
effective_action_sources: ["deterministic", "openai"],
|
||||
})
|
||||
).toMatchObject({
|
||||
can_view: true,
|
||||
can_review: true,
|
||||
can_execute: false,
|
||||
allowed_modes: ["dry_run", "execute"],
|
||||
effective_action_sources: ["deterministic", "openai"],
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes readiness budgets and fixed review targets", () => {
|
||||
expect(
|
||||
normalizeXlvaskAutomationReadiness({
|
||||
ready: true,
|
||||
review_progress: { attach_order: { reviewed: 12 }, create_order: { reviewed: 3 } },
|
||||
budgets: { attach_order: { remaining_global: 80, remaining_hall: 8 } },
|
||||
eligible_counts: { attach_order: 6, create_order: 2 },
|
||||
})
|
||||
).toMatchObject({
|
||||
ready: true,
|
||||
review_progress: {
|
||||
attach_order: { reviewed: 12, target: 200 },
|
||||
create_order: { reviewed: 3, target: 50 },
|
||||
},
|
||||
budgets: { attach_order: { remaining_global: 80, remaining_hall: 8 } },
|
||||
eligible_counts: { attach_order: 6, create_order: 2, total: 8 },
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts only explicit eligible review rows when the backend supplies the flag", () => {
|
||||
expect(
|
||||
isXlvaskReviewEligible({
|
||||
resolution_state: "needs_review",
|
||||
automation: { id: 1, review_eligible: true },
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
isXlvaskReviewEligible({
|
||||
resolution_state: "needs_review",
|
||||
automation: { id: 1, review_eligible: false },
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
isXlvaskReviewEligible({
|
||||
resolution_state: "needs_review",
|
||||
automation: { id: 1, status: "suggested", can_accept: true },
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
isXlvaskReviewEligible({
|
||||
resolution_state: "needs_review",
|
||||
automation: { id: 1, eligible: true },
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("treats malformed capabilities as denied", () => {
|
||||
expect(
|
||||
normalizeXlvaskAutomationCapabilities({
|
||||
can_view: "true",
|
||||
can_review: 1,
|
||||
can_execute: "yes",
|
||||
allowed_modes: "execute",
|
||||
blocked_reasons: { reason: "bad" },
|
||||
})
|
||||
).toMatchObject({
|
||||
can_view: false,
|
||||
can_review: false,
|
||||
can_execute: false,
|
||||
allowed_modes: [],
|
||||
blocked_reasons: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,203 +0,0 @@
|
||||
// @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",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -33,26 +33,26 @@ describe("xlvask usage filters", () => {
|
||||
expect(filterUsageOrdersByAttachment(sourceRows, false)).toEqual(sourceRows);
|
||||
});
|
||||
|
||||
it("treats accepted automation actions as attached", () => {
|
||||
it("treats linked and duplicate wash_ids as attached", () => {
|
||||
expect(
|
||||
isUsageOrderAttachedToOrder({
|
||||
wash_id: "wash-1",
|
||||
linked_order_id: 17,
|
||||
duplicates: [],
|
||||
automation: {
|
||||
status: "accepted",
|
||||
action: "attach_order",
|
||||
},
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isUsageOrderAttachedToOrder({
|
||||
wash_id: "wash-2",
|
||||
duplicates: [{ wash_id: "wash-2" }],
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isUsageOrderAttachedToOrder({
|
||||
wash_id: "wash-3",
|
||||
duplicates: [],
|
||||
automation: {
|
||||
status: "denied",
|
||||
action: "attach_order",
|
||||
},
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user