- Implement tests for basic and bearer authentication headers in `test_auth.py`. - Create tests for the `EasybillWebhookParser` in `test_webhooks.py`, covering JSON and form-encoded payloads, as well as a generic parse and acknowledgement method.
287 lines
16 KiB
Python
287 lines
16 KiB
Python
# coding: utf-8
|
|
|
|
"""
|
|
easybill REST API
|
|
|
|
The first version of the easybill REST API. [CHANGELOG](https://api.easybill.de/rest/v1/CHANGELOG.md) ## Authentication You can choose between two available methods: `Basic Auth` or `Bearer Token`. In each HTTP request, one of the following HTTP headers is required: ``` # Basic Auth Authorization: Basic base64_encode('<email>:<api_key>') # Bearer Token Authorization: Bearer <api_key> ``` ## Limitations ### Request Limit * PLUS: 10 requests per minute * BUSINESS: 60 requests per minute If the limit is exceeded, you will receive the HTTP error: `429 Too Many Requests` ### Result Limit All result lists are limited to 100 by default. This limit can be increased by the query parameter `limit` to a maximum of 1000. ## Query filter Many list resources can be filtered. In `/documents` you can filter e.g. by number with `/documents?number=111028654`. If you want to filter multiple numbers, you can either enter them separated by commas `/documents?number=111028654,222006895` or as an array `/documents?number[]=111028654&number[]=222006895`. **Warning**: The maximum size of an HTTP request line in bytes is 4094. If this limit is exceeded, you will receive the HTTP error: `414 Request-URI Too Large` ### Escape commas in query You can escape commans in query `name=Patrick\\, Peter` if you submit the header `X-Easybill-Escape: true` in your request. ## Property login_id This is the login of your admin or employee account. ## Date and Date-Time format Please use the timezone `Europe/Berlin`. * **date** = *Y-m-d* = `2016-12-31` * **date-time** = *Y-m-d H:i:s* = `2016-12-31 03:13:37` Date or datetime can be `null` because the attributes have been added later and the entry is older.
|
|
|
|
The version of the OpenAPI document: 1.96.0
|
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
|
|
Do not edit the class manually.
|
|
""" # noqa: E501
|
|
|
|
|
|
from __future__ import annotations
|
|
import pprint
|
|
import re # noqa: F401
|
|
import json
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, field_validator
|
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
|
from easybill_generated_async.models.position_export_identifier_extended import PositionExportIdentifierExtended
|
|
from typing import Optional, Set
|
|
from typing_extensions import Self
|
|
from pydantic_core import to_jsonable_python
|
|
|
|
class Position(BaseModel):
|
|
"""
|
|
Position
|
|
""" # noqa: E501
|
|
id: Optional[StrictInt] = None
|
|
type: Optional[StrictStr] = 'PRODUCT'
|
|
number: StrictStr
|
|
description: StrictStr = Field(description="The positions name or description")
|
|
document_note: Optional[StrictStr] = Field(default=None, description="This field can be used in the document text areas with the liquid placeholder {{document.item_notes}}. Every note is only displayed once for every kind of product. This is useful if you want to add something like an instruction.")
|
|
note: Optional[StrictStr] = Field(default='null', description="Note for internal use")
|
|
unit: Optional[StrictStr] = 'null'
|
|
export_identifier: Optional[StrictStr] = Field(default='null', description="The FAS-Account is the four-digit revenue account, in which the revenue will be entered when doing the export to your tax consultant. In case you want to split your revenue to several revenue accounts, please talk to your tax consultant before, to guarantee an unobstructed use of the interface. For every revenue element, there are number ranges, which can be used. Please avoid using combinations of numbers, which can not be used by your tax consultant.")
|
|
export_identifier_extended: Optional[PositionExportIdentifierExtended] = None
|
|
login_id: Optional[StrictInt] = None
|
|
price_type: Optional[StrictStr] = 'NETTO'
|
|
vat_percent: Optional[Union[StrictFloat, StrictInt]] = 19.0
|
|
sale_price: Union[StrictFloat, StrictInt] = Field(description="Price in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price2: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 2 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price3: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 3 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price4: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 4 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price5: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 5 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price6: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 6 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price7: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 7 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price8: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 8 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price9: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 9 in cents (e.g. \"150\" = 1.50€)")
|
|
sale_price10: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price for customers of group 10 in cents (e.g. \"150\" = 1.50€)")
|
|
cost_price: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Price in cents (e.g. \"150\" = 1.50€)")
|
|
export_cost1: Optional[StrictStr] = 'null'
|
|
export_cost2: Optional[StrictStr] = 'null'
|
|
group_id: Optional[StrictInt] = None
|
|
stock: Optional[StrictStr] = Field(default='NO', description="Activates stock management for this position")
|
|
stock_count: Optional[StrictInt] = Field(default=None, description="Current stock count")
|
|
stock_limit_notify: Optional[StrictBool] = Field(default=False, description="Notify when stock_count is lower than stock_limit")
|
|
stock_limit_notify_frequency: Optional[StrictStr] = Field(default='ALWAYS', description="Notify frequency when stock_count is lower than stock_limit (ALWAYS, ONCE)")
|
|
stock_limit: Optional[StrictInt] = None
|
|
quantity: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Used as the default quantity when adding this position to a document")
|
|
archived: Optional[StrictBool] = False
|
|
__properties: ClassVar[List[str]] = ["id", "type", "number", "description", "document_note", "note", "unit", "export_identifier", "export_identifier_extended", "login_id", "price_type", "vat_percent", "sale_price", "sale_price2", "sale_price3", "sale_price4", "sale_price5", "sale_price6", "sale_price7", "sale_price8", "sale_price9", "sale_price10", "cost_price", "export_cost1", "export_cost2", "group_id", "stock", "stock_count", "stock_limit_notify", "stock_limit_notify_frequency", "stock_limit", "quantity", "archived"]
|
|
|
|
@field_validator('type')
|
|
def type_validate_enum(cls, value):
|
|
"""Validates the enum"""
|
|
if value is None:
|
|
return value
|
|
|
|
if value not in set(['PRODUCT', 'SERVICE', 'TEXT']):
|
|
raise ValueError("must be one of enum values ('PRODUCT', 'SERVICE', 'TEXT')")
|
|
return value
|
|
|
|
@field_validator('price_type')
|
|
def price_type_validate_enum(cls, value):
|
|
"""Validates the enum"""
|
|
if value is None:
|
|
return value
|
|
|
|
if value not in set(['BRUTTO', 'NETTO']):
|
|
raise ValueError("must be one of enum values ('BRUTTO', 'NETTO')")
|
|
return value
|
|
|
|
@field_validator('stock')
|
|
def stock_validate_enum(cls, value):
|
|
"""Validates the enum"""
|
|
if value is None:
|
|
return value
|
|
|
|
if value not in set(['YES', 'NO']):
|
|
raise ValueError("must be one of enum values ('YES', 'NO')")
|
|
return value
|
|
|
|
@field_validator('stock_limit_notify_frequency')
|
|
def stock_limit_notify_frequency_validate_enum(cls, value):
|
|
"""Validates the enum"""
|
|
if value is None:
|
|
return value
|
|
|
|
if value not in set(['ALWAYS', 'ONCE']):
|
|
raise ValueError("must be one of enum values ('ALWAYS', 'ONCE')")
|
|
return value
|
|
|
|
model_config = ConfigDict(
|
|
validate_by_name=True,
|
|
validate_by_alias=True,
|
|
validate_assignment=True,
|
|
protected_namespaces=(),
|
|
)
|
|
|
|
|
|
def to_str(self) -> str:
|
|
"""Returns the string representation of the model using alias"""
|
|
return pprint.pformat(self.model_dump(by_alias=True))
|
|
|
|
def to_json(self) -> str:
|
|
"""Returns the JSON representation of the model using alias"""
|
|
return json.dumps(to_jsonable_python(self.to_dict()))
|
|
|
|
@classmethod
|
|
def from_json(cls, json_str: str) -> Optional[Self]:
|
|
"""Create an instance of Position from a JSON string"""
|
|
return cls.from_dict(json.loads(json_str))
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Return the dictionary representation of the model using alias.
|
|
|
|
This has the following differences from calling pydantic's
|
|
`self.model_dump(by_alias=True)`:
|
|
|
|
* `None` is only added to the output dict for nullable fields that
|
|
were set at model initialization. Other fields with value `None`
|
|
are ignored.
|
|
* OpenAPI `readOnly` fields are excluded.
|
|
* OpenAPI `readOnly` fields are excluded.
|
|
* OpenAPI `readOnly` fields are excluded.
|
|
"""
|
|
excluded_fields: Set[str] = set([
|
|
"id",
|
|
"login_id",
|
|
"stock_count",
|
|
])
|
|
|
|
_dict = self.model_dump(
|
|
by_alias=True,
|
|
exclude=excluded_fields,
|
|
exclude_none=True,
|
|
)
|
|
# override the default output from pydantic by calling `to_dict()` of export_identifier_extended
|
|
if self.export_identifier_extended:
|
|
_dict['export_identifier_extended'] = self.export_identifier_extended.to_dict()
|
|
# set to None if note (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.note is None and "note" in self.model_fields_set:
|
|
_dict['note'] = None
|
|
|
|
# set to None if unit (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.unit is None and "unit" in self.model_fields_set:
|
|
_dict['unit'] = None
|
|
|
|
# set to None if export_identifier (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.export_identifier is None and "export_identifier" in self.model_fields_set:
|
|
_dict['export_identifier'] = None
|
|
|
|
# set to None if sale_price2 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price2 is None and "sale_price2" in self.model_fields_set:
|
|
_dict['sale_price2'] = None
|
|
|
|
# set to None if sale_price3 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price3 is None and "sale_price3" in self.model_fields_set:
|
|
_dict['sale_price3'] = None
|
|
|
|
# set to None if sale_price4 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price4 is None and "sale_price4" in self.model_fields_set:
|
|
_dict['sale_price4'] = None
|
|
|
|
# set to None if sale_price5 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price5 is None and "sale_price5" in self.model_fields_set:
|
|
_dict['sale_price5'] = None
|
|
|
|
# set to None if sale_price6 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price6 is None and "sale_price6" in self.model_fields_set:
|
|
_dict['sale_price6'] = None
|
|
|
|
# set to None if sale_price7 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price7 is None and "sale_price7" in self.model_fields_set:
|
|
_dict['sale_price7'] = None
|
|
|
|
# set to None if sale_price8 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price8 is None and "sale_price8" in self.model_fields_set:
|
|
_dict['sale_price8'] = None
|
|
|
|
# set to None if sale_price9 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price9 is None and "sale_price9" in self.model_fields_set:
|
|
_dict['sale_price9'] = None
|
|
|
|
# set to None if sale_price10 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.sale_price10 is None and "sale_price10" in self.model_fields_set:
|
|
_dict['sale_price10'] = None
|
|
|
|
# set to None if cost_price (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.cost_price is None and "cost_price" in self.model_fields_set:
|
|
_dict['cost_price'] = None
|
|
|
|
# set to None if export_cost1 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.export_cost1 is None and "export_cost1" in self.model_fields_set:
|
|
_dict['export_cost1'] = None
|
|
|
|
# set to None if export_cost2 (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.export_cost2 is None and "export_cost2" in self.model_fields_set:
|
|
_dict['export_cost2'] = None
|
|
|
|
# set to None if group_id (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.group_id is None and "group_id" in self.model_fields_set:
|
|
_dict['group_id'] = None
|
|
|
|
# set to None if quantity (nullable) is None
|
|
# and model_fields_set contains the field
|
|
if self.quantity is None and "quantity" in self.model_fields_set:
|
|
_dict['quantity'] = None
|
|
|
|
return _dict
|
|
|
|
@classmethod
|
|
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
|
"""Create an instance of Position from a dict"""
|
|
if obj is None:
|
|
return None
|
|
|
|
if not isinstance(obj, dict):
|
|
return cls.model_validate(obj)
|
|
|
|
_obj = cls.model_validate({
|
|
"id": obj.get("id"),
|
|
"type": obj.get("type") if obj.get("type") is not None else 'PRODUCT',
|
|
"number": obj.get("number"),
|
|
"description": obj.get("description"),
|
|
"document_note": obj.get("document_note"),
|
|
"note": obj.get("note") if obj.get("note") is not None else 'null',
|
|
"unit": obj.get("unit") if obj.get("unit") is not None else 'null',
|
|
"export_identifier": obj.get("export_identifier") if obj.get("export_identifier") is not None else 'null',
|
|
"export_identifier_extended": PositionExportIdentifierExtended.from_dict(obj["export_identifier_extended"]) if obj.get("export_identifier_extended") is not None else None,
|
|
"login_id": obj.get("login_id"),
|
|
"price_type": obj.get("price_type") if obj.get("price_type") is not None else 'NETTO',
|
|
"vat_percent": obj.get("vat_percent") if obj.get("vat_percent") is not None else 19.0,
|
|
"sale_price": obj.get("sale_price"),
|
|
"sale_price2": obj.get("sale_price2"),
|
|
"sale_price3": obj.get("sale_price3"),
|
|
"sale_price4": obj.get("sale_price4"),
|
|
"sale_price5": obj.get("sale_price5"),
|
|
"sale_price6": obj.get("sale_price6"),
|
|
"sale_price7": obj.get("sale_price7"),
|
|
"sale_price8": obj.get("sale_price8"),
|
|
"sale_price9": obj.get("sale_price9"),
|
|
"sale_price10": obj.get("sale_price10"),
|
|
"cost_price": obj.get("cost_price"),
|
|
"export_cost1": obj.get("export_cost1") if obj.get("export_cost1") is not None else 'null',
|
|
"export_cost2": obj.get("export_cost2") if obj.get("export_cost2") is not None else 'null',
|
|
"group_id": obj.get("group_id"),
|
|
"stock": obj.get("stock") if obj.get("stock") is not None else 'NO',
|
|
"stock_count": obj.get("stock_count"),
|
|
"stock_limit_notify": obj.get("stock_limit_notify") if obj.get("stock_limit_notify") is not None else False,
|
|
"stock_limit_notify_frequency": obj.get("stock_limit_notify_frequency") if obj.get("stock_limit_notify_frequency") is not None else 'ALWAYS',
|
|
"stock_limit": obj.get("stock_limit"),
|
|
"quantity": obj.get("quantity"),
|
|
"archived": obj.get("archived") if obj.get("archived") is not None else False
|
|
})
|
|
return _obj
|
|
|
|
|