{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/frihet-io/json-schema/frihet-io-expense-create-schema.json",
  "title": "ExpenseCreate",
  "x-generated": "2026-11-09",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/frihet-io-openapi.json#/components/schemas/ExpenseCreate",
  "type": "object",
  "required": [
    "description",
    "amount"
  ],
  "properties": {
    "description": {
      "type": "string",
      "description": "Expense description"
    },
    "amount": {
      "type": "number",
      "description": "Expense amount"
    },
    "date": {
      "type": "string",
      "format": "date",
      "description": "Expense date in ISO format"
    },
    "paidDate": {
      "type": [
        "string",
        "null"
      ],
      "format": "date",
      "description": "Effective payment date (fecha de pago, YYYY-MM-DD). Drives cash-basis period assignment for Modelos 111/115/130. When absent: under accrual (devengo) criteria the expense `date` is used; under the opt-in Modelo 130 cash-basis criterion (criterio de cobros y pagos) an absent paidDate means not yet paid and the expense is excluded until paid. On PATCH, an explicit null clears the field."
    },
    "category": {
      "type": "string",
      "description": "Expense category"
    },
    "vendor": {
      "type": "string",
      "description": "Vendor or supplier name"
    },
    "taxDeductible": {
      "type": "boolean",
      "description": "Whether the expense is tax deductible"
    },
    "currency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3,
      "description": "ISO 4217 transaction currency. Defaults to EUR.",
      "default": "EUR"
    },
    "exchangeRate": {
      "type": "number",
      "exclusiveMinimum": 0,
      "description": "Exchange rate used to convert amount into functionalCurrency."
    },
    "exchangeRateSource": {
      "type": "string",
      "maxLength": 200,
      "description": "Source/provider of the exchange rate snapshot."
    },
    "exchangeRateDate": {
      "type": "string",
      "format": "date",
      "description": "Exchange rate date (YYYY-MM-DD)."
    },
    "functionalCurrency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3,
      "description": "Workspace/reporting currency. Defaults to EUR.",
      "default": "EUR"
    },
    "functionalAmount": {
      "type": "number",
      "description": "Amount converted into functionalCurrency."
    },
    "attachments": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/FileAttachment"
      },
      "readOnly": true
    },
    "recurring": {
      "$ref": "#/$defs/ExpenseRecurringConfig"
    }
  },
  "$defs": {
    "ExpenseRecurringConfig": {
      "type": "object",
      "required": [
        "enabled",
        "frequency",
        "nextDate"
      ],
      "description": "Recurring expense configuration. Set on a template expense to auto-generate copies on schedule.",
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Whether recurring generation is active"
        },
        "frequency": {
          "type": "string",
          "enum": [
            "weekly",
            "biweekly",
            "monthly",
            "quarterly",
            "yearly"
          ],
          "description": "How often to generate a new expense"
        },
        "nextDate": {
          "type": "string",
          "format": "date",
          "description": "Next scheduled generation date (ISO YYYY-MM-DD)"
        },
        "endDate": {
          "type": "string",
          "format": "date",
          "description": "Stop generating after this date (optional)"
        },
        "maxOccurrences": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of expenses to generate (optional)"
        },
        "occurrencesGenerated": {
          "type": "integer",
          "readOnly": true,
          "description": "Number of expenses generated so far (server-managed, read-only)"
        }
      }
    },
    "FileAttachment": {
      "type": "object",
      "required": [
        "fileId",
        "originalName",
        "contentType",
        "size"
      ],
      "additionalProperties": false,
      "properties": {
        "fileId": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Opaque uploaded file reference. Not a path or URL."
        },
        "originalName": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "description": "Filename only; local paths are rejected."
        },
        "contentType": {
          "type": "string",
          "enum": [
            "application/pdf",
            "image/jpeg",
            "image/jpg",
            "image/png",
            "image/webp",
            "image/heic",
            "image/heif",
            "image/tiff"
          ]
        },
        "size": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10485760
        },
        "checksumSha256": {
          "type": "string",
          "pattern": "^[A-Fa-f0-9]{64}$"
        }
      }
    }
  }
}