# coding: utf-8

"""
    FactPulse REST API

     REST API for electronic invoicing in France: Factur-X, AFNOR PDP/PA, electronic signatures.  ## 🎯 Main Features  ### 📄 Factur-X Invoice Generation - **Formats**: XML only or PDF/A-3 with embedded XML - **Profiles**: MINIMUM, BASIC, EN16931, EXTENDED - **Standards**: EN 16931 (EU directive 2014/55), ISO 19005-3 (PDF/A-3), CII (UN/CEFACT) - **🆕 Simplified Format**: Generation from SIRET + auto-enrichment (Chorus Pro API + Business Search)  ### ✅ Validation and Compliance - **XML Validation**: Schematron (45 to 210+ rules depending on profile) - **PDF Validation**: PDF/A-3, Factur-X XMP metadata, electronic signatures - **VeraPDF**: Strict PDF/A validation (146+ ISO 19005-3 rules) - **Asynchronous Processing**: Celery support for heavy validations (VeraPDF)  ### 📡 AFNOR PDP/PA Integration (XP Z12-013) - **Flow Submission**: Send invoices to Partner Dematerialization Platforms - **Flow Search**: View submitted invoices - **Download**: Retrieve PDF/A-3 with XML - **Directory Service**: Company search (SIREN/SIRET) - **Multi-client**: Support for multiple PDP configs per user (stored credentials or zero-storage)  ### ✍️ PDF Electronic Signature - **Standards**: PAdES-B-B, PAdES-B-T (RFC 3161 timestamping), PAdES-B-LT (long-term archival) - **eIDAS Levels**: SES (self-signed), AdES (commercial CA), QES (QTSP) - **Validation**: Cryptographic integrity and certificate verification - **Certificate Generation**: Self-signed X.509 certificates for testing  ### 🔄 Asynchronous Processing - **Celery**: Asynchronous generation, validation and signing - **Polling**: Status tracking via `/tasks/{task_id}/status` - **No timeout**: Ideal for large files or heavy validations  ## 🔒 Authentication  All requests require a **JWT token** in the Authorization header: ``` Authorization: Bearer YOUR_JWT_TOKEN ```  ### How to obtain a JWT token?  #### 🔑 Method 1: `/api/token/` API (Recommended)  **URL:** `https://factpulse.fr/api/token/`  This method is **recommended** for integration in your applications and CI/CD workflows.  **Prerequisites:** Having set a password on your account  **For users registered via email/password:** - You already have a password, use it directly  **For users registered via OAuth (Google/GitHub):** - You must first set a password at: https://factpulse.fr/accounts/password/set/ - Once the password is created, you can use the API  **Request example:** ```bash curl -X POST https://factpulse.fr/api/token/ \\   -H \"Content-Type: application/json\" \\   -d '{     \"username\": \"your_email@example.com\",     \"password\": \"your_password\"   }' ```  **Optional `client_uid` parameter:**  To select credentials for a specific client (PA/PDP, Chorus Pro, signing certificates), add `client_uid`:  ```bash curl -X POST https://factpulse.fr/api/token/ \\   -H \"Content-Type: application/json\" \\   -d '{     \"username\": \"your_email@example.com\",     \"password\": \"your_password\",     \"client_uid\": \"550e8400-e29b-41d4-a716-446655440000\"   }' ```  The `client_uid` will be included in the JWT and allow the API to automatically use: - AFNOR/PDP credentials configured for this client - Chorus Pro credentials configured for this client - Electronic signature certificates configured for this client  **Response:** ```json {   \"access\": \"eyJ0eXAiOiJKV1QiLCJhbGc...\",  // Access token (validity: 30 min)   \"refresh\": \"eyJ0eXAiOiJKV1QiLCJhbGc...\"  // Refresh token (validity: 7 days) } ```  **Advantages:** - ✅ Full automation (CI/CD, scripts) - ✅ Programmatic token management - ✅ Refresh token support for automatic access renewal - ✅ Easy integration in any language/tool  #### 🖥️ Method 2: Dashboard Generation (Alternative)  **URL:** https://factpulse.fr/api/dashboard/  This method is suitable for quick tests or occasional use via the graphical interface.  **How it works:** - Log in to the dashboard - Use the \"Generate Test Token\" or \"Generate Production Token\" buttons - Works for **all** users (OAuth and email/password), without requiring a password  **Token types:** - **Test Token**: 24h validity, 1000 calls/day quota (free) - **Production Token**: 7 days validity, quota based on your plan  **Advantages:** - ✅ Quick for API testing - ✅ No password required - ✅ Simple visual interface  **Disadvantages:** - ❌ Requires manual action - ❌ No refresh token - ❌ Less suited for automation  ### 📚 Full Documentation  For more information on authentication and API usage: https://factpulse.fr/documentation-api/     

    The version of the OpenAPI document: 1.0.0
    Contact: contact@factpulse.fr
    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, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from factpulse.models.additional_document import AdditionalDocument
from factpulse.models.allowance_charge import AllowanceCharge
from factpulse.models.allowance_reason_code import AllowanceReasonCode
from factpulse.models.gross_unit_price import GrossUnitPrice
from factpulse.models.invoice_line_allowance_amount import InvoiceLineAllowanceAmount
from factpulse.models.invoice_note import InvoiceNote
from factpulse.models.line_net_amount import LineNetAmount
from factpulse.models.line_sub_type import LineSubType
from factpulse.models.manual_vat_rate import ManualVatRate
from factpulse.models.price_allowance_amount import PriceAllowanceAmount
from factpulse.models.price_basis_quantity import PriceBasisQuantity
from factpulse.models.product_characteristic import ProductCharacteristic
from factpulse.models.product_classification import ProductClassification
from factpulse.models.quantity import Quantity
from factpulse.models.unit_net_price import UnitNetPrice
from factpulse.models.unit_of_measure import UnitOfMeasure
from factpulse.models.vat_category import VATCategory
from typing import Optional, Set
from typing_extensions import Self

class InvoiceLine(BaseModel):
    """
    Represents an invoice line item (BG-25).
    """ # noqa: E501
    line_number: StrictInt = Field(description="Invoice line identifier (BT-126).")
    line_note: Optional[StrictStr] = None
    parent_line_id: Optional[StrictStr] = None
    line_sub_type: Optional[LineSubType] = None
    reference: Optional[StrictStr] = None
    buyer_assigned_id: Optional[StrictStr] = None
    product_global_id: Optional[StrictStr] = None
    product_global_id_scheme: Optional[StrictStr] = None
    item_name: StrictStr = Field(description="Item name (BT-153).")
    item_description: Optional[StrictStr] = None
    origin_country: Optional[StrictStr] = None
    characteristics: Optional[List[ProductCharacteristic]] = None
    classifications: Optional[List[ProductClassification]] = None
    quantity: Quantity
    unit: UnitOfMeasure = Field(description="Invoiced quantity unit of measure code (BT-130).")
    gross_unit_price: Optional[GrossUnitPrice] = None
    unit_net_price: UnitNetPrice
    price_basis_quantity: Optional[PriceBasisQuantity] = None
    price_basis_unit: Optional[StrictStr] = None
    price_allowance_amount: Optional[PriceAllowanceAmount] = None
    line_net_amount: Optional[LineNetAmount] = Field(default=None, alias="lineNetAmount")
    allowance_amount: Optional[InvoiceLineAllowanceAmount] = Field(default=None, alias="allowanceAmount")
    allowance_reason_code: Optional[AllowanceReasonCode] = Field(default=None, alias="allowanceReasonCode")
    allowance_reason: Optional[StrictStr] = Field(default=None, alias="allowanceReason")
    allowances_charges: Optional[List[AllowanceCharge]] = None
    vat_rate: Optional[StrictStr] = None
    manual_vat_rate: Optional[ManualVatRate] = None
    vat_category: Optional[VATCategory] = None
    period_start_date: Optional[StrictStr] = None
    period_end_date: Optional[StrictStr] = None
    purchase_order_line_ref: Optional[StrictStr] = None
    accounting_account: Optional[StrictStr] = None
    additional_documents: Optional[List[AdditionalDocument]] = None
    line_notes: Optional[List[InvoiceNote]] = None
    __properties: ClassVar[List[str]] = ["line_number", "line_note", "parent_line_id", "line_sub_type", "reference", "buyer_assigned_id", "product_global_id", "product_global_id_scheme", "item_name", "item_description", "origin_country", "characteristics", "classifications", "quantity", "unit", "gross_unit_price", "unit_net_price", "price_basis_quantity", "price_basis_unit", "price_allowance_amount", "lineNetAmount", "allowanceAmount", "allowanceReasonCode", "allowanceReason", "allowances_charges", "vat_rate", "manual_vat_rate", "vat_category", "period_start_date", "period_end_date", "purchase_order_line_ref", "accounting_account", "additional_documents", "line_notes"]

    model_config = ConfigDict(
        populate_by_name=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"""
        # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
        return json.dumps(self.to_dict())

    @classmethod
    def from_json(cls, json_str: str) -> Optional[Self]:
        """Create an instance of InvoiceLine 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.
        """
        excluded_fields: Set[str] = set([
        ])

        _dict = self.model_dump(
            by_alias=True,
            exclude=excluded_fields,
            exclude_none=True,
        )
        # override the default output from pydantic by calling `to_dict()` of each item in characteristics (list)
        _items = []
        if self.characteristics:
            for _item_characteristics in self.characteristics:
                if _item_characteristics:
                    _items.append(_item_characteristics.to_dict())
            _dict['characteristics'] = _items
        # override the default output from pydantic by calling `to_dict()` of each item in classifications (list)
        _items = []
        if self.classifications:
            for _item_classifications in self.classifications:
                if _item_classifications:
                    _items.append(_item_classifications.to_dict())
            _dict['classifications'] = _items
        # override the default output from pydantic by calling `to_dict()` of quantity
        if self.quantity:
            _dict['quantity'] = self.quantity.to_dict()
        # override the default output from pydantic by calling `to_dict()` of gross_unit_price
        if self.gross_unit_price:
            _dict['gross_unit_price'] = self.gross_unit_price.to_dict()
        # override the default output from pydantic by calling `to_dict()` of unit_net_price
        if self.unit_net_price:
            _dict['unit_net_price'] = self.unit_net_price.to_dict()
        # override the default output from pydantic by calling `to_dict()` of price_basis_quantity
        if self.price_basis_quantity:
            _dict['price_basis_quantity'] = self.price_basis_quantity.to_dict()
        # override the default output from pydantic by calling `to_dict()` of price_allowance_amount
        if self.price_allowance_amount:
            _dict['price_allowance_amount'] = self.price_allowance_amount.to_dict()
        # override the default output from pydantic by calling `to_dict()` of line_net_amount
        if self.line_net_amount:
            _dict['lineNetAmount'] = self.line_net_amount.to_dict()
        # override the default output from pydantic by calling `to_dict()` of allowance_amount
        if self.allowance_amount:
            _dict['allowanceAmount'] = self.allowance_amount.to_dict()
        # override the default output from pydantic by calling `to_dict()` of each item in allowances_charges (list)
        _items = []
        if self.allowances_charges:
            for _item_allowances_charges in self.allowances_charges:
                if _item_allowances_charges:
                    _items.append(_item_allowances_charges.to_dict())
            _dict['allowances_charges'] = _items
        # override the default output from pydantic by calling `to_dict()` of manual_vat_rate
        if self.manual_vat_rate:
            _dict['manual_vat_rate'] = self.manual_vat_rate.to_dict()
        # override the default output from pydantic by calling `to_dict()` of each item in additional_documents (list)
        _items = []
        if self.additional_documents:
            for _item_additional_documents in self.additional_documents:
                if _item_additional_documents:
                    _items.append(_item_additional_documents.to_dict())
            _dict['additional_documents'] = _items
        # override the default output from pydantic by calling `to_dict()` of each item in line_notes (list)
        _items = []
        if self.line_notes:
            for _item_line_notes in self.line_notes:
                if _item_line_notes:
                    _items.append(_item_line_notes.to_dict())
            _dict['line_notes'] = _items
        # set to None if line_note (nullable) is None
        # and model_fields_set contains the field
        if self.line_note is None and "line_note" in self.model_fields_set:
            _dict['line_note'] = None

        # set to None if parent_line_id (nullable) is None
        # and model_fields_set contains the field
        if self.parent_line_id is None and "parent_line_id" in self.model_fields_set:
            _dict['parent_line_id'] = None

        # set to None if line_sub_type (nullable) is None
        # and model_fields_set contains the field
        if self.line_sub_type is None and "line_sub_type" in self.model_fields_set:
            _dict['line_sub_type'] = None

        # set to None if reference (nullable) is None
        # and model_fields_set contains the field
        if self.reference is None and "reference" in self.model_fields_set:
            _dict['reference'] = None

        # set to None if buyer_assigned_id (nullable) is None
        # and model_fields_set contains the field
        if self.buyer_assigned_id is None and "buyer_assigned_id" in self.model_fields_set:
            _dict['buyer_assigned_id'] = None

        # set to None if product_global_id (nullable) is None
        # and model_fields_set contains the field
        if self.product_global_id is None and "product_global_id" in self.model_fields_set:
            _dict['product_global_id'] = None

        # set to None if product_global_id_scheme (nullable) is None
        # and model_fields_set contains the field
        if self.product_global_id_scheme is None and "product_global_id_scheme" in self.model_fields_set:
            _dict['product_global_id_scheme'] = None

        # set to None if item_description (nullable) is None
        # and model_fields_set contains the field
        if self.item_description is None and "item_description" in self.model_fields_set:
            _dict['item_description'] = None

        # set to None if origin_country (nullable) is None
        # and model_fields_set contains the field
        if self.origin_country is None and "origin_country" in self.model_fields_set:
            _dict['origin_country'] = None

        # set to None if characteristics (nullable) is None
        # and model_fields_set contains the field
        if self.characteristics is None and "characteristics" in self.model_fields_set:
            _dict['characteristics'] = None

        # set to None if classifications (nullable) is None
        # and model_fields_set contains the field
        if self.classifications is None and "classifications" in self.model_fields_set:
            _dict['classifications'] = None

        # set to None if gross_unit_price (nullable) is None
        # and model_fields_set contains the field
        if self.gross_unit_price is None and "gross_unit_price" in self.model_fields_set:
            _dict['gross_unit_price'] = None

        # set to None if price_basis_quantity (nullable) is None
        # and model_fields_set contains the field
        if self.price_basis_quantity is None and "price_basis_quantity" in self.model_fields_set:
            _dict['price_basis_quantity'] = None

        # set to None if price_basis_unit (nullable) is None
        # and model_fields_set contains the field
        if self.price_basis_unit is None and "price_basis_unit" in self.model_fields_set:
            _dict['price_basis_unit'] = None

        # set to None if price_allowance_amount (nullable) is None
        # and model_fields_set contains the field
        if self.price_allowance_amount is None and "price_allowance_amount" in self.model_fields_set:
            _dict['price_allowance_amount'] = None

        # set to None if line_net_amount (nullable) is None
        # and model_fields_set contains the field
        if self.line_net_amount is None and "line_net_amount" in self.model_fields_set:
            _dict['lineNetAmount'] = None

        # set to None if allowance_amount (nullable) is None
        # and model_fields_set contains the field
        if self.allowance_amount is None and "allowance_amount" in self.model_fields_set:
            _dict['allowanceAmount'] = None

        # set to None if allowance_reason_code (nullable) is None
        # and model_fields_set contains the field
        if self.allowance_reason_code is None and "allowance_reason_code" in self.model_fields_set:
            _dict['allowanceReasonCode'] = None

        # set to None if allowance_reason (nullable) is None
        # and model_fields_set contains the field
        if self.allowance_reason is None and "allowance_reason" in self.model_fields_set:
            _dict['allowanceReason'] = None

        # set to None if allowances_charges (nullable) is None
        # and model_fields_set contains the field
        if self.allowances_charges is None and "allowances_charges" in self.model_fields_set:
            _dict['allowances_charges'] = None

        # set to None if vat_rate (nullable) is None
        # and model_fields_set contains the field
        if self.vat_rate is None and "vat_rate" in self.model_fields_set:
            _dict['vat_rate'] = None

        # set to None if vat_category (nullable) is None
        # and model_fields_set contains the field
        if self.vat_category is None and "vat_category" in self.model_fields_set:
            _dict['vat_category'] = None

        # set to None if period_start_date (nullable) is None
        # and model_fields_set contains the field
        if self.period_start_date is None and "period_start_date" in self.model_fields_set:
            _dict['period_start_date'] = None

        # set to None if period_end_date (nullable) is None
        # and model_fields_set contains the field
        if self.period_end_date is None and "period_end_date" in self.model_fields_set:
            _dict['period_end_date'] = None

        # set to None if purchase_order_line_ref (nullable) is None
        # and model_fields_set contains the field
        if self.purchase_order_line_ref is None and "purchase_order_line_ref" in self.model_fields_set:
            _dict['purchase_order_line_ref'] = None

        # set to None if accounting_account (nullable) is None
        # and model_fields_set contains the field
        if self.accounting_account is None and "accounting_account" in self.model_fields_set:
            _dict['accounting_account'] = None

        # set to None if additional_documents (nullable) is None
        # and model_fields_set contains the field
        if self.additional_documents is None and "additional_documents" in self.model_fields_set:
            _dict['additional_documents'] = None

        # set to None if line_notes (nullable) is None
        # and model_fields_set contains the field
        if self.line_notes is None and "line_notes" in self.model_fields_set:
            _dict['line_notes'] = None

        return _dict

    @classmethod
    def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
        """Create an instance of InvoiceLine from a dict"""
        if obj is None:
            return None

        if not isinstance(obj, dict):
            return cls.model_validate(obj)

        _obj = cls.model_validate({
            "line_number": obj.get("line_number"),
            "line_note": obj.get("line_note"),
            "parent_line_id": obj.get("parent_line_id"),
            "line_sub_type": obj.get("line_sub_type"),
            "reference": obj.get("reference"),
            "buyer_assigned_id": obj.get("buyer_assigned_id"),
            "product_global_id": obj.get("product_global_id"),
            "product_global_id_scheme": obj.get("product_global_id_scheme"),
            "item_name": obj.get("item_name"),
            "item_description": obj.get("item_description"),
            "origin_country": obj.get("origin_country"),
            "characteristics": [ProductCharacteristic.from_dict(_item) for _item in obj["characteristics"]] if obj.get("characteristics") is not None else None,
            "classifications": [ProductClassification.from_dict(_item) for _item in obj["classifications"]] if obj.get("classifications") is not None else None,
            "quantity": Quantity.from_dict(obj["quantity"]) if obj.get("quantity") is not None else None,
            "unit": obj.get("unit"),
            "gross_unit_price": GrossUnitPrice.from_dict(obj["gross_unit_price"]) if obj.get("gross_unit_price") is not None else None,
            "unit_net_price": UnitNetPrice.from_dict(obj["unit_net_price"]) if obj.get("unit_net_price") is not None else None,
            "price_basis_quantity": PriceBasisQuantity.from_dict(obj["price_basis_quantity"]) if obj.get("price_basis_quantity") is not None else None,
            "price_basis_unit": obj.get("price_basis_unit"),
            "price_allowance_amount": PriceAllowanceAmount.from_dict(obj["price_allowance_amount"]) if obj.get("price_allowance_amount") is not None else None,
            "lineNetAmount": LineNetAmount.from_dict(obj["lineNetAmount"]) if obj.get("lineNetAmount") is not None else None,
            "allowanceAmount": InvoiceLineAllowanceAmount.from_dict(obj["allowanceAmount"]) if obj.get("allowanceAmount") is not None else None,
            "allowanceReasonCode": obj.get("allowanceReasonCode"),
            "allowanceReason": obj.get("allowanceReason"),
            "allowances_charges": [AllowanceCharge.from_dict(_item) for _item in obj["allowances_charges"]] if obj.get("allowances_charges") is not None else None,
            "vat_rate": obj.get("vat_rate"),
            "manual_vat_rate": ManualVatRate.from_dict(obj["manual_vat_rate"]) if obj.get("manual_vat_rate") is not None else None,
            "vat_category": obj.get("vat_category"),
            "period_start_date": obj.get("period_start_date"),
            "period_end_date": obj.get("period_end_date"),
            "purchase_order_line_ref": obj.get("purchase_order_line_ref"),
            "accounting_account": obj.get("accounting_account"),
            "additional_documents": [AdditionalDocument.from_dict(_item) for _item in obj["additional_documents"]] if obj.get("additional_documents") is not None else None,
            "line_notes": [InvoiceNote.from_dict(_item) for _item in obj["line_notes"]] if obj.get("line_notes") is not None else None
        })
        return _obj


