openapi: 3.1.0
info:
  title: WorkSpace ERP (WS) API
  version: 3.1.0
  description: |
    REST API for WorkSpace ERP — multi-tenant ISP ERP.
    Backend target: Java Spring Boot modular monolith.
    Frontend consumer: React SPA (Node.js toolchain).

servers:
  - url: /api/v1
    description: Relative to API gateway

tags:
  - name: Platform
  - name: Tenant
  - name: Structure
  - name: Finance
  - name: Contacts
  - name: CRM
  - name: Access
  - name: Approval
  - name: Operations

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    TenantId:
      name: X-WS-Tenant-Id
      in: header
      required: true
      schema:
        type: string
        format: uuid
    CompanyId:
      name: X-WS-Company-Id
      in: header
      required: true
      schema:
        type: string
        format: uuid
  schemas:
    Error:
      type: object
      properties:
        code: { type: string }
        message: { type: string }
        requestId: { type: string }
    Page:
      type: object
      properties:
        content: { type: array, items: {} }
        page: { type: integer }
        size: { type: integer }
        totalElements: { type: integer }
    AuditableEntity:
      type: object
      properties:
        id: { type: string, format: uuid }
        createdAt: { type: string, format: date-time }
        createdBy: { type: string }
        updatedAt: { type: string, format: date-time }
        updatedBy: { type: string }
        version: { type: integer }
    Tenant:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            code: { type: string }
            name: { type: string }
            plan: { type: string, enum: [Basic, Custom, Dedicated] }
            status: { type: string }
            baseCurrency: { type: string }
            databaseMode: { type: string, enum: [SHARED, DEDICATED] }
            databaseRef: { type: string, nullable: true }
            kyc: { type: string }
    Company:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            tenantId: { type: string, format: uuid }
            code: { type: string }
            legalName: { type: string }
            tradingName: { type: string }
            status: { type: string }
    ChartOfAccount:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            tenantId: { type: string, format: uuid }
            companyId: { type: string, format: uuid }
            code: { type: string }
            name: { type: string }
            type: { type: string, enum: [Asset, Liability, Equity, Revenue, Expense] }
            currency: { type: string }
            parentId: { type: string, format: uuid, nullable: true }
            status: { type: string }
    JournalLine:
      type: object
      properties:
        accountId: { type: string, format: uuid }
        glGroupId: { type: string, format: uuid, nullable: true }
        description: { type: string }
        journalType: { type: string, enum: [Debit, Credit] }
        debit: { type: number, format: double }
        credit: { type: number, format: double }
        jnlAmount: { type: number, format: double }
        jnlCurrency: { type: string }
        txnAmount: { type: number, format: double }
        txnCurrency: { type: string }
        converted: { type: string, enum: [Yes, No] }
        exchangeRate: { type: number, format: double }
        subLedger: { type: string, enum: [Yes, No] }
        slGroup: { type: string }
        slAccountId: { type: string, nullable: true }
        clientName: { type: string }
        invoiceNumber: { type: string }
        invoiceDate: { type: string, format: date, nullable: true }
        branchId: { type: string, nullable: true }
        costCentreId: { type: string, nullable: true }
        hasAttachment: { type: string, enum: [Yes, No] }
        docCount: { type: integer }
        lineRole: { type: string, enum: [Income, Contra], nullable: true, description: "Income Journal only" }
        incomeCodeId: { type: string, nullable: true }
        defaultIncomeGlId: { type: string, nullable: true }
        splits:
          type: array
          description: Cost-centre apportionment for Income lines when split=Yes
          items:
            type: object
            properties:
              costCentreId: { type: string }
              amount: { type: number }
              percentage: { type: number }
    IncomeTransaction:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            tenantId: { type: string, format: uuid }
            companyId: { type: string, format: uuid }
            journalEntryId: { type: string, format: uuid, description: "FK to master journal_entries" }
            transactionRef: { type: string }
            batchId: { type: string }
            lineIndex: { type: integer }
            incomeCodeId: { type: string }
            defaultIncomeGlId: { type: string, nullable: true }
            accountId: { type: string }
            totalAmount: { type: number, description: "Full income credit amount mirrored from master JE line" }
            currency: { type: string }
            split: { type: string, enum: [Yes, No] }
            splitType: { type: string, enum: [Amount, Percentage] }
            splits:
              type: array
              items:
                type: object
                properties:
                  costCentreId: { type: string }
                  amount: { type: number }
                  percentage: { type: number }
            status: { type: string }
            valueDate: { type: string, format: date }
            description: { type: string }
    JournalEntry:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            tenantId: { type: string, format: uuid }
            companyId: { type: string, format: uuid }
            ref: { type: string, description: "Transaction number JE-YYYY-MM-###### from batch creation date" }
            batchId: { type: string, description: "Monthly batch number BID-YYYY-MM-##### from batch creation date" }
            batchDate: { type: string, format: date }
            journalGroup: { type: string }
            date: { type: string, format: date }
            eventDate: { type: string, format: date }
            processModule: { type: string }
            processType:
              type: string
              enum: [GL Account Journal, Income Journal, Expense Journal, Reversal Journal]
            processor: { type: string }
            processorDepartment: { type: string }
            processorUnit: { type: string }
            company: { type: string }
            description: { type: string }
            currency: { type: string }
            transactionCurrency: { type: string }
            exchangeRate: { type: number, format: double, nullable: true }
            recurring: { type: string, enum: [Yes, No] }
            split: { type: string, enum: [Yes, No] }
            splitType: { type: string, enum: [Amount, Percentage] }
            splitCount: { type: integer }
            incomeCodeId: { type: string, nullable: true }
            expenseCodeId: { type: string, nullable: true }
            revBatchId: { type: string, nullable: true }
            revTransactionId: { type: string, nullable: true }
            validationStatus: { type: string, enum: [Passed, Failed] }
            status: { type: string, enum: [Draft, Submitted, Pending approval, Approved, Returned, Rejected, Called Over, Audited, Posted, Reversed] }
            lines:
              type: array
              items: { $ref: '#/components/schemas/JournalLine' }
    ApprovalRequest:
      allOf:
        - $ref: '#/components/schemas/AuditableEntity'
        - type: object
          properties:
            tenantId: { type: string, format: uuid }
            companyId: { type: string, format: uuid }
            ref: { type: string }
            workflowId: { type: string, format: uuid }
            entity: { type: string }
            originator: { type: string }
            amount: { type: number }
            currency: { type: string }
            status: { type: string, enum: [Pending, Approved, Rejected, Returned] }
            submitted: { type: string, format: date }

security:
  - bearerAuth: []

paths:
  /platform/tenants:
    get:
      tags: [Platform]
      summary: List tenants
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Tenant' }
    post:
      tags: [Platform]
      summary: Create tenant
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/Tenant' }
      responses:
        '201':
          description: Created

  /tenant/companies:
    get:
      tags: [Tenant]
      summary: List companies for active tenant
      parameters:
        - $ref: '#/components/parameters/TenantId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Company' }

  /structure/divisions:
    get:
      tags: [Structure]
      summary: List org divisions
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '200': { description: OK }
    post:
      tags: [Structure]
      summary: Create division
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '201': { description: Created }

  /finance/chart-of-accounts:
    get:
      tags: [Finance]
      summary: List chart of accounts
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/ChartOfAccount' }

  /finance/journal-entries:
    get:
      tags: [Finance]
      summary: List journal entries
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/JournalEntry' }
    post:
      tags: [Finance]
      summary: Create journal entry (Draft)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/JournalEntry' }
      responses:
        '201': { description: Created }

  /finance/income-transactions:
    get:
      tags: [Finance]
      summary: List income transactions (splits linked to master journal entries)
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
        - name: journalEntryId
          in: query
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/IncomeTransaction' }

  /finance/journal-entries/{id}/post:
    post:
      tags: [Finance]
      summary: Post journal entry (balanced debit/credit required)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '200': { description: Posted }
        '422': { description: Journal not balanced }

  /approval/requests:
    get:
      tags: [Approval]
      summary: List approval requests
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/CompanyId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/ApprovalRequest' }
