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

import datetime as dt
import typing

from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.request_options import RequestOptions
from ..types.catalog_allowance_list_response import CatalogAllowanceListResponse
from ..types.catalog_allowance_object import CatalogAllowanceObject
from ..types.catalog_bulk_response import CatalogBulkResponse
from ..types.catalog_entry_list_response import CatalogEntryListResponse
from ..types.catalog_entry_object import CatalogEntryObject
from ..types.catalog_price_item import CatalogPriceItem
from ..types.invoice_item_variance_list_response import InvoiceItemVarianceListResponse
from ..types.variance_list_response import VarianceListResponse
from .raw_client import AsyncRawCatalogClient, RawCatalogClient
from .types.catalog_allowance_bulk_create_request_allowances_item import CatalogAllowanceBulkCreateRequestAllowancesItem
from .types.catalog_entry_bulk_create_request_entries_item import CatalogEntryBulkCreateRequestEntriesItem
from .types.catalog_prices_bulk_create_request_prices_item import CatalogPricesBulkCreateRequestPricesItem
from .types.get_catalog_prices_request_price_type import GetCatalogPricesRequestPriceType
from .types.get_catalog_prices_response import GetCatalogPricesResponse
from .types.get_catalog_variances_request_exception_type import GetCatalogVariancesRequestExceptionType
from .types.patch_catalog_allowances_id_request_allowance_type import PatchCatalogAllowancesIdRequestAllowanceType
from .types.patch_catalog_entries_id_request_dimensions import PatchCatalogEntriesIdRequestDimensions
from .types.patch_catalog_entries_id_request_properties import PatchCatalogEntriesIdRequestProperties
from .types.post_catalog_allowances_request_allowance_type import PostCatalogAllowancesRequestAllowanceType
from .types.post_catalog_entries_request_allowances_item import PostCatalogEntriesRequestAllowancesItem
from .types.post_catalog_entries_request_prices_item import PostCatalogEntriesRequestPricesItem
from .types.post_catalog_prices_bulk_response import PostCatalogPricesBulkResponse
from .types.post_catalog_prices_request_price_type import PostCatalogPricesRequestPriceType

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


class CatalogClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._raw_client = RawCatalogClient(client_wrapper=client_wrapper)

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

        Returns
        -------
        RawCatalogClient
        """
        return self._raw_client

    def get_catalog_entries(
        self,
        *,
        ottimate_company_id: int,
        erp_vendor_id: typing.Optional[str] = None,
        search: typing.Optional[str] = None,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        expand: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryListResponse:
        """
        Returns a paginated list of catalog entries, filterable by company, vendor, and search term.

        Parameters
        ----------
        ottimate_company_id : int
            Filter entries by company ID (required)

        erp_vendor_id : typing.Optional[str]
            Filter entries by vendor name

        search : typing.Optional[str]
            Search term based on item description or name

        page : typing.Optional[int]
            Page number (default: 1)

        limit : typing.Optional[int]
            Items per page (default: 50, max: 100)

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: prices, allowances. When used, returns full CatalogEntryObject instead of CatalogEntryBasicObject.

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

        Returns
        -------
        CatalogEntryListResponse
            Successful response with paginated catalog entry list

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_entries(
            ottimate_company_id=15196,
            page=1,
            limit=50,
            expand="prices,allowances",
        )
        """
        _response = self._raw_client.get_catalog_entries(
            ottimate_company_id=ottimate_company_id,
            erp_vendor_id=erp_vendor_id,
            search=search,
            page=page,
            limit=limit,
            expand=expand,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_entries(
        self,
        *,
        ottimate_company_id: int,
        erp_vendor_id: str,
        description: str,
        original_description: typing.Optional[str] = OMIT,
        sku: typing.Optional[str] = OMIT,
        upc: typing.Optional[str] = OMIT,
        size: typing.Optional[str] = OMIT,
        uom: typing.Optional[str] = OMIT,
        price: typing.Optional[float] = OMIT,
        unit_price: typing.Optional[float] = OMIT,
        is_split_case: typing.Optional[bool] = OMIT,
        authorized_vendor: typing.Optional[bool] = OMIT,
        catalog_unique_key: typing.Optional[str] = OMIT,
        last_purchased_price: typing.Optional[float] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        properties: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        prices: typing.Optional[typing.Sequence[PostCatalogEntriesRequestPricesItem]] = OMIT,
        allowances: typing.Optional[typing.Sequence[PostCatalogEntriesRequestAllowancesItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryObject:
        """
        Creates a new catalog entry or updates an existing one if catalog_unique_key matches.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this catalog entry belongs to

        erp_vendor_id : str
            ERP Vendor id from ERP/accounting system

        description : str
            Item name/description for the catalog entry

        original_description : typing.Optional[str]
            Original item description from source system

        sku : typing.Optional[str]
            SKU code

        upc : typing.Optional[str]
            UPC code

        size : typing.Optional[str]
            Pack size (e.g., '6 CT', '10 LB')

        uom : typing.Optional[str]
            Unit of measure (e.g., 'CASE', 'EACH', 'LB')

        price : typing.Optional[float]
            Price for the pack size

        unit_price : typing.Optional[float]
            Price per individual unit within the pack

        is_split_case : typing.Optional[bool]
            Whether items can be purchased in split case quantities

        authorized_vendor : typing.Optional[bool]
            Whether the vendor is authorized for this item

        catalog_unique_key : typing.Optional[str]
            Unique key for upsert operations - if provided and matches existing entry, it will be updated

        last_purchased_price : typing.Optional[float]
            Last purchased price for this item

        reference_id : typing.Optional[str]
            External reference ID for tracking

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Dimension key-value pairs for categorization (e.g., Account, Department)

        properties : typing.Optional[typing.Dict[str, typing.Any]]
            Additional custom properties as key-value pairs

        prices : typing.Optional[typing.Sequence[PostCatalogEntriesRequestPricesItem]]
            Nested prices to create with this catalog entry

        allowances : typing.Optional[typing.Sequence[PostCatalogEntriesRequestAllowancesItem]]
            Nested allowances to create with this catalog entry

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

        Returns
        -------
        CatalogEntryObject
            Catalog entry created successfully

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_entries(
            ottimate_company_id=15196,
            erp_vendor_id="Sysco Foods",
            description="Roma Tomatoes 6CT",
            sku="SKU-TOM-001",
            size="6 CT",
            uom="CASE",
            price=24.99,
            reference_id="sysco-tomato",
        )
        """
        _response = self._raw_client.post_catalog_entries(
            ottimate_company_id=ottimate_company_id,
            erp_vendor_id=erp_vendor_id,
            description=description,
            original_description=original_description,
            sku=sku,
            upc=upc,
            size=size,
            uom=uom,
            price=price,
            unit_price=unit_price,
            is_split_case=is_split_case,
            authorized_vendor=authorized_vendor,
            catalog_unique_key=catalog_unique_key,
            last_purchased_price=last_purchased_price,
            reference_id=reference_id,
            dimensions=dimensions,
            properties=properties,
            prices=prices,
            allowances=allowances,
            request_options=request_options,
        )
        return _response.data

    def get_catalog_entries_id(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> CatalogEntryObject:
        """
        Returns detailed information for a single catalog entry.

        Parameters
        ----------
        id : str
            The unique ID of the catalog entry

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

        Returns
        -------
        CatalogEntryObject
            Successful response with catalog entry details

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_entries_id(
            id="racc_90832490382904802384",
        )
        """
        _response = self._raw_client.get_catalog_entries_id(id, request_options=request_options)
        return _response.data

    def patch_catalog_entries_id(
        self,
        id: str,
        *,
        erp_vendor_id: typing.Optional[str] = OMIT,
        description: typing.Optional[str] = OMIT,
        original_description: typing.Optional[str] = OMIT,
        sku: typing.Optional[str] = OMIT,
        upc: typing.Optional[str] = OMIT,
        size: typing.Optional[str] = OMIT,
        uom: typing.Optional[str] = OMIT,
        price: typing.Optional[float] = OMIT,
        unit_price: typing.Optional[float] = OMIT,
        is_split_case: typing.Optional[bool] = OMIT,
        authorized_vendor: typing.Optional[bool] = OMIT,
        dimensions: typing.Optional[PatchCatalogEntriesIdRequestDimensions] = OMIT,
        properties: typing.Optional[PatchCatalogEntriesIdRequestProperties] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryObject:
        """
        Updates an existing catalog entry by ID.

        Parameters
        ----------
        id : str
            The unique ID of the catalog entry

        erp_vendor_id : typing.Optional[str]
            ERP Vendor id from ERP/accounting system

        description : typing.Optional[str]
            Item name/description

        original_description : typing.Optional[str]
            Original item description

        sku : typing.Optional[str]
            SKU code

        upc : typing.Optional[str]
            UPC code

        size : typing.Optional[str]
            Pack size

        uom : typing.Optional[str]
            Unit of measure

        price : typing.Optional[float]
            Price

        unit_price : typing.Optional[float]
            Unit price

        is_split_case : typing.Optional[bool]
            Split case indicator

        authorized_vendor : typing.Optional[bool]
            Whether vendor is authorized

        dimensions : typing.Optional[PatchCatalogEntriesIdRequestDimensions]
            Dimension key-value pairs

        properties : typing.Optional[PatchCatalogEntriesIdRequestProperties]
            Additional properties

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

        Returns
        -------
        CatalogEntryObject
            Catalog entry updated successfully

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.patch_catalog_entries_id(
            id="racc_90832490382904802384",
            description="Roma Tomatoes Premium 6CT",
            price=26.99,
            size="6 CT",
        )
        """
        _response = self._raw_client.patch_catalog_entries_id(
            id,
            erp_vendor_id=erp_vendor_id,
            description=description,
            original_description=original_description,
            sku=sku,
            upc=upc,
            size=size,
            uom=uom,
            price=price,
            unit_price=unit_price,
            is_split_case=is_split_case,
            authorized_vendor=authorized_vendor,
            dimensions=dimensions,
            properties=properties,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_entries_bulk(
        self,
        *,
        ottimate_company_id: int,
        entries: typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogBulkResponse:
        """
        Creates or updates multiple catalog entries in a single request. Uses upsert logic based on catalog_unique_key.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        entries : typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem]
            List of entries to create/update (max 1000)

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

        Returns
        -------
        CatalogBulkResponse
            All entries created/updated successfully

        Examples
        --------
        from ottimate import OttimateApi
        from ottimate.catalog import CatalogEntryBulkCreateRequestEntriesItem

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_entries_bulk(
            ottimate_company_id=15196,
            entries=[
                CatalogEntryBulkCreateRequestEntriesItem(
                    erp_vendor_id="Sysco Foods",
                    description="Fresh Spinach 2LB",
                    sku="SKU-SPN-001",
                    size="2 LB",
                    price=12.99,
                    reference_id="sysco-spinach",
                ),
                CatalogEntryBulkCreateRequestEntriesItem(
                    erp_vendor_id="Sysco Foods",
                    description="Baby Carrots 5LB",
                    sku="SKU-CAR-001",
                    size="5 LB",
                    price=18.99,
                    reference_id="sysco-carrot",
                ),
            ],
        )
        """
        _response = self._raw_client.post_catalog_entries_bulk(
            ottimate_company_id=ottimate_company_id, entries=entries, request_options=request_options
        )
        return _response.data

    def get_catalog_allowances(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: typing.Optional[str] = None,
        ottimate_location_id: typing.Optional[int] = None,
        effective_date: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceListResponse:
        """
        Returns a paginated list of catalog allowances, filterable by company, catalog entry, location, and effective date.

        Parameters
        ----------
        ottimate_company_id : int
            Filter allowances by company ID (required)

        catalog_entry_id : typing.Optional[str]
            Filter by catalog entry ID

        ottimate_location_id : typing.Optional[int]
            Filter by location ID

        effective_date : typing.Optional[str]
            Filter allowances valid on this date (YYYY-MM-DD)

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

        Returns
        -------
        CatalogAllowanceListResponse
            Successful response with paginated allowance list

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_allowances(
            ottimate_company_id=15196,
        )
        """
        _response = self._raw_client.get_catalog_allowances(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            ottimate_location_id=ottimate_location_id,
            effective_date=effective_date,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_allowances(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: int,
        amount: float,
        start_date: dt.datetime,
        allowance_type: typing.Optional[PostCatalogAllowancesRequestAllowanceType] = OMIT,
        unit_allowance: typing.Optional[float] = OMIT,
        target: typing.Optional[int] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceObject:
        """
        Creates a new catalog allowance for a catalog entry.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this allowance belongs to

        catalog_entry_id : int
            The ID of the catalog entry this allowance applies to

        amount : float
            Allowance amount for the pack size

        start_date : dt.datetime
            Allowance effective start date (ISO 8601)

        allowance_type : typing.Optional[PostCatalogAllowancesRequestAllowanceType]
            Type of allowance (default: 'allowance')

        unit_allowance : typing.Optional[float]
            Allowance amount per individual unit (default: 0)

        target : typing.Optional[int]
            Target level: 10=company (default), 15=location_group, 20=location

        ottimate_location_id : typing.Optional[int]
            Location ID (required when target is 20/location)

        ottimate_group_id : typing.Optional[int]
            Location group ID (required when target is 15/location_group)

        end_date : typing.Optional[dt.datetime]
            Allowance effective end date (ISO 8601, optional)

        reference_id : typing.Optional[str]
            Unique reference ID for upsert operations

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

        Returns
        -------
        CatalogAllowanceObject
            Catalog allowance created successfully

        Examples
        --------
        import datetime

        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_allowances(
            catalog_entry_id=12347,
            ottimate_company_id=15196,
            allowance_type="rebate",
            amount=2.0,
            start_date=datetime.datetime.fromisoformat(
                "2024-12-01 00:00:00+00:00",
            ),
            reference_id="allow-new",
        )
        """
        _response = self._raw_client.post_catalog_allowances(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            amount=amount,
            start_date=start_date,
            allowance_type=allowance_type,
            unit_allowance=unit_allowance,
            target=target,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            end_date=end_date,
            reference_id=reference_id,
            request_options=request_options,
        )
        return _response.data

    def patch_catalog_allowances_id(
        self,
        id: str,
        *,
        allowance_type: typing.Optional[PatchCatalogAllowancesIdRequestAllowanceType] = OMIT,
        amount: typing.Optional[float] = OMIT,
        unit_allowance: typing.Optional[float] = OMIT,
        target: typing.Optional[int] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        start_date: typing.Optional[dt.datetime] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceObject:
        """
        Updates an existing catalog allowance by ID.

        Parameters
        ----------
        id : str
            The unique ID of the catalog allowance

        allowance_type : typing.Optional[PatchCatalogAllowancesIdRequestAllowanceType]
            Type of allowance

        amount : typing.Optional[float]
            Allowance amount

        unit_allowance : typing.Optional[float]
            Unit allowance amount

        target : typing.Optional[int]
            Target (10=company, 15=location_group, 20=location)

        ottimate_location_id : typing.Optional[int]
            Location ID

        ottimate_group_id : typing.Optional[int]
            Restaurant group ID

        start_date : typing.Optional[dt.datetime]
            Start date

        end_date : typing.Optional[dt.datetime]
            End date

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

        Returns
        -------
        CatalogAllowanceObject
            Catalog allowance updated successfully

        Examples
        --------
        import datetime

        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.patch_catalog_allowances_id(
            id="racc_90832490382904802384",
            amount=2.5,
            end_date=datetime.datetime.fromisoformat(
                "2025-12-31 23:59:59+00:00",
            ),
        )
        """
        _response = self._raw_client.patch_catalog_allowances_id(
            id,
            allowance_type=allowance_type,
            amount=amount,
            unit_allowance=unit_allowance,
            target=target,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            start_date=start_date,
            end_date=end_date,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_allowances_bulk(
        self,
        *,
        ottimate_company_id: int,
        allowances: typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogBulkResponse:
        """
        Creates or updates multiple catalog allowances in a single request. Uses upsert logic based on reference_id.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        allowances : typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem]
            List of allowances to create/update (max 1000)

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

        Returns
        -------
        CatalogBulkResponse
            All allowances created/updated successfully

        Examples
        --------
        import datetime

        from ottimate import OttimateApi
        from ottimate.catalog import CatalogAllowanceBulkCreateRequestAllowancesItem

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_allowances_bulk(
            ottimate_company_id=15196,
            allowances=[
                CatalogAllowanceBulkCreateRequestAllowancesItem(
                    catalog_entry_id="cte_12348",
                    allowance_type="rebate",
                    amount=1.5,
                    start_date=datetime.datetime.fromisoformat(
                        "2024-12-01 00:00:00+00:00",
                    ),
                    reference_id="allow-bulk",
                ),
                CatalogAllowanceBulkCreateRequestAllowancesItem(
                    catalog_entry_id="cte_12349",
                    allowance_type="discount",
                    amount=2.0,
                    start_date=datetime.datetime.fromisoformat(
                        "2024-12-01 00:00:00+00:00",
                    ),
                    reference_id="allow-bulk",
                ),
            ],
        )
        """
        _response = self._raw_client.post_catalog_allowances_bulk(
            ottimate_company_id=ottimate_company_id, allowances=allowances, request_options=request_options
        )
        return _response.data

    def get_catalog_prices(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: typing.Optional[str] = None,
        ottimate_location_id: typing.Optional[int] = None,
        effective_date: typing.Optional[str] = None,
        price_type: typing.Optional[GetCatalogPricesRequestPriceType] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetCatalogPricesResponse:
        """
        Returns a paginated list of catalog prices, filterable by company, catalog entry, location, effective date, and price type.

        Parameters
        ----------
        ottimate_company_id : int
            Filter prices by company ID (required)

        catalog_entry_id : typing.Optional[str]
            Filter by catalog entry ID

        ottimate_location_id : typing.Optional[int]
            Filter by location ID

        effective_date : typing.Optional[str]
            Filter prices valid on this date (YYYY-MM-DD)

        price_type : typing.Optional[GetCatalogPricesRequestPriceType]
            Filter by price type

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

        Returns
        -------
        GetCatalogPricesResponse
            Successful response with paginated price list

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_prices(
            ottimate_company_id=15196,
        )
        """
        _response = self._raw_client.get_catalog_prices(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            ottimate_location_id=ottimate_location_id,
            effective_date=effective_date,
            price_type=price_type,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_prices(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: str,
        price: float,
        start_date: dt.datetime,
        unit_price: typing.Optional[float] = OMIT,
        price_type: typing.Optional[PostCatalogPricesRequestPriceType] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogPriceItem:
        """
        Creates a new catalog price for a catalog entry.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this price belongs to

        catalog_entry_id : str
            The ID of the catalog entry this price applies to

        price : float
            Price amount for the pack size

        start_date : dt.datetime
            Price effective start date (ISO 8601)

        unit_price : typing.Optional[float]
            Price amount per individual unit (optional)

        price_type : typing.Optional[PostCatalogPricesRequestPriceType]
            Price type: company (default), location_group, or location

        ottimate_location_id : typing.Optional[int]
            Location ID (required when price_type is 'location')

        ottimate_group_id : typing.Optional[int]
            Location group ID (required when price_type is 'location_group')

        end_date : typing.Optional[dt.datetime]
            Price effective end date (ISO 8601, optional)

        reference_id : typing.Optional[str]
            External reference ID for the price

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

        Returns
        -------
        CatalogPriceItem
            Price created successfully

        Examples
        --------
        import datetime

        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_prices(
            ottimate_company_id=15196,
            catalog_entry_id="example_catalog_entry_id",
            price=100.0,
            start_date=datetime.datetime.fromisoformat(
                "2024-12-05 10:30:00+00:00",
            ),
        )
        """
        _response = self._raw_client.post_catalog_prices(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            price=price,
            start_date=start_date,
            unit_price=unit_price,
            price_type=price_type,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            end_date=end_date,
            reference_id=reference_id,
            request_options=request_options,
        )
        return _response.data

    def post_catalog_prices_bulk(
        self,
        *,
        ottimate_company_id: int,
        prices: typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostCatalogPricesBulkResponse:
        """
        Creates multiple catalog prices in a single request. Unlike allowances, this endpoint does not support upsert - duplicate reference_ids will be rejected.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        prices : typing.Sequence[CatalogPricesBulkCreateRequestPricesItem]
            List of prices to create (max 1000)

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

        Returns
        -------
        PostCatalogPricesBulkResponse
            All prices created successfully

        Examples
        --------
        import datetime

        from ottimate import OttimateApi
        from ottimate.catalog import CatalogPricesBulkCreateRequestPricesItem

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.post_catalog_prices_bulk(
            ottimate_company_id=15196,
            prices=[
                CatalogPricesBulkCreateRequestPricesItem(
                    catalog_entry_id="cte_12345",
                    price=25.99,
                    unit_price=2.16,
                    price_type="company",
                    start_date=datetime.datetime.fromisoformat(
                        "2024-01-01 00:00:00+00:00",
                    ),
                    reference_id="price-sysco-tom-1",
                )
            ],
        )
        """
        _response = self._raw_client.post_catalog_prices_bulk(
            ottimate_company_id=ottimate_company_id, prices=prices, request_options=request_options
        )
        return _response.data

    def get_catalog_variances(
        self,
        *,
        ottimate_company_id: int,
        variance_only: typing.Optional[bool] = None,
        expand: typing.Optional[str] = None,
        invoice_start_date: typing.Optional[str] = None,
        invoice_end_date: typing.Optional[str] = None,
        erp_vendor_ids: typing.Optional[str] = None,
        ottimate_location_ids: typing.Optional[str] = None,
        exception_type: typing.Optional[GetCatalogVariancesRequestExceptionType] = None,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> VarianceListResponse:
        """
        Returns catalog entries with price variance information from invoice items. By default, returns all items that had purchases in the specified period, including items with zero variance. Use variance_only=true to return only entries with non-zero variance. Use expand=invoice_items to include all invoice items inline (otherwise only the most recent invoice item is returned).

        Parameters
        ----------
        ottimate_company_id : int
            Filter by company ID (required)

        variance_only : typing.Optional[bool]
            If true, returns only entries with non-zero variance. Default: false (returns all items including zero variance)

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Supports 'invoice_items' to include all invoice items for each entry.

        invoice_start_date : typing.Optional[str]
            Start of date range (YYYY-MM-DD)

        invoice_end_date : typing.Optional[str]
            End of date range (YYYY-MM-DD)

        erp_vendor_ids : typing.Optional[str]
            Comma-separated ERP vendor IDs (accounting vendor IDs) to filter variances by vendor.

        ottimate_location_ids : typing.Optional[str]
            Comma-separated location IDs

        exception_type : typing.Optional[GetCatalogVariancesRequestExceptionType]
            Filter by variance type

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

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

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

        Returns
        -------
        VarianceListResponse
            Successful response with paginated variance list

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_variances(
            ottimate_company_id=15196,
            variance_only=False,
            page=1,
            limit=20,
        )
        """
        _response = self._raw_client.get_catalog_variances(
            ottimate_company_id=ottimate_company_id,
            variance_only=variance_only,
            expand=expand,
            invoice_start_date=invoice_start_date,
            invoice_end_date=invoice_end_date,
            erp_vendor_ids=erp_vendor_ids,
            ottimate_location_ids=ottimate_location_ids,
            exception_type=exception_type,
            page=page,
            limit=limit,
            request_options=request_options,
        )
        return _response.data

    def get_catalog_variances_id_invoice_items(
        self,
        id: str,
        *,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceItemVarianceListResponse:
        """
        Returns paginated invoice items with price variances for a specific catalog entry. Use this endpoint when the expand=invoice_items response indicates has_more=true.

        Parameters
        ----------
        id : str
            The catalog entry ID

        page : typing.Optional[int]
            Page number

        limit : typing.Optional[int]
            Items per page (default: 20)

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

        Returns
        -------
        InvoiceItemVarianceListResponse
            Successful response with paginated invoice items

        Examples
        --------
        from ottimate import OttimateApi

        client = OttimateApi(
            api_key="YOUR_API_KEY",
            api_version="YOUR_API_VERSION",
            api_key="YOUR_API_KEY",
        )
        client.catalog.get_catalog_variances_id_invoice_items(
            id="racc_90832490382904802384",
            page=1,
            limit=20,
        )
        """
        _response = self._raw_client.get_catalog_variances_id_invoice_items(
            id, page=page, limit=limit, request_options=request_options
        )
        return _response.data


class AsyncCatalogClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._raw_client = AsyncRawCatalogClient(client_wrapper=client_wrapper)

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

        Returns
        -------
        AsyncRawCatalogClient
        """
        return self._raw_client

    async def get_catalog_entries(
        self,
        *,
        ottimate_company_id: int,
        erp_vendor_id: typing.Optional[str] = None,
        search: typing.Optional[str] = None,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        expand: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryListResponse:
        """
        Returns a paginated list of catalog entries, filterable by company, vendor, and search term.

        Parameters
        ----------
        ottimate_company_id : int
            Filter entries by company ID (required)

        erp_vendor_id : typing.Optional[str]
            Filter entries by vendor name

        search : typing.Optional[str]
            Search term based on item description or name

        page : typing.Optional[int]
            Page number (default: 1)

        limit : typing.Optional[int]
            Items per page (default: 50, max: 100)

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Options: prices, allowances. When used, returns full CatalogEntryObject instead of CatalogEntryBasicObject.

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

        Returns
        -------
        CatalogEntryListResponse
            Successful response with paginated catalog entry list

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.get_catalog_entries(
                ottimate_company_id=15196,
                page=1,
                limit=50,
                expand="prices,allowances",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_catalog_entries(
            ottimate_company_id=ottimate_company_id,
            erp_vendor_id=erp_vendor_id,
            search=search,
            page=page,
            limit=limit,
            expand=expand,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_entries(
        self,
        *,
        ottimate_company_id: int,
        erp_vendor_id: str,
        description: str,
        original_description: typing.Optional[str] = OMIT,
        sku: typing.Optional[str] = OMIT,
        upc: typing.Optional[str] = OMIT,
        size: typing.Optional[str] = OMIT,
        uom: typing.Optional[str] = OMIT,
        price: typing.Optional[float] = OMIT,
        unit_price: typing.Optional[float] = OMIT,
        is_split_case: typing.Optional[bool] = OMIT,
        authorized_vendor: typing.Optional[bool] = OMIT,
        catalog_unique_key: typing.Optional[str] = OMIT,
        last_purchased_price: typing.Optional[float] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        dimensions: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        properties: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        prices: typing.Optional[typing.Sequence[PostCatalogEntriesRequestPricesItem]] = OMIT,
        allowances: typing.Optional[typing.Sequence[PostCatalogEntriesRequestAllowancesItem]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryObject:
        """
        Creates a new catalog entry or updates an existing one if catalog_unique_key matches.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this catalog entry belongs to

        erp_vendor_id : str
            ERP Vendor id from ERP/accounting system

        description : str
            Item name/description for the catalog entry

        original_description : typing.Optional[str]
            Original item description from source system

        sku : typing.Optional[str]
            SKU code

        upc : typing.Optional[str]
            UPC code

        size : typing.Optional[str]
            Pack size (e.g., '6 CT', '10 LB')

        uom : typing.Optional[str]
            Unit of measure (e.g., 'CASE', 'EACH', 'LB')

        price : typing.Optional[float]
            Price for the pack size

        unit_price : typing.Optional[float]
            Price per individual unit within the pack

        is_split_case : typing.Optional[bool]
            Whether items can be purchased in split case quantities

        authorized_vendor : typing.Optional[bool]
            Whether the vendor is authorized for this item

        catalog_unique_key : typing.Optional[str]
            Unique key for upsert operations - if provided and matches existing entry, it will be updated

        last_purchased_price : typing.Optional[float]
            Last purchased price for this item

        reference_id : typing.Optional[str]
            External reference ID for tracking

        dimensions : typing.Optional[typing.Dict[str, typing.Any]]
            Dimension key-value pairs for categorization (e.g., Account, Department)

        properties : typing.Optional[typing.Dict[str, typing.Any]]
            Additional custom properties as key-value pairs

        prices : typing.Optional[typing.Sequence[PostCatalogEntriesRequestPricesItem]]
            Nested prices to create with this catalog entry

        allowances : typing.Optional[typing.Sequence[PostCatalogEntriesRequestAllowancesItem]]
            Nested allowances to create with this catalog entry

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

        Returns
        -------
        CatalogEntryObject
            Catalog entry created successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.post_catalog_entries(
                ottimate_company_id=15196,
                erp_vendor_id="Sysco Foods",
                description="Roma Tomatoes 6CT",
                sku="SKU-TOM-001",
                size="6 CT",
                uom="CASE",
                price=24.99,
                reference_id="sysco-tomato",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_entries(
            ottimate_company_id=ottimate_company_id,
            erp_vendor_id=erp_vendor_id,
            description=description,
            original_description=original_description,
            sku=sku,
            upc=upc,
            size=size,
            uom=uom,
            price=price,
            unit_price=unit_price,
            is_split_case=is_split_case,
            authorized_vendor=authorized_vendor,
            catalog_unique_key=catalog_unique_key,
            last_purchased_price=last_purchased_price,
            reference_id=reference_id,
            dimensions=dimensions,
            properties=properties,
            prices=prices,
            allowances=allowances,
            request_options=request_options,
        )
        return _response.data

    async def get_catalog_entries_id(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> CatalogEntryObject:
        """
        Returns detailed information for a single catalog entry.

        Parameters
        ----------
        id : str
            The unique ID of the catalog entry

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

        Returns
        -------
        CatalogEntryObject
            Successful response with catalog entry details

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


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


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

    async def patch_catalog_entries_id(
        self,
        id: str,
        *,
        erp_vendor_id: typing.Optional[str] = OMIT,
        description: typing.Optional[str] = OMIT,
        original_description: typing.Optional[str] = OMIT,
        sku: typing.Optional[str] = OMIT,
        upc: typing.Optional[str] = OMIT,
        size: typing.Optional[str] = OMIT,
        uom: typing.Optional[str] = OMIT,
        price: typing.Optional[float] = OMIT,
        unit_price: typing.Optional[float] = OMIT,
        is_split_case: typing.Optional[bool] = OMIT,
        authorized_vendor: typing.Optional[bool] = OMIT,
        dimensions: typing.Optional[PatchCatalogEntriesIdRequestDimensions] = OMIT,
        properties: typing.Optional[PatchCatalogEntriesIdRequestProperties] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogEntryObject:
        """
        Updates an existing catalog entry by ID.

        Parameters
        ----------
        id : str
            The unique ID of the catalog entry

        erp_vendor_id : typing.Optional[str]
            ERP Vendor id from ERP/accounting system

        description : typing.Optional[str]
            Item name/description

        original_description : typing.Optional[str]
            Original item description

        sku : typing.Optional[str]
            SKU code

        upc : typing.Optional[str]
            UPC code

        size : typing.Optional[str]
            Pack size

        uom : typing.Optional[str]
            Unit of measure

        price : typing.Optional[float]
            Price

        unit_price : typing.Optional[float]
            Unit price

        is_split_case : typing.Optional[bool]
            Split case indicator

        authorized_vendor : typing.Optional[bool]
            Whether vendor is authorized

        dimensions : typing.Optional[PatchCatalogEntriesIdRequestDimensions]
            Dimension key-value pairs

        properties : typing.Optional[PatchCatalogEntriesIdRequestProperties]
            Additional properties

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

        Returns
        -------
        CatalogEntryObject
            Catalog entry updated successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.patch_catalog_entries_id(
                id="racc_90832490382904802384",
                description="Roma Tomatoes Premium 6CT",
                price=26.99,
                size="6 CT",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.patch_catalog_entries_id(
            id,
            erp_vendor_id=erp_vendor_id,
            description=description,
            original_description=original_description,
            sku=sku,
            upc=upc,
            size=size,
            uom=uom,
            price=price,
            unit_price=unit_price,
            is_split_case=is_split_case,
            authorized_vendor=authorized_vendor,
            dimensions=dimensions,
            properties=properties,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_entries_bulk(
        self,
        *,
        ottimate_company_id: int,
        entries: typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogBulkResponse:
        """
        Creates or updates multiple catalog entries in a single request. Uses upsert logic based on catalog_unique_key.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        entries : typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem]
            List of entries to create/update (max 1000)

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

        Returns
        -------
        CatalogBulkResponse
            All entries created/updated successfully

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi
        from ottimate.catalog import CatalogEntryBulkCreateRequestEntriesItem

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


        async def main() -> None:
            await client.catalog.post_catalog_entries_bulk(
                ottimate_company_id=15196,
                entries=[
                    CatalogEntryBulkCreateRequestEntriesItem(
                        erp_vendor_id="Sysco Foods",
                        description="Fresh Spinach 2LB",
                        sku="SKU-SPN-001",
                        size="2 LB",
                        price=12.99,
                        reference_id="sysco-spinach",
                    ),
                    CatalogEntryBulkCreateRequestEntriesItem(
                        erp_vendor_id="Sysco Foods",
                        description="Baby Carrots 5LB",
                        sku="SKU-CAR-001",
                        size="5 LB",
                        price=18.99,
                        reference_id="sysco-carrot",
                    ),
                ],
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_entries_bulk(
            ottimate_company_id=ottimate_company_id, entries=entries, request_options=request_options
        )
        return _response.data

    async def get_catalog_allowances(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: typing.Optional[str] = None,
        ottimate_location_id: typing.Optional[int] = None,
        effective_date: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceListResponse:
        """
        Returns a paginated list of catalog allowances, filterable by company, catalog entry, location, and effective date.

        Parameters
        ----------
        ottimate_company_id : int
            Filter allowances by company ID (required)

        catalog_entry_id : typing.Optional[str]
            Filter by catalog entry ID

        ottimate_location_id : typing.Optional[int]
            Filter by location ID

        effective_date : typing.Optional[str]
            Filter allowances valid on this date (YYYY-MM-DD)

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

        Returns
        -------
        CatalogAllowanceListResponse
            Successful response with paginated allowance list

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.get_catalog_allowances(
                ottimate_company_id=15196,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_catalog_allowances(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            ottimate_location_id=ottimate_location_id,
            effective_date=effective_date,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_allowances(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: int,
        amount: float,
        start_date: dt.datetime,
        allowance_type: typing.Optional[PostCatalogAllowancesRequestAllowanceType] = OMIT,
        unit_allowance: typing.Optional[float] = OMIT,
        target: typing.Optional[int] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceObject:
        """
        Creates a new catalog allowance for a catalog entry.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this allowance belongs to

        catalog_entry_id : int
            The ID of the catalog entry this allowance applies to

        amount : float
            Allowance amount for the pack size

        start_date : dt.datetime
            Allowance effective start date (ISO 8601)

        allowance_type : typing.Optional[PostCatalogAllowancesRequestAllowanceType]
            Type of allowance (default: 'allowance')

        unit_allowance : typing.Optional[float]
            Allowance amount per individual unit (default: 0)

        target : typing.Optional[int]
            Target level: 10=company (default), 15=location_group, 20=location

        ottimate_location_id : typing.Optional[int]
            Location ID (required when target is 20/location)

        ottimate_group_id : typing.Optional[int]
            Location group ID (required when target is 15/location_group)

        end_date : typing.Optional[dt.datetime]
            Allowance effective end date (ISO 8601, optional)

        reference_id : typing.Optional[str]
            Unique reference ID for upsert operations

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

        Returns
        -------
        CatalogAllowanceObject
            Catalog allowance created successfully

        Examples
        --------
        import asyncio
        import datetime

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.post_catalog_allowances(
                catalog_entry_id=12347,
                ottimate_company_id=15196,
                allowance_type="rebate",
                amount=2.0,
                start_date=datetime.datetime.fromisoformat(
                    "2024-12-01 00:00:00+00:00",
                ),
                reference_id="allow-new",
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_allowances(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            amount=amount,
            start_date=start_date,
            allowance_type=allowance_type,
            unit_allowance=unit_allowance,
            target=target,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            end_date=end_date,
            reference_id=reference_id,
            request_options=request_options,
        )
        return _response.data

    async def patch_catalog_allowances_id(
        self,
        id: str,
        *,
        allowance_type: typing.Optional[PatchCatalogAllowancesIdRequestAllowanceType] = OMIT,
        amount: typing.Optional[float] = OMIT,
        unit_allowance: typing.Optional[float] = OMIT,
        target: typing.Optional[int] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        start_date: typing.Optional[dt.datetime] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogAllowanceObject:
        """
        Updates an existing catalog allowance by ID.

        Parameters
        ----------
        id : str
            The unique ID of the catalog allowance

        allowance_type : typing.Optional[PatchCatalogAllowancesIdRequestAllowanceType]
            Type of allowance

        amount : typing.Optional[float]
            Allowance amount

        unit_allowance : typing.Optional[float]
            Unit allowance amount

        target : typing.Optional[int]
            Target (10=company, 15=location_group, 20=location)

        ottimate_location_id : typing.Optional[int]
            Location ID

        ottimate_group_id : typing.Optional[int]
            Restaurant group ID

        start_date : typing.Optional[dt.datetime]
            Start date

        end_date : typing.Optional[dt.datetime]
            End date

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

        Returns
        -------
        CatalogAllowanceObject
            Catalog allowance updated successfully

        Examples
        --------
        import asyncio
        import datetime

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.patch_catalog_allowances_id(
                id="racc_90832490382904802384",
                amount=2.5,
                end_date=datetime.datetime.fromisoformat(
                    "2025-12-31 23:59:59+00:00",
                ),
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.patch_catalog_allowances_id(
            id,
            allowance_type=allowance_type,
            amount=amount,
            unit_allowance=unit_allowance,
            target=target,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            start_date=start_date,
            end_date=end_date,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_allowances_bulk(
        self,
        *,
        ottimate_company_id: int,
        allowances: typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogBulkResponse:
        """
        Creates or updates multiple catalog allowances in a single request. Uses upsert logic based on reference_id.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        allowances : typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem]
            List of allowances to create/update (max 1000)

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

        Returns
        -------
        CatalogBulkResponse
            All allowances created/updated successfully

        Examples
        --------
        import asyncio
        import datetime

        from ottimate import AsyncOttimateApi
        from ottimate.catalog import CatalogAllowanceBulkCreateRequestAllowancesItem

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


        async def main() -> None:
            await client.catalog.post_catalog_allowances_bulk(
                ottimate_company_id=15196,
                allowances=[
                    CatalogAllowanceBulkCreateRequestAllowancesItem(
                        catalog_entry_id="cte_12348",
                        allowance_type="rebate",
                        amount=1.5,
                        start_date=datetime.datetime.fromisoformat(
                            "2024-12-01 00:00:00+00:00",
                        ),
                        reference_id="allow-bulk",
                    ),
                    CatalogAllowanceBulkCreateRequestAllowancesItem(
                        catalog_entry_id="cte_12349",
                        allowance_type="discount",
                        amount=2.0,
                        start_date=datetime.datetime.fromisoformat(
                            "2024-12-01 00:00:00+00:00",
                        ),
                        reference_id="allow-bulk",
                    ),
                ],
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_allowances_bulk(
            ottimate_company_id=ottimate_company_id, allowances=allowances, request_options=request_options
        )
        return _response.data

    async def get_catalog_prices(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: typing.Optional[str] = None,
        ottimate_location_id: typing.Optional[int] = None,
        effective_date: typing.Optional[str] = None,
        price_type: typing.Optional[GetCatalogPricesRequestPriceType] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> GetCatalogPricesResponse:
        """
        Returns a paginated list of catalog prices, filterable by company, catalog entry, location, effective date, and price type.

        Parameters
        ----------
        ottimate_company_id : int
            Filter prices by company ID (required)

        catalog_entry_id : typing.Optional[str]
            Filter by catalog entry ID

        ottimate_location_id : typing.Optional[int]
            Filter by location ID

        effective_date : typing.Optional[str]
            Filter prices valid on this date (YYYY-MM-DD)

        price_type : typing.Optional[GetCatalogPricesRequestPriceType]
            Filter by price type

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

        Returns
        -------
        GetCatalogPricesResponse
            Successful response with paginated price list

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.get_catalog_prices(
                ottimate_company_id=15196,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_catalog_prices(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            ottimate_location_id=ottimate_location_id,
            effective_date=effective_date,
            price_type=price_type,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_prices(
        self,
        *,
        ottimate_company_id: int,
        catalog_entry_id: str,
        price: float,
        start_date: dt.datetime,
        unit_price: typing.Optional[float] = OMIT,
        price_type: typing.Optional[PostCatalogPricesRequestPriceType] = OMIT,
        ottimate_location_id: typing.Optional[int] = OMIT,
        ottimate_group_id: typing.Optional[int] = OMIT,
        end_date: typing.Optional[dt.datetime] = OMIT,
        reference_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> CatalogPriceItem:
        """
        Creates a new catalog price for a catalog entry.

        Parameters
        ----------
        ottimate_company_id : int
            The Ottimate company ID this price belongs to

        catalog_entry_id : str
            The ID of the catalog entry this price applies to

        price : float
            Price amount for the pack size

        start_date : dt.datetime
            Price effective start date (ISO 8601)

        unit_price : typing.Optional[float]
            Price amount per individual unit (optional)

        price_type : typing.Optional[PostCatalogPricesRequestPriceType]
            Price type: company (default), location_group, or location

        ottimate_location_id : typing.Optional[int]
            Location ID (required when price_type is 'location')

        ottimate_group_id : typing.Optional[int]
            Location group ID (required when price_type is 'location_group')

        end_date : typing.Optional[dt.datetime]
            Price effective end date (ISO 8601, optional)

        reference_id : typing.Optional[str]
            External reference ID for the price

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

        Returns
        -------
        CatalogPriceItem
            Price created successfully

        Examples
        --------
        import asyncio
        import datetime

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.post_catalog_prices(
                ottimate_company_id=15196,
                catalog_entry_id="example_catalog_entry_id",
                price=100.0,
                start_date=datetime.datetime.fromisoformat(
                    "2024-12-05 10:30:00+00:00",
                ),
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_prices(
            ottimate_company_id=ottimate_company_id,
            catalog_entry_id=catalog_entry_id,
            price=price,
            start_date=start_date,
            unit_price=unit_price,
            price_type=price_type,
            ottimate_location_id=ottimate_location_id,
            ottimate_group_id=ottimate_group_id,
            end_date=end_date,
            reference_id=reference_id,
            request_options=request_options,
        )
        return _response.data

    async def post_catalog_prices_bulk(
        self,
        *,
        ottimate_company_id: int,
        prices: typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> PostCatalogPricesBulkResponse:
        """
        Creates multiple catalog prices in a single request. Unlike allowances, this endpoint does not support upsert - duplicate reference_ids will be rejected.

        Parameters
        ----------
        ottimate_company_id : int
            Company ID

        prices : typing.Sequence[CatalogPricesBulkCreateRequestPricesItem]
            List of prices to create (max 1000)

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

        Returns
        -------
        PostCatalogPricesBulkResponse
            All prices created successfully

        Examples
        --------
        import asyncio
        import datetime

        from ottimate import AsyncOttimateApi
        from ottimate.catalog import CatalogPricesBulkCreateRequestPricesItem

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


        async def main() -> None:
            await client.catalog.post_catalog_prices_bulk(
                ottimate_company_id=15196,
                prices=[
                    CatalogPricesBulkCreateRequestPricesItem(
                        catalog_entry_id="cte_12345",
                        price=25.99,
                        unit_price=2.16,
                        price_type="company",
                        start_date=datetime.datetime.fromisoformat(
                            "2024-01-01 00:00:00+00:00",
                        ),
                        reference_id="price-sysco-tom-1",
                    )
                ],
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.post_catalog_prices_bulk(
            ottimate_company_id=ottimate_company_id, prices=prices, request_options=request_options
        )
        return _response.data

    async def get_catalog_variances(
        self,
        *,
        ottimate_company_id: int,
        variance_only: typing.Optional[bool] = None,
        expand: typing.Optional[str] = None,
        invoice_start_date: typing.Optional[str] = None,
        invoice_end_date: typing.Optional[str] = None,
        erp_vendor_ids: typing.Optional[str] = None,
        ottimate_location_ids: typing.Optional[str] = None,
        exception_type: typing.Optional[GetCatalogVariancesRequestExceptionType] = None,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> VarianceListResponse:
        """
        Returns catalog entries with price variance information from invoice items. By default, returns all items that had purchases in the specified period, including items with zero variance. Use variance_only=true to return only entries with non-zero variance. Use expand=invoice_items to include all invoice items inline (otherwise only the most recent invoice item is returned).

        Parameters
        ----------
        ottimate_company_id : int
            Filter by company ID (required)

        variance_only : typing.Optional[bool]
            If true, returns only entries with non-zero variance. Default: false (returns all items including zero variance)

        expand : typing.Optional[str]
            Comma-separated list of fields to expand. Supports 'invoice_items' to include all invoice items for each entry.

        invoice_start_date : typing.Optional[str]
            Start of date range (YYYY-MM-DD)

        invoice_end_date : typing.Optional[str]
            End of date range (YYYY-MM-DD)

        erp_vendor_ids : typing.Optional[str]
            Comma-separated ERP vendor IDs (accounting vendor IDs) to filter variances by vendor.

        ottimate_location_ids : typing.Optional[str]
            Comma-separated location IDs

        exception_type : typing.Optional[GetCatalogVariancesRequestExceptionType]
            Filter by variance type

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

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

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

        Returns
        -------
        VarianceListResponse
            Successful response with paginated variance list

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.get_catalog_variances(
                ottimate_company_id=15196,
                variance_only=False,
                page=1,
                limit=20,
            )


        asyncio.run(main())
        """
        _response = await self._raw_client.get_catalog_variances(
            ottimate_company_id=ottimate_company_id,
            variance_only=variance_only,
            expand=expand,
            invoice_start_date=invoice_start_date,
            invoice_end_date=invoice_end_date,
            erp_vendor_ids=erp_vendor_ids,
            ottimate_location_ids=ottimate_location_ids,
            exception_type=exception_type,
            page=page,
            limit=limit,
            request_options=request_options,
        )
        return _response.data

    async def get_catalog_variances_id_invoice_items(
        self,
        id: str,
        *,
        page: typing.Optional[int] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> InvoiceItemVarianceListResponse:
        """
        Returns paginated invoice items with price variances for a specific catalog entry. Use this endpoint when the expand=invoice_items response indicates has_more=true.

        Parameters
        ----------
        id : str
            The catalog entry ID

        page : typing.Optional[int]
            Page number

        limit : typing.Optional[int]
            Items per page (default: 20)

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

        Returns
        -------
        InvoiceItemVarianceListResponse
            Successful response with paginated invoice items

        Examples
        --------
        import asyncio

        from ottimate import AsyncOttimateApi

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


        async def main() -> None:
            await client.catalog.get_catalog_variances_id_invoice_items(
                id="racc_90832490382904802384",
                page=1,
                limit=20,
            )


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