fix: remove nested .git folders, re-add as normal directories

This commit is contained in:
2026-03-22 17:50:47 -05:00
parent f55c7e47c9
commit 6b7eec67b8
1870 changed files with 4170168 additions and 3 deletions
+64
View File
@@ -0,0 +1,64 @@
<script lang="ts">
import { page } from "$app/stores";
import "../../styles/procurement.css";
const tabs = [
{ label: "Product Catalog", href: "/procurement/catalog", exact: false },
] as const;
function isActive(
tab: { href: string; exact?: boolean },
pathname: string,
): boolean {
if (tab.exact) return pathname === tab.href;
return pathname.startsWith(tab.href);
}
</script>
<svelte:head>
<title>Procurement — Project Optima</title>
</svelte:head>
<div class="procurement-page">
<div class="procurement-pane">
<!-- Pane header + tabs in one row -->
<div class="procurement-header">
<div class="procurement-header-left">
<svg
class="procurement-header-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
></path>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</svg>
<h2 class="procurement-title">Procurement</h2>
</div>
<nav class="tab-bar" role="tablist">
{#each tabs as tab}
<a
href={tab.href}
class="tab-btn"
class:active={isActive(tab, $page.url.pathname)}
role="tab"
aria-selected={isActive(tab, $page.url.pathname)}
>
{tab.label}
</a>
{/each}
</nav>
</div>
<!-- Tab content -->
<div class="procurement-body">
<slot />
</div>
</div>
</div>
+8
View File
@@ -0,0 +1,8 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { onMount } from "svelte";
onMount(() => {
goto("/procurement/catalog", { replaceState: true });
});
</script>
@@ -0,0 +1,60 @@
import { optima } from "$lib";
import { handleApiError } from "$lib/optima-api/errorHandler";
import { checkPermissions } from "$lib/permissions";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ locals, url }) => {
const accessToken = locals.session?.accessToken;
if (!accessToken) {
return {
items: [],
totalPages: 1,
currentPage: 1,
totalRecords: 0,
search: "",
permissions: {},
};
}
const page = Math.max(1, Number(url.searchParams.get("page")) || 1);
const search = url.searchParams.get("search") || "";
const includeInactive = url.searchParams.get("includeInactive") === "true";
try {
const [result, permissions] = await Promise.all([
optima.procurement
.fetchMany(accessToken, page, { search, includeInactive }, 30)
.catch((err) => {
console.error(
"Failed to fetch catalog items:",
err?.response?.data ?? err?.message ?? err,
);
return {
data: [],
meta: {
pagination: { totalPages: 1, currentPage: 1, totalRecords: 0 },
},
};
}),
checkPermissions(accessToken, [
"procurement.catalog.fetch.many",
"procurement.catalog.inventory.refresh",
"procurement.catalog.fetch",
"procurement.catalog.link",
]),
]);
return {
items: result?.data ?? [],
totalPages: result?.meta?.pagination?.totalPages ?? 1,
currentPage: result?.meta?.pagination?.currentPage ?? page,
totalRecords:
result?.meta?.pagination?.totalRecords ?? result?.data?.length ?? 0,
search,
includeInactive,
permissions,
};
} catch (err) {
handleApiError(err);
}
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
import { optima } from "$lib";
import { json, error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
/** GET /procurement/catalog/linked?id=<identifier> — fetch linked items */
export const GET: RequestHandler = async ({ url, locals }) => {
const accessToken = locals.session?.accessToken;
if (!accessToken) throw error(401, "Unauthorized");
const identifier = url.searchParams.get("id");
if (!identifier) throw error(400, "Missing item id");
try {
const result = await optima.procurement.fetchLinkedItems(
accessToken,
identifier,
);
return json(result);
} catch (err: unknown) {
console.error("Failed to fetch linked items:", err);
const status =
err && typeof err === "object" && "status" in err
? (err as { status: number }).status
: 500;
throw error(status, "Failed to fetch linked items");
}
};
/** POST /procurement/catalog/linked — link or unlink items */
export const POST: RequestHandler = async ({ request, locals }) => {
const accessToken = locals.session?.accessToken;
if (!accessToken) throw error(401, "Unauthorized");
const body = await request.json();
const { action, identifier, targetId } = body;
if (!identifier || !targetId || !action) {
throw error(400, "Missing identifier, targetId, or action");
}
try {
if (action === "link") {
const result = await optima.procurement.linkItem(
accessToken,
identifier,
targetId,
);
return json(result);
} else if (action === "unlink") {
const result = await optima.procurement.unlinkItem(
accessToken,
identifier,
targetId,
);
return json(result);
} else {
throw error(400, "Invalid action — must be 'link' or 'unlink'");
}
} catch (err: unknown) {
console.error(`Failed to ${action} items:`, err);
const status =
err && typeof err === "object" && "status" in err
? (err as { status: number }).status
: 500;
throw error(status, `Failed to ${action} items`);
}
};
@@ -0,0 +1,162 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { mockOptima, mockJson, mockError } = vi.hoisted(() => ({
mockOptima: {
procurement: {
fetchLinkedItems: vi.fn(),
linkItem: vi.fn(),
unlinkItem: vi.fn(),
},
},
mockJson: vi.fn((data, init?) => {
return new Response(JSON.stringify(data), {
status: init?.status ?? 200,
});
}),
mockError: vi.fn((status: number, message: string) => {
throw { status, body: { message } };
}),
}));
vi.mock("$lib", () => ({ optima: mockOptima }));
vi.mock("@sveltejs/kit", () => ({ json: mockJson, error: mockError }));
import { GET, POST } from "./+server";
describe("/procurement/catalog/linked", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, "error").mockImplementation(() => {});
});
describe("GET", () => {
it("throws 401 when no access token", async () => {
const event = {
locals: {},
url: new URL("http://localhost/linked?id=item-1"),
};
await expect(GET(event as any)).rejects.toBeDefined();
});
it("throws 400 when id is missing", async () => {
const event = {
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/linked"),
};
await expect(GET(event as any)).rejects.toEqual(
expect.objectContaining({ status: 400 }),
);
});
it("fetches linked items", async () => {
mockOptima.procurement.fetchLinkedItems.mockResolvedValueOnce({
data: [{ id: "linked-1" }],
});
const event = {
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/linked?id=item-1"),
};
await GET(event as any);
expect(mockOptima.procurement.fetchLinkedItems).toHaveBeenCalledWith(
"tok",
"item-1",
);
expect(mockJson).toHaveBeenCalledWith({ data: [{ id: "linked-1" }] });
});
});
describe("POST", () => {
it("throws 401 when no access token", async () => {
const event = {
locals: {},
request: {
json: vi.fn().mockResolvedValue({
action: "link",
identifier: "a",
targetId: "b",
}),
},
};
await expect(POST(event as any)).rejects.toBeDefined();
});
it("throws 400 when fields are missing", async () => {
const event = {
locals: { session: { accessToken: "tok" } },
request: {
json: vi.fn().mockResolvedValue({ action: "link" }),
},
};
await expect(POST(event as any)).rejects.toEqual(
expect.objectContaining({ status: 400 }),
);
});
it("links items", async () => {
mockOptima.procurement.linkItem.mockResolvedValueOnce({ ok: true });
const event = {
locals: { session: { accessToken: "tok" } },
request: {
json: vi.fn().mockResolvedValue({
action: "link",
identifier: "a",
targetId: "b",
}),
},
};
await POST(event as any);
expect(mockOptima.procurement.linkItem).toHaveBeenCalledWith(
"tok",
"a",
"b",
);
expect(mockJson).toHaveBeenCalledWith({ ok: true });
});
it("unlinks items", async () => {
mockOptima.procurement.unlinkItem.mockResolvedValueOnce({ ok: true });
const event = {
locals: { session: { accessToken: "tok" } },
request: {
json: vi.fn().mockResolvedValue({
action: "unlink",
identifier: "a",
targetId: "b",
}),
},
};
await POST(event as any);
expect(mockOptima.procurement.unlinkItem).toHaveBeenCalledWith(
"tok",
"a",
"b",
);
});
it("throws 400 for invalid action", async () => {
const event = {
locals: { session: { accessToken: "tok" } },
request: {
json: vi.fn().mockResolvedValue({
action: "destroy",
identifier: "a",
targetId: "b",
}),
},
};
await expect(POST(event as any)).rejects.toEqual(
expect.objectContaining({ status: 400 }),
);
});
});
});
@@ -0,0 +1,88 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { mockOptima, mockCheckPermissions, mockHandleApiError } = vi.hoisted(
() => ({
mockOptima: {
procurement: { fetchMany: vi.fn() },
},
mockCheckPermissions: vi.fn(),
mockHandleApiError: vi.fn(),
}),
);
vi.mock("$lib", () => ({ optima: mockOptima }));
vi.mock("$lib/permissions", () => ({
checkPermissions: mockCheckPermissions,
}));
vi.mock("$lib/optima-api/errorHandler", () => ({
handleApiError: mockHandleApiError,
}));
import { load } from "./+page.server";
describe("procurement/catalog +page.server.ts load", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns empty data when no token", async () => {
const result = await load({
locals: {},
url: new URL("http://localhost/procurement/catalog"),
} as any);
expect(result).toMatchObject({
items: [],
totalPages: 1,
currentPage: 1,
totalRecords: 0,
search: "",
});
});
it("fetches catalog items with pagination", async () => {
mockOptima.procurement.fetchMany.mockResolvedValueOnce({
data: [{ id: "item-1" }],
meta: {
pagination: { totalPages: 2, currentPage: 1, totalRecords: 45 },
},
});
mockCheckPermissions.mockResolvedValueOnce({
"procurement.catalog.fetch.many": true,
});
const result = await load({
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/procurement/catalog?page=1&search=widget"),
} as any);
expect(result).toMatchObject({
items: [{ id: "item-1" }],
totalPages: 2,
totalRecords: 45,
search: "widget",
});
});
it("passes includeInactive when param is true", async () => {
mockOptima.procurement.fetchMany.mockResolvedValueOnce({
data: [],
meta: {
pagination: { totalPages: 1, currentPage: 1, totalRecords: 0 },
},
});
mockCheckPermissions.mockResolvedValueOnce({});
await load({
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/procurement/catalog?includeInactive=true"),
} as any);
expect(mockOptima.procurement.fetchMany).toHaveBeenCalledWith(
"tok",
1,
{ search: "", includeInactive: true },
30,
);
});
});
@@ -0,0 +1,29 @@
import { optima } from "$lib";
import { json, error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
/** GET /procurement/catalog/search?q=<query> — search catalog items for linking */
export const GET: RequestHandler = async ({ url, locals }) => {
const accessToken = locals.session?.accessToken;
if (!accessToken) throw error(401, "Unauthorized");
const query = url.searchParams.get("q") || "";
if (!query.trim()) return json({ data: [] });
try {
const result = await optima.procurement.fetchMany(
accessToken,
1,
{ search: query, includeInactive: true },
20,
);
return json({ data: result?.data ?? [] });
} catch (err: unknown) {
console.error("Failed to search catalog items:", err);
const status =
err && typeof err === "object" && "status" in err
? (err as { status: number }).status
: 500;
throw error(status, "Failed to search catalog items");
}
};
@@ -0,0 +1,83 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { mockOptima, mockJson, mockError } = vi.hoisted(() => ({
mockOptima: {
procurement: { fetchMany: vi.fn() },
},
mockJson: vi.fn((data, init?) => {
return new Response(JSON.stringify(data), {
status: init?.status ?? 200,
});
}),
mockError: vi.fn((status: number, message: string) => {
throw { status, body: { message } };
}),
}));
vi.mock("$lib", () => ({ optima: mockOptima }));
vi.mock("@sveltejs/kit", () => ({ json: mockJson, error: mockError }));
import { GET } from "./+server";
describe("GET /procurement/catalog/search", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, "error").mockImplementation(() => {});
});
it("throws 401 when no access token", async () => {
const event = {
locals: {},
url: new URL("http://localhost/search?q=widget"),
};
await expect(GET(event as any)).rejects.toBeDefined();
});
it("returns empty data when query is empty", async () => {
const event = {
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/search"),
};
GET(event as any);
expect(mockJson).toHaveBeenCalledWith({ data: [] });
expect(mockOptima.procurement.fetchMany).not.toHaveBeenCalled();
});
it("searches catalog items", async () => {
mockOptima.procurement.fetchMany.mockResolvedValueOnce({
data: [{ id: "item-1", name: "Widget" }],
});
const event = {
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/search?q=widget"),
};
await GET(event as any);
expect(mockOptima.procurement.fetchMany).toHaveBeenCalledWith(
"tok",
1,
{ search: "widget", includeInactive: true },
20,
);
expect(mockJson).toHaveBeenCalledWith({
data: [{ id: "item-1", name: "Widget" }],
});
});
it("throws on failure", async () => {
mockOptima.procurement.fetchMany.mockRejectedValueOnce({
status: 500,
});
const event = {
locals: { session: { accessToken: "tok" } },
url: new URL("http://localhost/search?q=widget"),
};
await expect(GET(event as any)).rejects.toBeDefined();
});
});