FEDEO MCP-Tools für Organisation erweitern
Ergänzt MCP-Tools für Kunden-, Projekt-, Anlagen- und Terminabfragen sowie das Laden einzelner Bankumsätze. Aufgaben können nun zusätzlich gezielt archiviert werden.
This commit is contained in:
@@ -157,6 +157,32 @@ export const accountingTools: McpTool[] = [
|
||||
return { rows }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "accounting.bank_statements.get",
|
||||
title: "Bankumsatz laden",
|
||||
description: "Lädt einen Bankumsatz des aktiven Mandanten anhand seiner ID.",
|
||||
requiredPermissions: ["accounting.bank.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "number" },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const id = numberArg(args, "id")
|
||||
if (!id) throw new Error("id ist erforderlich")
|
||||
|
||||
const rows = await context.server.db
|
||||
.select()
|
||||
.from(bankstatements)
|
||||
.where(and(eq(bankstatements.id, id), eq(bankstatements.tenant, context.tenantId)))
|
||||
.limit(1)
|
||||
|
||||
if (!rows[0]) throw new Error("Bankumsatz nicht gefunden")
|
||||
return { bankStatement: rows[0] }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "accounting.statement_allocations.list",
|
||||
title: "Buchungszuordnungen auflisten",
|
||||
@@ -191,4 +217,3 @@ export const accountingTools: McpTool[] = [
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { and, desc, eq, ilike, or } from "drizzle-orm"
|
||||
import { tasks } from "../../../db/schema"
|
||||
import { customers, events, plants, projects, tasks } from "../../../db/schema"
|
||||
import { McpTool } from "../types"
|
||||
|
||||
const limitFromArgs = (args: Record<string, unknown>, fallback = 25) => {
|
||||
@@ -19,6 +19,201 @@ const numberArg = (args: Record<string, unknown>, key: string) => {
|
||||
}
|
||||
|
||||
export const organisationTools: McpTool[] = [
|
||||
{
|
||||
name: "organisation.customers.search",
|
||||
title: "Kunden suchen",
|
||||
description: "Sucht aktive Kunden des aktiven Mandanten.",
|
||||
requiredPermissions: ["organisation.customers.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Suchtext für Name, Kundennummer, Vorname, Nachname oder Notizen." },
|
||||
includeArchived: { type: "boolean", default: false },
|
||||
limit: { type: "number", minimum: 1, maximum: 100 },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const conditions = [eq(customers.tenant, context.tenantId)]
|
||||
const query = stringArg(args, "query")
|
||||
|
||||
if (query) {
|
||||
conditions.push(or(
|
||||
ilike(customers.name, `%${query}%`),
|
||||
ilike(customers.customerNumber, `%${query}%`),
|
||||
ilike(customers.firstname, `%${query}%`),
|
||||
ilike(customers.lastname, `%${query}%`),
|
||||
ilike(customers.notes, `%${query}%`)
|
||||
))
|
||||
}
|
||||
if (args.includeArchived !== true) conditions.push(eq(customers.archived, false))
|
||||
|
||||
const rows = await context.server.db
|
||||
.select({
|
||||
id: customers.id,
|
||||
customerNumber: customers.customerNumber,
|
||||
name: customers.name,
|
||||
firstname: customers.firstname,
|
||||
lastname: customers.lastname,
|
||||
type: customers.type,
|
||||
isCompany: customers.isCompany,
|
||||
active: customers.active,
|
||||
archived: customers.archived,
|
||||
})
|
||||
.from(customers)
|
||||
.where(and(...conditions))
|
||||
.orderBy(customers.name)
|
||||
.limit(limitFromArgs(args))
|
||||
|
||||
return { rows }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organisation.projects.list",
|
||||
title: "Projekte auflisten",
|
||||
description: "Listet Projekte des aktiven Mandanten mit optionalen Filtern.",
|
||||
requiredPermissions: ["organisation.projects.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Suchtext für Name, Projektnummer, Kundenreferenz oder Notizen." },
|
||||
customer: { type: "number" },
|
||||
activePhase: { type: "string" },
|
||||
includeArchived: { type: "boolean", default: false },
|
||||
limit: { type: "number", minimum: 1, maximum: 100 },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const conditions = [eq(projects.tenant, context.tenantId)]
|
||||
const query = stringArg(args, "query")
|
||||
const customer = numberArg(args, "customer")
|
||||
const activePhase = stringArg(args, "activePhase")
|
||||
|
||||
if (query) {
|
||||
conditions.push(or(
|
||||
ilike(projects.name, `%${query}%`),
|
||||
ilike(projects.projectNumber, `%${query}%`),
|
||||
ilike(projects.customerRef, `%${query}%`),
|
||||
ilike(projects.notes, `%${query}%`)
|
||||
))
|
||||
}
|
||||
if (customer) conditions.push(eq(projects.customer, customer))
|
||||
if (activePhase) conditions.push(eq(projects.active_phase, activePhase))
|
||||
if (args.includeArchived !== true) conditions.push(eq(projects.archived, false))
|
||||
|
||||
const rows = await context.server.db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(and(...conditions))
|
||||
.orderBy(desc(projects.createdAt))
|
||||
.limit(limitFromArgs(args))
|
||||
|
||||
return { rows }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organisation.projects.get",
|
||||
title: "Projekt laden",
|
||||
description: "Lädt ein Projekt des aktiven Mandanten anhand seiner ID.",
|
||||
requiredPermissions: ["organisation.projects.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "number" },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const id = numberArg(args, "id")
|
||||
if (!id) throw new Error("id ist erforderlich")
|
||||
|
||||
const rows = await context.server.db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, id), eq(projects.tenant, context.tenantId)))
|
||||
.limit(1)
|
||||
|
||||
if (!rows[0]) throw new Error("Projekt nicht gefunden")
|
||||
return { project: rows[0] }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organisation.plants.list",
|
||||
title: "Anlagen auflisten",
|
||||
description: "Listet Anlagen des aktiven Mandanten mit optionalem Kundenfilter.",
|
||||
requiredPermissions: ["organisation.plants.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Suchtext für Name." },
|
||||
customer: { type: "number" },
|
||||
includeArchived: { type: "boolean", default: false },
|
||||
limit: { type: "number", minimum: 1, maximum: 100 },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const conditions = [eq(plants.tenant, context.tenantId)]
|
||||
const query = stringArg(args, "query")
|
||||
const customer = numberArg(args, "customer")
|
||||
|
||||
if (query) conditions.push(ilike(plants.name, `%${query}%`))
|
||||
if (customer) conditions.push(eq(plants.customer, customer))
|
||||
if (args.includeArchived !== true) conditions.push(eq(plants.archived, false))
|
||||
|
||||
const rows = await context.server.db
|
||||
.select()
|
||||
.from(plants)
|
||||
.where(and(...conditions))
|
||||
.orderBy(plants.name)
|
||||
.limit(limitFromArgs(args))
|
||||
|
||||
return { rows }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organisation.events.list",
|
||||
title: "Termine auflisten",
|
||||
description: "Listet Termine des aktiven Mandanten mit optionalen Projekt- oder Kundenfiltern.",
|
||||
requiredPermissions: ["organisation.events.read"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Suchtext für Name, Notizen oder Link." },
|
||||
project: { type: "number" },
|
||||
customer: { type: "number" },
|
||||
eventtype: { type: "string" },
|
||||
includeArchived: { type: "boolean", default: false },
|
||||
limit: { type: "number", minimum: 1, maximum: 100 },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const conditions = [eq(events.tenant, context.tenantId)]
|
||||
const query = stringArg(args, "query")
|
||||
const project = numberArg(args, "project")
|
||||
const customer = numberArg(args, "customer")
|
||||
const eventtype = stringArg(args, "eventtype")
|
||||
|
||||
if (query) {
|
||||
conditions.push(or(
|
||||
ilike(events.name, `%${query}%`),
|
||||
ilike(events.notes, `%${query}%`),
|
||||
ilike(events.link, `%${query}%`)
|
||||
))
|
||||
}
|
||||
if (project) conditions.push(eq(events.project, project))
|
||||
if (customer) conditions.push(eq(events.customer, customer))
|
||||
if (eventtype) conditions.push(eq(events.eventtype, eventtype))
|
||||
if (args.includeArchived !== true) conditions.push(eq(events.archived, false))
|
||||
|
||||
const rows = await context.server.db
|
||||
.select()
|
||||
.from(events)
|
||||
.where(and(...conditions))
|
||||
.orderBy(desc(events.startDate))
|
||||
.limit(limitFromArgs(args))
|
||||
|
||||
return { rows }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organisation.tasks.list",
|
||||
title: "Aufgaben auflisten",
|
||||
@@ -179,5 +374,34 @@ export const organisationTools: McpTool[] = [
|
||||
return { task: updated }
|
||||
},
|
||||
},
|
||||
]
|
||||
{
|
||||
name: "organisation.tasks.archive",
|
||||
title: "Aufgabe archivieren",
|
||||
description: "Archiviert eine Aufgabe im aktiven Mandanten.",
|
||||
requiredPermissions: ["organisation.tasks.write"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "number" },
|
||||
},
|
||||
},
|
||||
async handler(context, args) {
|
||||
const id = numberArg(args, "id")
|
||||
if (!id) throw new Error("id ist erforderlich")
|
||||
|
||||
const [updated] = await context.server.db
|
||||
.update(tasks)
|
||||
.set({
|
||||
archived: true,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: context.userId,
|
||||
})
|
||||
.where(and(eq(tasks.id, id), eq(tasks.tenant, context.tenantId)))
|
||||
.returning()
|
||||
|
||||
if (!updated) throw new Error("Aufgabe nicht gefunden")
|
||||
return { task: updated }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user