feat(delivery): add supplier article lookup, article creation, and unified parsing
- Unified PDF/TXT parsing to single POST /api/delivery/parse endpoint - Added supplier article lookup with SKU/EAN matching in delivery lines - Added "Artikel anlegen" button to create articles from missing SKUs - Fixed delivery line taxrate field (was sending 'tax', now 'taxrate') - Extended TXT file support for rescan functionality - Updated ApiClient with getArticles method for supplier article lookup - Added supplier article state management to deliveryEditorAutomat Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -80,6 +80,18 @@ export class ApiClient {
|
||||
update: (id, body) => this.#json('PATCH', `/api/supplier/${enc(id)}`, { body }),
|
||||
/** DELETE /api/supplier/{id} → 204 */
|
||||
delete: (id) => this.#empty('DELETE', `/api/supplier/${enc(id)}`),
|
||||
/** GET /api/supplier/{id}/articles?skus=... → { articles: SupplierArticle[] } */
|
||||
getArticles: (id, skus) => {
|
||||
let query = '';
|
||||
if (skus != null) {
|
||||
const list = Array.isArray(skus) ? skus : [skus];
|
||||
const filtered = list.map((s) => String(s).trim()).filter(Boolean);
|
||||
if (filtered.length > 0) {
|
||||
query = `?skus=${encodeURIComponent(filtered.join(','))}`;
|
||||
}
|
||||
}
|
||||
return this.#json('GET', `/api/supplier/${enc(id)}/articles${query}`);
|
||||
},
|
||||
};
|
||||
|
||||
this.delivery = {
|
||||
@@ -94,14 +106,15 @@ export class ApiClient {
|
||||
/** DELETE /api/delivery/{id} → 204 */
|
||||
delete: (id) => this.#empty('DELETE', `/api/delivery/${enc(id)}`),
|
||||
/**
|
||||
* POST /api/delivery/parsePDF — multipart/form-data, field "pdf"
|
||||
* POST /api/delivery/parse — multipart/form-data, field "file"
|
||||
* Accepts PDF or text files (max 50 MB), detection via mimetype/filename
|
||||
* Returns an async generator that yields SSE event objects:
|
||||
* { event: 'status'|'llm_call'|'tool_call'|'usage'|'progress'|'result'|'error', data: object }
|
||||
*/
|
||||
parsePdfSse: async function* (file) {
|
||||
parseSse: async function* (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('pdf', file, file.name);
|
||||
const res = await fetch('/api/delivery/parsePDF', {
|
||||
formData.append('file', file, file.name);
|
||||
const res = await fetch('/api/delivery/parse', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
|
||||
@@ -611,6 +611,22 @@ export const supplier = {
|
||||
delete(id) {
|
||||
return http(`/api/supplier/${id}`, { method: 'DELETE' });
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/supplier/:id/articles?skus=...
|
||||
* → 200 { articles: SupplierArticle[] }
|
||||
*/
|
||||
getArticles(id, skus) {
|
||||
let query = '';
|
||||
if (skus != null) {
|
||||
const list = Array.isArray(skus) ? skus : [skus];
|
||||
const filtered = list.map((s) => String(s).trim()).filter(Boolean);
|
||||
if (filtered.length > 0) {
|
||||
query = `?skus=${encodeURIComponent(filtered.join(','))}`;
|
||||
}
|
||||
}
|
||||
return http(`/api/supplier/${encodeURIComponent(id)}/articles${query}`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -662,18 +678,6 @@ export const delivery = {
|
||||
return http(`/api/delivery/${id}`, { method: 'DELETE' });
|
||||
},
|
||||
|
||||
/**
|
||||
* POST /api/delivery/parse-pdf
|
||||
* Body: raw PDF bytes (Content-Type: application/pdf)
|
||||
*/
|
||||
parsePdf(file) {
|
||||
return httpBinary('/api/delivery/parse-pdf', {
|
||||
method: 'POST',
|
||||
contentType: 'application/pdf',
|
||||
body: file,
|
||||
headers: file?.name ? { 'X-Filename': encodeURIComponent(file.name) } : undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user