@flowselections/mailbox-orders 1.0.57 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/CartAssignmentWizard.d.ts +6 -0
- package/dist-lib/components/CartAssignmentWizard.d.ts.map +1 -0
- package/dist-lib/components/CartAssignmentWizard.js +199 -0
- package/dist-lib/components/EmailPreview.d.ts.map +1 -1
- package/dist-lib/components/EmailPreview.js +2 -1
- package/dist-lib/components/MailboxInboxPage.d.ts +2 -0
- package/dist-lib/components/MailboxInboxPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxInboxPage.js +120 -0
- package/dist-lib/components/MailboxPageFrame.js +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +3 -3
- package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxSkippedPage.js +4 -4
- package/dist-lib/components/fs-ui/DataTable.js +3 -3
- package/dist-lib/components/settings/ImapAccountsCard.d.ts +2 -0
- package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/ImapAccountsCard.js +136 -0
- package/dist-lib/components/settings/ImapProfilesCard.d.ts +2 -0
- package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +1 -0
- package/dist-lib/components/settings/ImapProfilesCard.js +101 -0
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxSettingsCard.js +85 -0
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +76 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/imap.functions.d.ts +32852 -0
- package/dist-lib/lib/imap.functions.d.ts.map +1 -0
- package/dist-lib/lib/imap.functions.js +235 -0
- package/dist-lib/lib/mailbox-auto.server.d.ts +9 -0
- package/dist-lib/lib/mailbox-auto.server.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-auto.server.js +198 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +2 -2
- package/dist-lib/lib/mailbox-filter-settings.functions.js +3 -3
- package/dist-lib/lib/mailbox.functions.d.ts +1 -1
- package/dist-lib/lib/mailbox.functions.js +6 -6
- package/package.json +2 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Server functions voor centraal IMAP-beheer (accounts + profielen).
|
|
2
|
+
// Admins beheren accounts & profielen; modules pollen via profile_key.
|
|
3
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
4
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
async function assertAdmin(supabase, userId) {
|
|
7
|
+
const { data } = await supabase.rpc("has_role", { _user_id: userId, _role: "admin" });
|
|
8
|
+
if (!data)
|
|
9
|
+
throw new Error("Alleen admins mogen IMAP-instellingen wijzigen");
|
|
10
|
+
}
|
|
11
|
+
// ─── Accounts ────────────────────────────────────────────────────────────────
|
|
12
|
+
export const listImapAccounts = createServerFn({ method: "GET" })
|
|
13
|
+
.middleware([requireSupabaseAuth])
|
|
14
|
+
.handler(async ({ context }) => {
|
|
15
|
+
const { data, error } = await context.supabase
|
|
16
|
+
.from("imap_accounts")
|
|
17
|
+
.select("id, name, email, host, port, username, use_tls, password_secret_name, is_active, last_tested_at, last_test_ok, last_test_error, created_at, updated_at")
|
|
18
|
+
.order("name");
|
|
19
|
+
if (error)
|
|
20
|
+
throw new Error(error.message);
|
|
21
|
+
return { items: Array.isArray(data) ? data : [] };
|
|
22
|
+
});
|
|
23
|
+
const AccountInput = z.object({
|
|
24
|
+
id: z.string().uuid().optional(),
|
|
25
|
+
name: z.string().min(1).max(255),
|
|
26
|
+
email: z.string().email().max(255).nullable().optional(),
|
|
27
|
+
host: z.string().min(1).max(255),
|
|
28
|
+
port: z.number().int().min(1).max(65535),
|
|
29
|
+
username: z.string().min(1).max(255),
|
|
30
|
+
use_tls: z.boolean(),
|
|
31
|
+
password: z.string().max(1024).optional(), // leeg = bestaand wachtwoord behouden
|
|
32
|
+
password_secret_name: z.string().min(1).max(255).regex(/^[A-Za-z_][A-Za-z0-9_]*$/).nullable().optional(),
|
|
33
|
+
is_active: z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
export const upsertImapAccount = createServerFn({ method: "POST" })
|
|
36
|
+
.middleware([requireSupabaseAuth])
|
|
37
|
+
.inputValidator((input) => AccountInput.parse(input))
|
|
38
|
+
.handler(async ({ data, context }) => {
|
|
39
|
+
await assertAdmin(context.supabase, context.userId);
|
|
40
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
41
|
+
const payload = {
|
|
42
|
+
name: data.name,
|
|
43
|
+
email: data.email ?? null,
|
|
44
|
+
host: data.host,
|
|
45
|
+
port: data.port,
|
|
46
|
+
username: data.username,
|
|
47
|
+
use_tls: data.use_tls,
|
|
48
|
+
password_secret_name: data.password_secret_name ?? null,
|
|
49
|
+
is_active: data.is_active ?? true,
|
|
50
|
+
};
|
|
51
|
+
if (typeof data.password === "string" && data.password.length > 0) {
|
|
52
|
+
payload.password = data.password;
|
|
53
|
+
}
|
|
54
|
+
if (data.id) {
|
|
55
|
+
const { error } = await supabaseAdmin.from("imap_accounts").update(payload).eq("id", data.id);
|
|
56
|
+
if (error)
|
|
57
|
+
throw new Error(error.message);
|
|
58
|
+
return { ok: true, id: data.id };
|
|
59
|
+
}
|
|
60
|
+
const { data: row, error } = await supabaseAdmin.from("imap_accounts").insert(payload).select("id").single();
|
|
61
|
+
if (error)
|
|
62
|
+
throw new Error(error.message);
|
|
63
|
+
return { ok: true, id: row.id };
|
|
64
|
+
});
|
|
65
|
+
// Markeer 1 account als de actieve voor het mailbox_orders profiel
|
|
66
|
+
export const activateImapAccount = createServerFn({ method: "POST" })
|
|
67
|
+
.middleware([requireSupabaseAuth])
|
|
68
|
+
.inputValidator((input) => z.object({ account_id: z.string().uuid() }).parse(input))
|
|
69
|
+
.handler(async ({ data, context }) => {
|
|
70
|
+
await assertAdmin(context.supabase, context.userId);
|
|
71
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
72
|
+
// Zorg dat het standaardprofiel bestaat
|
|
73
|
+
const { data: prof } = await supabaseAdmin
|
|
74
|
+
.from("imap_profiles")
|
|
75
|
+
.upsert({ profile_key: "mailbox_orders", name: "Mailbox Orders", folder: "INBOX", polling_enabled: true, account_id: data.account_id }, { onConflict: "profile_key" })
|
|
76
|
+
.select("id")
|
|
77
|
+
.single();
|
|
78
|
+
// Update voor zekerheid (upsert zou ook account_id moeten zetten, maar PostgREST upsert kan default-merge gedrag hebben)
|
|
79
|
+
if (prof?.id) {
|
|
80
|
+
await supabaseAdmin.from("imap_profiles").update({ account_id: data.account_id, polling_enabled: true }).eq("id", prof.id);
|
|
81
|
+
}
|
|
82
|
+
return { ok: true };
|
|
83
|
+
});
|
|
84
|
+
export const deleteImapAccount = createServerFn({ method: "POST" })
|
|
85
|
+
.middleware([requireSupabaseAuth])
|
|
86
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
87
|
+
.handler(async ({ data, context }) => {
|
|
88
|
+
await assertAdmin(context.supabase, context.userId);
|
|
89
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
90
|
+
const { error } = await supabaseAdmin.from("imap_accounts").delete().eq("id", data.id);
|
|
91
|
+
if (error)
|
|
92
|
+
throw new Error(error.message);
|
|
93
|
+
return { ok: true };
|
|
94
|
+
});
|
|
95
|
+
export const testImapAccount = createServerFn({ method: "POST" })
|
|
96
|
+
.middleware([requireSupabaseAuth])
|
|
97
|
+
.inputValidator((input) => z.object({
|
|
98
|
+
id: z.string().uuid().optional(),
|
|
99
|
+
host: z.string().max(255).optional(),
|
|
100
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
101
|
+
username: z.string().max(255).optional(),
|
|
102
|
+
use_tls: z.boolean().optional(),
|
|
103
|
+
password: z.string().max(1024).optional(),
|
|
104
|
+
password_secret_name: z.string().max(255).optional(),
|
|
105
|
+
}).parse(input ?? {}))
|
|
106
|
+
.handler(async ({ data, context }) => {
|
|
107
|
+
await assertAdmin(context.supabase, context.userId);
|
|
108
|
+
const body = { action: "test" };
|
|
109
|
+
if (data.id)
|
|
110
|
+
body.account_id = data.id;
|
|
111
|
+
else {
|
|
112
|
+
body.host = data.host;
|
|
113
|
+
body.port = data.port;
|
|
114
|
+
body.username = data.username;
|
|
115
|
+
body.use_tls = data.use_tls;
|
|
116
|
+
body.password = data.password;
|
|
117
|
+
body.password_secret_name = data.password_secret_name;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const { data: res, error } = await context.supabase.functions.invoke("mailbox-imap", { body });
|
|
121
|
+
if (error)
|
|
122
|
+
return { ok: false, error: error.message };
|
|
123
|
+
return res;
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
return { ok: false, error: e?.message ?? "Onbekende fout" };
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// ─── Profielen ───────────────────────────────────────────────────────────────
|
|
130
|
+
export const listImapProfiles = createServerFn({ method: "GET" })
|
|
131
|
+
.middleware([requireSupabaseAuth])
|
|
132
|
+
.handler(async ({ context }) => {
|
|
133
|
+
const { data, error } = await context.supabase
|
|
134
|
+
.from("imap_profiles")
|
|
135
|
+
.select("id, profile_key, name, account_id, folder, polling_enabled, last_polled_at, last_uid_seen, last_error, created_at, updated_at, imap_accounts(id, name, email, host)")
|
|
136
|
+
.order("name");
|
|
137
|
+
if (error)
|
|
138
|
+
throw new Error(error.message);
|
|
139
|
+
return { items: Array.isArray(data) ? data : [] };
|
|
140
|
+
});
|
|
141
|
+
const ProfileInput = z.object({
|
|
142
|
+
id: z.string().uuid().optional(),
|
|
143
|
+
profile_key: z.string().min(1).max(64).regex(/^[a-z0-9_]+$/),
|
|
144
|
+
name: z.string().min(1).max(255),
|
|
145
|
+
account_id: z.string().uuid().nullable(),
|
|
146
|
+
folder: z.string().min(1).max(255),
|
|
147
|
+
polling_enabled: z.boolean(),
|
|
148
|
+
});
|
|
149
|
+
export const upsertImapProfile = createServerFn({ method: "POST" })
|
|
150
|
+
.middleware([requireSupabaseAuth])
|
|
151
|
+
.inputValidator((input) => ProfileInput.parse(input))
|
|
152
|
+
.handler(async ({ data, context }) => {
|
|
153
|
+
await assertAdmin(context.supabase, context.userId);
|
|
154
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
155
|
+
const payload = {
|
|
156
|
+
profile_key: data.profile_key,
|
|
157
|
+
name: data.name,
|
|
158
|
+
account_id: data.account_id,
|
|
159
|
+
folder: data.folder,
|
|
160
|
+
polling_enabled: data.polling_enabled,
|
|
161
|
+
};
|
|
162
|
+
if (data.id) {
|
|
163
|
+
const { error } = await supabaseAdmin.from("imap_profiles").update(payload).eq("id", data.id);
|
|
164
|
+
if (error)
|
|
165
|
+
throw new Error(error.message);
|
|
166
|
+
return { ok: true, id: data.id };
|
|
167
|
+
}
|
|
168
|
+
const { data: row, error } = await supabaseAdmin
|
|
169
|
+
.from("imap_profiles")
|
|
170
|
+
.upsert(payload, { onConflict: "profile_key" })
|
|
171
|
+
.select("id").single();
|
|
172
|
+
if (error)
|
|
173
|
+
throw new Error(error.message);
|
|
174
|
+
return { ok: true, id: row.id };
|
|
175
|
+
});
|
|
176
|
+
export const deleteImapProfile = createServerFn({ method: "POST" })
|
|
177
|
+
.middleware([requireSupabaseAuth])
|
|
178
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
179
|
+
.handler(async ({ data, context }) => {
|
|
180
|
+
await assertAdmin(context.supabase, context.userId);
|
|
181
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
182
|
+
const { error } = await supabaseAdmin.from("imap_profiles").delete().eq("id", data.id);
|
|
183
|
+
if (error)
|
|
184
|
+
throw new Error(error.message);
|
|
185
|
+
return { ok: true };
|
|
186
|
+
});
|
|
187
|
+
export const setProfileAccount = createServerFn({ method: "POST" })
|
|
188
|
+
.middleware([requireSupabaseAuth])
|
|
189
|
+
.inputValidator((input) => z.object({ profile_id: z.string().uuid(), account_id: z.string().uuid().nullable() }).parse(input))
|
|
190
|
+
.handler(async ({ data, context }) => {
|
|
191
|
+
await assertAdmin(context.supabase, context.userId);
|
|
192
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
193
|
+
const { error } = await supabaseAdmin.from("imap_profiles").update({ account_id: data.account_id }).eq("id", data.profile_id);
|
|
194
|
+
if (error)
|
|
195
|
+
throw new Error(error.message);
|
|
196
|
+
return { ok: true };
|
|
197
|
+
});
|
|
198
|
+
export const pollImapProfile = createServerFn({ method: "POST" })
|
|
199
|
+
.middleware([requireSupabaseAuth])
|
|
200
|
+
.inputValidator((input) => z.object({ profile_id: z.string().uuid() }).parse(input))
|
|
201
|
+
.handler(async ({ data, context }) => {
|
|
202
|
+
try {
|
|
203
|
+
const { data: res, error } = await context.supabase.functions.invoke("mailbox-imap", {
|
|
204
|
+
body: { action: "poll", profile_id: data.profile_id },
|
|
205
|
+
});
|
|
206
|
+
if (error)
|
|
207
|
+
return { ok: false, error: error.message, inserted: 0 };
|
|
208
|
+
const r = res;
|
|
209
|
+
// Auto-verwerk binnenkomende mails (alleen mailbox_orders profiel heeft AI-flow)
|
|
210
|
+
if (r.ok && (r.inserted ?? 0) > 0) {
|
|
211
|
+
const { autoProcessNewMessagesInternal } = await import("./mailbox-auto.server.js");
|
|
212
|
+
await autoProcessNewMessagesInternal();
|
|
213
|
+
}
|
|
214
|
+
return r;
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
return { ok: false, error: e?.message ?? "Onbekende fout", inserted: 0 };
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
export const listImapSyncLog = createServerFn({ method: "GET" })
|
|
221
|
+
.middleware([requireSupabaseAuth])
|
|
222
|
+
.inputValidator((input) => z.object({ profile_id: z.string().uuid().optional() }).parse(input ?? {}))
|
|
223
|
+
.handler(async ({ data, context }) => {
|
|
224
|
+
let q = context.supabase
|
|
225
|
+
.from("imap_sync_log")
|
|
226
|
+
.select("id, profile_id, account_id, started_at, finished_at, inserted, ok, error")
|
|
227
|
+
.order("started_at", { ascending: false })
|
|
228
|
+
.limit(50);
|
|
229
|
+
if (data.profile_id)
|
|
230
|
+
q = q.eq("profile_id", data.profile_id);
|
|
231
|
+
const { data: rows, error } = await q;
|
|
232
|
+
if (error)
|
|
233
|
+
throw new Error(error.message);
|
|
234
|
+
return { items: Array.isArray(rows) ? rows : [] };
|
|
235
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-auto.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-auto.server.ts"],"names":[],"mappings":"AAIA,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AA0LD,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,aAAa,CAAC,CA2C7E"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
async function parseOneMessage(messageId) {
|
|
2
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
3
|
+
const { data: msg, error: msgErr } = await supabaseAdmin
|
|
4
|
+
.from("mailbox_messages").select("*").eq("id", messageId).single();
|
|
5
|
+
if (msgErr || !msg)
|
|
6
|
+
return { ok: false, error: "Bericht niet gevonden" };
|
|
7
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
8
|
+
.update({ status: "parsing", error_message: null }).eq("id", msg.id);
|
|
9
|
+
try {
|
|
10
|
+
const { data: fields } = await supabaseAdmin
|
|
11
|
+
.from("mailbox_order_template_fields")
|
|
12
|
+
.select("*")
|
|
13
|
+
.order("sort_order", { ascending: true });
|
|
14
|
+
const templateFields = (fields ?? []).filter((f) => f.ai_enabled);
|
|
15
|
+
const [{ data: products }, { data: customers }, { data: suppliers }] = await Promise.all([
|
|
16
|
+
supabaseAdmin.from("products").select("id, product, product_type").order("product").range(0, 1999),
|
|
17
|
+
supabaseAdmin.from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999),
|
|
18
|
+
supabaseAdmin.from("suppliers").select("id, name").order("name").range(0, 999),
|
|
19
|
+
]);
|
|
20
|
+
const catalog = (products ?? []).map((p) => ({ id: p.id, name: p.product }));
|
|
21
|
+
const plantTypeSet = new Set();
|
|
22
|
+
(products ?? []).forEach((p) => { if (p.product_type)
|
|
23
|
+
plantTypeSet.add(p.product_type); });
|
|
24
|
+
const plantTypes = Array.from(plantTypeSet).sort().map((v) => ({ id: v, name: v }));
|
|
25
|
+
const spec = templateFields.map((f) => {
|
|
26
|
+
let allowed;
|
|
27
|
+
if (f.source === "database") {
|
|
28
|
+
switch (f.field_type) {
|
|
29
|
+
case "customer":
|
|
30
|
+
allowed = (customers ?? []).map((c) => ({ id: c.id, name: c.company_name }));
|
|
31
|
+
break;
|
|
32
|
+
case "product":
|
|
33
|
+
allowed = catalog;
|
|
34
|
+
break;
|
|
35
|
+
case "plant_type":
|
|
36
|
+
allowed = plantTypes;
|
|
37
|
+
break;
|
|
38
|
+
case "supplier":
|
|
39
|
+
allowed = (suppliers ?? []).map((s) => ({ id: s.id, name: s.name }));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
key: f.key,
|
|
45
|
+
label: f.label,
|
|
46
|
+
field_type: f.field_type,
|
|
47
|
+
ai_hint: f.ai_hint,
|
|
48
|
+
allowed,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const { parseOrderEmail } = await import("./mailbox-ai.server.js");
|
|
52
|
+
const parsed = await parseOrderEmail(msg.body_text ?? msg.body_html ?? "", msg.subject ?? "", spec, catalog);
|
|
53
|
+
const customerId = parsed.fields["customer"]?.value;
|
|
54
|
+
const deliveryDate = parsed.fields["delivery_date"]?.value;
|
|
55
|
+
const notesValue = parsed.fields["notes"]?.value;
|
|
56
|
+
const matchedCustomerId = typeof customerId === "string" && customerId.length === 36 ? customerId : null;
|
|
57
|
+
let finalCustomerId = matchedCustomerId;
|
|
58
|
+
if (!finalCustomerId && msg.from_email) {
|
|
59
|
+
const { data: customer } = await supabaseAdmin
|
|
60
|
+
.from("customers").select("id").eq("email", msg.from_email).maybeSingle();
|
|
61
|
+
finalCustomerId = customer?.id ?? null;
|
|
62
|
+
}
|
|
63
|
+
const { data: proposal, error: propErr } = await supabaseAdmin
|
|
64
|
+
.from("mailbox_order_proposals").insert({
|
|
65
|
+
message_id: msg.id,
|
|
66
|
+
matched_customer_id: finalCustomerId,
|
|
67
|
+
confidence: parsed.overall_confidence,
|
|
68
|
+
delivery_date: typeof deliveryDate === "string" ? deliveryDate : null,
|
|
69
|
+
notes: typeof notesValue === "string" ? notesValue : null,
|
|
70
|
+
parsed_payload: parsed,
|
|
71
|
+
status: "pending",
|
|
72
|
+
}).select("id").single();
|
|
73
|
+
if (propErr)
|
|
74
|
+
throw new Error(propErr.message);
|
|
75
|
+
const fieldRows = Object.entries(parsed.fields).map(([key, v]) => ({
|
|
76
|
+
proposal_id: proposal.id,
|
|
77
|
+
field_key: key,
|
|
78
|
+
value: v.value === undefined ? null : v.value,
|
|
79
|
+
ai_confidence: v.confidence,
|
|
80
|
+
ai_filled: v.value !== null && v.value !== undefined,
|
|
81
|
+
needs_review: v.needs_review,
|
|
82
|
+
}));
|
|
83
|
+
if (fieldRows.length > 0) {
|
|
84
|
+
await supabaseAdmin.from("mailbox_proposal_field_values").insert(fieldRows);
|
|
85
|
+
}
|
|
86
|
+
if (parsed.lines.length > 0) {
|
|
87
|
+
const linesToInsert = parsed.lines.map((l, idx) => ({
|
|
88
|
+
proposal_id: proposal.id,
|
|
89
|
+
product_id: l.product_id,
|
|
90
|
+
raw_product_text: l.raw_product_text,
|
|
91
|
+
quantity: l.quantity,
|
|
92
|
+
unit: l.unit,
|
|
93
|
+
match_confidence: l.match_confidence,
|
|
94
|
+
sort_order: idx,
|
|
95
|
+
}));
|
|
96
|
+
await supabaseAdmin.from("mailbox_proposal_lines").insert(linesToInsert);
|
|
97
|
+
}
|
|
98
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
99
|
+
.update({ status: "parsed" }).eq("id", msg.id);
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
proposalId: proposal.id,
|
|
103
|
+
parsed,
|
|
104
|
+
customerId: finalCustomerId,
|
|
105
|
+
deliveryDate: typeof deliveryDate === "string" ? deliveryDate : null,
|
|
106
|
+
notes: typeof notesValue === "string" ? notesValue : null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
111
|
+
.update({ status: "failed", error_message: e?.message ?? "Parsefout" }).eq("id", msg.id);
|
|
112
|
+
return { ok: false, error: e?.message ?? "Parsefout" };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function autoApproveProposal(args) {
|
|
116
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
117
|
+
const customFields = {};
|
|
118
|
+
for (const [k, v] of Object.entries(args.parsed.fields)) {
|
|
119
|
+
customFields[k] = v.value ?? null;
|
|
120
|
+
}
|
|
121
|
+
const orderNumber = `MB-${Date.now()}`;
|
|
122
|
+
const { data: order, error: orderErr } = await supabaseAdmin
|
|
123
|
+
.from("orders").insert({
|
|
124
|
+
order_number: orderNumber,
|
|
125
|
+
customer_name: args.customerName,
|
|
126
|
+
customer_id: args.customerId,
|
|
127
|
+
order_date: new Date().toISOString().slice(0, 10),
|
|
128
|
+
delivery_date: args.deliveryDate,
|
|
129
|
+
status: "nieuw",
|
|
130
|
+
total: "0",
|
|
131
|
+
notes: args.notes,
|
|
132
|
+
custom_fields: { ...customFields, source: "mailbox", auto_created: true },
|
|
133
|
+
}).select("id").single();
|
|
134
|
+
if (orderErr || !order)
|
|
135
|
+
return false;
|
|
136
|
+
const items = args.parsed.lines.map((l) => ({
|
|
137
|
+
order_id: order.id,
|
|
138
|
+
product_id: l.product_id,
|
|
139
|
+
product_name: l.raw_product_text || "Onbekend product",
|
|
140
|
+
quantity: Math.max(1, Math.round(l.quantity ?? 1)),
|
|
141
|
+
unit: l.unit ?? "stuks",
|
|
142
|
+
}));
|
|
143
|
+
if (items.length > 0) {
|
|
144
|
+
const { error: itemsErr } = await supabaseAdmin.from("order_items").insert(items);
|
|
145
|
+
if (itemsErr)
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
await supabaseAdmin.from("mailbox_order_proposals").update({
|
|
149
|
+
status: "approved",
|
|
150
|
+
reviewed_at: new Date().toISOString(),
|
|
151
|
+
created_order_id: order.id,
|
|
152
|
+
}).eq("id", args.proposalId);
|
|
153
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
154
|
+
.update({ status: "converted" }).eq("id", args.messageId);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
export async function autoProcessNewMessagesInternal() {
|
|
158
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
159
|
+
const { data: newMsgs } = await supabaseAdmin
|
|
160
|
+
.from("mailbox_messages")
|
|
161
|
+
.select("id")
|
|
162
|
+
.eq("status", "new")
|
|
163
|
+
.order("received_at", { ascending: true })
|
|
164
|
+
.limit(20);
|
|
165
|
+
const result = { processed: 0, approved: 0, pending: 0, failed: 0 };
|
|
166
|
+
for (const m of newMsgs ?? []) {
|
|
167
|
+
result.processed++;
|
|
168
|
+
const r = await parseOneMessage(m.id);
|
|
169
|
+
if (!r.ok || !r.proposalId || !r.parsed) {
|
|
170
|
+
result.failed++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const hasCustomer = !!r.customerId;
|
|
174
|
+
const hasDelivery = !!r.deliveryDate;
|
|
175
|
+
const hasLines = r.parsed.lines.length > 0;
|
|
176
|
+
if (!hasCustomer || !hasDelivery || !hasLines) {
|
|
177
|
+
result.pending++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Haal klantnaam op
|
|
181
|
+
const { data: cust } = await supabaseAdmin
|
|
182
|
+
.from("customers").select("company_name").eq("id", r.customerId).maybeSingle();
|
|
183
|
+
const ok = await autoApproveProposal({
|
|
184
|
+
proposalId: r.proposalId,
|
|
185
|
+
messageId: m.id,
|
|
186
|
+
customerId: r.customerId,
|
|
187
|
+
customerName: cust?.company_name ?? "Onbekende klant",
|
|
188
|
+
deliveryDate: r.deliveryDate,
|
|
189
|
+
notes: r.notes ?? null,
|
|
190
|
+
parsed: r.parsed,
|
|
191
|
+
});
|
|
192
|
+
if (ok)
|
|
193
|
+
result.approved++;
|
|
194
|
+
else
|
|
195
|
+
result.pending++;
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
@@ -107,7 +107,7 @@ export declare const runTestCase: import("@tanstack/start-client-core").Optional
|
|
|
107
107
|
}, Promise<{
|
|
108
108
|
checks: Check[];
|
|
109
109
|
passed: boolean;
|
|
110
|
-
filter: import("
|
|
110
|
+
filter: import("../lib/mailbox-filter.js").FilterResult;
|
|
111
111
|
sender: string;
|
|
112
112
|
forwarded: boolean;
|
|
113
113
|
analyzed: boolean;
|
|
@@ -119,7 +119,7 @@ export declare const runTestCase: import("@tanstack/start-client-core").Optional
|
|
|
119
119
|
} | {
|
|
120
120
|
checks: Check[];
|
|
121
121
|
passed: boolean;
|
|
122
|
-
filter: import("
|
|
122
|
+
filter: import("../lib/mailbox-filter.js").FilterResult;
|
|
123
123
|
sender: string;
|
|
124
124
|
forwarded: boolean;
|
|
125
125
|
analyzed: boolean;
|
|
@@ -113,8 +113,8 @@ export const countLikelyFakeProposals = createServerFn({ method: "GET" })
|
|
|
113
113
|
.handler(async () => {
|
|
114
114
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
115
115
|
const admin = supabaseAdmin;
|
|
116
|
-
const { loadFilterSettings } = await import("
|
|
117
|
-
const { classifyMessage } = await import("
|
|
116
|
+
const { loadFilterSettings } = await import("../lib/mailbox-ingest.server.js");
|
|
117
|
+
const { classifyMessage } = await import("../lib/mailbox-filter.js");
|
|
118
118
|
const s = await loadFilterSettings(admin);
|
|
119
119
|
let total = 0, known = 0, wouldSkip = 0, withSignals = 0;
|
|
120
120
|
for (let from = 0; from < 20000; from += 1000) {
|
|
@@ -236,7 +236,7 @@ export const runTestCase = createServerFn({ method: "POST" })
|
|
|
236
236
|
const { data: tc, error } = await admin.from("mailbox_test_cases").select("*").eq("id", data.id).maybeSingle();
|
|
237
237
|
if (error || !tc)
|
|
238
238
|
throw new Error("Voorbeeld niet gevonden.");
|
|
239
|
-
const { dryRunAnalysis } = await import("
|
|
239
|
+
const { dryRunAnalysis } = await import("../lib/mailbox-ingest.server.js");
|
|
240
240
|
const run = await dryRunAnalysis(admin, {
|
|
241
241
|
subject: tc.subject ?? "",
|
|
242
242
|
body: tc.body_text ?? "",
|
|
@@ -2,7 +2,7 @@ import { type ActiveMailboxStatus } from "./mailbox-active.js";
|
|
|
2
2
|
export declare const getActiveOrdersMailbox: import("@tanstack/start-client-core").OptionalFetcher<undefined, undefined, Promise<ActiveMailboxStatus>>;
|
|
3
3
|
export declare const syncMailboxNow: import("@tanstack/start-client-core").OptionalFetcher<undefined, (input: unknown) => {
|
|
4
4
|
trigger: "realtime" | "manual";
|
|
5
|
-
}, Promise<import("
|
|
5
|
+
}, Promise<import("../lib/mailbox-ingest.server.js").IngestRunResult | {
|
|
6
6
|
ok: false;
|
|
7
7
|
error: string;
|
|
8
8
|
processed: 0;
|
|
@@ -64,7 +64,7 @@ export const syncMailboxNow = createServerFn({ method: "POST" })
|
|
|
64
64
|
if (userErr || !userData?.user) {
|
|
65
65
|
return { ok: false, error: "unauthorized", processed: 0, inserted: 0, errors: 1 };
|
|
66
66
|
}
|
|
67
|
-
const { runIngest } = await import("
|
|
67
|
+
const { runIngest } = await import("../lib/mailbox-ingest.server.js");
|
|
68
68
|
return await runIngest(data.trigger);
|
|
69
69
|
}
|
|
70
70
|
catch (e) {
|
|
@@ -95,7 +95,7 @@ export const reparseProposalNow = createServerFn({ method: "POST" })
|
|
|
95
95
|
.inputValidator((input) => z.object({ proposalId: z.string().uuid() }).parse(input))
|
|
96
96
|
.handler(async ({ data }) => {
|
|
97
97
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
98
|
-
const { reparseProposal } = await import("
|
|
98
|
+
const { reparseProposal } = await import("../lib/mailbox-ingest.server.js");
|
|
99
99
|
return reparseProposal(supabaseAdmin, data.proposalId);
|
|
100
100
|
});
|
|
101
101
|
/** Detecteert een gewijzigde AI-agent en herverwerkt openstaande voorstellen. */
|
|
@@ -103,7 +103,7 @@ export const refreshProposalsForAgentChange = createServerFn({ method: "POST" })
|
|
|
103
103
|
.middleware([requireSupabaseAuth])
|
|
104
104
|
.handler(async () => {
|
|
105
105
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
106
|
-
const { reparseChangedAgentProposals } = await import("
|
|
106
|
+
const { reparseChangedAgentProposals } = await import("../lib/mailbox-ingest.server.js");
|
|
107
107
|
return reparseChangedAgentProposals(supabaseAdmin);
|
|
108
108
|
});
|
|
109
109
|
// ─── Messages & Proposals ────────────────────────────────────────────────────
|
|
@@ -125,7 +125,7 @@ export const parseMessage = createServerFn({ method: "POST" })
|
|
|
125
125
|
.inputValidator((input) => z.object({ messageId: z.string().uuid() }).parse(input))
|
|
126
126
|
.handler(async ({ data }) => {
|
|
127
127
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
128
|
-
const { createStubProposal } = await import("
|
|
128
|
+
const { createStubProposal } = await import("../lib/mailbox-ingest.server.js");
|
|
129
129
|
const { data: msg, error: msgErr } = await supabaseAdmin
|
|
130
130
|
.from("mailbox_messages").select("*").eq("id", data.messageId).single();
|
|
131
131
|
if (msgErr || !msg)
|
|
@@ -1189,7 +1189,7 @@ export const analyzeMessageNow = createServerFn({ method: "POST" })
|
|
|
1189
1189
|
.inputValidator((input) => z.object({ messageId: z.string().uuid() }).parse(input))
|
|
1190
1190
|
.handler(async ({ data }) => {
|
|
1191
1191
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1192
|
-
const { createStubProposal } = await import("
|
|
1192
|
+
const { createStubProposal } = await import("../lib/mailbox-ingest.server.js");
|
|
1193
1193
|
try {
|
|
1194
1194
|
const res = await createStubProposal(supabaseAdmin, data.messageId, null, { force: true, trigger: "handmatig" });
|
|
1195
1195
|
if (res.status === "busy")
|
|
@@ -1205,7 +1205,7 @@ export const countOutdatedProposals = createServerFn({ method: "GET" })
|
|
|
1205
1205
|
.middleware([requireSupabaseAuth])
|
|
1206
1206
|
.handler(async () => {
|
|
1207
1207
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1208
|
-
const { countOutdatedAgentProposals } = await import("
|
|
1208
|
+
const { countOutdatedAgentProposals } = await import("../lib/mailbox-ingest.server.js");
|
|
1209
1209
|
return { count: await countOutdatedAgentProposals(supabaseAdmin) };
|
|
1210
1210
|
});
|
|
1211
1211
|
// ─── Stickervinkje: beschikbaarheid + standaardwaarden ─────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/mailbox-orders",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.59",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@cloudflare/vite-plugin": "^1.25.5",
|
|
52
52
|
"@eslint/js": "^9.32.0",
|
|
53
|
-
"@flowselections/core": "1.0.
|
|
53
|
+
"@flowselections/core": "1.0.37",
|
|
54
54
|
"@flowselections/e-mail-authenticatie-module": "^1.0.8",
|
|
55
55
|
"@lovable.dev/vite-tanstack-config": "2.26.5",
|
|
56
56
|
"@rsbuild/core": "^1.0.0",
|