Add document lifecycle tracking with order/production status

- Extended PDFStatus with full lifecycle: pending → uploading → in_basket →
  ordering → in_production → sent/canceled
- Added shipDocument() and getDocumentStatus() API methods
- Grouped UI sections: Ready to Upload, In Basket, In Production, Completed
- Order button for documents in basket to place production order
- Refresh button to check current status from Binect server
- Display price and recipient address for uploaded documents
- Status icons and color-coded indicators for each state

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 10:17:28 +01:00
parent 724940ebf7
commit 3a48d4f497
5 changed files with 815 additions and 78 deletions

View File

@@ -325,3 +325,151 @@ export async function testConnection(username: string, password: string): Promis
return false;
}
}
/**
* Document status information returned by getDocumentStatus
*/
export interface DocumentStatusInfo {
status: number;
statusText: string;
price?: number;
recipientAddress?: string;
}
/**
* Ship a document (place order for production)
*
* This announces the document for delivery, transitioning it from
* SHIPPABLE to PRODUCTION_QUEUE.
*
* @param documentId - Binect document ID
* @param username - Binect username
* @param password - Binect password
* @returns Updated status info
*/
export async function shipDocument(
documentId: number,
username: string,
password: string
): Promise<DocumentStatusInfo> {
console.log('[Binect API] Shipping document:', documentId);
try {
const client = new BinectClient({
username,
password,
});
// Send the document for production
const sending = await client.sendings.send(String(documentId));
console.log('[Binect API] Document shipped successfully');
console.log('[Binect API] New status:', sending.status);
return {
status: sending.status,
statusText: getStatusText(sending.status),
price: sending.price,
};
} catch (error) {
console.error('[Binect API] Ship error:', error);
if (error instanceof BinectAuthError) {
throw new BinectAPIError('Invalid credentials', 401);
}
if (error instanceof BinectApiError) {
// Check for insufficient balance (error code 2330)
if (error.message.includes('2330') || error.message.includes('balance')) {
throw new BinectAPIError('Insufficient account balance', 402);
}
throw BinectAPIError.fromBinectError(error);
}
if (error instanceof BinectAPIError) {
throw error;
}
throw new BinectAPIError(
`Failed to ship document: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Get current status of a document
*
* @param documentId - Binect document ID
* @param username - Binect username
* @param password - Binect password
* @returns Current status info
*/
export async function getDocumentStatus(
documentId: number,
username: string,
password: string
): Promise<DocumentStatusInfo> {
console.log('[Binect API] Getting document status:', documentId);
try {
const client = new BinectClient({
username,
password,
});
// Fetch document details
const doc = await client.documents.get(String(documentId));
console.log('[Binect API] Document status:', doc.status);
// Extract price and recipient if available
let price: number | undefined;
let recipientAddress: string | undefined;
if (doc.letter?.letterData) {
price = doc.letter.letterData.price?.priceAfterTax;
recipientAddress = doc.letter.letterData.recipientAddress;
}
return {
status: doc.status.code,
statusText: doc.status.text || getStatusText(doc.status.code),
price,
recipientAddress,
};
} catch (error) {
console.error('[Binect API] Get status error:', error);
if (error instanceof BinectAuthError) {
throw new BinectAPIError('Invalid credentials', 401);
}
if (error instanceof BinectApiError) {
throw BinectAPIError.fromBinectError(error);
}
if (error instanceof BinectAPIError) {
throw error;
}
throw new BinectAPIError(
`Failed to get document status: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Get human-readable status text for a Binect status code
*/
function getStatusText(statusCode: number): string {
switch (statusCode) {
case 1: return 'In preparation';
case 2: return 'Ready to ship';
case 3: return 'In production queue';
case 4: return 'Printing';
case 5: return 'Sent';
case 6: return 'Canceled';
case 7: return 'Has errors';
default: return 'Unknown status';
}
}