/** Types for the ERPNext commands (see src-tauri/src/commands/erpnext.rs); the UI helpers live in erpnextUi.ts. */ export type ErpnextNamingMode = "mirror" | "series"; /** What the backend returns. The API secret is never included: `apiSecretSet` says whether one is stored. */ export interface ErpnextConfig { baseUrl: string; apiKey: string; apiSecretSet: boolean; extraCaPem: string; company: string; companyAddress: string; namingMode: ErpnextNamingMode; namingSeries: string; incomeAccount: string; costCenter: string; cgstAccount: string; sgstAccount: string; utgstAccount: string; igstAccount: string; taxTemplateIntra: string; taxTemplateInter: string; paymentBankAccount: string; tdsAccount: string; defaultItemCode: string; /** Voiced unit (second, minute, hour, session, unit) to ERPNext UOM. */ uomMap: Record; customerGroup: string; territory: string; sellingPriceList: string; submitOnPush: boolean; attachPdf: boolean; autoPushOnIssue: boolean; createMissingCustomers: boolean; /** The last connection test, or null if none was stored. */ lastDetectResult: ErpnextConnectionTest | null; } /** * What the webview sends to save or test. A blank `apiSecret` keeps the stored secret, a value replaces it, * and `clearSecret` removes it. `lastDetectResult` omitted keeps the stored one. */ export type ErpnextConfigInput = Omit & { apiSecret: string; clearSecret?: boolean; lastDetectResult?: ErpnextConnectionTest | null; }; export interface ErpnextWarning { code: string; message: string; } export interface ErpnextVersions { frappe: string | null; erpnext: string | null; indiaCompliance: string | null; } export interface ErpnextConnectionTest { user: string; versions: ErpnextVersions; features: { v2Naming: boolean; indiaCompliance: boolean }; indiaCompliance: boolean; warnings: ErpnextWarning[]; } export interface ErpnextOptionItem { /** The value to store (the document name). */ name: string; label: string; detail: string; } export interface ErpnextOptions { companies: ErpnextOptionItem[]; companyAddresses: ErpnextOptionItem[]; incomeAccounts: ErpnextOptionItem[]; taxAccounts: ErpnextOptionItem[]; taxTemplates: ErpnextOptionItem[]; namingSeries: ErpnextOptionItem[]; itemGroups: ErpnextOptionItem[]; uoms: ErpnextOptionItem[]; costCenters: ErpnextOptionItem[]; priceLists: ErpnextOptionItem[]; customerGroups: ErpnextOptionItem[]; territories: ErpnextOptionItem[]; /** Lists that failed to load; the others are still filled. */ errors: { list: string; message: string }[]; } export type ErpnextErrorKind = | "auth" | "permission" | "not_found" | "duplicate" | "validation" | "rate_limit" | "server" | "timeout" | "network" | "config" | "protocol" | "conflict" | "precondition" | "other"; /** `refused`: nothing was sent and nothing was recorded (a draft or cancelled invoice). */ export type ErpnextPushStatus = "synced" | "error" | "conflict" | "refused"; export interface ErpnextPushResult { invoiceId: number; number: string; ok: boolean; status: ErpnextPushStatus; remoteName: string; /** 0 draft, 1 submitted. */ remoteDocstatus: number; /** Created by this call, not found already there. */ created: boolean; /** Already synced with the same payload; nothing was done. */ noOp: boolean; /** The archived PDF is attached on the remote document. */ attached: boolean; error: string | null; errorKind: ErpnextErrorKind | null; /** Non-fatal notes, e.g. sent without the PDF because the invoice is not archived. */ warnings: string[]; } export interface ErpnextPaymentPushResult { paymentId: number; invoiceId: number; ok: boolean; entryName: string | null; /** Sent earlier; nothing was posted. */ alreadySynced: boolean; error: string | null; errorKind: ErpnextErrorKind | null; } /** `none` until something was pushed. */ export type ErpnextSyncState = "none" | "pending" | "synced" | "error" | "conflict"; export interface ErpnextSyncStatus { invoiceId: number; status: ErpnextSyncState; remoteName: string; /** 0 draft, 1 submitted. */ remoteDocstatus: number; lastError: string; syncedAt: string | null; attached: boolean; }