# This file was auto-generated by Fern from our API Definition.

import typing

from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.request_options import RequestOptions
from ..types.bulk_invoice_operation_response import BulkInvoiceOperationResponse
from ..types.bulk_invoice_unexport_operation_response import BulkInvoiceUnexportOperationResponse
from ..types.invoice_creation_response import InvoiceCreationResponse
from ..types.invoice_detail_object import InvoiceDetailObject
from .raw_client import AsyncRawInvoicesClient, RawInvoicesClient
from .types.delete_invoices_id_approvers_response import DeleteInvoicesIdApproversResponse
from .types.get_invoices_id_approvers_response import GetInvoicesIdApproversResponse
from .types.get_invoices_root_request_payment_status import GetInvoicesRootRequestPaymentStatus
from .types.get_invoices_root_request_status import GetInvoicesRootRequestStatus
from .types.get_invoices_root_response import GetInvoicesRootResponse
from .types.get_invoices_uploads_response import GetInvoicesUploadsResponse
from .types.patch_invoices_id_request_items_item import PatchInvoicesIdRequestItemsItem
from .types.patch_invoices_id_request_type import PatchInvoicesIdRequestType
from .types.post_invoices_id_approve_response import PostInvoicesIdApproveResponse
from .types.post_invoices_id_approvers_response import PostInvoicesIdApproversResponse
from .types.post_invoices_id_flag_response import PostInvoicesIdFlagResponse
from .types.post_invoices_mark_exported_request_invoices_item import PostInvoicesMarkExportedRequestInvoicesItem
from .types.post_invoices_mark_unexported_request_invoices_item import PostInvoicesMarkUnexportedRequestInvoicesItem
from .types.post_invoices_root_request_items_item import PostInvoicesRootRequestItemsItem
from .types.post_invoices_root_request_type import PostInvoicesRootRequestType
from .types.post_invoices_root_response import PostInvoicesRootResponse

# this is used as the default value for optional parameters
OMIT = typing.cast(typing.Any, ...)


class InvoicesClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._raw_client = RawInvoicesClient(client_wrapper=client_wrapper)

    @property
    def with_raw_response(self) -> RawInvoicesClient:
        """
        Retrieves a raw implementation of this client that returns raw responses.

        Returns
        -------
        RawInvoicesClient
        """
        return self._raw_client

    def get_invoices_root(
        self,
        *,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        date_from: typing.Optional[str] = None,
        date_to: typing.Optional[str] = None,
        status: typing.Optional[GetInvoicesRootRequestStatus] = None,
        ottimate_location_ids: typing.Optional[str] = None,
        payment_status: typing.Optional[GetInvoicesRootRequestPaymentStatus] = None,
        ottimate_vendor_id: typing.Optional[str] = None,
        expand: typing.Optional[str] = None,
        invoice_ids: typing.Optional[str] = None,
        search: typing.Optional[str] = None,
        custom_fields: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetInvoicesRootResponse:
        """
        Returns a paginated list of invoices with comprehensive filtering capabilities.

        ### Filtering Options
        - Date range filtering by creation date (from/to dates)
        - Vendor filtering by name or ID
        - Location filtering by ottimate_location_id
        - Status filtering (approved, incomplete, processing, etc.)

        ### Pagination
        Results are returned in pages with configurable size (1-100 items per page). Results are ordered by creation date (newest first).

        Parameters
        ----------
        page : typing.Optional[int]
            Page number for pagination

        limit : typing.Optional[int]
            Number of items per page

        date_from : typing.Optional[str]
            Filter invoices from this date (YYYY-MM-DD format)

        date_to : typing.Optional[str]
            Filter invoices to this date (YYYY-MM-DD format)

        status : typing.Optional[GetInvoicesRootRequestStatus]
            Filter by invoice status

        ottimate_location_ids : typing.Optional[str]
            Filter by a comma-separated list of location IDs

        payment_status : typing.Optional[GetInvoicesRootRequestPaymentStatus]
            Filter by payment status

        ottimate_vendor_id : typing.Optional[str]
            Filter by specific vendor ID

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: items. When used, returns full Line item details along with Catalog entry details

        invoice_ids : typing.Optional[str]
            Filter by a comma-separated list of invoice IDs

        search : typing.Optional[str]
            Search across multiple invoice fields

        custom_fields : typing.Optional[str]
            Filter by custom field values. Pass a JSON object where each key is a custom field name and each value is the exact value to match. Multiple keys are ANDed together.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesRootResponse
            Successful response with paginated invoice list

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.get_invoices_root(
            page=1,
            limit=20,
            date_from="2024-01-01",
            date_to="2024-12-31",
            ottimate_location_ids="101,202",
            ottimate_vendor_id="vendor-550e8400-e29b-41d4-a716-446655440000",
            expand="items",
            invoice_ids="17746059,17746058",
            search="INV-2024-001",
            custom_fields='{"SmartPO_TransactionId":"TXN-12345"}',
        )
        """
        _response = self._raw_client.get_invoices_root(
            page=page,
            limit=limit,
            date_from=date_from,
            date_to=date_to,
            status=status,
            ottimate_location_ids=ottimate_location_ids,
            payment_status=payment_status,
            ottimate_vendor_id=ottimate_vendor_id,
            expand=expand,
            invoice_ids=invoice_ids,
            search=search,
            custom_fields=custom_fields,
            request_options=request_options,
        )
        return _response.data

    def post_invoices_root(
        self,
        *,
        upload_id: str,
        ottimate_location_id: int,
        erp_vendor_id: typing.Optional[str] = OMIT,
        invoice_number: typing.Optional[str] = OMIT,
        invoice_date: typing.Optional[str] = OMIT,
        due_date: typing.Optional[str] = OMIT,
        posting_date: typing.Optional[str] = OMIT,
        total_amount: typing.Optional[float] = OMIT,
        total_tax: typing.Optional[float] = OMIT,
        purchase_order: typing.Optional[str] = OMIT,
        notes: typing.Optional[str] = OMIT,
        currency: typing.Optional[str] = OMIT,
        type: typing.Optional[PostInvoicesRootRequestType] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        custom_fields: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        items: typing.Optional[typing.Sequence[PostInvoicesRootRequestItemsItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesRootResponse:
        """
        Creates an invoice along with its line items. The invoice header and items are created in separate transactions.

        ### Features
        - Create invoice header fields (date, amount, vendor, notes, etc.)
        - Create new line items.
        - Attach custom metadata fields to invoice header and line items (requires account-level custom field spec configuration).

        ### Use Cases
        - Programmatic invoice creation from external systems.

        Parameters
        ----------
        upload_id : str
            A mandatory unique identifier for the creating an invoice.

        ottimate_location_id : int
            The ID of the location for this invoice.

        erp_vendor_id : typing.Optional[str]
            The ERP ID of the vendor for this invoice.

        invoice_number : typing.Optional[str]

        invoice_date : typing.Optional[str]

        due_date : typing.Optional[str]

        posting_date : typing.Optional[str]

        total_amount : typing.Optional[float]

        total_tax : typing.Optional[float]

        purchase_order : typing.Optional[str]

        notes : typing.Optional[str]

        currency : typing.Optional[str]

        type : typing.Optional[PostInvoicesRootRequestType]
            Type of document.

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level dimension mappings. Keys are dimension types (e.g., 'DEPARTMENT', 'CLASS') and values are `erp_dimension_id` strings from the `GET /dimensions` endpoint. If omitted, no dimensions are set. Invalid dimension types or IDs return a `400` error.

        custom_fields : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level custom metadata fields for this invoice. Keys and value types are validated against the custom field spec configured for your account in Ottimate.

            **Requirements**: Custom fields must be configured for your account before use. Sending `custom_fields` when no spec is configured returns a `400` error. Unrecognised keys return a `400` error.

            **Behaviour**: Omitted keys are filled with their configured default values

        items : typing.Optional[typing.Sequence[PostInvoicesRootRequestItemsItem]]
            Line items to create for the invoice.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesRootResponse
            Invoice created successfully. Check if 'errors' are present in the response to identify any issues during creation. For 'items' creation errors, the invoice will be created but items may be missing or incomplete. Refer to 'items' for details.

        Examples
        --------
        from ottimate import Ottimate
        from ottimate.invoices import PostInvoicesRootRequestItemsItem

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_root(
            upload_id="upload-550e8400-e29b-41d4-a716-446655440000",
            ottimate_location_id=123,
            erp_vendor_id="456",
            invoice_number="INV-2025-11-001",
            invoice_date="2025-11-27",
            total_amount=250.75,
            type="invoice",
            dimensions={"DEPARTMENT": "erp_dim_dept123", "CLASS": "erp_dim_class456"},
            items=[
                PostInvoicesRootRequestItemsItem(
                    name="Gluten-Free Bread",
                    sku="GF-BREAD-001",
                    quantity=3.0,
                    price=25.25,
                    is_taxed=True,
                    dimensions={"DEPARTMENT": "erp_dim_demo2C"},
                )
            ],
        )
        """
        _response = self._raw_client.post_invoices_root(
            upload_id=upload_id,
            ottimate_location_id=ottimate_location_id,
            erp_vendor_id=erp_vendor_id,
            invoice_number=invoice_number,
            invoice_date=invoice_date,
            due_date=due_date,
            posting_date=posting_date,
            total_amount=total_amount,
            total_tax=total_tax,
            purchase_order=purchase_order,
            notes=notes,
            currency=currency,
            type=type,
            dimensions=dimensions,
            custom_fields=custom_fields,
            items=items,
            request_options=request_options,
        )
        return _response.data

    def post_invoices_upload(
        self,
        *,
        ottimate_location_id: int,
        image_url: typing.Optional[str] = OMIT,
        filename: typing.Optional[str] = OMIT,
        metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        created_email: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceCreationResponse:
        """
        Creates a new invoice by uploading a file directly or downloading from a URL. This endpoint combines file processing, S3 storage, and invoice creation in a single API call.

        ### File Processing
        - Multiple formats supported: PDF, JPG, PNG, TIFF
        - Direct file upload via multipart form data
        - URL-based file download and ingestion
        - Automatic format and size validation
        - Secure S3 storage integration

        ### Invoice Creation Features
        - Single-step upload and creation process
        - Custom metadata support for tracking
        - Location assignment
        - Email sender tracking
        - Integration with existing document processing pipeline

        ### Use Cases
        - Email attachment processing
        - Mobile application file uploads
        - Webhook-based ingestion from vendor portals
        - Programmatic batch processing

        ### Technical Specifications
        - File size limit: 25MB per file
        - Supported formats: PDF, JPG, JPEG, PNG, TIFF
        - Authentication: API key required
        - Access control: Location-level validation
        - Storage: Automatic S3 upload with encryption

        Parameters
        ----------
        ottimate_location_id : int
            Location ID where invoice should be processed

        image_url : typing.Optional[str]
            URL to download invoice file from (alternative to file upload)

        filename : typing.Optional[str]
            Filename to use (optional, derived from URL if not provided)

        metadata : typing.Optional[typing.Dict[str, typing.Any]]
            Additional invoice metadata

        created_email : typing.Optional[str]
            Email address of invoice sender (for tracking)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        InvoiceCreationResponse
            Invoice created successfully. Returns details of the created invoice, processing batch, file container, upload information, and processing status. The invoice will be processed asynchronously.

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_upload(
            ottimate_location_id=456,
            image_url="https://vendor.com/invoices/12345.pdf",
            filename="vendor-invoice-2024-001.pdf",
            created_email="billing@acmefoods.com",
        )
        """
        _response = self._raw_client.post_invoices_upload(
            ottimate_location_id=ottimate_location_id,
            image_url=image_url,
            filename=filename,
            metadata=metadata,
            created_email=created_email,
            request_options=request_options,
        )
        return _response.data

    def get_invoices_id(
        self, id: str, *, expand: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> InvoiceDetailObject:
        """
        Returns **comprehensive details** for a single invoice including all metadata, processing information, and payment status.
        
        ## 📋 What You Get
        
        This endpoint provides the **complete invoice record** with:
        
        - 🧾 **Full invoice metadata** (dates, amounts, status)
        - 🏢 **Vendor information** and contact details
        - 📍 **Location** where invoice was received
        - 💳 **Payment information** (when invoice is paid)
        - ⚠️ **Error details** (if invoice has processing issues)
        - 📤 **Export tracking** (integration status)
        
        ## 🔍 Usage Examples
        
        ### Basic Retrieval
        ```bash
        curl -H "X-Api-Key: your-api-key" \\
             https://api.acme.com/v1/invoices/550e8400-e29b-41d4-a716-446655440000
        ```
        
        ### With Version Header
        ```bash
        curl -H "X-Api-Key: your-api-key" \\
             -H "X-API-Version: 1.0.0" \\
             https://api.acme.com/v1/invoices/550e8400-e29b-41d4-a716-446655440000
        ```
        
        ## ⚡ Performance Notes
        
        - **Response time**: Typically < 100ms
        - **Caching**: Results cached for 5 minutes
        - **Rate limits**: Applies to your overall API quota
        
        > 💡 **Tip**: Use this endpoint after getting results from the list endpoint to get full details for specific invoices.
        
        Parameters
        ----------
        id : str
            Invoice UUID - Unique identifier of the invoice to retrieve
        
        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: `history`, `gl_splits`. Use `history` to include the full audit log. Use `gl_splits` to include GL account splits (real splits or virtual splits derived from line-item mappings).
        
        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.
        
        Returns
        -------
        InvoiceDetailObject
            Successful response with invoice details
        
        Examples
        --------
        from ottimate import Ottimate
        
        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.get_invoices_id(
            id="racc_90832490382904802384",
            expand="gl_splits,history",
        )
        """
        _response = self._raw_client.get_invoices_id(id, expand=expand, request_options=request_options)
        return _response.data

    def patch_invoices_id(
        self,
        id: str,
        *,
        ottimate_location_id: typing.Optional[int] = OMIT,
        erp_vendor_id: typing.Optional[str] = OMIT,
        erp_vendor_name: typing.Optional[str] = OMIT,
        invoice_number: typing.Optional[str] = OMIT,
        invoice_date: typing.Optional[str] = OMIT,
        due_date: typing.Optional[str] = OMIT,
        posting_date: typing.Optional[str] = OMIT,
        total_amount: typing.Optional[float] = OMIT,
        total_tax: typing.Optional[float] = OMIT,
        discount_amount: typing.Optional[float] = OMIT,
        purchase_order: typing.Optional[str] = OMIT,
        notes: typing.Optional[str] = OMIT,
        currency: typing.Optional[str] = OMIT,
        type: typing.Optional[PatchInvoicesIdRequestType] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        custom_fields: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        items: typing.Optional[typing.Sequence[PatchInvoicesIdRequestItemsItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceDetailObject:
        """
        Updates an invoice along with its line items and dimensions.

        ### Features
        - Update invoice header fields (date, amount, vendor, notes, etc.)
        - Create new line items (omit `id`)
        - Update existing line items (include `id`)
        - Set, update, or remove header and item-level dimensions
        - Update custom metadata fields at header and item level (requires account-level custom field spec configuration)

        ### Use Cases
        - Invoice corrections
        - Invoice reconciliation with updated line items
        - Adding or updating dimensions after initial invoice creation
        - Multi-field updates in a single request

        Parameters
        ----------
        id : str
            Invoice ID to update

        ottimate_location_id : typing.Optional[int]
            The ID of the location for this invoice.

        erp_vendor_id : typing.Optional[str]
            The ERP ID of the vendor for this invoice.

        erp_vendor_name : typing.Optional[str]
            ERP vendor name. Required when creating a new vendor via erp_vendor_id.

        invoice_number : typing.Optional[str]

        invoice_date : typing.Optional[str]

        due_date : typing.Optional[str]

        posting_date : typing.Optional[str]

        total_amount : typing.Optional[float]

        total_tax : typing.Optional[float]

        discount_amount : typing.Optional[float]

        purchase_order : typing.Optional[str]

        notes : typing.Optional[str]

        currency : typing.Optional[str]

        type : typing.Optional[PatchInvoicesIdRequestType]
            Type of document.

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level dimension mappings. Keys are dimension types (e.g., 'DEPARTMENT') and values are `erp_dimension_id` strings from the `GET /dimensions` endpoint. Replaces all header dimensions with the provided set. Omit the field entirely to preserve existing dimensions. Pass `{}` to clear all header dimensions.

        custom_fields : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level custom metadata fields. Replaces the stored custom fields with the provided object — any keys not included are reset to their configured default values.

            **Requirements**: Custom fields must be configured for your account before use. Returns a `400` error if no spec is found or if unrecognised keys are sent.

            **Behaviour**: Omit the field entirely to preserve existing custom fields.

        items : typing.Optional[typing.Sequence[PatchInvoicesIdRequestItemsItem]]
            Line items to create or update. Include `id` to update an existing item, omit `id` to create a new item.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        InvoiceDetailObject
            Invoice updated successfully

        Examples
        --------
        from ottimate import Ottimate
        from ottimate.invoices import PatchInvoicesIdRequestItemsItem

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.patch_invoices_id(
            id="racc_90832490382904802384",
            invoice_date="2024-01-15",
            total_amount=1500.0,
            notes="Updated invoice",
            dimensions={"DEPARTMENT": "erp_dim_dept123", "CLASS": "erp_dim_class456"},
            items=[
                PatchInvoicesIdRequestItemsItem(
                    id=456,
                    quantity=10.0,
                    price=50.0,
                    dimensions={"DEPARTMENT": "erp_dim_dept789"},
                ),
                PatchInvoicesIdRequestItemsItem(
                    name="New Line Item",
                    quantity=5.0,
                    price=20.0,
                    dimensions={"CLASS": "erp_dim_class456"},
                ),
            ],
        )
        """
        _response = self._raw_client.patch_invoices_id(
            id,
            ottimate_location_id=ottimate_location_id,
            erp_vendor_id=erp_vendor_id,
            erp_vendor_name=erp_vendor_name,
            invoice_number=invoice_number,
            invoice_date=invoice_date,
            due_date=due_date,
            posting_date=posting_date,
            total_amount=total_amount,
            total_tax=total_tax,
            discount_amount=discount_amount,
            purchase_order=purchase_order,
            notes=notes,
            currency=currency,
            type=type,
            dimensions=dimensions,
            custom_fields=custom_fields,
            items=items,
            request_options=request_options,
        )
        return _response.data

    def post_invoices_mark_exported(
        self,
        *,
        invoices: typing.Sequence[PostInvoicesMarkExportedRequestInvoicesItem],
        export_batch_id: typing.Optional[str] = None,
        export_transaction_id: typing.Optional[str] = OMIT,
        marked_reason: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> BulkInvoiceOperationResponse:
        """


        Parameters
        ----------
        invoices : typing.Sequence[PostInvoicesMarkExportedRequestInvoicesItem]

        export_batch_id : typing.Optional[str]
            Optional export batch identifier used for logging

        export_transaction_id : typing.Optional[str]
            Batch-level transaction ID for all invoices

        marked_reason : typing.Optional[str]
            Batch-level reason for all invoices

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        BulkInvoiceOperationResponse
            Operation result

        Examples
        --------
        from ottimate import Ottimate
        from ottimate.invoices import PostInvoicesMarkExportedRequestInvoicesItem

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_mark_exported(
            invoices=[
                PostInvoicesMarkExportedRequestInvoicesItem(
                    invoice_id=12345,
                ),
                PostInvoicesMarkExportedRequestInvoicesItem(
                    invoice_id=12346,
                ),
                PostInvoicesMarkExportedRequestInvoicesItem(
                    invoice_id=12347,
                ),
            ],
            export_transaction_id="EXP-2024-001",
            marked_reason="Exported to NetSuite batch 2024-001",
        )
        """
        _response = self._raw_client.post_invoices_mark_exported(
            invoices=invoices,
            export_batch_id=export_batch_id,
            export_transaction_id=export_transaction_id,
            marked_reason=marked_reason,
            request_options=request_options,
        )
        return _response.data

    def post_invoices_mark_unexported(
        self,
        *,
        invoices: typing.Sequence[PostInvoicesMarkUnexportedRequestInvoicesItem],
        marked_reason: typing.Optional[str] = OMIT,
        flag_invoice: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> BulkInvoiceUnexportOperationResponse:
        """


        Parameters
        ----------
        invoices : typing.Sequence[PostInvoicesMarkUnexportedRequestInvoicesItem]

        marked_reason : typing.Optional[str]
            Batch-level reason for all invoices

        flag_invoice : typing.Optional[bool]
            Flag the invoice as unexported

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        BulkInvoiceUnexportOperationResponse
            Operation result

        Examples
        --------
        from ottimate import Ottimate
        from ottimate.invoices import PostInvoicesMarkUnexportedRequestInvoicesItem

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_mark_unexported(
            invoices=[
                PostInvoicesMarkUnexportedRequestInvoicesItem(
                    invoice_id=12345,
                ),
                PostInvoicesMarkUnexportedRequestInvoicesItem(
                    invoice_id=12346,
                ),
            ],
            marked_reason="Reverted export due to incorrect GL codes",
            flag_invoice=True,
        )
        """
        _response = self._raw_client.post_invoices_mark_unexported(
            invoices=invoices, marked_reason=marked_reason, flag_invoice=flag_invoice, request_options=request_options
        )
        return _response.data

    def get_invoices_uploads(
        self,
        *,
        ottimate_location_id: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetInvoicesUploadsResponse:
        """
        Returns a list of uploads that have been uploaded but not yet processed.

        Parameters
        ----------
        ottimate_location_id : typing.Optional[int]
            Filter by a specific location ID

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesUploadsResponse
            Successful response with a list of uploads

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.get_invoices_uploads()
        """
        _response = self._raw_client.get_invoices_uploads(
            ottimate_location_id=ottimate_location_id, request_options=request_options
        )
        return _response.data

    def get_invoices_id_approvers(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> GetInvoicesIdApproversResponse:
        """
        Returns a list of all approvers assigned to an invoice, including their approval status.

        ### Features
        - View all assigned approvers for an invoice
        - Check approval status (pending, approved)
        - See approver details (name, email)
        - View policy-based and manual approvers

        ### Use Cases
        - Check who needs to approve an invoice
        - Monitor approval progress
        - Audit approval chain

        Parameters
        ----------
        id : str
            Invoice ID

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesIdApproversResponse
            Successful response with list of approvers

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.get_invoices_id_approvers(
            id="racc_90832490382904802384",
        )
        """
        _response = self._raw_client.get_invoices_id_approvers(id, request_options=request_options)
        return _response.data

    def post_invoices_id_approvers(
        self, id: str, *, approver: int, request_options: typing.Optional[RequestOptions] = None
    ) -> PostInvoicesIdApproversResponse:
        """
        Adds a new manual approver to an invoice. The approver will need to approve the invoice before it can proceed.

        ### Features
        - Add any user as a manual approver
        - Approver receives email notification
        - If invoice was already approved, it will be reset to pending approval

        ### Use Cases
        - Add ad-hoc approvers for special review
        - Require additional sign-off for high-value invoices
        - Include department heads in approval chain

        Parameters
        ----------
        id : str
            Invoice ID

        approver : int
            User ID of the approver to add

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdApproversResponse
            Approver added successfully

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_id_approvers(
            id="racc_90832490382904802384",
            approver=12345,
        )
        """
        _response = self._raw_client.post_invoices_id_approvers(id, approver=approver, request_options=request_options)
        return _response.data

    def delete_invoices_id_approvers(
        self, id: str, *, approval_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> DeleteInvoicesIdApproversResponse:
        """
        Removes a specific approver from an invoice by approval ID.

        ### Features
        - Remove specific approver by approval_id
        - Approval chain is recalculated after removal
        - Audit trail is maintained

        ### Use Cases
        - Remove incorrectly assigned approvers
        - Adjust approval chain when approver is unavailable
        - Clean up approval workflow

        Parameters
        ----------
        id : str
            Invoice ID

        approval_id : str
            The approval record ID to remove (e.g., pbjappr_xxx)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        DeleteInvoicesIdApproversResponse
            Approver removed successfully

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.delete_invoices_id_approvers(
            id="racc_90832490382904802384",
            approval_id="pbjappr_550e8400e29b41d4a716446655440000",
        )
        """
        _response = self._raw_client.delete_invoices_id_approvers(
            id, approval_id=approval_id, request_options=request_options
        )
        return _response.data

    def post_invoices_id_approve(
        self,
        id: str,
        *,
        force_approve: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesIdApproveResponse:
        """
        Approves an invoice, marking it ready for export/payment. The invoice must be in a verifiable state.

        ### Features
        - Approves invoice and marks approved_date
        - Automatically verifies invoice if not already verified
        - Supports force approval for authorized users
        - Sends approval notifications

        ### Use Cases
        - Approve invoices after review
        - Programmatic approval workflows
        - Batch approval integrations

        Parameters
        ----------
        id : str
            Invoice ID

        force_approve : typing.Optional[bool]
            Force approve the invoice (requires elevated permissions)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdApproveResponse
            Invoice approved successfully

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_id_approve(
            id="racc_90832490382904802384",
            force_approve=False,
        )
        """
        _response = self._raw_client.post_invoices_id_approve(
            id, force_approve=force_approve, request_options=request_options
        )
        return _response.data

    def post_invoices_id_flag(
        self,
        id: str,
        *,
        flagging_reason: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesIdFlagResponse:
        """
        Flags an invoice for review, moving it to incomplete status with a flagging reason.

        ### Features
        - Flags invoice and moves to incomplete status
        - Supports optional flagging reason
        - Triggers notifications to relevant users

        ### Use Cases
        - Flag invoices with issues for review
        - Request corrections or additional information
        - Mark invoices needing manual intervention

        Parameters
        ----------
        id : str
            Invoice ID

        flagging_reason : typing.Optional[str]
            Reason for flagging the invoice

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdFlagResponse
            Invoice flagged successfully

        Examples
        --------
        from ottimate import Ottimate

        client = Ottimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.invoices.post_invoices_id_flag(
            id="racc_90832490382904802384",
            flagging_reason="Missing line items",
        )
        """
        _response = self._raw_client.post_invoices_id_flag(
            id, flagging_reason=flagging_reason, request_options=request_options
        )
        return _response.data


class AsyncInvoicesClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._raw_client = AsyncRawInvoicesClient(client_wrapper=client_wrapper)

    @property
    def with_raw_response(self) -> AsyncRawInvoicesClient:
        """
        Retrieves a raw implementation of this client that returns raw responses.

        Returns
        -------
        AsyncRawInvoicesClient
        """
        return self._raw_client

    async def get_invoices_root(
        self,
        *,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        date_from: typing.Optional[str] = None,
        date_to: typing.Optional[str] = None,
        status: typing.Optional[GetInvoicesRootRequestStatus] = None,
        ottimate_location_ids: typing.Optional[str] = None,
        payment_status: typing.Optional[GetInvoicesRootRequestPaymentStatus] = None,
        ottimate_vendor_id: typing.Optional[str] = None,
        expand: typing.Optional[str] = None,
        invoice_ids: typing.Optional[str] = None,
        search: typing.Optional[str] = None,
        custom_fields: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetInvoicesRootResponse:
        """
        Returns a paginated list of invoices with comprehensive filtering capabilities.

        ### Filtering Options
        - Date range filtering by creation date (from/to dates)
        - Vendor filtering by name or ID
        - Location filtering by ottimate_location_id
        - Status filtering (approved, incomplete, processing, etc.)

        ### Pagination
        Results are returned in pages with configurable size (1-100 items per page). Results are ordered by creation date (newest first).

        Parameters
        ----------
        page : typing.Optional[int]
            Page number for pagination

        limit : typing.Optional[int]
            Number of items per page

        date_from : typing.Optional[str]
            Filter invoices from this date (YYYY-MM-DD format)

        date_to : typing.Optional[str]
            Filter invoices to this date (YYYY-MM-DD format)

        status : typing.Optional[GetInvoicesRootRequestStatus]
            Filter by invoice status

        ottimate_location_ids : typing.Optional[str]
            Filter by a comma-separated list of location IDs

        payment_status : typing.Optional[GetInvoicesRootRequestPaymentStatus]
            Filter by payment status

        ottimate_vendor_id : typing.Optional[str]
            Filter by specific vendor ID

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: items. When used, returns full Line item details along with Catalog entry details

        invoice_ids : typing.Optional[str]
            Filter by a comma-separated list of invoice IDs

        search : typing.Optional[str]
            Search across multiple invoice fields

        custom_fields : typing.Optional[str]
            Filter by custom field values. Pass a JSON object where each key is a custom field name and each value is the exact value to match. Multiple keys are ANDed together.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesRootResponse
            Successful response with paginated invoice list

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.get_invoices_root(
                page=1,
                limit=20,
                date_from="2024-01-01",
                date_to="2024-12-31",
                ottimate_location_ids="101,202",
                ottimate_vendor_id="vendor-550e8400-e29b-41d4-a716-446655440000",
                expand="items",
                invoice_ids="17746059,17746058",
                search="INV-2024-001",
                custom_fields='{"SmartPO_TransactionId":"TXN-12345"}',
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_invoices_root(
            page=page,
            limit=limit,
            date_from=date_from,
            date_to=date_to,
            status=status,
            ottimate_location_ids=ottimate_location_ids,
            payment_status=payment_status,
            ottimate_vendor_id=ottimate_vendor_id,
            expand=expand,
            invoice_ids=invoice_ids,
            search=search,
            custom_fields=custom_fields,
            request_options=request_options,
        )
        return _response.data

    async def post_invoices_root(
        self,
        *,
        upload_id: str,
        ottimate_location_id: int,
        erp_vendor_id: typing.Optional[str] = OMIT,
        invoice_number: typing.Optional[str] = OMIT,
        invoice_date: typing.Optional[str] = OMIT,
        due_date: typing.Optional[str] = OMIT,
        posting_date: typing.Optional[str] = OMIT,
        total_amount: typing.Optional[float] = OMIT,
        total_tax: typing.Optional[float] = OMIT,
        purchase_order: typing.Optional[str] = OMIT,
        notes: typing.Optional[str] = OMIT,
        currency: typing.Optional[str] = OMIT,
        type: typing.Optional[PostInvoicesRootRequestType] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        custom_fields: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        items: typing.Optional[typing.Sequence[PostInvoicesRootRequestItemsItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesRootResponse:
        """
        Creates an invoice along with its line items. The invoice header and items are created in separate transactions.

        ### Features
        - Create invoice header fields (date, amount, vendor, notes, etc.)
        - Create new line items.
        - Attach custom metadata fields to invoice header and line items (requires account-level custom field spec configuration).

        ### Use Cases
        - Programmatic invoice creation from external systems.

        Parameters
        ----------
        upload_id : str
            A mandatory unique identifier for the creating an invoice.

        ottimate_location_id : int
            The ID of the location for this invoice.

        erp_vendor_id : typing.Optional[str]
            The ERP ID of the vendor for this invoice.

        invoice_number : typing.Optional[str]

        invoice_date : typing.Optional[str]

        due_date : typing.Optional[str]

        posting_date : typing.Optional[str]

        total_amount : typing.Optional[float]

        total_tax : typing.Optional[float]

        purchase_order : typing.Optional[str]

        notes : typing.Optional[str]

        currency : typing.Optional[str]

        type : typing.Optional[PostInvoicesRootRequestType]
            Type of document.

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level dimension mappings. Keys are dimension types (e.g., 'DEPARTMENT', 'CLASS') and values are `erp_dimension_id` strings from the `GET /dimensions` endpoint. If omitted, no dimensions are set. Invalid dimension types or IDs return a `400` error.

        custom_fields : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level custom metadata fields for this invoice. Keys and value types are validated against the custom field spec configured for your account in Ottimate.

            **Requirements**: Custom fields must be configured for your account before use. Sending `custom_fields` when no spec is configured returns a `400` error. Unrecognised keys return a `400` error.

            **Behaviour**: Omitted keys are filled with their configured default values

        items : typing.Optional[typing.Sequence[PostInvoicesRootRequestItemsItem]]
            Line items to create for the invoice.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesRootResponse
            Invoice created successfully. Check if 'errors' are present in the response to identify any issues during creation. For 'items' creation errors, the invoice will be created but items may be missing or incomplete. Refer to 'items' for details.

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate
        from ottimate.invoices import PostInvoicesRootRequestItemsItem

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_root(
                upload_id="upload-550e8400-e29b-41d4-a716-446655440000",
                ottimate_location_id=123,
                erp_vendor_id="456",
                invoice_number="INV-2025-11-001",
                invoice_date="2025-11-27",
                total_amount=250.75,
                type="invoice",
                dimensions={
                    "DEPARTMENT": "erp_dim_dept123",
                    "CLASS": "erp_dim_class456",
                },
                items=[
                    PostInvoicesRootRequestItemsItem(
                        name="Gluten-Free Bread",
                        sku="GF-BREAD-001",
                        quantity=3.0,
                        price=25.25,
                        is_taxed=True,
                        dimensions={"DEPARTMENT": "erp_dim_demo2C"},
                    )
                ],
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_root(
            upload_id=upload_id,
            ottimate_location_id=ottimate_location_id,
            erp_vendor_id=erp_vendor_id,
            invoice_number=invoice_number,
            invoice_date=invoice_date,
            due_date=due_date,
            posting_date=posting_date,
            total_amount=total_amount,
            total_tax=total_tax,
            purchase_order=purchase_order,
            notes=notes,
            currency=currency,
            type=type,
            dimensions=dimensions,
            custom_fields=custom_fields,
            items=items,
            request_options=request_options,
        )
        return _response.data

    async def post_invoices_upload(
        self,
        *,
        ottimate_location_id: int,
        image_url: typing.Optional[str] = OMIT,
        filename: typing.Optional[str] = OMIT,
        metadata: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        created_email: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceCreationResponse:
        """
        Creates a new invoice by uploading a file directly or downloading from a URL. This endpoint combines file processing, S3 storage, and invoice creation in a single API call.

        ### File Processing
        - Multiple formats supported: PDF, JPG, PNG, TIFF
        - Direct file upload via multipart form data
        - URL-based file download and ingestion
        - Automatic format and size validation
        - Secure S3 storage integration

        ### Invoice Creation Features
        - Single-step upload and creation process
        - Custom metadata support for tracking
        - Location assignment
        - Email sender tracking
        - Integration with existing document processing pipeline

        ### Use Cases
        - Email attachment processing
        - Mobile application file uploads
        - Webhook-based ingestion from vendor portals
        - Programmatic batch processing

        ### Technical Specifications
        - File size limit: 25MB per file
        - Supported formats: PDF, JPG, JPEG, PNG, TIFF
        - Authentication: API key required
        - Access control: Location-level validation
        - Storage: Automatic S3 upload with encryption

        Parameters
        ----------
        ottimate_location_id : int
            Location ID where invoice should be processed

        image_url : typing.Optional[str]
            URL to download invoice file from (alternative to file upload)

        filename : typing.Optional[str]
            Filename to use (optional, derived from URL if not provided)

        metadata : typing.Optional[typing.Dict[str, typing.Any]]
            Additional invoice metadata

        created_email : typing.Optional[str]
            Email address of invoice sender (for tracking)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        InvoiceCreationResponse
            Invoice created successfully. Returns details of the created invoice, processing batch, file container, upload information, and processing status. The invoice will be processed asynchronously.

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_upload(
                ottimate_location_id=456,
                image_url="https://vendor.com/invoices/12345.pdf",
                filename="vendor-invoice-2024-001.pdf",
                created_email="billing@acmefoods.com",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_upload(
            ottimate_location_id=ottimate_location_id,
            image_url=image_url,
            filename=filename,
            metadata=metadata,
            created_email=created_email,
            request_options=request_options,
        )
        return _response.data

    async def get_invoices_id(
        self, id: str, *, expand: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> InvoiceDetailObject:
        """
        Returns **comprehensive details** for a single invoice including all metadata, processing information, and payment status.
        
        ## 📋 What You Get
        
        This endpoint provides the **complete invoice record** with:
        
        - 🧾 **Full invoice metadata** (dates, amounts, status)
        - 🏢 **Vendor information** and contact details
        - 📍 **Location** where invoice was received
        - 💳 **Payment information** (when invoice is paid)
        - ⚠️ **Error details** (if invoice has processing issues)
        - 📤 **Export tracking** (integration status)
        
        ## 🔍 Usage Examples
        
        ### Basic Retrieval
        ```bash
        curl -H "X-Api-Key: your-api-key" \\
             https://api.acme.com/v1/invoices/550e8400-e29b-41d4-a716-446655440000
        ```
        
        ### With Version Header
        ```bash
        curl -H "X-Api-Key: your-api-key" \\
             -H "X-API-Version: 1.0.0" \\
             https://api.acme.com/v1/invoices/550e8400-e29b-41d4-a716-446655440000
        ```
        
        ## ⚡ Performance Notes
        
        - **Response time**: Typically < 100ms
        - **Caching**: Results cached for 5 minutes
        - **Rate limits**: Applies to your overall API quota
        
        > 💡 **Tip**: Use this endpoint after getting results from the list endpoint to get full details for specific invoices.
        
        Parameters
        ----------
        id : str
            Invoice UUID - Unique identifier of the invoice to retrieve
        
        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: `history`, `gl_splits`. Use `history` to include the full audit log. Use `gl_splits` to include GL account splits (real splits or virtual splits derived from line-item mappings).
        
        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.
        
        Returns
        -------
        InvoiceDetailObject
            Successful response with invoice details
        
        Examples
        --------
        import asyncio
        
        from ottimate import AsyncOttimate
        
        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        
        
        async def main() -> None:
            await client.invoices.get_invoices_id(
                id="racc_90832490382904802384",
                expand="gl_splits,history",
            )
        
        
        asyncio.run(main())
        """
        _response = await self._raw_client.get_invoices_id(id, expand=expand, request_options=request_options)
        return _response.data

    async def patch_invoices_id(
        self,
        id: str,
        *,
        ottimate_location_id: typing.Optional[int] = OMIT,
        erp_vendor_id: typing.Optional[str] = OMIT,
        erp_vendor_name: typing.Optional[str] = OMIT,
        invoice_number: typing.Optional[str] = OMIT,
        invoice_date: typing.Optional[str] = OMIT,
        due_date: typing.Optional[str] = OMIT,
        posting_date: typing.Optional[str] = OMIT,
        total_amount: typing.Optional[float] = OMIT,
        total_tax: typing.Optional[float] = OMIT,
        discount_amount: typing.Optional[float] = OMIT,
        purchase_order: typing.Optional[str] = OMIT,
        notes: typing.Optional[str] = OMIT,
        currency: typing.Optional[str] = OMIT,
        type: typing.Optional[PatchInvoicesIdRequestType] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        custom_fields: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        items: typing.Optional[typing.Sequence[PatchInvoicesIdRequestItemsItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceDetailObject:
        """
        Updates an invoice along with its line items and dimensions.

        ### Features
        - Update invoice header fields (date, amount, vendor, notes, etc.)
        - Create new line items (omit `id`)
        - Update existing line items (include `id`)
        - Set, update, or remove header and item-level dimensions
        - Update custom metadata fields at header and item level (requires account-level custom field spec configuration)

        ### Use Cases
        - Invoice corrections
        - Invoice reconciliation with updated line items
        - Adding or updating dimensions after initial invoice creation
        - Multi-field updates in a single request

        Parameters
        ----------
        id : str
            Invoice ID to update

        ottimate_location_id : typing.Optional[int]
            The ID of the location for this invoice.

        erp_vendor_id : typing.Optional[str]
            The ERP ID of the vendor for this invoice.

        erp_vendor_name : typing.Optional[str]
            ERP vendor name. Required when creating a new vendor via erp_vendor_id.

        invoice_number : typing.Optional[str]

        invoice_date : typing.Optional[str]

        due_date : typing.Optional[str]

        posting_date : typing.Optional[str]

        total_amount : typing.Optional[float]

        total_tax : typing.Optional[float]

        discount_amount : typing.Optional[float]

        purchase_order : typing.Optional[str]

        notes : typing.Optional[str]

        currency : typing.Optional[str]

        type : typing.Optional[PatchInvoicesIdRequestType]
            Type of document.

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level dimension mappings. Keys are dimension types (e.g., 'DEPARTMENT') and values are `erp_dimension_id` strings from the `GET /dimensions` endpoint. Replaces all header dimensions with the provided set. Omit the field entirely to preserve existing dimensions. Pass `{}` to clear all header dimensions.

        custom_fields : typing.Optional[typing.Dict[str, typing.Any]]
            Header-level custom metadata fields. Replaces the stored custom fields with the provided object — any keys not included are reset to their configured default values.

            **Requirements**: Custom fields must be configured for your account before use. Returns a `400` error if no spec is found or if unrecognised keys are sent.

            **Behaviour**: Omit the field entirely to preserve existing custom fields.

        items : typing.Optional[typing.Sequence[PatchInvoicesIdRequestItemsItem]]
            Line items to create or update. Include `id` to update an existing item, omit `id` to create a new item.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        InvoiceDetailObject
            Invoice updated successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate
        from ottimate.invoices import PatchInvoicesIdRequestItemsItem

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.patch_invoices_id(
                id="racc_90832490382904802384",
                invoice_date="2024-01-15",
                total_amount=1500.0,
                notes="Updated invoice",
                dimensions={
                    "DEPARTMENT": "erp_dim_dept123",
                    "CLASS": "erp_dim_class456",
                },
                items=[
                    PatchInvoicesIdRequestItemsItem(
                        id=456,
                        quantity=10.0,
                        price=50.0,
                        dimensions={"DEPARTMENT": "erp_dim_dept789"},
                    ),
                    PatchInvoicesIdRequestItemsItem(
                        name="New Line Item",
                        quantity=5.0,
                        price=20.0,
                        dimensions={"CLASS": "erp_dim_class456"},
                    ),
                ],
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.patch_invoices_id(
            id,
            ottimate_location_id=ottimate_location_id,
            erp_vendor_id=erp_vendor_id,
            erp_vendor_name=erp_vendor_name,
            invoice_number=invoice_number,
            invoice_date=invoice_date,
            due_date=due_date,
            posting_date=posting_date,
            total_amount=total_amount,
            total_tax=total_tax,
            discount_amount=discount_amount,
            purchase_order=purchase_order,
            notes=notes,
            currency=currency,
            type=type,
            dimensions=dimensions,
            custom_fields=custom_fields,
            items=items,
            request_options=request_options,
        )
        return _response.data

    async def post_invoices_mark_exported(
        self,
        *,
        invoices: typing.Sequence[PostInvoicesMarkExportedRequestInvoicesItem],
        export_batch_id: typing.Optional[str] = None,
        export_transaction_id: typing.Optional[str] = OMIT,
        marked_reason: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> BulkInvoiceOperationResponse:
        """


        Parameters
        ----------
        invoices : typing.Sequence[PostInvoicesMarkExportedRequestInvoicesItem]

        export_batch_id : typing.Optional[str]
            Optional export batch identifier used for logging

        export_transaction_id : typing.Optional[str]
            Batch-level transaction ID for all invoices

        marked_reason : typing.Optional[str]
            Batch-level reason for all invoices

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        BulkInvoiceOperationResponse
            Operation result

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate
        from ottimate.invoices import PostInvoicesMarkExportedRequestInvoicesItem

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_mark_exported(
                invoices=[
                    PostInvoicesMarkExportedRequestInvoicesItem(
                        invoice_id=12345,
                    ),
                    PostInvoicesMarkExportedRequestInvoicesItem(
                        invoice_id=12346,
                    ),
                    PostInvoicesMarkExportedRequestInvoicesItem(
                        invoice_id=12347,
                    ),
                ],
                export_transaction_id="EXP-2024-001",
                marked_reason="Exported to NetSuite batch 2024-001",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_mark_exported(
            invoices=invoices,
            export_batch_id=export_batch_id,
            export_transaction_id=export_transaction_id,
            marked_reason=marked_reason,
            request_options=request_options,
        )
        return _response.data

    async def post_invoices_mark_unexported(
        self,
        *,
        invoices: typing.Sequence[PostInvoicesMarkUnexportedRequestInvoicesItem],
        marked_reason: typing.Optional[str] = OMIT,
        flag_invoice: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> BulkInvoiceUnexportOperationResponse:
        """


        Parameters
        ----------
        invoices : typing.Sequence[PostInvoicesMarkUnexportedRequestInvoicesItem]

        marked_reason : typing.Optional[str]
            Batch-level reason for all invoices

        flag_invoice : typing.Optional[bool]
            Flag the invoice as unexported

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        BulkInvoiceUnexportOperationResponse
            Operation result

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate
        from ottimate.invoices import PostInvoicesMarkUnexportedRequestInvoicesItem

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_mark_unexported(
                invoices=[
                    PostInvoicesMarkUnexportedRequestInvoicesItem(
                        invoice_id=12345,
                    ),
                    PostInvoicesMarkUnexportedRequestInvoicesItem(
                        invoice_id=12346,
                    ),
                ],
                marked_reason="Reverted export due to incorrect GL codes",
                flag_invoice=True,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_mark_unexported(
            invoices=invoices, marked_reason=marked_reason, flag_invoice=flag_invoice, request_options=request_options
        )
        return _response.data

    async def get_invoices_uploads(
        self,
        *,
        ottimate_location_id: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetInvoicesUploadsResponse:
        """
        Returns a list of uploads that have been uploaded but not yet processed.

        Parameters
        ----------
        ottimate_location_id : typing.Optional[int]
            Filter by a specific location ID

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesUploadsResponse
            Successful response with a list of uploads

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.get_invoices_uploads()


        asyncio.run(main())
        """
        _response = await self._raw_client.get_invoices_uploads(
            ottimate_location_id=ottimate_location_id, request_options=request_options
        )
        return _response.data

    async def get_invoices_id_approvers(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> GetInvoicesIdApproversResponse:
        """
        Returns a list of all approvers assigned to an invoice, including their approval status.

        ### Features
        - View all assigned approvers for an invoice
        - Check approval status (pending, approved)
        - See approver details (name, email)
        - View policy-based and manual approvers

        ### Use Cases
        - Check who needs to approve an invoice
        - Monitor approval progress
        - Audit approval chain

        Parameters
        ----------
        id : str
            Invoice ID

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        GetInvoicesIdApproversResponse
            Successful response with list of approvers

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.get_invoices_id_approvers(
                id="racc_90832490382904802384",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_invoices_id_approvers(id, request_options=request_options)
        return _response.data

    async def post_invoices_id_approvers(
        self, id: str, *, approver: int, request_options: typing.Optional[RequestOptions] = None
    ) -> PostInvoicesIdApproversResponse:
        """
        Adds a new manual approver to an invoice. The approver will need to approve the invoice before it can proceed.

        ### Features
        - Add any user as a manual approver
        - Approver receives email notification
        - If invoice was already approved, it will be reset to pending approval

        ### Use Cases
        - Add ad-hoc approvers for special review
        - Require additional sign-off for high-value invoices
        - Include department heads in approval chain

        Parameters
        ----------
        id : str
            Invoice ID

        approver : int
            User ID of the approver to add

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdApproversResponse
            Approver added successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_id_approvers(
                id="racc_90832490382904802384",
                approver=12345,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_id_approvers(
            id, approver=approver, request_options=request_options
        )
        return _response.data

    async def delete_invoices_id_approvers(
        self, id: str, *, approval_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> DeleteInvoicesIdApproversResponse:
        """
        Removes a specific approver from an invoice by approval ID.

        ### Features
        - Remove specific approver by approval_id
        - Approval chain is recalculated after removal
        - Audit trail is maintained

        ### Use Cases
        - Remove incorrectly assigned approvers
        - Adjust approval chain when approver is unavailable
        - Clean up approval workflow

        Parameters
        ----------
        id : str
            Invoice ID

        approval_id : str
            The approval record ID to remove (e.g., pbjappr_xxx)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        DeleteInvoicesIdApproversResponse
            Approver removed successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.delete_invoices_id_approvers(
                id="racc_90832490382904802384",
                approval_id="pbjappr_550e8400e29b41d4a716446655440000",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.delete_invoices_id_approvers(
            id, approval_id=approval_id, request_options=request_options
        )
        return _response.data

    async def post_invoices_id_approve(
        self,
        id: str,
        *,
        force_approve: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesIdApproveResponse:
        """
        Approves an invoice, marking it ready for export/payment. The invoice must be in a verifiable state.

        ### Features
        - Approves invoice and marks approved_date
        - Automatically verifies invoice if not already verified
        - Supports force approval for authorized users
        - Sends approval notifications

        ### Use Cases
        - Approve invoices after review
        - Programmatic approval workflows
        - Batch approval integrations

        Parameters
        ----------
        id : str
            Invoice ID

        force_approve : typing.Optional[bool]
            Force approve the invoice (requires elevated permissions)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdApproveResponse
            Invoice approved successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_id_approve(
                id="racc_90832490382904802384",
                force_approve=False,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_id_approve(
            id, force_approve=force_approve, request_options=request_options
        )
        return _response.data

    async def post_invoices_id_flag(
        self,
        id: str,
        *,
        flagging_reason: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostInvoicesIdFlagResponse:
        """
        Flags an invoice for review, moving it to incomplete status with a flagging reason.

        ### Features
        - Flags invoice and moves to incomplete status
        - Supports optional flagging reason
        - Triggers notifications to relevant users

        ### Use Cases
        - Flag invoices with issues for review
        - Request corrections or additional information
        - Mark invoices needing manual intervention

        Parameters
        ----------
        id : str
            Invoice ID

        flagging_reason : typing.Optional[str]
            Reason for flagging the invoice

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        PostInvoicesIdFlagResponse
            Invoice flagged successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimate

        client = AsyncOttimate(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )


        async def main() -> None:
            await client.invoices.post_invoices_id_flag(
                id="racc_90832490382904802384",
                flagging_reason="Missing line items",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_invoices_id_flag(
            id, flagging_reason=flagging_reason, request_options=request_options
        )
        return _response.data
