openapi: 3.0.3
info:
  title: Prosperity Document Management API
  version: 2.4.0
  description: |
    Document storage, generation, retrieval and directory management API for Prosperity Health.

    ## Authentication
    All endpoints require a valid session cookie (`auth_token`).
    Obtain one via `POST /user/login` before calling any other endpoint.

    ## Folder / Path model
    Paths are `/`-separated strings that map to a hierarchy of DMS tags.
    Example: `Remittances/2026/February` resolves (and auto-creates) three nested tag nodes.

    ## Template system
    `POST /document/generate/XML` dispatches to a registered document template:
    - Specify `templateName` to select a template explicitly.
    - Omit `templateName` to auto-detect from the XML root element.
    - List all available templates via `GET /document/templates`.

    ### Built-in templates

    | templateName | Category | XML Root | Description |
    |---|---|---|---|
    | `NAM_REM_MEM_19` | prosperity | `Root` | Member remittance – Society 19: Renaissance Health |
    | `NAM_REM_SUP_19` | prosperity | `Root` | Supplier remittance – Society 19: Renaissance Health |
    | `NAM_REM_MEM_86` | prosperity | `Root` | Member remittance – Society 86: PHN Life |
    | `NAM_REM_SUP_86` | prosperity | `Root` | Supplier remittance – Society 86: PHN Life |
    | `NAM_REM_MEM_116` | prosperity | `Root` | Member remittance – Society 116: Napotel |
    | `NAM_REM_SUP_116` | prosperity | `Root` | Supplier remittance – Society 116: Napotel |
    | `NAM_REM_MEM_130` | prosperity | `Root` | Member remittance – Society 130: GemHealth |
    | `NAM_REM_SUP_130` | prosperity | `Root` | Supplier remittance – Society 130: GemHealth |
    | `NAM_REM_MEM_133` | prosperity | `Root` | Member remittance – Society 133: RCC |
    | `NAM_REM_SUP_133` | prosperity | `Root` | Supplier remittance – Society 133: RCC |
    | `SER_REM_MEM_2771` | prosperity | `Root` | Member remittance – Society 2771: NUFAWSA |
    | `SER_REM_SUP_2771` | prosperity | `Root` | Supplier remittance – Society 2771: NUFAWSA |
    | `SER_REM_MEM_434` | prosperity | `Root` | Member remittance – Society 434: FURNMED |
    | `SER_REM_SUP_434` | prosperity | `Root` | Supplier remittance – Society 434: FURNMED |
    | `remittance` | prosperity | `Root` | Prosperity Health remittance – generic fallback (no JobName match) |
    | `benefit-confirmation` | prosperity | `BenefitConfirmationSummary` | Member benefit confirmation summary |
    | `authorization-event` | prosperity | `Event` | Pre-authorisation / benefit confirmation letter (APPROVED/REVERSED) |
    | `supplier-summary` | prosperity | `SupplierSummary` | Supplier / practice profile summary |
    | `tax-invoice-19` | prosperity | `TaxInvoice` | Tax invoice – Society 19: Renaissance Health Medical Aid Fund |
    | `tax-invoice-86` | prosperity | `TaxInvoice` | Tax invoice – Society 86: Prosperity Lifecare Insurance Limited |
    | `tax-invoice-116` | prosperity | `TaxInvoice` | Tax invoice – Society 116: Napotel Medical Aid Fund |
    | `tax-invoice-130` | prosperity | `TaxInvoice` | Tax invoice – Society 130: GemHealth Medical Aid Scheme |
    | `tax-invoice-133` | prosperity | `TaxInvoice` | Tax invoice – Society 133: Road Contractor Company |
    | `tax-invoice-2771` | prosperity | `TaxInvoice` | Tax invoice – Society 2771: Napotel |
    | `tax-invoice-3786` | prosperity | `TaxInvoice` | Tax invoice – Society 3786: Prosperity Lifecare Insurance Ltd |
    | `tax-invoice` | prosperity | `TaxInvoice` | Tax invoice – generic fallback for unknown SocietyId |
    | `billing-sheet` | emed | `CRMBillingDocument` | EMED billing sheet / invoice |
    | `ledger-customer` | emed | `SupplierLedgerForm` | Customer/scheme ledger statement |
    | `ledger-supplier` | emed | `SupplierLedgerForm` | Supplier ledger statement |
    | `inventory-medical-bag` | emed | `InventoryReport` | Medical bag stock-take |
    | `inventory-stock-take` | emed | `InventoryReport` | Categorised stock-take |
    | `asset-stock-take` | emed | `TransferNote` | Asset stock-take report |
    | `transfer-note-stock` | emed | `TransferNote` | Stock transfer note (model/serial) |
    | `transfer-note` | emed | `TransferNote` | Internal site-to-site transfer |
    | `fuel-request` | emed | `Form` | Fuel / air service request |
    | `ground-ambulance` | emed | `Form` | Ground ambulance request |
    | `pre-auth` | emed | `Form` | Pre-authorisation / cost estimate |
    | `monthly-station-report` | emed | `Form` | Monthly base station report |
    | `order-form` | emed | `OrderForm` | Stock order form |
    | `payment-receipt` | emed | `PaymentReceipt` | Payment receipt |
    | `wastage-form` | emed | `WastageForm` | Wastage / disposal form |
    | `checklist` | emed-shift | `Checklist` | Ambulance / vehicle shift checklist |
    | `shift-leader-report` | emed-shift | `Report` | Shift leader incident report |
    | `accommodation-tax-invoice` | pos | `AccommodationTaxInvoice` | Lodge accommodation tax invoice with guest details and itemised charges |
    | `excursion-tax-invoice` | pos | `ExcursionTaxInvoice` | Lodge excursion tax invoice with guest details and itemised charges |
    | `account-payment` | pos | `AccountPayment` | Lodge account payment receipt covering one or more invoices |
    | `current-guest-list` | pos | `CurrentGuestList` | Lodge current guest list showing all occupied and arriving units |
    | `excursion-form` | pos | `ExcursionForm` | Lodge excursion booking confirmation with participant list |
    | `asset-inventory-report` | pos | `AssetInventoryReport` | Lodge asset inventory report grouped by category |
    | `asset-purchase-order` | pos | `AssetPurchaseOrderForm` | Lodge asset purchase order with supplier details |
    | `asset-transfer-note` | pos | `AssetTransferNote` | Lodge asset transfer note between sites |
    | `stock-sheet` | pos | `StockSheet` | Lodge stock count sheet for manual quantity entry (V1 flat and V2 category layouts) |
    | `stock-requisition` | pos | `RequisitionReport` | Lodge stock requisition form with par and available quantities |
    | `ingredient-form` | pos | `IngredientForm` | Lodge kitchen recipe ingredients list |
    | `staff-meal-form` | pos | `StaffMealForm` | Lodge staff meal plan with ingredient requirements |
    | `cash-up-report` | pos | `CashUpReport` | Lodge consolidated cash-up report with denomination breakdown and per-shift summary |
    | `cash-up-shift-report` | pos | `CashUpShiftReport` | Lodge single-shift cash-up report with denomination breakdown |
    | `table-reservation` | pos | `TableReservation` | Lodge restaurant table reservation slip |

servers:
  - url: https://prosperity.docs.i-suite.co.za/api
    description: Production
  - url: https://prosperity.docs.i-suitedemo.co.za/api
    description: Staging

security:
  - cookieAuth: []

components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: auth_token

  schemas:
    ErrorResponse:
      type: object
      properties:
        type:
          type: string
          example: ProcessingError
        message:
          type: string
          example: Detailed error description

    LoginRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          example: admin
        password:
          type: string
          format: password
        remember:
          type: boolean
          default: false
          description: If true, creates a long-lived session token

    DocumentNode:
      type: object
      properties:
        name:
          type: string
          example: myDoc.pdf
        type:
          type: string
          enum: [file, folder]
        mimeType:
          type: string
          nullable: true
          example: application/pdf
        size:
          type: integer
          nullable: true
          example: 45385
        id:
          type: string
          description: Document or tag ID
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890

    StoreResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        documentId:
          type: string
          description: DMS document ID
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        fileId:
          type: string
          description: ID of the stored file within the document
          example: b2c3d4e5-f6a7-8901-bcde-f12345678901
        size:
          type: integer
          description: File size in bytes
          example: 45385

    TemplateSummary:
      type: object
      properties:
        name:
          type: string
          example: remittance
        displayName:
          type: string
          example: Prosperity Remittance
        description:
          type: string
        category:
          type: string
          example: prosperity
        xmlRootElement:
          type: string
          description: XML root element tag used for auto-detection
          example: Root

    TemplateDetail:
      allOf:
        - $ref: '#/components/schemas/TemplateSummary'
        - type: object
          properties:
            parameters:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                    example: Member / Practice Number
                  type:
                    type: string
                    enum: [field, table, option]
                    example: field
                  description:
                    type: string
                  required:
                    type: boolean
                  xmlPath:
                    type: string
                    description: XPath relative to document root (absent for option type)
                    example: Remittance/REMITTANCE_HEADER/MEMBER_NUMBER

    LogoItem:
      type: object
      properties:
        filename:
          type: string
          example: Logo-ProsperityLife.png
        deletable:
          type: boolean
          description: True if uploaded at runtime (can be deleted); false if bundled
        source:
          type: string
          enum: [runtime, bundled]

paths:

  # ===========================================================================
  # Authentication
  # ===========================================================================

  /user/login:
    post:
      summary: Login and obtain session cookie
      security: []
      tags:
        - Auth
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        "200":
          description: Authenticated — sets `auth_token` cookie
          headers:
            Set-Cookie:
              schema:
                type: string
                example: auth_token=abc123; Path=/; HttpOnly
        "403":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /user/logout:
    post:
      summary: Invalidate session cookie
      tags:
        - Auth
      responses:
        "200":
          description: Logged out

  # ===========================================================================
  # Document management
  # ===========================================================================

  /document:
    get:
      summary: Retrieve a document file by path
      description: |
        Resolves the path to a DMS document, decrypts the first attached file,
        and streams the raw bytes. Optionally decrypts a password-protected PDF.
      tags:
        - Document
      parameters:
        - name: path
          in: query
          required: true
          description: Full path including filename (e.g. `Remittances/2026/February/568272-19_2026.pdf`)
          schema:
            type: string
            example: Remittances/2026/February/568272-19_2026.pdf
        - name: password
          in: query
          required: false
          description: Password to decrypt a password-protected PDF
          schema:
            type: string
            example: test1234
      responses:
        "200":
          description: File byte stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "403":
          description: Access denied
        "404":
          description: Path or file not found

  /document/list:
    get:
      summary: List folder contents or search documents
      description: |
        **Folder listing mode** — pass `path` to list sub-folders and files at that path.
        Omit `path` or leave it empty to list the root.

        **Document search mode** — omit `path` and use one or more `search[*]` parameters
        to filter documents.

        ### Tag filtering options

        | Parameter | Matching | Use when |
        |---|---|---|
        | `search[tag]` | Case-insensitive **name prefix** across all tags | Simple / legacy |
        | `search[tagid]` | **Exact UUID** — includes the tag and all its children | You have the tag ID |
        | `search[path]` | **Full folder path** resolved to an exact tag — includes children | Recommended for API callers |

        `search[path]` is the safest option for API callers because it resolves the full
        parent chain (e.g. `remit/Namibia/supplier/130`) and cannot accidentally match a
        tag with the same name in a different branch (e.g. `remit/Namibia/customer/130`).

        Multiple `search[*]` parameters are ANDed together.
      tags:
        - Document
      parameters:
        - name: path
          in: query
          required: false
          description: Folder path to list (folder-listing mode). Omit for document search mode.
          schema:
            type: string
            example: Remittances/2026/
        - name: search[path]
          in: query
          required: false
          description: |
            Folder path used to filter documents (search mode). Resolves each path segment
            by name **and parent**, so `remit/Namibia/supplier/130` and
            `remit/Namibia/customer/130` are treated as distinct even though both contain
            a segment named `130`. Documents under all child paths are included.
          schema:
            type: string
            example: remit/Namibia/supplier/130
        - name: search[tagid]
          in: query
          required: false
          description: |
            Exact tag UUID. Includes the tag and all its descendants.
            Use `GET /tag/list` to obtain IDs.
          schema:
            type: string
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        - name: search[tag]
          in: query
          required: false
          description: |
            Tag name prefix (case-insensitive). Matches **all** tags whose name starts
            with this value across the entire tree. Avoid when the same name appears in
            multiple branches — prefer `search[path]` instead.
          schema:
            type: string
            example: supplier
        - name: search[title]
          in: query
          required: false
          description: Exact document title match.
          schema:
            type: string
        - name: search[full]
          in: query
          required: false
          description: Full-text search across document content.
          schema:
            type: string
        - name: search[simple]
          in: query
          required: false
          description: Simple keyword search.
          schema:
            type: string
        - name: search[after]
          in: query
          required: false
          description: Created after date (yyyy-MM-dd).
          schema:
            type: string
            example: "2026-01-01"
        - name: search[before]
          in: query
          required: false
          description: Created before date (yyyy-MM-dd).
          schema:
            type: string
            example: "2026-12-31"
        - name: limit
          in: query
          required: false
          description: Page size (default 10).
          schema:
            type: integer
            example: 50
        - name: offset
          in: query
          required: false
          description: Page offset (default 0).
          schema:
            type: integer
            example: 0
      responses:
        "200":
          description: Folder listing (when `path` is set) or document search results
          content:
            application/json:
              schema:
                oneOf:
                  - description: Folder listing response
                    type: object
                    properties:
                      path:
                        type: string
                        example: Remittances/2026/
                      nodes:
                        type: array
                        items:
                          $ref: '#/components/schemas/DocumentNode'
                  - description: Document search response
                    type: object
                    properties:
                      total:
                        type: integer
                      documents:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            title:
                              type: string
                            tags:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                  name:
                                    type: string
                                  color:
                                    type: string
        "403":
          description: Access denied

  /document/folder:
    post:
      summary: Create a folder node (idempotent)
      description: |
        Creates a single child folder node under the given parent path.
        All intermediate segments in `path` must already exist; they are not
        auto-created. The new node is named `nodeName`. Calling this endpoint
        when the folder already exists is safe (no-op).
      tags:
        - Document
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - nodeName
              properties:
                path:
                  type: string
                  description: |
                    Parent folder path. Omit or leave empty to create at root.
                  example: Remittances/2026
                nodeName:
                  type: string
                  description: Name of the folder node to create (max 36 characters)
                  example: February
      responses:
        "200":
          description: Folder created or already exists
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  path:
                    type: string
                    description: Full path of the created/existing folder
                    example: Remittances/2026/February/
        "400":
          description: Missing or invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Access denied

  /document/store:
    post:
      summary: Upload and store a file at a folder path
      description: |
        Stores an uploaded file as a new DMS document tagged to the given folder path.
        The folder path is auto-created (all segments) if any segment does not exist.

        **Field name aliases** — the server accepts any of these equivalents:
        - Folder path: `path` **or** `nodePath` **or** `nodenodePath`
        - Filename: `filename` **or** `fileName` (falls back to multipart `Content-Disposition` filename)
      tags:
        - Document
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                path:
                  type: string
                  description: "Target folder path (auto-created if absent). Aliases: `nodePath`, `nodenodePath`"
                  example: Documents/Prosperity/remit/Namibia/supplier/130/20251022
                filename:
                  type: string
                  description: "Override filename. Alias: `fileName`"
                  example: 6260772.pdf
                file:
                  type: string
                  format: binary
                  description: File content
                mimeType:
                  type: string
                  description: MIME type of the file
                  example: application/pdf
      responses:
        "200":
          description: Stored successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoreResponse'
        "403":
          description: Access denied
        "500":
          description: Storage error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /document/generate:
    post:
      summary: Decode a base64 payload and stream it back (no storage)
      description: |
        Accepts a base64-encoded file and streams the decoded bytes back.
        Does not store anything in the DMS. Useful for client-side preview before storing.
        `templateName` is accepted for future template-based rendering but has no effect yet.
      tags:
        - Document
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - file
                - mimeType
              properties:
                file:
                  type: string
                  format: byte
                  description: Base64-encoded file content
                mimeType:
                  type: string
                  example: application/pdf
                filename:
                  type: string
                  description: Used in Content-Disposition header (optional)
                  example: preview.pdf
                templateName:
                  type: string
                  description: Template name for future rendering support (reserved)
                  example: remittance-standard
      responses:
        "200":
          description: Decoded file byte stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "403":
          description: Access denied
        "500":
          description: Decode error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # ===========================================================================
  # Template registry
  # ===========================================================================

  /document/templates:
    get:
      summary: List all registered document templates
      description: |
        Returns every template registered in the server's template registry,
        including its name, display name, description, category and the XML root
        element it targets for auto-detection.
      tags:
        - Template
      responses:
        "200":
          description: Template list
          content:
            application/json:
              schema:
                type: object
                properties:
                  templates:
                    type: array
                    items:
                      $ref: '#/components/schemas/TemplateSummary'
        "403":
          description: Access denied

  /document/templates/{name}:
    get:
      summary: Get template detail and parameter schema
      description: |
        Returns full detail for a single template, including the list of
        XML parameters / fields the template expects.
      tags:
        - Template
      parameters:
        - name: name
          in: path
          required: true
          description: Template name (e.g. `remittance`, `billing-sheet`)
          schema:
            type: string
            example: remittance
      responses:
        "200":
          description: Template detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateDetail'
        "403":
          description: Access denied
        "404":
          description: Template not found

  /document/generate/XML:
    post:
      summary: Generate a document from Prosperity XML and stream it back (no storage)
      description: |
        Parses a Prosperity Health remittance XML, generates a document using the
        configured template, and returns the raw bytes as an octet-stream.
        Does not store anything in the DMS.

        To persist the result, pipe the response body into `POST /document/store`.

        The optional `logo` field overrides the automatic fund logo selection.
        Available logos are listed via `GET /prosperity/logos`.

        `templateName` is accepted for future template selection but currently
        has no effect — the default Prosperity renderer is always used.
      tags:
        - Document
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - xml
              properties:
                xml:
                  type: string
                  description: Prosperity Health remittance XML content
                filename:
                  type: string
                  description: Source filename — used in error messages only
                  example: 568272_19_20260203.xml
                logo:
                  type: string
                  description: Logo filename override (from /prosperity/logos list)
                  example: Logo-ProsperityLife.png
                templateName:
                  type: string
                  description: |
                    Template name to use for rendering (see `GET /document/templates`).
                    If omitted, the template is auto-detected from the XML root element.
                    If detection is ambiguous, specify this field explicitly.
                  example: remittance
      responses:
        "200":
          description: Generated document byte stream
          headers:
            Content-Disposition:
              schema:
                type: string
                example: inline; filename="568272-19_2026.pdf"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "403":
          description: Access denied
        "500":
          description: XML parsing or document generation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # ===========================================================================
  # Stored remittance retrieval
  # ===========================================================================

  /prosperity/{documentId}/pdf:
    get:
      summary: Download stored remittance PDF (unprotected)
      description: |
        Retrieves and decrypts the first file attached to the given DMS document.
        The document must have been created by storing a remittance via
        `POST /document/store`.
      tags:
        - Prosperity Remittance
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        "200":
          description: PDF byte stream
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "403":
          description: Access denied
        "404":
          description: Document or file not found

  /prosperity/{documentId}/pdf/protected:
    get:
      summary: Download stored remittance PDF (password-protected variant)
      description: |
        Returns the second file attached to the given DMS document,
        which is expected to be the password-protected PDF variant.
      tags:
        - Prosperity Remittance
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
      responses:
        "200":
          description: Password-protected PDF byte stream
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "403":
          description: Access denied
        "404":
          description: Document or protected file not found

  # ===========================================================================
  # Logo management
  # ===========================================================================

  /prosperity/logos:
    get:
      summary: List available logos
      description: |
        Returns all logo filenames — both bundled (read-only, shipped in the JAR)
        and runtime-uploaded logos. Only runtime logos can be deleted.
      tags:
        - Logo Management
      responses:
        "200":
          description: Logo list
          content:
            application/json:
              schema:
                type: object
                properties:
                  logos:
                    type: array
                    items:
                      $ref: '#/components/schemas/LogoItem'
        "403":
          description: Access denied

    post:
      summary: Upload or replace a logo
      description: |
        Uploads a logo to the runtime logos directory.
        If a logo with the same filename already exists it is replaced.
        Runtime logos take priority over bundled logos with the same name.
      tags:
        - Logo Management
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: Logo image file (PNG, JPG, etc.)
                filename:
                  type: string
                  description: Override filename (optional — falls back to multipart filename)
                  example: Logo-MyFund.png
      responses:
        "200":
          description: Logo saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  filename:
                    type: string
                    example: Logo-MyFund.png
        "403":
          description: Access denied
        "500":
          description: Upload failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /prosperity/logos/{filename}:
    delete:
      summary: Delete a runtime logo
      description: |
        Deletes a logo from the runtime directory.
        Bundled classpath logos cannot be deleted — they will return 404
        since they are not present in runtime storage.
      tags:
        - Logo Management
      parameters:
        - name: filename
          in: path
          required: true
          schema:
            type: string
            example: Logo-MyFund.png
      responses:
        "200":
          description: Logo deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  filename:
                    type: string
                    example: Logo-MyFund.png
        "403":
          description: Access denied
        "404":
          description: Logo not found in runtime storage
        "500":
          description: Delete failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
