Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cod-client-astro/src/components/layout/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ export function useNavSections(): NavSection[] {
label: tN("sidebar.mcp"),
icon: Sparkles,
scope: "mcp:view",
badge: "BETA",
},
{
kind: "leaf",
Expand Down
10 changes: 10 additions & 0 deletions cod-client-astro/src/features/landing-pages/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { apiFetch } from "@/lib/api";
import type { PresignedUpload } from "@/features/products/api";
import type {
CreateLandingPageInput,
LandingPage,
Expand Down Expand Up @@ -104,3 +105,12 @@ export function deleteLandingPageImage(lpId: string, imageId: string) {
{ method: "DELETE" },
);
}

export async function getPresignedLandingUploadUrl(contentType: string) {
return (
await apiFetch<DataEnvelope<PresignedUpload>>(
"/api/images/presign",
json({ method: "POST", body: JSON.stringify({ contentType, folder: "landing" }) }),
)
).data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import { SCOPES } from "../../../../../cod-shared/rbac/scopes";
import {
deleteLandingPageImage,
getLandingPage,
getPresignedLandingUploadUrl,
publishLandingPage,
reorderLandingPageImages,
saveLandingPageImage,
unpublishLandingPage,
updateLandingPage,
} from "@/features/landing-pages/api";
import { getPresignedUploadUrl } from "@/features/products/api";
import { landingPageErrorMessage, landingPagePublicUrl } from "@/features/landing-pages/model";
import type { LandingPage, LandingPageImage } from "@/features/landing-pages/types";

Expand Down Expand Up @@ -223,7 +223,7 @@ function Gated({ landingPageId }: { landingPageId: string }) {
setUploading(true);
try {
for (const file of arr) {
const { presignedUrl, key, publicUrl } = await getPresignedUploadUrl(file.type);
const { presignedUrl, key, publicUrl } = await getPresignedLandingUploadUrl(file.type);
const putRes = await fetch(presignedUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
Expand Down
36 changes: 36 additions & 0 deletions cod-server/src/endpoints/customer-groups/ai-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
customerGroupFiltersSchema,
} from "./validation";
import { getDb } from "@/db";
import { toolOutput, timestampSchema } from "@/lib/tool-output-schema";

/**
* Layer-2 validation schemas, hoisted to module level and exported so the MCP
Expand Down Expand Up @@ -46,6 +47,41 @@ export const CUSTOMER_GROUP_TOOL_SCHEMAS: Record<string, z.ZodRawShape> = {
removeCustomerFromGroup: removeCustomerFromGroupSchema.shape,
};

const customerGroupRowSchema = z.looseObject({
id: z.string().describe("Group UUID"),
name: z.string().describe("Names are not unique — distinguish groups by color or description"),
description: z.string().nullable().optional().describe("Internal policy note, never shown to shoppers"),
color: z.string().optional().describe("Hex color for dashboard identification"),
memberCount: z.number().int().optional().describe("Denormalized member counter — doubles as the deletion guard"),
});

export const CUSTOMER_GROUP_TOOL_OUTPUT_SCHEMAS: Record<string, z.ZodType> = {
listCustomerGroups: toolOutput({
count: z.number().int(),
groups: z.array(customerGroupRowSchema).describe("Customer groups"),
}),
getCustomerGroupDetails: toolOutput({
group: customerGroupRowSchema.describe("The group; includes members when withMembers was true"),
}),
createCustomerGroup: toolOutput({
group: customerGroupRowSchema.describe("The created group"),
message: z.string(),
}),
updateCustomerGroup: toolOutput({
group: customerGroupRowSchema.describe("The updated group"),
message: z.string(),
}),
deleteCustomerGroup: toolOutput({
message: z.string(),
}),
addCustomerToGroup: toolOutput({
message: z.string().describe("Idempotent — adding an existing member succeeds without change"),
}),
removeCustomerFromGroup: toolOutput({
message: z.string().describe("Silent on unknown pairings — returns success"),
}),
};

/**
* AI Tools for Customer Group Management
*
Expand Down
35 changes: 35 additions & 0 deletions cod-server/src/endpoints/customer-tags/ai-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
customerTagFiltersSchema,
} from "./validation";
import { getDb } from "@/db";
import { toolOutput } from "@/lib/tool-output-schema";

/**
* Layer-2 validation schemas, hoisted to module level and exported so the MCP
Expand Down Expand Up @@ -46,6 +47,40 @@ export const CUSTOMER_TAG_TOOL_SCHEMAS: Record<string, z.ZodRawShape> = {
unassignTagFromCustomer: unassignTagFromCustomerSchema.shape,
};

const customerTagRowSchema = z.looseObject({
id: z.string().describe("Tag UUID"),
name: z.string().describe("Unique across the whole store"),
color: z.string().optional().describe("Hex color for dashboards"),
assignmentCount: z.number().int().optional().describe("Denormalized counter — doubles as the deletion guard"),
});

export const CUSTOMER_TAG_TOOL_OUTPUT_SCHEMAS: Record<string, z.ZodType> = {
listCustomerTags: toolOutput({
count: z.number().int(),
tags: z.array(customerTagRowSchema).describe("Customer tags"),
}),
getCustomerTagDetails: toolOutput({
tag: customerTagRowSchema.describe("The tag; includes assigned customers when withCustomers was true"),
}),
createCustomerTag: toolOutput({
tag: customerTagRowSchema.describe("The created tag"),
message: z.string(),
}),
updateCustomerTag: toolOutput({
tag: customerTagRowSchema.describe("The updated tag"),
message: z.string(),
}),
deleteCustomerTag: toolOutput({
message: z.string(),
}),
assignTagToCustomer: toolOutput({
message: z.string().describe("Idempotent — assigning an already-tagged customer succeeds without change"),
}),
unassignTagFromCustomer: toolOutput({
message: z.string().describe("Silent on unknown pairings — returns success"),
}),
};

/**
* AI Tools for Customer Tag Management
*
Expand Down
58 changes: 58 additions & 0 deletions cod-server/src/endpoints/customers/ai-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
updateCustomerSchema
} from "./validation";
import { getDb } from "@/db";
import { toolOutput, timestampSchema } from "@/lib/tool-output-schema";

/**
* Layer-2 validation schemas, hoisted to module level and exported so the MCP
Expand Down Expand Up @@ -47,6 +48,63 @@ export const CUSTOMER_TOOL_SCHEMAS: Record<string, z.ZodRawShape> = {
deleteCustomer: deleteCustomerSchema.shape,
};

const customerRowSchema = z.looseObject({
id: z.string().describe("Customer UUID"),
name: z.string(),
phone: z.string().describe("Primary Algerian mobile (05/06/07) — the customer's identity anchor"),
wilaya: z.string().optional().describe("Wilaya display-name snapshot"),
commune: z.string().optional().describe("Commune display-name snapshot"),
totalSpent: z.number().optional().describe("Running value of kept orders in DZD"),
totalOrders: z.number().optional().describe("Orders ever placed, minus deleted ones"),
lastOrderAt: timestampSchema.nullable().optional().describe("Creation time of the most recent order, or null"),
});

export const CUSTOMER_TOOL_OUTPUT_SCHEMAS: Record<string, z.ZodType> = {
listCustomers: toolOutput({
count: z.number().int(),
customers: z.array(customerRowSchema).describe("Customers matching the filters"),
}),
getCustomerDetails: toolOutput({
customer: customerRowSchema.describe("The customer profile"),
}),
findCustomerByPhone: toolOutput({
customer: customerRowSchema.describe("The customer owning that phone number"),
}),
createNewCustomer: toolOutput({
customer: customerRowSchema.describe("The created customer"),
message: z.string(),
}),
updateCustomerProfile: toolOutput({
customer: customerRowSchema.describe("The updated customer"),
message: z.string(),
}),
getCustomerOrderHistory: toolOutput({
count: z.number().int(),
orders: z
.array(
z.looseObject({
id: z.string().describe("Order UUID"),
orderNumber: z.string().describe("Human-readable order number (ORD-YYYYMMDD-NNNN)"),
status: z.string().describe("Order lifecycle status"),
totalPrice: z.number().describe("Product subtotal excluding delivery fee (DZD)"),
createdAt: timestampSchema,
wilaya: z.string(),
commune: z.string(),
}),
)
.describe("The customer's orders, newest first"),
}),
getCustomerMemberships: toolOutput({
groups: z.array(z.looseObject({ id: z.string(), name: z.string() })).describe("Customer groups this customer belongs to"),
tags: z.array(z.looseObject({ id: z.string(), name: z.string() })).describe("Tags assigned to this customer"),
groupCount: z.number().int(),
tagCount: z.number().int(),
}),
deleteCustomer: toolOutput({
message: z.string(),
}),
};

/**
* AI Tools for Customer Management
*
Expand Down
39 changes: 39 additions & 0 deletions cod-server/src/endpoints/driver-payments/ai-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from "zod";
import * as queries from "./queries";
import { createPaymentSchema } from "./validation";
import { getDb } from "@/db";
import { toolOutput, timestampSchema } from "@/lib/tool-output-schema";

/**
* Layer-2 validation schemas, hoisted to module level and exported so the MCP
Expand All @@ -26,6 +27,44 @@ export const DRIVER_PAYMENT_TOOL_SCHEMAS: Record<string, z.ZodRawShape> = {
createDriverSettlement: createDriverSettlementSchema.shape,
};

export const DRIVER_PAYMENT_TOOL_OUTPUT_SCHEMAS: Record<string, z.ZodType> = {
listDriverPayments: toolOutput({
count: z.number().int(),
payments: z.array(
z.looseObject({
id: z.string().describe("Payment record UUID"),
type: z.string().describe("Settlement type (COD remittance, fee payment, or net settlement)"),
amount: z.number().describe("Server-computed settlement amount in DZD"),
orderCount: z.number().int().describe("Orders settled in this payment"),
createdAt: timestampSchema,
createdByName: z.string().describe("Who created the settlement (audit attribution)"),
notes: z.string().nullable(),
}),
).describe("Settlement history, newest first"),
}),
getPendingSettlements: toolOutput({
count: z.number().int(),
orders: z.array(
z.looseObject({
id: z.string().describe("Order UUID"),
orderNumber: z.string(),
codAmount: z.number().describe("Customer cash the driver still owes the shop (DZD)"),
driverFee: z.number().describe("Frozen per-delivery fee owed to the driver (DZD)"),
updatedAt: timestampSchema,
status: z.string(),
}),
).describe("Delivered orders awaiting settlement"),
}),
createDriverSettlement: toolOutput({
settlement: z.looseObject({
type: z.string().optional().describe("Settlement type"),
orderCount: z.number().int().optional().describe("Orders settled"),
amount: z.number().optional().describe("Settled amount in DZD"),
}).describe("The created payment record"),
message: z.string(),
}),
};

/**
* AI Tools for Driver Payment Management
*
Expand Down
47 changes: 47 additions & 0 deletions cod-server/src/endpoints/drivers/ai-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
updateDriverStatusSchema as updateDriverStatusInputSchema,
} from "./validation";
import { getDb } from "@/db";
import { toolOutput } from "@/lib/tool-output-schema";

/**
* Layer-2 validation schemas, hoisted to module level and exported so the MCP
Expand Down Expand Up @@ -41,6 +42,52 @@ export const DRIVER_TOOL_SCHEMAS: Record<string, z.ZodRawShape> = {
deleteDriver: deleteDriverSchema.shape,
};

const driverRowSchema = z.looseObject({
id: z.string().describe("Driver UUID"),
firstName: z.string(),
lastName: z.string(),
phone: z.string().describe("Primary phone — uniquely identifies the driver"),
status: z.string().optional().describe("Availability: available | busy | inactive (changed only by its dedicated endpoint)"),
vehicleType: z.string().nullable().optional().describe("motorcycle | car | van, or null when unknown"),
});

export const DRIVER_TOOL_OUTPUT_SCHEMAS: Record<string, z.ZodType> = {
listDrivers: toolOutput({
count: z.number().int(),
drivers: z.array(
z.looseObject({
id: z.string().describe("Driver UUID"),
firstName: z.string(),
lastName: z.string(),
phone: z.string(),
status: z.string().describe("Availability: available | busy | inactive"),
vehicleType: z.string().nullable().describe("motorcycle | car | van, or null"),
compensationWilayaCount: z.number().int().describe("Wilayas with a configured pay rate — zero is legal but unpaid work"),
totalDelivered: z.number().int().describe("Delivered orders all time"),
pendingCash: z.number().describe("Collected customer cash not yet remitted, in DZD"),
}),
).describe("Drivers matching the filters"),
}),
getDriverDetails: toolOutput({
driver: driverRowSchema.describe("Driver profile, compensation grid, and recent orders"),
}),
createNewDriver: toolOutput({
driver: driverRowSchema.describe("The created driver"),
message: z.string(),
}),
updateDriverProfile: toolOutput({
driver: driverRowSchema.describe("The updated driver"),
message: z.string(),
}),
updateDriverStatus: toolOutput({
driver: driverRowSchema.describe("The driver with its new availability status"),
message: z.string(),
}),
deleteDriver: toolOutput({
message: z.string(),
}),
};

/**
* AI Tools for Driver Management
*
Expand Down
Loading