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

import datetime as dt
import typing
from json.decoder import JSONDecodeError

from ..core.api_error import ApiError
from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.http_response import AsyncHttpResponse, HttpResponse
from ..core.jsonable_encoder import jsonable_encoder
from ..core.pydantic_utilities import parse_obj_as
from ..core.request_options import RequestOptions
from ..core.serialization import convert_and_respect_annotation_metadata
from ..errors.bad_request_error import BadRequestError
from ..errors.forbidden_error import ForbiddenError
from ..errors.not_found_error import NotFoundError
from ..errors.unauthorized_error import UnauthorizedError
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.error_response import ErrorResponse
from ..types.invoice_item_variance_list_response import InvoiceItemVarianceListResponse
from ..types.variance_list_response import VarianceListResponse
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 RawCatalogClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._client_wrapper = client_wrapper

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogEntryListResponse]
            Successful response with paginated catalog entry list
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/entries",
            method="GET",
            params={
                "ottimate_company_id": ottimate_company_id,
                "erp_vendor_id": erp_vendor_id,
                "search": search,
                "page": page,
                "limit": limit,
                "expand": expand,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryListResponse,
                    parse_obj_as(
                        type_=CatalogEntryListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogEntryObject]
            Catalog entry created successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/entries",
            method="POST",
            json={
                "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": convert_and_respect_annotation_metadata(
                    object_=prices, annotation=typing.Sequence[PostCatalogEntriesRequestPricesItem], direction="write"
                ),
                "allowances": convert_and_respect_annotation_metadata(
                    object_=allowances,
                    annotation=typing.Sequence[PostCatalogEntriesRequestAllowancesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def get_catalog_entries_id(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogEntryObject]
            Successful response with catalog entry details
        """
        _response = self._client_wrapper.httpx_client.request(
            f"catalog/entries/{jsonable_encoder(id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogEntryObject]
            Catalog entry updated successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            f"catalog/entries/{jsonable_encoder(id)}",
            method="PATCH",
            json={
                "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": convert_and_respect_annotation_metadata(
                    object_=dimensions, annotation=PatchCatalogEntriesIdRequestDimensions, direction="write"
                ),
                "properties": convert_and_respect_annotation_metadata(
                    object_=properties, annotation=PatchCatalogEntriesIdRequestProperties, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def post_catalog_entries_bulk(
        self,
        *,
        ottimate_company_id: int,
        entries: typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogBulkResponse]
            All entries created/updated successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/entries/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "entries": convert_and_respect_annotation_metadata(
                    object_=entries,
                    annotation=typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogBulkResponse,
                    parse_obj_as(
                        type_=CatalogBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogAllowanceListResponse]
            Successful response with paginated allowance list
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/allowances",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceListResponse,
                    parse_obj_as(
                        type_=CatalogAllowanceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogAllowanceObject]
            Catalog allowance created successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/allowances",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "catalog_entry_id": catalog_entry_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,
                "reference_id": reference_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceObject,
                    parse_obj_as(
                        type_=CatalogAllowanceObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogAllowanceObject]
            Catalog allowance updated successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            f"catalog/allowances/{jsonable_encoder(id)}",
            method="PATCH",
            json={
                "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,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceObject,
                    parse_obj_as(
                        type_=CatalogAllowanceObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def post_catalog_allowances_bulk(
        self,
        *,
        ottimate_company_id: int,
        allowances: typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogBulkResponse]
            All allowances created/updated successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/allowances/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "allowances": convert_and_respect_annotation_metadata(
                    object_=allowances,
                    annotation=typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogBulkResponse,
                    parse_obj_as(
                        type_=CatalogBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[GetCatalogPricesResponse]
            Successful response with paginated price list
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/prices",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    GetCatalogPricesResponse,
                    parse_obj_as(
                        type_=GetCatalogPricesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[CatalogPriceItem]
            Price created successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/prices",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "catalog_entry_id": catalog_entry_id,
                "price": price,
                "unit_price": unit_price,
                "price_type": price_type,
                "ottimate_location_id": ottimate_location_id,
                "ottimate_group_id": ottimate_group_id,
                "start_date": start_date,
                "end_date": end_date,
                "reference_id": reference_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogPriceItem,
                    parse_obj_as(
                        type_=CatalogPriceItem,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def post_catalog_prices_bulk(
        self,
        *,
        ottimate_company_id: int,
        prices: typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[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
        -------
        HttpResponse[PostCatalogPricesBulkResponse]
            All prices created successfully
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/prices/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "prices": convert_and_respect_annotation_metadata(
                    object_=prices,
                    annotation=typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    PostCatalogPricesBulkResponse,
                    parse_obj_as(
                        type_=PostCatalogPricesBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[VarianceListResponse]
            Successful response with paginated variance list
        """
        _response = self._client_wrapper.httpx_client.request(
            "catalog/variances",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    VarianceListResponse,
                    parse_obj_as(
                        type_=VarianceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> HttpResponse[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
        -------
        HttpResponse[InvoiceItemVarianceListResponse]
            Successful response with paginated invoice items
        """
        _response = self._client_wrapper.httpx_client.request(
            f"catalog/variances/{jsonable_encoder(id)}/invoice-items",
            method="GET",
            params={
                "page": page,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    InvoiceItemVarianceListResponse,
                    parse_obj_as(
                        type_=InvoiceItemVarianceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)


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

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogEntryListResponse]
            Successful response with paginated catalog entry list
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/entries",
            method="GET",
            params={
                "ottimate_company_id": ottimate_company_id,
                "erp_vendor_id": erp_vendor_id,
                "search": search,
                "page": page,
                "limit": limit,
                "expand": expand,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryListResponse,
                    parse_obj_as(
                        type_=CatalogEntryListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogEntryObject]
            Catalog entry created successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/entries",
            method="POST",
            json={
                "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": convert_and_respect_annotation_metadata(
                    object_=prices, annotation=typing.Sequence[PostCatalogEntriesRequestPricesItem], direction="write"
                ),
                "allowances": convert_and_respect_annotation_metadata(
                    object_=allowances,
                    annotation=typing.Sequence[PostCatalogEntriesRequestAllowancesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def get_catalog_entries_id(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogEntryObject]
            Successful response with catalog entry details
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"catalog/entries/{jsonable_encoder(id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogEntryObject]
            Catalog entry updated successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"catalog/entries/{jsonable_encoder(id)}",
            method="PATCH",
            json={
                "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": convert_and_respect_annotation_metadata(
                    object_=dimensions, annotation=PatchCatalogEntriesIdRequestDimensions, direction="write"
                ),
                "properties": convert_and_respect_annotation_metadata(
                    object_=properties, annotation=PatchCatalogEntriesIdRequestProperties, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogEntryObject,
                    parse_obj_as(
                        type_=CatalogEntryObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def post_catalog_entries_bulk(
        self,
        *,
        ottimate_company_id: int,
        entries: typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogBulkResponse]
            All entries created/updated successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/entries/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "entries": convert_and_respect_annotation_metadata(
                    object_=entries,
                    annotation=typing.Sequence[CatalogEntryBulkCreateRequestEntriesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogBulkResponse,
                    parse_obj_as(
                        type_=CatalogBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogAllowanceListResponse]
            Successful response with paginated allowance list
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/allowances",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceListResponse,
                    parse_obj_as(
                        type_=CatalogAllowanceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogAllowanceObject]
            Catalog allowance created successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/allowances",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "catalog_entry_id": catalog_entry_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,
                "reference_id": reference_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceObject,
                    parse_obj_as(
                        type_=CatalogAllowanceObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogAllowanceObject]
            Catalog allowance updated successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"catalog/allowances/{jsonable_encoder(id)}",
            method="PATCH",
            json={
                "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,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogAllowanceObject,
                    parse_obj_as(
                        type_=CatalogAllowanceObject,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def post_catalog_allowances_bulk(
        self,
        *,
        ottimate_company_id: int,
        allowances: typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogBulkResponse]
            All allowances created/updated successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/allowances/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "allowances": convert_and_respect_annotation_metadata(
                    object_=allowances,
                    annotation=typing.Sequence[CatalogAllowanceBulkCreateRequestAllowancesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogBulkResponse,
                    parse_obj_as(
                        type_=CatalogBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[GetCatalogPricesResponse]
            Successful response with paginated price list
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/prices",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    GetCatalogPricesResponse,
                    parse_obj_as(
                        type_=GetCatalogPricesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[CatalogPriceItem]
            Price created successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/prices",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "catalog_entry_id": catalog_entry_id,
                "price": price,
                "unit_price": unit_price,
                "price_type": price_type,
                "ottimate_location_id": ottimate_location_id,
                "ottimate_group_id": ottimate_group_id,
                "start_date": start_date,
                "end_date": end_date,
                "reference_id": reference_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CatalogPriceItem,
                    parse_obj_as(
                        type_=CatalogPriceItem,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def post_catalog_prices_bulk(
        self,
        *,
        ottimate_company_id: int,
        prices: typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[PostCatalogPricesBulkResponse]
            All prices created successfully
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/prices/bulk",
            method="POST",
            json={
                "ottimate_company_id": ottimate_company_id,
                "prices": convert_and_respect_annotation_metadata(
                    object_=prices,
                    annotation=typing.Sequence[CatalogPricesBulkCreateRequestPricesItem],
                    direction="write",
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    PostCatalogPricesBulkResponse,
                    parse_obj_as(
                        type_=PostCatalogPricesBulkResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[VarianceListResponse]
            Successful response with paginated variance list
        """
        _response = await self._client_wrapper.httpx_client.request(
            "catalog/variances",
            method="GET",
            params={
                "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,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    VarianceListResponse,
                    parse_obj_as(
                        type_=VarianceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    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,
    ) -> AsyncHttpResponse[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
        -------
        AsyncHttpResponse[InvoiceItemVarianceListResponse]
            Successful response with paginated invoice items
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"catalog/variances/{jsonable_encoder(id)}/invoice-items",
            method="GET",
            params={
                "page": page,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    InvoiceItemVarianceListResponse,
                    parse_obj_as(
                        type_=InvoiceItemVarianceListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorResponse,
                        parse_obj_as(
                            type_=ErrorResponse,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
