247 lines
6.5 KiB
C#
247 lines
6.5 KiB
C#
|
|
using System.Text.Json.Serialization;
|
||
|
|
|
||
|
|
namespace Books.Api.AiBookkeeper;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Client for the AI Bookkeeper service that analyzes documents
|
||
|
|
/// and suggests bookkeeping entries.
|
||
|
|
/// </summary>
|
||
|
|
public interface IAiBookkeeperClient
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Process a document (invoice, receipt, etc.) and get AI-suggested bookkeeping.
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="document">The document file stream</param>
|
||
|
|
/// <param name="fileName">Original filename</param>
|
||
|
|
/// <param name="contentType">MIME type of the document</param>
|
||
|
|
/// <param name="chartOfAccounts">Chart of accounts data</param>
|
||
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
||
|
|
/// <returns>AI extraction and suggested bookkeeping</returns>
|
||
|
|
Task<AiBookkeeperResponse> ProcessDocumentAsync(
|
||
|
|
Stream document,
|
||
|
|
string fileName,
|
||
|
|
string contentType,
|
||
|
|
ChartOfAccountsDto chartOfAccounts,
|
||
|
|
CancellationToken cancellationToken = default);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Response from AI Bookkeeper document processing.
|
||
|
|
/// </summary>
|
||
|
|
public class AiBookkeeperResponse
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Whether the document was successfully analyzed.
|
||
|
|
/// </summary>
|
||
|
|
public bool Success { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Error message if processing failed.
|
||
|
|
/// </summary>
|
||
|
|
public string? ErrorMessage { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Extracted document information.
|
||
|
|
/// </summary>
|
||
|
|
public DocumentExtraction? Extraction { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Suggested bookkeeping entry.
|
||
|
|
/// </summary>
|
||
|
|
public BookkeepingSuggestion? Suggestion { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Extracted information from the document.
|
||
|
|
/// </summary>
|
||
|
|
public class DocumentExtraction
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Detected document type (invoice, receipt, credit_note, etc.)
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("documentType")]
|
||
|
|
public string? DocumentType { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Vendor/supplier name.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("vendor")]
|
||
|
|
public string? Vendor { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Vendor CVR number (Danish company registration).
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("vendorCvr")]
|
||
|
|
public string? VendorCvr { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Invoice/receipt number.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("invoiceNumber")]
|
||
|
|
public string? InvoiceNumber { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Document date.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("date")]
|
||
|
|
public DateOnly? Date { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Due date (for invoices).
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("dueDate")]
|
||
|
|
public DateOnly? DueDate { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Total amount including VAT.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("totalAmount")]
|
||
|
|
public decimal? TotalAmount { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Amount excluding VAT.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("amountExVat")]
|
||
|
|
public decimal? AmountExVat { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// VAT amount.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("vatAmount")]
|
||
|
|
public decimal? VatAmount { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Currency code (default DKK).
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("currency")]
|
||
|
|
public string Currency { get; set; } = "DKK";
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Extracted line items.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("lineItems")]
|
||
|
|
public List<ExtractedLineItem> LineItems { get; set; } = [];
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Payment reference (FIK, girocard number, etc.)
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("paymentReference")]
|
||
|
|
public string? PaymentReference { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Raw text extracted from the document.
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("rawText")]
|
||
|
|
public string? RawText { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Extracted line item from invoice/receipt.
|
||
|
|
/// </summary>
|
||
|
|
public class ExtractedLineItem
|
||
|
|
{
|
||
|
|
[JsonPropertyName("description")]
|
||
|
|
public string? Description { get; set; }
|
||
|
|
|
||
|
|
[JsonPropertyName("quantity")]
|
||
|
|
public decimal? Quantity { get; set; }
|
||
|
|
|
||
|
|
[JsonPropertyName("unitPrice")]
|
||
|
|
public decimal? UnitPrice { get; set; }
|
||
|
|
|
||
|
|
[JsonPropertyName("amount")]
|
||
|
|
public decimal? Amount { get; set; }
|
||
|
|
|
||
|
|
[JsonPropertyName("vatRate")]
|
||
|
|
public decimal? VatRate { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// AI-suggested bookkeeping entry.
|
||
|
|
/// </summary>
|
||
|
|
public class BookkeepingSuggestion
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Suggested description for the journal entry.
|
||
|
|
/// </summary>
|
||
|
|
public string? Description { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Suggested account lines.
|
||
|
|
/// </summary>
|
||
|
|
public List<SuggestedLine> Lines { get; set; } = [];
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Overall confidence in the suggestion (0.0 - 1.0).
|
||
|
|
/// </summary>
|
||
|
|
public decimal Confidence { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// A suggested bookkeeping line with standard account number.
|
||
|
|
/// </summary>
|
||
|
|
public class SuggestedLine
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Standard account number from Erhvervsstyrelsen.
|
||
|
|
/// </summary>
|
||
|
|
public string? StandardAccountNumber { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Suggested account name/description.
|
||
|
|
/// </summary>
|
||
|
|
public string? AccountName { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Debit amount.
|
||
|
|
/// </summary>
|
||
|
|
public decimal DebitAmount { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Credit amount.
|
||
|
|
/// </summary>
|
||
|
|
public decimal CreditAmount { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// VAT code (I25, U25, etc.)
|
||
|
|
/// </summary>
|
||
|
|
public string? VatCode { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Confidence in this specific line (0.0 - 1.0).
|
||
|
|
/// </summary>
|
||
|
|
public decimal Confidence { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// AI service's single account suggestion.
|
||
|
|
/// The AI analyzes the document and suggests the most appropriate expense account.
|
||
|
|
/// </summary>
|
||
|
|
public class AiAccountSuggestion
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Company account number suggested by AI (e.g., "6080" for parking).
|
||
|
|
/// </summary>
|
||
|
|
public required string AccountNumber { get; init; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Account name (e.g., "Parkering (gulplade)").
|
||
|
|
/// </summary>
|
||
|
|
public required string AccountName { get; init; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// VAT code for this account (e.g., "I25" for 25% input VAT).
|
||
|
|
/// </summary>
|
||
|
|
public string? VatCode { get; init; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Confidence in the suggestion (0.0 - 1.0).
|
||
|
|
/// </summary>
|
||
|
|
public decimal Confidence { get; init; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// AI's reasoning for why this account was chosen.
|
||
|
|
/// </summary>
|
||
|
|
public string? Reasoning { get; init; }
|
||
|
|
}
|