@flowselections/mailbox-orders 1.0.17 → 1.0.19
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/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/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.d.ts +16055 -9007
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +2 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +16055 -9007
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.js +92 -0
- package/dist-lib/integrations/supabase/types.d.ts +16247 -9101
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +4 -0
- 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.functions.d.ts +2 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +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";
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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
|
+
}
|
|
@@ -290,10 +290,12 @@ export declare const listOrderCarts: import("@tanstack/start-client-core").Requi
|
|
|
290
290
|
boxes: number | null;
|
|
291
291
|
cart_id: string;
|
|
292
292
|
crates: number;
|
|
293
|
+
crates_in_layer: number | null;
|
|
293
294
|
crates_per_layer: number;
|
|
294
295
|
created_at: string;
|
|
295
296
|
id: string;
|
|
296
297
|
layer_fraction: number;
|
|
298
|
+
layer_index: number | null;
|
|
297
299
|
layer_share_pct: number;
|
|
298
300
|
layers_per_cart: number;
|
|
299
301
|
order_item_id: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAyB9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;QAiBf,MAAM;cACM,MAAM;mBACI,MAAM;kBACE,MAAM,GAAG,IAAI;YACzB,MAAM,GAAG,IAAI;gBACL,MAAM,GAAG,IAAI;sBACD,MAAM,GAAG,IAAI;;;GAGnE,CAAC;AAWL,eAAO,MAAM,cAAc,2EACD,OAAO;;;;;;;;GA+B7B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;GAWzB,CAAC;AAEL,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;8CAEL,OAAO;;;;;GAK7B,CAAC;AAEL,iFAAiF;AACjF,eAAO,MAAM,8BAA8B;;;;;;;;;GAMvC,CAAC;AAML,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;GAWrB,CAAC;AAEL,eAAO,MAAM,YAAY;;;;8CAEC,OAAO;;;;GAa7B,CAAC;AAKL,eAAO,MAAM,aAAa;;;;8CAEA,OAAO;;;;GAmC7B,CAAC;
|
|
1
|
+
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -149,8 +149,8 @@ export const listProposals = createServerFn({ method: "GET" })
|
|
|
149
149
|
const sel = (s) => s;
|
|
150
150
|
let q = supabaseAdmin
|
|
151
151
|
.from("mailbox_order_proposals")
|
|
152
|
-
.select(sel("id, status, created_at, reviewed_at, matched_customer_id, ai_ok
|
|
153
|
-
"mailbox_messages(subject, from_email, from_name, received_at, connection_id,
|
|
152
|
+
.select(sel("id, status, created_at, reviewed_at, matched_customer_id, ai_ok, " +
|
|
153
|
+
"mailbox_messages(subject, from_email, from_name, received_at, connection_id, preview), " +
|
|
154
154
|
"mailbox_proposal_field_values(field_key, value, needs_review)"))
|
|
155
155
|
.order("created_at", { ascending: false })
|
|
156
156
|
.limit(200);
|
|
@@ -159,17 +159,17 @@ export const listProposals = createServerFn({ method: "GET" })
|
|
|
159
159
|
const { data: rows, error } = await q;
|
|
160
160
|
if (error)
|
|
161
161
|
throw new Error(error.message);
|
|
162
|
-
//
|
|
162
|
+
// De preview wordt in de database bijgehouden (trigger), zodat de lijst geen
|
|
163
|
+
// volledige mailtekst hoeft te lezen.
|
|
163
164
|
const items = (Array.isArray(rows) ? rows : []).map((row) => {
|
|
164
165
|
const m = row?.mailbox_messages;
|
|
165
166
|
if (!m)
|
|
166
167
|
return row;
|
|
167
|
-
const
|
|
168
|
-
const preview = stripInvisible(String(body_text ?? ""))
|
|
168
|
+
const preview = stripInvisible(String(m.preview ?? ""))
|
|
169
169
|
.replace(/\s+/g, " ")
|
|
170
170
|
.trim()
|
|
171
171
|
.slice(0, 180);
|
|
172
|
-
return { ...row, mailbox_messages: { ...
|
|
172
|
+
return { ...row, mailbox_messages: { ...m, preview } };
|
|
173
173
|
});
|
|
174
174
|
return { items };
|
|
175
175
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/mailbox-orders",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@cloudflare/vite-plugin": "^1.25.5",
|
|
48
48
|
"@eslint/js": "^9.32.0",
|
|
49
|
-
"@flowselections/core": "1.0.
|
|
49
|
+
"@flowselections/core": "1.0.21",
|
|
50
50
|
"@lovable.dev/vite-tanstack-config": "2.13.1",
|
|
51
51
|
"@rsbuild/core": "^1.0.0",
|
|
52
52
|
"@tailwindcss/vite": "^4.2.1",
|