[
  {
    "filePath": "D:\\nap-dms.lcbp3\\.agents\\scripts\\start-mcp.js",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\analyze-any.cjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\eslint.config.mjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\app.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\app.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\app.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\app.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\auth.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\auth.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\auth.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\auth.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 21,
        "column": 7,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 21,
        "endColumn": 33
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'jwtService' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 26,
        "column": 7,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 26,
        "endColumn": 17
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'tokenRepo' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 27,
        "column": 7,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 27,
        "endColumn": 16
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-call",
        "severity": 2,
        "message": "Unsafe call of an `any` typed value.",
        "line": 56,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unsafeCall",
        "endLine": 56,
        "endColumn": 37
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .compare on an `any` value.",
        "line": 56,
        "column": 12,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 56,
        "endColumn": 19
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-call",
        "severity": 2,
        "message": "Unsafe call of an `any` typed value.",
        "line": 131,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "unsafeCall",
        "endLine": 131,
        "endColumn": 43
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .compare on an `any` value.",
        "line": 131,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 131,
        "endColumn": 21
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 165,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 165,
        "endColumn": 32
      }
    ],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-require-imports",
        "severity": 2,
        "message": "A `require()` style import is forbidden.",
        "line": 21,
        "column": 16,
        "nodeType": "CallExpression",
        "messageId": "noRequireImports",
        "endLine": 21,
        "endColumn": 33,
        "suppressions": [{ "kind": "directive", "justification": "" }]
      }
    ],
    "errorCount": 8,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { AuthService } from './auth.service';\r\nimport { UserService } from '../../modules/user/user.service';\r\nimport { JwtService } from '@nestjs/jwt';\r\nimport { ConfigService } from '@nestjs/config';\r\nimport { CACHE_MANAGER } from '@nestjs/cache-manager';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { User } from '../../modules/user/entities/user.entity';\r\nimport { RefreshToken } from './entities/refresh-token.entity';\r\nimport { Repository } from 'typeorm';\r\nimport { UnauthorizedException } from '@nestjs/common';\r\n\r\n// Mock bcrypt at top level\r\njest.mock('bcrypt', () => ({\r\n  compare: jest.fn(),\r\n  hash: jest.fn().mockResolvedValue('hashedpassword'),\r\n  genSalt: jest.fn().mockResolvedValue('salt'),\r\n}));\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-require-imports\r\nconst bcrypt = require('bcrypt');\r\n\r\ndescribe('AuthService', () => {\r\n  let service: AuthService;\r\n  let userService: UserService;\r\n  let jwtService: JwtService;\r\n  let tokenRepo: Repository<RefreshToken>;\r\n\r\n  const mockUser = {\r\n    user_id: 1,\r\n    username: 'testuser',\r\n    password: 'hashedpassword',\r\n    primaryOrganizationId: 1,\r\n  };\r\n\r\n  const mockQueryBuilder = {\r\n    addSelect: jest.fn().mockReturnThis(),\r\n    leftJoinAndSelect: jest.fn().mockReturnThis(),\r\n    where: jest.fn().mockReturnThis(),\r\n    getOne: jest.fn().mockResolvedValue(mockUser),\r\n  };\r\n\r\n  const mockUserRepo = {\r\n    createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),\r\n  };\r\n\r\n  const mockTokenRepo = {\r\n    create: jest.fn(),\r\n    save: jest.fn(),\r\n    findOne: jest.fn(),\r\n    update: jest.fn(),\r\n  };\r\n\r\n  beforeEach(async () => {\r\n    // Reset bcrypt mocks\r\n    bcrypt.compare.mockResolvedValue(true);\r\n\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        AuthService,\r\n        {\r\n          provide: UserService,\r\n          useValue: {\r\n            findOneByUsername: jest.fn(),\r\n            create: jest.fn(),\r\n            findOne: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: JwtService,\r\n          useValue: {\r\n            signAsync: jest.fn().mockResolvedValue('jwt_token'),\r\n            decode: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: ConfigService,\r\n          useValue: {\r\n            get: jest.fn().mockImplementation((key: string) => {\r\n              if (key.includes('EXPIRATION')) return '1h';\r\n              return 'secret';\r\n            }),\r\n          },\r\n        },\r\n        {\r\n          provide: CACHE_MANAGER,\r\n          useValue: {\r\n            set: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(User),\r\n          useValue: mockUserRepo,\r\n        },\r\n        {\r\n          provide: getRepositoryToken(RefreshToken),\r\n          useValue: mockTokenRepo,\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<AuthService>(AuthService);\r\n    userService = module.get<UserService>(UserService);\r\n    jwtService = module.get<JwtService>(JwtService);\r\n    tokenRepo = module.get(getRepositoryToken(RefreshToken));\r\n  });\r\n\r\n  afterEach(() => {\r\n    jest.clearAllMocks();\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('validateUser', () => {\r\n    it('should return user without password if validation succeeds', async () => {\r\n      const result = await service.validateUser('testuser', 'password');\r\n      expect(result).toBeDefined();\r\n      expect(result).not.toHaveProperty('password');\r\n      expect(result.username).toBe('testuser');\r\n    });\r\n\r\n    it('should return null if user not found', async () => {\r\n      mockQueryBuilder.getOne.mockResolvedValueOnce(null);\r\n      const result = await service.validateUser('unknown', 'password');\r\n      expect(result).toBeNull();\r\n    });\r\n\r\n    it('should return null if password mismatch', async () => {\r\n      bcrypt.compare.mockResolvedValueOnce(false);\r\n      const result = await service.validateUser('testuser', 'wrongpassword');\r\n      expect(result).toBeNull();\r\n    });\r\n  });\r\n\r\n  describe('login', () => {\r\n    it('should return access and refresh tokens', async () => {\r\n      mockTokenRepo.create.mockReturnValue({ id: 1 });\r\n      mockTokenRepo.save.mockResolvedValue({ id: 1 });\r\n\r\n      const result = await service.login(mockUser);\r\n\r\n      expect(result).toHaveProperty('access_token');\r\n      expect(result).toHaveProperty('refresh_token');\r\n      expect(mockTokenRepo.save).toHaveBeenCalled();\r\n    });\r\n  });\r\n\r\n  describe('register', () => {\r\n    it('should register a new user', async () => {\r\n      (userService.findOneByUsername as jest.Mock).mockResolvedValue(null);\r\n      (userService.create as jest.Mock).mockResolvedValue(mockUser);\r\n\r\n      const dto = {\r\n        username: 'newuser',\r\n        password: 'password',\r\n        email: 'test@example.com',\r\n        firstName: 'Test',\r\n        lastName: 'User',\r\n      };\r\n\r\n      const result = await service.register(dto);\r\n      expect(result).toBeDefined();\r\n      expect(userService.create).toHaveBeenCalled();\r\n    });\r\n  });\r\n\r\n  describe('refreshToken', () => {\r\n    it('should return new tokens if valid', async () => {\r\n      const mockStoredToken = {\r\n        tokenHash: 'somehash',\r\n        isRevoked: false,\r\n        expiresAt: new Date(Date.now() + 10000),\r\n      };\r\n      mockTokenRepo.findOne.mockResolvedValue(mockStoredToken);\r\n      (userService.findOne as jest.Mock).mockResolvedValue(mockUser);\r\n\r\n      const result = await service.refreshToken(1, 'valid_refresh_token');\r\n\r\n      expect(result.access_token).toBeDefined();\r\n      expect(result.refresh_token).toBeDefined();\r\n      // Should mark old token as revoked\r\n      expect(mockTokenRepo.save).toHaveBeenCalledWith(\r\n        expect.objectContaining({ isRevoked: true })\r\n      );\r\n    });\r\n\r\n    it('should throw UnauthorizedException if token revoked', async () => {\r\n      const mockStoredToken = {\r\n        tokenHash: 'somehash',\r\n        isRevoked: true,\r\n        expiresAt: new Date(Date.now() + 10000),\r\n      };\r\n      mockTokenRepo.findOne.mockResolvedValue(mockStoredToken);\r\n\r\n      await expect(service.refreshToken(1, 'revoked_token')).rejects.toThrow(\r\n        UnauthorizedException\r\n      );\r\n    });\r\n\r\n    it('should allow refresh within 30s grace period if already revoked', async () => {\r\n      const updatedAt = new Date(Date.now() - 5000); // 5 seconds ago\r\n      const mockStoredToken = {\r\n        tokenHash: 'somehash',\r\n        isRevoked: true,\r\n        updatedAt: updatedAt,\r\n        replacedByToken: 'new_token_hash',\r\n        expiresAt: new Date(Date.now() + 10000),\r\n      };\r\n      mockTokenRepo.findOne.mockResolvedValue(mockStoredToken);\r\n      (userService.findOne as jest.Mock).mockResolvedValue(mockUser);\r\n      mockTokenRepo.create.mockReturnValue({ token_id: 2 });\r\n      mockTokenRepo.save.mockResolvedValue({ token_id: 2 });\r\n\r\n      const result = await service.refreshToken(1, 'valid_refresh_token');\r\n\r\n      expect(result.access_token).toBeDefined();\r\n      expect(result.refresh_token).toBeDefined();\r\n      // Should not call revokeAllUserTokens\r\n      expect(mockTokenRepo.update).not.toHaveBeenCalled();\r\n    });\r\n\r\n    it('should throw UnauthorizedException if token revoked more than 30s ago', async () => {\r\n      const updatedAt = new Date(Date.now() - 35000); // 35 seconds ago\r\n      const mockStoredToken = {\r\n        tokenHash: 'somehash',\r\n        isRevoked: true,\r\n        updatedAt: updatedAt,\r\n        replacedByToken: 'new_token_hash',\r\n        expiresAt: new Date(Date.now() + 10000),\r\n      };\r\n      mockTokenRepo.findOne.mockResolvedValue(mockStoredToken);\r\n\r\n      await expect(service.refreshToken(1, 'revoked_token')).rejects.toThrow(\r\n        UnauthorizedException\r\n      );\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\auth.service.ts",
    "messages": [],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'password' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 82,
        "column": 15,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 82,
        "endColumn": 23,
        "suppressions": [{ "kind": "directive", "justification": "" }]
      }
    ],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\casl\\casl.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\dto\\login.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\dto\\register.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\entities\\refresh-token.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\guards\\permissions.guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\session.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\strategies\\jwt-refresh.strategy.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/require-await",
        "severity": 2,
        "message": "Async method 'validate' has no 'await' expression.",
        "line": 27,
        "column": 3,
        "nodeType": "FunctionExpression",
        "messageId": "missingAwait",
        "endLine": 27,
        "endColumn": 17,
        "suggestions": [
          { "messageId": "removeAsync", "fix": { "range": [960, 966], "text": "" }, "desc": "Remove 'async'." }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/common/auth/strategies/jwt-refresh.strategy.ts\r\n// บันทึกการแก้ไข: Strategy สำหรับ Refresh Token (T1.2)\r\n// บันทึกการแก้ไข: แก้ไข TS2345 โดยยืนยันค่า secretOrKey ด้วย ! (Non-null assertion)\r\n\r\nimport { ExtractJwt, Strategy } from 'passport-jwt';\r\nimport { PassportStrategy } from '@nestjs/passport';\r\nimport { Injectable } from '@nestjs/common';\r\nimport { ConfigService } from '@nestjs/config';\r\nimport { Request } from 'express';\r\nimport type { JwtPayload } from './jwt.strategy';\r\n\r\n@Injectable()\r\nexport class JwtRefreshStrategy extends PassportStrategy(\r\n  Strategy,\r\n  'jwt-refresh'\r\n) {\r\n  constructor(configService: ConfigService) {\r\n    super({\r\n      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),\r\n      ignoreExpiration: false,\r\n      // ✅ Fix: ใส่ ! เพื่อบอก TS ว่าค่านี้มีอยู่จริง (จาก env validation)\r\n      secretOrKey: configService.get<string>('JWT_REFRESH_SECRET')!,\r\n      passReqToCallback: true,\r\n    });\r\n  }\r\n\r\n  async validate(req: Request, payload: JwtPayload) {\r\n    const refreshToken = ExtractJwt.fromAuthHeaderAsBearerToken()(req);\r\n    return {\r\n      ...payload,\r\n      refreshToken,\r\n    };\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\auth\\strategies\\jwt.strategy.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\common.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\config\\env.validation.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\config\\redis.config.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\audit.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\bypass-maintenance.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\circuit-breaker.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\current-user.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\idempotency.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\require-permission.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\decorators\\retry.decorator.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\entities\\audit-log.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\entities\\base.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\exceptions\\http-exception.filter.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\entities\\attachment.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-cleanup.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-storage.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 88,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 88,
        "endColumn": 35
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 89,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 89,
        "endColumn": 33
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 121,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 121,
        "endColumn": 33
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 139,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 139,
        "endColumn": 35
      }
    ],
    "suppressedMessages": [],
    "errorCount": 4,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { FileStorageService } from './file-storage.service';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { Attachment } from './entities/attachment.entity';\r\nimport { ConfigService } from '@nestjs/config';\r\nimport * as fs from 'fs-extra';\r\nimport {\r\n  BadRequestException,\r\n  NotFoundException,\r\n  ForbiddenException,\r\n} from '@nestjs/common';\r\nimport { Repository } from 'typeorm';\r\n\r\n// Mock fs-extra\r\njest.mock('fs-extra');\r\n\r\ndescribe('FileStorageService', () => {\r\n  let service: FileStorageService;\r\n  let attachmentRepo: Repository<Attachment>;\r\n\r\n  const mockAttachment = {\r\n    id: 1,\r\n    originalFilename: 'test.pdf',\r\n    storedFilename: 'uuid.pdf',\r\n    filePath: '/permanent/2024/12/uuid.pdf',\r\n    fileSize: 1024,\r\n    uploadedByUserId: 1,\r\n  } as Attachment;\r\n\r\n  const mockFile = {\r\n    originalname: 'test.pdf',\r\n    mimetype: 'application/pdf',\r\n    size: 1024,\r\n    buffer: Buffer.from('test-content'),\r\n  } as Express.Multer.File;\r\n\r\n  beforeEach(async () => {\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        FileStorageService,\r\n        {\r\n          provide: getRepositoryToken(Attachment),\r\n          useValue: {\r\n            create: jest.fn().mockReturnValue(mockAttachment),\r\n            save: jest.fn().mockResolvedValue(mockAttachment),\r\n            find: jest.fn(),\r\n            findOne: jest.fn(),\r\n            remove: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: ConfigService,\r\n          useValue: {\r\n            get: jest.fn((key) => {\r\n              if (key === 'NODE_ENV') return 'test';\r\n              return null;\r\n            }),\r\n          },\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<FileStorageService>(FileStorageService);\r\n    attachmentRepo = module.get(getRepositoryToken(Attachment));\r\n\r\n    jest.clearAllMocks();\r\n    (fs.ensureDirSync as unknown as jest.Mock).mockReturnValue(true);\r\n    (fs.writeFile as unknown as jest.Mock).mockResolvedValue(undefined);\r\n    (fs.pathExists as unknown as jest.Mock).mockResolvedValue(true);\r\n    (fs.move as unknown as jest.Mock).mockResolvedValue(undefined);\r\n    (fs.remove as unknown as jest.Mock).mockResolvedValue(undefined);\r\n    (fs.readFile as unknown as jest.Mock).mockResolvedValue(\r\n      Buffer.from('test')\r\n    );\r\n    (fs.stat as unknown as jest.Mock).mockResolvedValue({ size: 1024 });\r\n    (fs.ensureDir as unknown as jest.Mock).mockResolvedValue(undefined);\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('upload', () => {\r\n    it('should save file to temp and create DB record', async () => {\r\n      const result = await service.upload(mockFile, 1);\r\n\r\n      expect(fs.writeFile).toHaveBeenCalled();\r\n      expect(attachmentRepo.create).toHaveBeenCalled();\r\n      expect(attachmentRepo.save).toHaveBeenCalled();\r\n      expect(result).toBeDefined();\r\n    });\r\n\r\n    it('should throw BadRequestException if write fails', async () => {\r\n      (fs.writeFile as unknown as jest.Mock).mockRejectedValueOnce(\r\n        new Error('Write error')\r\n      );\r\n      await expect(service.upload(mockFile, 1)).rejects.toThrow(\r\n        BadRequestException\r\n      );\r\n    });\r\n  });\r\n\r\n  describe('commit', () => {\r\n    it('should move files to permanent storage', async () => {\r\n      const tempIds = ['uuid-1'];\r\n      const mockAttachments = [\r\n        {\r\n          ...mockAttachment,\r\n          isTemporary: true,\r\n          tempId: 'uuid-1',\r\n          filePath: '/temp/uuid.pdf',\r\n        },\r\n      ];\r\n\r\n      (attachmentRepo.find as jest.Mock).mockResolvedValue(mockAttachments);\r\n\r\n      await service.commit(tempIds);\r\n\r\n      expect(fs.ensureDir).toHaveBeenCalled();\r\n      expect(fs.move).toHaveBeenCalled();\r\n      expect(attachmentRepo.save).toHaveBeenCalled();\r\n    });\r\n\r\n    it('should show warning if file counts mismatch', async () => {\r\n      (attachmentRepo.find as jest.Mock).mockResolvedValue([]);\r\n      await expect(service.commit(['uuid-1'])).rejects.toThrow(\r\n        NotFoundException\r\n      );\r\n    });\r\n  });\r\n\r\n  describe('delete', () => {\r\n    it('should delete file if user owns it', async () => {\r\n      (attachmentRepo.findOne as jest.Mock).mockResolvedValue(mockAttachment);\r\n\r\n      await service.delete(1, 1);\r\n\r\n      expect(fs.remove).toHaveBeenCalled();\r\n      expect(attachmentRepo.remove).toHaveBeenCalled();\r\n    });\r\n\r\n    it('should throw ForbiddenException if user does not own file', async () => {\r\n      (attachmentRepo.findOne as jest.Mock).mockResolvedValue(mockAttachment);\r\n      await expect(service.delete(1, 999)).rejects.toThrow(ForbiddenException);\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\guards\\jwt-auth.guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\guards\\jwt-refresh.guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\guards\\maintenance-mode.guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\guards\\rbac.guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\interceptors\\audit-log.interceptor.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\interceptors\\idempotency.interceptor.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\interceptors\\performance.interceptor.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\interceptors\\transform.interceptor.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\interfaces\\request-with-user.interface.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\resilience\\resilience.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\services\\crypto.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\services\\request-context.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\common\\utils\\uuid-guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\config\\database.config.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\migrations\\1701676800000-V1_5_1_Schema_Update.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\migrations\\InitialSchema.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\seeds\\organization.seed.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\seeds\\run-seed.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `DataSourceOptions`.",
        "line": 7,
        "column": 37,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 7,
        "endColumn": 58
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 7,
        "column": 55,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 7,
        "endColumn": 58,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [288, 291], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [288, 291], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 11,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 11,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [344, 382], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 16,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 16,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [468, 504], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 18,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 18,
        "endColumn": 18,
        "suggestions": [
          {
            "fix": { "range": [531, 573], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "error" },
            "desc": "Remove the console.error()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-floating-promises",
        "severity": 2,
        "message": "Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.",
        "line": 24,
        "column": 1,
        "nodeType": "ExpressionStatement",
        "messageId": "floatingVoid",
        "endLine": 24,
        "endColumn": 12,
        "suggestions": [
          {
            "messageId": "floatingFixVoid",
            "fix": { "range": [633, 633], "text": "void " },
            "desc": "Add void operator to ignore."
          },
          {
            "messageId": "floatingFixAwait",
            "fix": { "range": [633, 633], "text": "await " },
            "desc": "Add await operator."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 6,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { DataSource } from 'typeorm';\r\nimport { databaseConfig } from '../../config/database.config';\r\nimport { seedOrganizations } from './organization.seed';\r\nimport { seedUsers } from './user.seed';\r\n\r\nasync function runSeeds() {\r\n  const dataSource = new DataSource(databaseConfig as any);\r\n  await dataSource.initialize();\r\n\r\n  try {\r\n    console.log('🌱 Seeding database...');\r\n\r\n    await seedOrganizations(dataSource);\r\n    await seedUsers(dataSource);\r\n\r\n    console.log('✅ Seeding completed!');\r\n  } catch (error) {\r\n    console.error('❌ Seeding failed:', error);\r\n  } finally {\r\n    await dataSource.destroy();\r\n  }\r\n}\r\n\r\nrunSeeds();\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\seeds\\user.seed.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/ban-ts-comment",
        "severity": 2,
        "message": "Use \"@ts-expect-error\" instead of \"@ts-ignore\", as \"@ts-ignore\" will do nothing if the following line is error-free.",
        "line": 61,
        "column": 7,
        "nodeType": "Line",
        "messageId": "tsIgnoreInsteadOfExpectError",
        "endLine": 61,
        "endColumn": 20,
        "suggestions": [
          {
            "messageId": "replaceTsIgnoreWithTsExpectError",
            "fix": { "range": [2088, 2101], "text": "// @ts-expect-error" },
            "desc": "Replace \"@ts-ignore\" with \"@ts-expect-error\"."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 115,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 115,
        "endColumn": 43
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 119,
        "column": 11,
        "nodeType": "Property",
        "messageId": "anyAssignment",
        "endLine": 119,
        "endColumn": 15
      }
    ],
    "suppressedMessages": [],
    "errorCount": 3,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { DataSource } from 'typeorm';\r\nimport { User } from '../../modules/user/entities/user.entity';\r\nimport { Role, RoleScope } from '../../modules/user/entities/role.entity';\r\nimport { UserAssignment } from '../../modules/user/entities/user-assignment.entity';\r\nimport * as bcrypt from 'bcrypt';\r\n\r\nexport async function seedUsers(dataSource: DataSource) {\r\n  const userRepo = dataSource.getRepository(User);\r\n  const roleRepo = dataSource.getRepository(Role);\r\n  const assignmentRepo = dataSource.getRepository(UserAssignment);\r\n\r\n  // Create Roles\r\n  const rolesData = [\r\n    {\r\n      roleName: 'Superadmin',\r\n      scope: RoleScope.GLOBAL,\r\n      description:\r\n        'ผู้ดูแลระบบสูงสุด: สามารถทำทุกอย่างในระบบ, จัดการองค์กร, และจัดการข้อมูลหลักระดับ Global',\r\n    },\r\n    {\r\n      roleName: 'Org Admin',\r\n      scope: RoleScope.ORGANIZATION,\r\n      description:\r\n        'ผู้ดูแลองค์กร: จัดการผู้ใช้ในองค์กร, จัดการบทบาท / สิทธิ์ภายในองค์กร, และดูรายงานขององค์กร',\r\n    },\r\n    {\r\n      roleName: 'Document Control',\r\n      scope: RoleScope.ORGANIZATION,\r\n      description:\r\n        'ควบคุมเอกสารขององค์กร: เพิ่ม / แก้ไข / ลบเอกสาร, และกำหนดสิทธิ์เอกสารภายในองค์กร',\r\n    },\r\n    {\r\n      roleName: 'Editor',\r\n      scope: RoleScope.PROJECT,\r\n      description:\r\n        'ผู้แก้ไขเอกสารขององค์กร: เพิ่ม / แก้ไขเอกสารที่ได้รับมอบหมาย',\r\n    },\r\n    {\r\n      roleName: 'Viewer',\r\n      scope: RoleScope.PROJECT,\r\n      description: 'ผู้ดูเอกสารขององค์กร: ดูเอกสารที่มีสิทธิ์เข้าถึงเท่านั้น',\r\n    },\r\n    {\r\n      roleName: 'Project Manager',\r\n      scope: RoleScope.PROJECT,\r\n      description:\r\n        'ผู้จัดการโครงการ: จัดการสมาชิกในโครงการ, สร้าง / จัดการสัญญาในโครงการ, และดูรายงานโครงการ',\r\n    },\r\n    {\r\n      roleName: 'Contract Admin',\r\n      scope: RoleScope.CONTRACT,\r\n      description:\r\n        'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา',\r\n    },\r\n  ];\r\n\r\n  const roleMap = new Map();\r\n  for (const r of rolesData) {\r\n    let role = await roleRepo.findOneBy({ roleName: r.roleName });\r\n    if (!role) {\r\n      // @ts-ignore\r\n      role = await roleRepo.save(roleRepo.create(r));\r\n    }\r\n    roleMap.set(r.roleName, role);\r\n  }\r\n\r\n  // Create Users\r\n  const usersData = [\r\n    {\r\n      username: 'superadmin',\r\n      email: 'superadmin@example.com',\r\n      firstName: 'Super',\r\n      lastName: 'Admin',\r\n      roleName: 'Superadmin',\r\n    },\r\n    {\r\n      username: 'admin',\r\n      email: 'admin@example.com',\r\n      firstName: 'Admin',\r\n      lastName: 'คคง.',\r\n      roleName: 'Org Admin',\r\n    },\r\n    {\r\n      username: 'editor01',\r\n      email: 'editor01@example.com',\r\n      firstName: 'DC',\r\n      lastName: 'C1',\r\n      roleName: 'Editor',\r\n    },\r\n    {\r\n      username: 'viewer01',\r\n      email: 'viewer01@example.com',\r\n      firstName: 'Viewer',\r\n      lastName: 'สคฉ.03',\r\n      roleName: 'Viewer',\r\n    },\r\n  ];\r\n\r\n  const salt = await bcrypt.genSalt();\r\n  const password = await bcrypt.hash('password123', salt); // Default password\r\n\r\n  for (const u of usersData) {\r\n    let user = await userRepo.findOneBy({ username: u.username });\r\n    if (!user) {\r\n      user = userRepo.create({\r\n        username: u.username,\r\n        email: u.email,\r\n        firstName: u.firstName,\r\n        lastName: u.lastName,\r\n        password, // Fixed: password instead of passwordHash\r\n      });\r\n      user = await userRepo.save(user);\r\n\r\n      // Create Assignment\r\n      const role = roleMap.get(u.roleName);\r\n      if (role) {\r\n        const assignment = assignmentRepo.create({\r\n          user,\r\n          role,\r\n          assignedAt: new Date(),\r\n        });\r\n        await assignmentRepo.save(assignment);\r\n      }\r\n    }\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\database\\seeds\\workflow-definitions.seed.ts",
    "messages": [
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `········`",
        "line": 133,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 133,
        "endColumn": 1,
        "fix": { "range": [4038, 4038], "text": "        " }
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 134,
        "column": 16,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 134,
        "endColumn": 21
      },
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `········`",
        "line": 135,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 135,
        "endColumn": 1,
        "fix": { "range": [4142, 4142], "text": "        " }
      },
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `······`",
        "line": 138,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 138,
        "endColumn": 1,
        "fix": { "range": [4245, 4245], "text": "      " }
      },
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `······`",
        "line": 139,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 139,
        "endColumn": 1,
        "fix": { "range": [4268, 4268], "text": "      " }
      },
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `······`",
        "line": 140,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 140,
        "endColumn": 1,
        "fix": { "range": [4343, 4343], "text": "      " }
      }
    ],
    "suppressedMessages": [],
    "errorCount": 6,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 5,
    "fixableWarningCount": 0,
    "source": "// src/database/seeds/workflow-definitions.seed.ts\r\n\r\nimport { DataSource } from 'typeorm';\r\nimport { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity';\r\nimport { WorkflowDslService } from '../../modules/workflow-engine/workflow-dsl.service';\r\n\r\nexport const seedWorkflowDefinitions = async (dataSource: DataSource) => {\r\n  const repo = dataSource.getRepository(WorkflowDefinition);\r\n  const dslService = new WorkflowDslService();\r\n\r\n  // 1. RFA Workflow (Standard)\r\n  const rfaDsl = {\r\n    workflow: 'RFA_FLOW_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน RfaWorkflowService\r\n    version: 1,\r\n    description: 'Standard RFA Approval Workflow',\r\n    states: [\r\n      {\r\n        name: 'DRAFT',\r\n        initial: true,\r\n        on: {\r\n          SUBMIT: {\r\n            to: 'IN_REVIEW',\r\n            require: { role: 'Editor' }, // [FIX] แก้ไข Syntax เป็น Object\r\n          },\r\n        },\r\n      },\r\n      {\r\n        name: 'IN_REVIEW',\r\n        on: {\r\n          APPROVE_1: {\r\n            to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง\r\n            require: { role: 'Contract Admin' },\r\n            condition: \"context.priority === 'HIGH'\",\r\n          },\r\n          APPROVE_2: {\r\n            to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง\r\n            require: { role: 'Contract Admin' },\r\n            condition: \"context.priority === 'NORMAL'\",\r\n          },\r\n          REJECT: {\r\n            to: 'REJECTED',\r\n            require: { role: 'Contract Admin' },\r\n          },\r\n          COMMENT: {\r\n            to: 'DRAFT',\r\n            require: { role: 'Contract Admin' },\r\n          }, // ส่งกลับแก้ไข\r\n        },\r\n      },\r\n      { name: 'APPROVED', terminal: true },\r\n      { name: 'REJECTED', terminal: true },\r\n    ],\r\n  };\r\n\r\n  // 2. Circulation Workflow\r\n  const circulationDsl = {\r\n    workflow: 'CIRCULATION_INTERNAL_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน CirculationWorkflowService\r\n    version: 1,\r\n    description: 'Internal Document Circulation',\r\n    states: [\r\n      {\r\n        name: 'OPEN',\r\n        initial: true,\r\n        on: {\r\n          START: {\r\n            // [FIX] เปลี่ยนชื่อ Action ให้ตรงกับที่ Service เรียกใช้ ('START')\r\n            to: 'IN_REVIEW',\r\n          },\r\n        },\r\n      },\r\n      {\r\n        name: 'IN_REVIEW',\r\n        on: {\r\n          COMPLETE_TASK: {\r\n            // [FIX] เปลี่ยนให้สอดคล้องกับ Action ที่ใช้จริง\r\n            to: 'COMPLETED',\r\n          },\r\n          CANCEL: { to: 'CANCELLED' },\r\n        },\r\n      },\r\n      { name: 'COMPLETED', terminal: true },\r\n      { name: 'CANCELLED', terminal: true },\r\n    ],\r\n  };\r\n\r\n  // 3. Correspondence Workflow (Optional - ถ้ามี)\r\n  const correspondenceDsl = {\r\n    workflow: 'CORRESPONDENCE_FLOW_V1',\r\n    version: 1,\r\n    description: 'Standard Correspondence Routing',\r\n    states: [\r\n      {\r\n        name: 'DRAFT',\r\n        initial: true,\r\n        on: { SUBMIT: { to: 'IN_REVIEW' } },\r\n      },\r\n      {\r\n        name: 'IN_REVIEW',\r\n        on: {\r\n          APPROVE: { to: 'APPROVED' },\r\n          REJECT: { to: 'REJECTED' },\r\n        },\r\n      },\r\n      { name: 'APPROVED', terminal: true },\r\n      { name: 'REJECTED', terminal: true },\r\n    ],\r\n  };\r\n\r\n  const workflows = [rfaDsl, circulationDsl, correspondenceDsl];\r\n\r\n  for (const dsl of workflows) {\r\n    const exists = await repo.findOne({\r\n      where: { workflow_code: dsl.workflow, version: dsl.version },\r\n    });\r\n\r\n    if (!exists) {\r\n      try {\r\n        // Compile เพื่อ Validate และ Normalize ก่อนบันทึก\r\n        const compiled = dslService.compile(\r\n          dsl as unknown as import('../../modules/workflow-engine/workflow-dsl.service').RawWorkflowDSL\r\n        );\r\n\r\n        await repo.save(\r\n          repo.create({\r\n            workflow_code: dsl.workflow,\r\n            version: dsl.version,\r\n            description: dsl.description,\r\n            dsl: dsl as unknown as Record<string, unknown>,\r\n            compiled: compiled as unknown as Record<string, unknown>,\r\n            is_active: true,\r\n          })\r\n        );\r\n//         console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);\r\n      } catch (error) {\r\n//         console.error(`❌ Failed to seed workflow ${dsl.workflow}:`, error);\r\n      }\r\n    } else {\r\n//       console.log(\r\n//         `⏭️  Workflow already exists: ${dsl.workflow} v${dsl.version}`\r\n//       );\r\n    }\r\n  }\r\n};\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\main.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\audit-log\\audit-log.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\audit-log\\audit-log.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\audit-log\\audit-log.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\auth\\entities\\role.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\circulation-workflow.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\circulation.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\circulation.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\circulation.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\dto\\create-circulation.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\dto\\search-circulation.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\dto\\update-circulation-routing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\entities\\circulation-routing.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\entities\\circulation-status-code.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\circulation\\entities\\circulation.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\contract.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\contract.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\contract.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\dto\\create-contract.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\dto\\search-contract.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\dto\\update-contract.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\entities\\contract-organization.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\contract\\entities\\contract.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence-workflow.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'CorrespondenceRecipient' is defined but never used. Allowed unused vars must match /^_/u.",
        "line": 11,
        "column": 10,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 11,
        "endColumn": 33,
        "suggestions": [
          {
            "messageId": "removeUnusedImportDeclaration",
            "data": { "varName": "CorrespondenceRecipient" },
            "fix": { "range": [693, 780], "text": "" },
            "desc": "Remove unused import declaration."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 22,
        "column": 27,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 22,
        "endColumn": 30,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1400, 1403], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1400, 1403], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 23,
        "column": 21,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 23,
        "endColumn": 24,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1426, 1429], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1426, 1429], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'dataSource' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 24,
        "column": 7,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 24,
        "endColumn": 17
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 24,
        "column": 19,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 24,
        "endColumn": 22,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1450, 1453], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1450, 1453], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 133,
        "column": 5,
        "nodeType": "AssignmentExpression",
        "messageId": "anyAssignment",
        "endLine": 133,
        "endColumn": 72
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 134,
        "column": 5,
        "nodeType": "AssignmentExpression",
        "messageId": "anyAssignment",
        "endLine": 134,
        "endColumn": 74
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 135,
        "column": 5,
        "nodeType": "AssignmentExpression",
        "messageId": "anyAssignment",
        "endLine": 135,
        "endColumn": 40
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 144,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 144,
        "endColumn": 72
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 144,
        "column": 69,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 144,
        "endColumn": 72,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [5152, 5155], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [5152, 5155], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'mockStatus' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 154,
        "column": 13,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 154,
        "endColumn": 23
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `UpdateCorrespondenceDto`.",
        "line": 177,
        "column": 31,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 177,
        "endColumn": 47
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 177,
        "column": 44,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 177,
        "endColumn": 47,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [6388, 6391], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [6388, 6391], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `User`.",
        "line": 177,
        "column": 49,
        "nodeType": "Identifier",
        "messageId": "unsafeArgument",
        "endLine": 177,
        "endColumn": 57
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 180,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 180,
        "endColumn": 51
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 184,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 184,
        "endColumn": 72
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 184,
        "column": 69,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 184,
        "endColumn": 72,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [6692, 6695], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [6692, 6695], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `UpdateCorrespondenceDto`.",
        "line": 208,
        "column": 31,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 208,
        "endColumn": 47
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 208,
        "column": 44,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 208,
        "endColumn": 47,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [7419, 7422], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [7419, 7422], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `User`.",
        "line": 208,
        "column": 49,
        "nodeType": "Identifier",
        "messageId": "unsafeArgument",
        "endLine": 208,
        "endColumn": 57
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 210,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 210,
        "endColumn": 51
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 213,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 213,
        "endColumn": 72
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 213,
        "column": 69,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 213,
        "endColumn": 72,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [7663, 7666], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [7663, 7666], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `UpdateCorrespondenceDto`.",
        "line": 237,
        "column": 31,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 237,
        "endColumn": 47
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 237,
        "column": 44,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 237,
        "endColumn": 47,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [8372, 8375], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [8372, 8375], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `User`.",
        "line": 237,
        "column": 49,
        "nodeType": "Identifier",
        "messageId": "unsafeArgument",
        "endLine": 237,
        "endColumn": 57
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 239,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 239,
        "endColumn": 51
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 243,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 243,
        "endColumn": 72
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 243,
        "column": 69,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 243,
        "endColumn": 72,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [8627, 8630], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [8627, 8630], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-call",
        "severity": 2,
        "message": "Unsafe call of a type that could not be resolved.",
        "line": 262,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "errorCall",
        "endLine": 264,
        "endColumn": 27
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .mockResolvedValue on a type that cannot be resolved.",
        "line": 264,
        "column": 10,
        "nodeType": "Identifier",
        "messageId": "errorMemberExpression",
        "endLine": 264,
        "endColumn": 27
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 264,
        "column": 71,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 264,
        "endColumn": 74,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [9355, 9358], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [9355, 9358], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `UpdateCorrespondenceDto`.",
        "line": 270,
        "column": 31,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 270,
        "endColumn": 47
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 270,
        "column": 44,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 270,
        "endColumn": 47,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [9525, 9528], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [9525, 9528], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `User`.",
        "line": 270,
        "column": 49,
        "nodeType": "Identifier",
        "messageId": "unsafeArgument",
        "endLine": 270,
        "endColumn": 57
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 272,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 272,
        "endColumn": 51
      }
    ],
    "suppressedMessages": [],
    "errorCount": 36,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { DataSource } from 'typeorm';\r\nimport { CorrespondenceService } from './correspondence.service';\r\nimport { Correspondence } from './entities/correspondence.entity';\r\nimport { CorrespondenceRevision } from './entities/correspondence-revision.entity';\r\nimport { CorrespondenceType } from './entities/correspondence-type.entity';\r\nimport { CorrespondenceStatus } from './entities/correspondence-status.entity';\r\nimport { CorrespondenceReference } from './entities/correspondence-reference.entity';\r\nimport { Organization } from '../organization/entities/organization.entity';\r\nimport { CorrespondenceRecipient } from './entities/correspondence-recipient.entity';\r\nimport { DocumentNumberingService } from '../document-numbering/services/document-numbering.service';\r\nimport { JsonSchemaService } from '../json-schema/json-schema.service';\r\nimport { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';\r\nimport { UserService } from '../user/user.service';\r\nimport { SearchService } from '../search/search.service';\r\nimport { FileStorageService } from '../../common/file-storage/file-storage.service';\r\n\r\ndescribe('CorrespondenceService', () => {\r\n  let service: CorrespondenceService;\r\n  let numberingService: DocumentNumberingService;\r\n  let correspondenceRepo: any;\r\n  let revisionRepo: any;\r\n  let dataSource: any;\r\n\r\n  const createMockRepository = () => ({\r\n    find: jest.fn(),\r\n    findOne: jest.fn(),\r\n    create: jest.fn(),\r\n    save: jest.fn(),\r\n    update: jest.fn(),\r\n    delete: jest.fn(),\r\n    softDelete: jest.fn(),\r\n    createQueryBuilder: jest.fn(() => ({\r\n      leftJoinAndSelect: jest.fn().mockReturnThis(),\r\n      where: jest.fn().mockReturnThis(),\r\n      andWhere: jest.fn().mockReturnThis(),\r\n      orderBy: jest.fn().mockReturnThis(),\r\n      skip: jest.fn().mockReturnThis(),\r\n      take: jest.fn().mockReturnThis(),\r\n      getOne: jest.fn().mockResolvedValue(null),\r\n      getMany: jest.fn().mockResolvedValue([]),\r\n      getManyAndCount: jest.fn().mockResolvedValue([[], 0]),\r\n    })),\r\n  });\r\n\r\n  const mockDataSource = {\r\n    createQueryRunner: jest.fn(() => ({\r\n      connect: jest.fn(),\r\n      startTransaction: jest.fn(),\r\n      commitTransaction: jest.fn(),\r\n      rollbackTransaction: jest.fn(),\r\n      release: jest.fn(),\r\n      manager: {\r\n        create: jest.fn(),\r\n        save: jest.fn(),\r\n        findOne: jest.fn(),\r\n      },\r\n    })),\r\n    getRepository: jest.fn(() => createMockRepository()),\r\n  };\r\n\r\n  beforeEach(async () => {\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        CorrespondenceService,\r\n        {\r\n          provide: getRepositoryToken(Correspondence),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: getRepositoryToken(CorrespondenceRevision),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: getRepositoryToken(CorrespondenceType),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: getRepositoryToken(CorrespondenceStatus),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: getRepositoryToken(CorrespondenceReference),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: getRepositoryToken(Organization),\r\n          useValue: createMockRepository(),\r\n        },\r\n        {\r\n          provide: DocumentNumberingService,\r\n          useValue: {\r\n            generateNextNumber: jest.fn(),\r\n            updateNumberForDraft: jest.fn(),\r\n            previewNextNumber: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: JsonSchemaService,\r\n          useValue: { validate: jest.fn() },\r\n        },\r\n        {\r\n          provide: WorkflowEngineService,\r\n          useValue: { createInstance: jest.fn() },\r\n        },\r\n        {\r\n          provide: UserService,\r\n          useValue: {\r\n            findOne: jest.fn(),\r\n            getUserPermissions: jest.fn().mockResolvedValue([]),\r\n          },\r\n        },\r\n        {\r\n          provide: DataSource,\r\n          useValue: mockDataSource,\r\n        },\r\n        {\r\n          provide: SearchService,\r\n          useValue: { indexDocument: jest.fn() },\r\n        },\r\n        {\r\n          provide: FileStorageService,\r\n          useValue: { commit: jest.fn().mockResolvedValue([]) },\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<CorrespondenceService>(CorrespondenceService);\r\n    numberingService = module.get<DocumentNumberingService>(\r\n      DocumentNumberingService\r\n    );\r\n    correspondenceRepo = module.get(getRepositoryToken(Correspondence));\r\n    revisionRepo = module.get(getRepositoryToken(CorrespondenceRevision));\r\n    dataSource = module.get(DataSource);\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('update', () => {\r\n    it('should NOT regenerate number if critical fields unchanged', async () => {\r\n      const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;\r\n      const mockRevision = {\r\n        id: 100,\r\n        correspondenceId: 1,\r\n        isCurrent: true,\r\n        statusId: 5,\r\n      }; // Status 5 = Draft handled by logic?\r\n      // Mock status repo to return DRAFT\r\n      // But strict logic: revision.statusId check\r\n      jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);\r\n      const mockStatus = { id: 5, statusCode: 'DRAFT' };\r\n      // Need to set statusRepo mock behavior... simplified here for brevity or assume defaults\r\n      // Injecting internal access to statusRepo is hard without `module.get` if I didn't save it.\r\n      // Let's assume it passes check for now.\r\n\r\n      const mockCorr = {\r\n        id: 1,\r\n        projectId: 1,\r\n        correspondenceTypeId: 2,\r\n        disciplineId: 3,\r\n        originatorId: 10,\r\n        correspondenceNumber: 'OLD-NUM',\r\n        recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],\r\n      };\r\n      jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);\r\n\r\n      // Update DTO with same values\r\n      const updateDto = {\r\n        projectId: 1,\r\n        disciplineId: 3,\r\n        // recipients missing -> imply no change\r\n      };\r\n\r\n      await service.update(1, updateDto as any, mockUser);\r\n\r\n      // Check that updateNumberForDraft was NOT called\r\n      expect(numberingService.updateNumberForDraft).not.toHaveBeenCalled();\r\n    });\r\n\r\n    it('should regenerate number if Project ID changes', async () => {\r\n      const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;\r\n      const mockRevision = {\r\n        id: 100,\r\n        correspondenceId: 1,\r\n        isCurrent: true,\r\n        statusId: 5,\r\n      };\r\n      jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);\r\n\r\n      const mockCorr = {\r\n        id: 1,\r\n        projectId: 1, // Old Project\r\n        correspondenceTypeId: 2,\r\n        disciplineId: 3,\r\n        originatorId: 10,\r\n        correspondenceNumber: 'OLD-NUM',\r\n        recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],\r\n      };\r\n      jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);\r\n\r\n      const updateDto = {\r\n        projectId: 2, // New Project -> Change!\r\n      };\r\n\r\n      await service.update(1, updateDto as any, mockUser);\r\n\r\n      expect(numberingService.updateNumberForDraft).toHaveBeenCalled();\r\n    });\r\n    it('should regenerate number if Document Type changes', async () => {\r\n      const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;\r\n      const mockRevision = {\r\n        id: 100,\r\n        correspondenceId: 1,\r\n        isCurrent: true,\r\n        statusId: 5,\r\n      };\r\n      jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);\r\n\r\n      const mockCorr = {\r\n        id: 1,\r\n        projectId: 1,\r\n        correspondenceTypeId: 2, // Old Type\r\n        disciplineId: 3,\r\n        originatorId: 10,\r\n        correspondenceNumber: 'OLD-NUM',\r\n        recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],\r\n      };\r\n      jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);\r\n\r\n      const updateDto = {\r\n        typeId: 999, // New Type\r\n      };\r\n\r\n      await service.update(1, updateDto as any, mockUser);\r\n\r\n      expect(numberingService.updateNumberForDraft).toHaveBeenCalled();\r\n    });\r\n\r\n    it('should regenerate number if Recipient Organization changes', async () => {\r\n      const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;\r\n      const mockRevision = {\r\n        id: 100,\r\n        correspondenceId: 1,\r\n        isCurrent: true,\r\n        statusId: 5,\r\n      };\r\n      jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);\r\n\r\n      const mockCorr = {\r\n        id: 1,\r\n        projectId: 1,\r\n        correspondenceTypeId: 2,\r\n        disciplineId: 3,\r\n        originatorId: 10,\r\n        correspondenceNumber: 'OLD-NUM',\r\n        recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }], // Old Recipient 99\r\n      };\r\n      jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);\r\n      jest\r\n        .spyOn(service['orgRepo'], 'findOne')\r\n        .mockResolvedValue({ id: 88, organizationCode: 'NEW-ORG' } as any);\r\n\r\n      const updateDto = {\r\n        recipients: [{ type: 'TO', organizationId: 88 }], // New Recipient 88\r\n      };\r\n\r\n      await service.update(1, updateDto as any, mockUser);\r\n\r\n      expect(numberingService.updateNumberForDraft).toHaveBeenCalled();\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\add-reference.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\create-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\create-routing-template.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\search-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\submit-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\update-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\dto\\workflow-action.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-recipient.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-reference.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-revision.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-routing.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-status.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-sub-type.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence-type.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\correspondence.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\routing-template-step.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\correspondence\\entities\\routing-template.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dashboard.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dashboard.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dashboard.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dto\\dashboard-stats.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dto\\get-activity.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dto\\get-pending.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\dashboard\\dto\\index.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\controllers\\document-numbering-admin.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\controllers\\document-numbering.controller.ts",
    "messages": [
      {
        "ruleId": null,
        "nodeType": null,
        "fatal": true,
        "severity": 2,
        "message": "Parsing error: Declaration or statement expected.",
        "line": 122,
        "column": 4
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 1,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import {\r\n  Controller,\r\n  Get,\r\n  Post,\r\n  Patch,\r\n  Param,\r\n  Body,\r\n  UseGuards,\r\n  Query,\r\n  ParseIntPipe,\r\n} from '@nestjs/common';\r\nimport {\r\n  ApiTags,\r\n  ApiOperation,\r\n  ApiResponse,\r\n  ApiBearerAuth,\r\n  ApiQuery,\r\n} from '@nestjs/swagger';\r\nimport { JwtAuthGuard } from '../../../common/guards/jwt-auth.guard';\r\nimport { RbacGuard } from '../../../common/guards/rbac.guard';\r\nimport { RequirePermission } from '../../../common/decorators/require-permission.decorator';\r\nimport { DocumentNumberingService } from '../services/document-numbering.service';\r\nimport { PreviewNumberDto } from '../dto/preview-number.dto';\r\n\r\n@ApiTags('Document Numbering')\r\n@ApiBearerAuth()\r\n@Controller('document-numbering')\r\n@UseGuards(JwtAuthGuard, RbacGuard)\r\nexport class DocumentNumberingController {\r\n  constructor(private readonly numberingService: DocumentNumberingService) {}\r\n\r\n  // ----------------------------------------------------------\r\n  // Logs\r\n  // ----------------------------------------------------------\r\n\r\n  @Get('logs/audit')\r\n  @ApiOperation({ summary: 'Get document generation audit logs' })\r\n  @ApiResponse({ status: 200, description: 'List of audit logs' })\r\n  @ApiQuery({ name: 'limit', required: false, type: Number })\r\n  @RequirePermission('system.view_logs')\r\n  getAuditLogs(@Query('limit') limit?: number) {\r\n    return this.numberingService.getAuditLogs(limit ? Number(limit) : 100);\r\n  }\r\n\r\n  @Get('logs/errors')\r\n  @ApiOperation({ summary: 'Get document generation error logs' })\r\n  @ApiResponse({ status: 200, description: 'List of error logs' })\r\n  @ApiQuery({ name: 'limit', required: false, type: Number })\r\n  @RequirePermission('system.view_logs')\r\n  getErrorLogs(@Query('limit') limit?: number) {\r\n    return this.numberingService.getErrorLogs(limit ? Number(limit) : 100);\r\n  }\r\n\r\n  // ----------------------------------------------------------\r\n  // Sequences / Counters\r\n  // ----------------------------------------------------------\r\n\r\n  @Get('sequences')\r\n  @ApiOperation({ summary: 'Get all number sequences/counters' })\r\n  @ApiResponse({ status: 200, description: 'List of counter sequences' })\r\n  @ApiQuery({ name: 'projectId', required: false, type: Number })\r\n  @RequirePermission('correspondence.read')\r\n  getSequences(@Query('projectId') projectId?: number) {\r\n    return this.numberingService.getSequences(\r\n      projectId ? Number(projectId) : undefined\r\n    );\r\n  }\r\n\r\n  @Patch('counters/:id')\r\n  @ApiOperation({ summary: 'Update counter sequence value (Admin only)' })\r\n  @RequirePermission('numbering.manage_formats')\r\n  async updateCounter(\r\n    @Param('id', ParseIntPipe) id: number,\r\n    @Body('sequence') sequence: number\r\n  ) {\r\n    return this.numberingService.setCounterValue(id, sequence);\r\n  }\r\n\r\n  // ----------------------------------------------------------\r\n  // Preview / Test\r\n  // ----------------------------------------------------------\r\n\r\n  @Post('preview')\r\n  @ApiOperation({ summary: 'Preview what a document number would look like' })\r\n  @ApiResponse({\r\n    status: 200,\r\n    description: 'Preview result without incrementing counter',\r\n  })\r\n  @RequirePermission('correspondence.read')\r\n  async previewNumber(@Body() dto: PreviewNumberDto) {\r\n    // ADR-019: Resolve UUID→INT for project and organization IDs\r\n    const resolvedProjectId = await this.numberingService.resolveIdForPreview(\r\n      'project',\r\n      dto.projectId\r\n    );\r\n    const resolvedOriginatorId =\r\n      await this.numberingService.resolveIdForPreview(\r\n        'organization',\r\n        dto.originatorOrganizationId\r\n      );\r\n    const resolvedRecipientId = dto.recipientOrganizationId\r\n      ? await this.numberingService.resolveIdForPreview(\r\n          'organization',\r\n          dto.recipientOrganizationId\r\n        )\r\n      : undefined;\r\n\r\n    const result = await this.numberingService.previewNumber({\r\n      projectId: resolvedProjectId,\r\n      originatorOrganizationId: resolvedOriginatorId,\r\n      typeId: dto.correspondenceTypeId,\r\n      subTypeId: dto.subTypeId,\r\n      rfaTypeId: dto.rfaTypeId,\r\n      disciplineId: dto.disciplineId,\r\n      recipientOrganizationId: resolvedRecipientId,\r\n      year: dto.year,\r\n      customTokens: dto.customTokens,\r\n    });\r\n//     console.log(\r\n      '[DocumentNumberingController] Preview result:',\r\n      JSON.stringify(result)\r\n    );\r\n    return result;\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\controllers\\numbering-metrics.controller.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/require-await",
        "severity": 2,
        "message": "Async method 'getMetrics' has no 'await' expression.",
        "line": 13,
        "column": 3,
        "nodeType": "FunctionExpression",
        "messageId": "missingAwait",
        "endLine": 13,
        "endColumn": 19,
        "suggestions": [
          { "messageId": "removeAsync", "fix": { "range": [513, 519], "text": "" }, "desc": "Remove 'async'." }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Controller, Get, _UseGuards } from '@nestjs/common';\nimport { MetricsService } from '../services/metrics.service';\n// import { PermissionGuard } from '../../auth/guards/permission.guard';\n// import { Permissions } from '../../auth/decorators/permissions.decorator';\n\n@Controller('admin/document-numbering/metrics')\n// @UseGuards(PermissionGuard)\nexport class NumberingMetricsController {\n  constructor(private readonly metricsService: MetricsService) {}\n\n  @Get()\n  // @Permissions('system.view_logs')\n  async getMetrics() {\n    // Determine how to return metrics.\n    // Standard Prometheus metrics are usually exposed via a separate /metrics endpoint processing all metrics.\n    // If the frontend needs JSON data, we might need to query the current values from the registry or metrics service.\n\n    // For now, returning a simple status or aggregated view if supported by MetricsService,\n    // otherwise this might be a placeholder for a custom dashboard API.\n    return {\n      status: 'Metrics are being collected',\n      // TODO: Implement custom JSON export of metric values if needed for custom dashboard\n    };\n  }\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 127,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 127,
        "endColumn": 45
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 128,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 128,
        "endColumn": 34
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 145,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 145,
        "endColumn": 76
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .findOne on an `any` value.",
        "line": 146,
        "column": 18,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 146,
        "endColumn": 25
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .save on an `any` value.",
        "line": 151,
        "column": 18,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 151,
        "endColumn": 22
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .save on an `any` value.",
        "line": 159,
        "column": 24,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 159,
        "endColumn": 28
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 163,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 163,
        "endColumn": 76
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .findOne on an `any` value.",
        "line": 164,
        "column": 18,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 164,
        "endColumn": 25
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .save on an `any` value.",
        "line": 168,
        "column": 18,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 168,
        "endColumn": 22
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .save on an `any` value.",
        "line": 176,
        "column": 24,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 176,
        "endColumn": 28
      }
    ],
    "suppressedMessages": [],
    "errorCount": 10,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { DocumentNumberingService } from './services/document-numbering.service';\r\nimport { CounterService } from './services/counter.service';\r\nimport { ReservationService } from './services/reservation.service';\r\nimport { FormatService } from './services/format.service';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { ConfigService } from '@nestjs/config';\r\nimport { DocumentNumberFormat } from './entities/document-number-format.entity';\r\nimport { DocumentNumberAudit } from './entities/document-number-audit.entity';\r\nimport { DocumentNumberError } from './entities/document-number-error.entity';\r\n\r\nimport { DocumentNumberingLockService } from './services/document-numbering-lock.service';\r\nimport { ManualOverrideService } from './services/manual-override.service';\r\nimport { MetricsService } from './services/metrics.service';\r\n\r\ndescribe('DocumentNumberingService', () => {\r\n  let service: DocumentNumberingService;\r\n  let module: TestingModule;\r\n  let counterService: CounterService;\r\n  let formatService: FormatService;\r\n\r\n  const mockContext = {\r\n    projectId: 1,\r\n    originatorOrganizationId: 1,\r\n    recipientOrganizationId: 1,\r\n    typeId: 1,\r\n    subTypeId: 1,\r\n    rfaTypeId: 1,\r\n    disciplineId: 1,\r\n    year: 2025,\r\n    customTokens: { TYPE_CODE: 'COR', ORG_CODE: 'GGL' },\r\n  };\r\n\r\n  beforeEach(async () => {\r\n    module = await Test.createTestingModule({\r\n      providers: [\r\n        DocumentNumberingService,\r\n        {\r\n          provide: ConfigService,\r\n          useValue: { get: jest.fn().mockReturnValue('localhost') },\r\n        },\r\n        {\r\n          provide: CounterService,\r\n          useValue: {\r\n            incrementCounter: jest.fn().mockResolvedValue(1),\r\n            getCurrentSequence: jest.fn().mockResolvedValue(0),\r\n          },\r\n        },\r\n        {\r\n          provide: ReservationService,\r\n          useValue: {\r\n            reserve: jest.fn(),\r\n            confirm: jest.fn(),\r\n            cancel: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: FormatService,\r\n          useValue: {\r\n            format: jest.fn().mockResolvedValue('0001'),\r\n          },\r\n        },\r\n        {\r\n          provide: DocumentNumberingLockService,\r\n          useValue: {\r\n            acquireLock: jest.fn().mockResolvedValue({ release: jest.fn() }),\r\n            releaseLock: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: ManualOverrideService,\r\n          useValue: { applyOverride: jest.fn() },\r\n        },\r\n        {\r\n          provide: MetricsService,\r\n          useValue: {\r\n            numbersGenerated: { inc: jest.fn() },\r\n            lockFailures: { inc: jest.fn() },\r\n          },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(DocumentNumberFormat),\r\n          useValue: { findOne: jest.fn() },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(DocumentNumberAudit),\r\n          useValue: {\r\n            create: jest.fn().mockReturnValue({ id: 1 }),\r\n            save: jest.fn().mockResolvedValue({ id: 1 }),\r\n            findOne: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(DocumentNumberError),\r\n          useValue: {\r\n            create: jest.fn().mockReturnValue({}),\r\n            save: jest.fn().mockResolvedValue({}),\r\n          },\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<DocumentNumberingService>(DocumentNumberingService);\r\n    counterService = module.get<CounterService>(CounterService);\r\n    formatService = module.get<FormatService>(FormatService);\r\n  });\r\n\r\n  afterEach(() => {\r\n    jest.clearAllMocks();\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('generateNextNumber', () => {\r\n    it('should generate a new number successfully', async () => {\r\n      (counterService.incrementCounter as jest.Mock).mockResolvedValue(1);\r\n      (formatService.format as jest.Mock).mockResolvedValue('DOC-0001');\r\n\r\n      const result = await service.generateNextNumber(mockContext);\r\n\r\n      // Service returns object with number and auditId\r\n      expect(result).toHaveProperty('number');\r\n      expect(result).toHaveProperty('auditId');\r\n      expect(result.number).toBe('DOC-0001');\r\n      expect(counterService.incrementCounter).toHaveBeenCalled();\r\n      expect(formatService.format).toHaveBeenCalled();\r\n    });\r\n\r\n    it('should throw error when increment fails', async () => {\r\n      // Mock CounterService to throw error\r\n      (counterService.incrementCounter as jest.Mock).mockRejectedValue(\r\n        new Error('Transaction failed')\r\n      );\r\n\r\n      await expect(service.generateNextNumber(mockContext)).rejects.toThrow(\r\n        'Transaction failed'\r\n      );\r\n    });\r\n  });\r\n\r\n  describe('Admin Operations', () => {\r\n    it('voidAndReplace should verify audit log exists', async () => {\r\n      const auditRepo = module.get(getRepositoryToken(DocumentNumberAudit));\r\n      (auditRepo.findOne as jest.Mock).mockResolvedValue({\r\n        documentNumber: 'DOC-001',\r\n        counterKey: JSON.stringify({ projectId: 1, correspondenceTypeId: 1 }),\r\n        templateUsed: 'test',\r\n      });\r\n      (auditRepo.save as jest.Mock).mockResolvedValue({ id: 2 });\r\n\r\n      const result = await service.voidAndReplace({\r\n        documentNumber: 'DOC-001',\r\n        reason: 'test',\r\n        replace: false,\r\n      });\r\n      expect(result.status).toBe('VOIDED');\r\n      expect(auditRepo.save).toHaveBeenCalled();\r\n    });\r\n\r\n    it('cancelNumber should log cancellation', async () => {\r\n      const auditRepo = module.get(getRepositoryToken(DocumentNumberAudit));\r\n      (auditRepo.findOne as jest.Mock).mockResolvedValue({\r\n        documentNumber: 'DOC-002',\r\n        counterKey: {},\r\n      });\r\n      (auditRepo.save as jest.Mock).mockResolvedValue({ id: 3 });\r\n\r\n      const result = await service.cancelNumber({\r\n        documentNumber: 'DOC-002',\r\n        reason: 'bad',\r\n        projectId: 1,\r\n      });\r\n      expect(result.status).toBe('CANCELLED');\r\n      expect(auditRepo.save).toHaveBeenCalled();\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\dto\\confirm-reservation.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\dto\\counter-key.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\dto\\manual-override.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\dto\\preview-number.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\dto\\reserve-number.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\entities\\document-number-audit.entity.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-redundant-type-constituents",
        "severity": 2,
        "message": "'unknown' overrides all other types in this union type.",
        "line": 28,
        "column": 42,
        "nodeType": "TSUnknownKeyword",
        "messageId": "overrides",
        "endLine": 28,
        "endColumn": 49
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import {\r\n  Entity,\r\n  PrimaryGeneratedColumn,\r\n  Column,\r\n  CreateDateColumn,\r\n  Index,\r\n} from 'typeorm';\r\n\r\n@Entity('document_number_audit')\r\n@Index(['createdAt'])\r\n@Index(['userId'])\r\n@Index(['documentId'])\r\n@Index(['status'])\r\n@Index(['operation'])\r\n@Index(['documentNumber'])\r\n@Index(['reservationToken'])\r\nexport class DocumentNumberAudit {\r\n  @PrimaryGeneratedColumn()\r\n  id!: number;\r\n\r\n  @Column({ name: 'document_id', nullable: true })\r\n  documentId?: number;\r\n\r\n  @Column({ name: 'document_number', length: 100 })\r\n  documentNumber!: string;\r\n\r\n  @Column({ name: 'counter_key', type: 'json' })\r\n  counterKey!: Record<string, unknown> | unknown;\r\n\r\n  @Column({ name: 'template_used', length: 200 })\r\n  templateUsed!: string;\r\n\r\n  @Column({\r\n    name: 'operation',\r\n    type: 'enum',\r\n    enum: [\r\n      'RESERVE',\r\n      'CONFIRM',\r\n      'CANCEL',\r\n      'MANUAL_OVERRIDE',\r\n      'VOID',\r\n      'VOID_REPLACE',\r\n      'GENERATE',\r\n    ],\r\n    default: 'CONFIRM',\r\n  })\r\n  operation!: string;\r\n\r\n  @Column({\r\n    name: 'status',\r\n    type: 'enum',\r\n    enum: ['RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID', 'MANUAL'],\r\n    nullable: true,\r\n  })\r\n  status?: string;\r\n\r\n  @Column({ name: 'reservation_token', length: 36, nullable: true })\r\n  reservationToken?: string;\r\n\r\n  @Column({ name: 'idempotency_key', length: 36, nullable: true })\r\n  idempotencyKey?: string;\r\n\r\n  @Column({ name: 'originator_organization_id', nullable: true })\r\n  originatorOrganizationId?: number;\r\n\r\n  @Column({ name: 'recipient_organization_id', nullable: true })\r\n  recipientOrganizationId?: number;\r\n\r\n  @Column({ name: 'old_value', type: 'text', nullable: true })\r\n  oldValue?: string;\r\n\r\n  @Column({ name: 'new_value', type: 'text', nullable: true })\r\n  newValue?: string;\r\n\r\n  @Column({ name: 'metadata', type: 'json', nullable: true })\r\n  metadata?: Record<string, unknown>;\r\n\r\n  @Column({ name: 'user_id', nullable: true })\r\n  userId?: number;\r\n\r\n  @Column({ name: 'ip_address', length: 45, nullable: true })\r\n  ipAddress?: string;\r\n\r\n  @Column({ name: 'user_agent', type: 'text', nullable: true })\r\n  userAgent?: string;\r\n\r\n  @Column({ name: 'is_success', default: true })\r\n  isSuccess!: boolean;\r\n\r\n  @Column({ name: 'retry_count', default: 0 })\r\n  retryCount!: number;\r\n\r\n  @Column({ name: 'lock_wait_ms', nullable: true })\r\n  lockWaitMs?: number;\r\n\r\n  @Column({ name: 'total_duration_ms', nullable: true })\r\n  totalDurationMs?: number;\r\n\r\n  @Column({\r\n    name: 'fallback_used',\r\n    type: 'enum',\r\n    enum: ['NONE', 'DB_LOCK', 'RETRY'],\r\n    default: 'NONE',\r\n  })\r\n  fallbackUsed?: string;\r\n\r\n  @CreateDateColumn({ name: 'created_at' })\r\n  createdAt!: Date;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\entities\\document-number-counter.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\entities\\document-number-error.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\entities\\document-number-format.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\entities\\document-number-reservation.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\interfaces\\document-numbering.interface.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\audit.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\counter.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\document-numbering-lock.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\document-numbering.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\format.service.ts",
    "messages": [
      {
        "ruleId": null,
        "nodeType": null,
        "fatal": true,
        "severity": 2,
        "message": "Parsing error: Declaration or statement expected.",
        "line": 56,
        "column": 4
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 1,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Injectable } from '@nestjs/common';\r\nimport { InjectRepository } from '@nestjs/typeorm';\r\nimport { Repository, IsNull } from 'typeorm';\r\nimport { DocumentNumberFormat } from '../entities/document-number-format.entity';\r\nimport { Project } from '../../project/entities/project.entity';\r\nimport { CorrespondenceType } from '../../correspondence/entities/correspondence-type.entity';\r\nimport { Organization } from '../../organization/entities/organization.entity';\r\nimport { Discipline } from '../../master/entities/discipline.entity';\r\n\r\nexport interface FormatOptions {\r\n  projectId: number;\r\n  correspondenceTypeId: number;\r\n  subTypeId?: number;\r\n  rfaTypeId?: number;\r\n  disciplineId?: number;\r\n  sequence: number;\r\n  resetScope: string;\r\n  year?: number;\r\n  originatorOrganizationId: number;\r\n  recipientOrganizationId?: number;\r\n}\r\n\r\nexport interface DecodedTokens {\r\n  [key: string]: string;\r\n}\r\n\r\n@Injectable()\r\nexport class FormatService {\r\n  constructor(\r\n    @InjectRepository(DocumentNumberFormat)\r\n    private formatRepo: Repository<DocumentNumberFormat>,\r\n    @InjectRepository(Project)\r\n    private projectRepo: Repository<Project>,\r\n    @InjectRepository(CorrespondenceType)\r\n    private typeRepo: Repository<CorrespondenceType>,\r\n    @InjectRepository(Organization)\r\n    private orgRepo: Repository<Organization>,\r\n    @InjectRepository(Discipline)\r\n    private disciplineRepo: Repository<Discipline>\r\n  ) {}\r\n\r\n  async format(\r\n    options: FormatOptions\r\n  ): Promise<{ previewNumber: string; isDefault: boolean }> {\r\n    const { template, isDefault } = await this.resolveFormatAndScope(options);\r\n    const currentYear = options.year || new Date().getFullYear();\r\n    const tokens = await this.resolveTokens(options, currentYear);\r\n\r\n    const previewNumber = this.replaceTokens(\r\n      template,\r\n      tokens,\r\n      options.sequence\r\n    );\r\n//     console.log(\r\n      `[FormatService] Generated: \"${previewNumber}\" | Template: \"${template}\" | isDefault: ${isDefault}`\r\n    );\r\n    return { previewNumber, isDefault };\r\n  }\r\n\r\n  // --- Helpers ---\r\n\r\n  private async resolveFormatAndScope(options: FormatOptions): Promise<{\r\n    template: string;\r\n    resetSequenceYearly: boolean;\r\n    isDefault: boolean;\r\n  }> {\r\n    // 1. Specific Format\r\n    const specificFormat = await this.formatRepo.findOne({\r\n      where: {\r\n        projectId: options.projectId,\r\n        correspondenceTypeId: options.correspondenceTypeId,\r\n      },\r\n    });\r\n    if (specificFormat)\r\n      return {\r\n        template: specificFormat.formatTemplate,\r\n        resetSequenceYearly: specificFormat.resetSequenceYearly,\r\n        isDefault: false,\r\n      };\r\n\r\n    // 2. Default Format\r\n    const defaultFormat = await this.formatRepo.findOne({\r\n      where: { projectId: options.projectId, correspondenceTypeId: IsNull() },\r\n    });\r\n    if (defaultFormat)\r\n      return {\r\n        template: defaultFormat.formatTemplate,\r\n        resetSequenceYearly: defaultFormat.resetSequenceYearly,\r\n        isDefault: true,\r\n      };\r\n\r\n    // 3. Fallback\r\n    return {\r\n      template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}',\r\n      resetSequenceYearly: true,\r\n      isDefault: true,\r\n    };\r\n  }\r\n\r\n  private async resolveTokens(\r\n    options: FormatOptions,\r\n    year: number\r\n  ): Promise<DecodedTokens> {\r\n    const [project, type, recipientCode, disciplineCode, orgCode] =\r\n      await Promise.all([\r\n        this.projectRepo.findOne({\r\n          where: { id: options.projectId },\r\n          select: ['projectCode'],\r\n        }),\r\n        this.typeRepo.findOne({\r\n          where: { id: options.correspondenceTypeId },\r\n          select: ['typeCode'],\r\n        }),\r\n        this.resolveRecipientCode(options.recipientOrganizationId),\r\n        this.resolveDisciplineCode(options.disciplineId),\r\n        this.resolveOrgCode(options.originatorOrganizationId),\r\n      ]);\r\n\r\n    return {\r\n      '{PROJECT}': project?.projectCode || 'PROJ',\r\n      '{TYPE}': type?.typeCode || 'DOC',\r\n      '{ORG}': orgCode,\r\n      '{RECIPIENT}': recipientCode,\r\n      '{DISCIPLINE}': disciplineCode,\r\n      '{YEAR}': year.toString(),\r\n      '{YEAR:BE}': (year + 543).toString(),\r\n      '{REV}': '0',\r\n    };\r\n  }\r\n\r\n  private replaceTokens(\r\n    template: string,\r\n    tokens: DecodedTokens,\r\n    sequence: number\r\n  ): string {\r\n    let result = template;\r\n    for (const [key, value] of Object.entries(tokens)) {\r\n      result = result.replace(\r\n        new RegExp(key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\r\n        value\r\n      );\r\n    }\r\n    const seqMatch = result.match(/{SEQ:(\\d+)}/);\r\n    if (seqMatch) {\r\n      const padding = Number(seqMatch[1]);\r\n      result = result.replace(\r\n        seqMatch[0],\r\n        sequence.toString().padStart(padding, '0')\r\n      );\r\n    }\r\n    return result;\r\n  }\r\n\r\n  private async resolveRecipientCode(recipientId?: number): Promise<string> {\r\n    if (!recipientId) return 'GEN';\r\n    const org = await this.orgRepo.findOne({\r\n      where: { id: recipientId },\r\n      select: ['organizationCode'],\r\n    });\r\n    return org ? org.organizationCode : 'GEN';\r\n  }\r\n\r\n  private async resolveOrgCode(orgId?: number): Promise<string> {\r\n    if (!orgId) return 'GEN';\r\n    const org = await this.orgRepo.findOne({\r\n      where: { id: orgId },\r\n      select: ['organizationCode'],\r\n    });\r\n    return org ? org.organizationCode : 'GEN';\r\n  }\r\n\r\n  private async resolveDisciplineCode(disciplineId?: number): Promise<string> {\r\n    if (!disciplineId) return 'GEN';\r\n    const discipline = await this.disciplineRepo.findOne({\r\n      where: { id: disciplineId },\r\n      select: ['disciplineCode'],\r\n    });\r\n    return discipline ? discipline.disciplineCode : 'GEN';\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 56,
        "column": 12,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 56,
        "endColumn": 45
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 57,
        "column": 12,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 57,
        "endColumn": 28
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { ManualOverrideService } from './manual-override.service';\r\nimport { CounterService } from './counter.service';\r\nimport { AuditService } from './audit.service';\r\nimport { ManualOverrideDto } from '../dto/manual-override.dto';\r\n\r\ndescribe('ManualOverrideService', () => {\r\n  let service: ManualOverrideService;\r\n  let counterService: CounterService;\r\n  let auditService: AuditService;\r\n\r\n  const mockCounterService = {\r\n    forceUpdateCounter: jest.fn(),\r\n  };\r\n\r\n  const mockAuditService = {\r\n    log: jest.fn(),\r\n  };\r\n\r\n  beforeEach(async () => {\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        ManualOverrideService,\r\n        { provide: CounterService, useValue: mockCounterService },\r\n        { provide: AuditService, useValue: mockAuditService },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<ManualOverrideService>(ManualOverrideService);\r\n    counterService = module.get<CounterService>(CounterService);\r\n    auditService = module.get<AuditService>(AuditService);\r\n  });\r\n\r\n  afterEach(() => {\r\n    jest.clearAllMocks();\r\n  });\r\n\r\n  it('should apply override and log audit', async () => {\r\n    const dto: ManualOverrideDto = {\r\n      projectId: 1,\r\n      originatorOrganizationId: 2,\r\n      recipientOrganizationId: 3,\r\n      correspondenceTypeId: 4,\r\n      subTypeId: 5,\r\n      rfaTypeId: 6,\r\n      disciplineId: 7,\r\n      resetScope: 'YEAR_2024',\r\n      newLastNumber: 999,\r\n      reason: 'System sync',\r\n      reference: 'TICKET-123',\r\n    };\r\n    const userId = 101;\r\n\r\n    await service.applyOverride(dto, userId);\r\n\r\n    expect(counterService.forceUpdateCounter).toHaveBeenCalledWith(dto, 999);\r\n    expect(auditService.log).toHaveBeenCalledWith(\r\n      expect.objectContaining({\r\n        documentNumber: 'OVERRIDE-TO-999',\r\n        operation: 'MANUAL_OVERRIDE',\r\n        status: 'MANUAL',\r\n        userId: userId,\r\n        metadata: { reason: 'System sync', reference: 'TICKET-123' },\r\n      })\r\n    );\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\metrics.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\reservation.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\document-numbering\\services\\template.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\asbuilt-drawing.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\asbuilt-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\contract-drawing.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\contract-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\drawing-master-data.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\drawing-master-data.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\drawing.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\create-asbuilt-drawing-revision.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\create-asbuilt-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\create-contract-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\create-shop-drawing-revision.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\create-shop-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\search-asbuilt-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\search-contract-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\search-shop-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\dto\\update-contract-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\asbuilt-drawing-revision.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\asbuilt-drawing.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\contract-drawing-category.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\contract-drawing-sub-category.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\contract-drawing-subcat-cat-map.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\contract-drawing-volume.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\contract-drawing.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\shop-drawing-main-category.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\shop-drawing-revision.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\shop-drawing-sub-category.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\entities\\shop-drawing.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\shop-drawing.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\drawing\\shop-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\dto\\create-json-schema.dto.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 51,
        "column": 37,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 51,
        "endColumn": 40,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [966, 969], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [966, 969], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 55,
        "column": 29,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 55,
        "endColumn": 32,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1035, 1038], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1035, 1038], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 65,
        "column": 36,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 65,
        "endColumn": 39,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1264, 1267], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1264, 1267], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 3,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/json-schema/dto/create-json-schema.dto.ts\r\nimport {\r\n  IsString,\r\n  IsNotEmpty,\r\n  IsInt,\r\n  IsOptional,\r\n  IsBoolean,\r\n  IsObject,\r\n  IsArray,\r\n  ValidateNested,\r\n} from 'class-validator';\r\nimport { Type } from 'class-transformer';\r\n\r\nexport class VirtualColumnConfigDto {\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  json_path!: string;\r\n\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  column_name!: string;\r\n\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  data_type!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME';\r\n\r\n  @IsString()\r\n  @IsOptional()\r\n  index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';\r\n\r\n  @IsBoolean()\r\n  @IsOptional()\r\n  is_required?: boolean;\r\n}\r\n\r\nexport class CreateJsonSchemaDto {\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  schemaCode!: string;\r\n\r\n  @IsString() // ✅ เพิ่ม Validation\r\n  @IsNotEmpty()\r\n  tableName!: string;\r\n\r\n  @IsInt()\r\n  @IsOptional()\r\n  version?: number;\r\n\r\n  @IsObject()\r\n  @IsNotEmpty()\r\n  schemaDefinition!: Record<string, any>;\r\n\r\n  @IsObject()\r\n  @IsOptional()\r\n  uiSchema?: Record<string, any>;\r\n\r\n  @IsArray()\r\n  @IsOptional()\r\n  @ValidateNested({ each: true })\r\n  @Type(() => VirtualColumnConfigDto)\r\n  virtualColumns?: VirtualColumnConfigDto[];\r\n\r\n  @IsObject()\r\n  @IsOptional()\r\n  migrationScript?: Record<string, any>;\r\n\r\n  @IsBoolean()\r\n  @IsOptional()\r\n  isActive?: boolean;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\dto\\migrate-data.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\dto\\search-json-schema.dto.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-return",
        "severity": 2,
        "message": "Unsafe return of a value of type `any`.",
        "line": 14,
        "column": 5,
        "nodeType": "ReturnStatement",
        "messageId": "unsafeReturn",
        "endLine": 14,
        "endColumn": 18
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { IsString, IsOptional, IsBoolean, IsInt } from 'class-validator';\r\nimport { Type, Transform } from 'class-transformer';\r\n\r\nexport class SearchJsonSchemaDto {\r\n  @IsString()\r\n  @IsOptional()\r\n  search?: string; // ค้นหาจาก schemaCode\r\n\r\n  @IsBoolean()\r\n  @IsOptional()\r\n  @Transform(({ value }) => {\r\n    if (value === 'true') return true;\r\n    if (value === 'false') return false;\r\n    return value;\r\n  })\r\n  isActive?: boolean;\r\n\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  page: number = 1;\r\n\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  limit: number = 20;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\dto\\update-json-schema.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\entities\\json-schema.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\interfaces\\ui-schema.interface.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\interfaces\\validation-result.interface.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\json-schema.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\json-schema.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 265,
        "column": 11,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 267,
        "endColumn": 6
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/json-schema/json-schema.service.ts\r\n// บันทึกการแก้ไข: Fix TS2345 (undefined check)\r\n\r\nimport {\r\n  BadRequestException,\r\n  Injectable,\r\n  Logger,\r\n  NotFoundException,\r\n  OnModuleInit,\r\n} from '@nestjs/common';\r\nimport { InjectRepository } from '@nestjs/typeorm';\r\nimport Ajv, { ValidateFunction } from 'ajv';\r\nimport addFormats from 'ajv-formats';\r\nimport { Repository } from 'typeorm';\r\n\r\nimport { CreateJsonSchemaDto } from './dto/create-json-schema.dto';\r\nimport { SearchJsonSchemaDto } from './dto/search-json-schema.dto';\r\nimport { UpdateJsonSchemaDto } from './dto/update-json-schema.dto';\r\nimport { JsonSchema } from './entities/json-schema.entity';\r\n\r\n// Services ย่อยที่แยกตามหน้าที่ (Single Responsibility)\r\nimport {\r\n  JsonSecurityService,\r\n  SecurityContext,\r\n} from './services/json-security.service';\r\nimport { UiSchemaService } from './services/ui-schema.service';\r\nimport { UiSchema } from './interfaces/ui-schema.interface';\r\nimport { VirtualColumnService } from './services/virtual-column.service';\r\n\r\nimport {\r\n  ValidationErrorDetail,\r\n  ValidationOptions,\r\n  ValidationResult,\r\n} from './interfaces/validation-result.interface';\r\n\r\n@Injectable()\r\nexport class JsonSchemaService implements OnModuleInit {\r\n  private ajv: Ajv;\r\n  private validators = new Map<string, ValidateFunction>(); // Cache สำหรับเก็บ Validator ที่ Compile แล้ว\r\n  private readonly logger = new Logger(JsonSchemaService.name);\r\n\r\n  // ค่า Default สำหรับการตรวจสอบข้อมูล\r\n  private readonly defaultOptions: ValidationOptions = {\r\n    removeAdditional: true, // ลบฟิลด์เกิน\r\n    coerceTypes: true, // แปลงชนิดข้อมูลอัตโนมัติ (เช่น \"123\" -> 123)\r\n    useDefaults: true, // ใส่ค่า Default ถ้าไม่มีข้อมูล\r\n  };\r\n\r\n  constructor(\r\n    @InjectRepository(JsonSchema)\r\n    private readonly jsonSchemaRepository: Repository<JsonSchema>,\r\n    private readonly virtualColumnService: VirtualColumnService,\r\n    private readonly uiSchemaService: UiSchemaService,\r\n    private readonly jsonSecurityService: JsonSecurityService\r\n  ) {\r\n    // กำหนดค่าเริ่มต้นให้กับ AJV Validation Engine\r\n    this.ajv = new Ajv({\r\n      allErrors: true, // แสดง Error ทั้งหมด ไม่หยุดแค่จุดแรก\r\n      strict: false, // ไม่เคร่งครัดเกินไป (ยอมรับ Keyword แปลกๆ เช่น ui:widget)\r\n      coerceTypes: true,\r\n      useDefaults: true,\r\n      removeAdditional: true,\r\n    });\r\n    addFormats(this.ajv); // เพิ่ม Format มาตรฐาน (email, date, uri ฯลฯ)\r\n    this.registerCustomValidators(); // ลงทะเบียน Validator เฉพาะของโปรเจกต์\r\n  }\r\n\r\n  async onModuleInit() {\r\n    // สามารถโหลด Schema ที่ Active ทั้งหมดมา Cache ไว้ล่วงหน้าได้ที่นี่ เพื่อความเร็วในการตอบสนองครั้งแรก\r\n  }\r\n\r\n  /**\r\n   * ลงทะเบียน Custom Validators เฉพาะสำหรับ LCBP3\r\n   */\r\n  private registerCustomValidators() {\r\n    // 1. ตรวจสอบรูปแบบเลขที่เอกสาร (เช่น TEAM-RFA-STR-0001)\r\n    this.ajv.addFormat('document-number', {\r\n      type: 'string',\r\n      validate: (value: string) => {\r\n        // Regex อย่างง่าย: กลุ่มตัวอักษรขีดคั่นด้วย -\r\n        return /^[A-Z0-9]{2,10}-[A-Z]{2,5}(-[A-Z0-9]{2,5})?-\\d{4}-\\d{3,5}$/.test(\r\n          value\r\n        );\r\n      },\r\n    });\r\n\r\n    // 2. Keyword สำหรับระบุ Role ที่จำเป็น (ใช้ร่วมกับ Security Service)\r\n    this.ajv.addKeyword({\r\n      keyword: 'requiredRole',\r\n      type: 'string',\r\n      metaSchema: { type: 'string' },\r\n      validate: (_schema: string, _data: unknown) => true, // ผ่านเสมอในขั้น AJV (Security Service จะจัดการเอง)\r\n    });\r\n  }\r\n\r\n  /**\r\n   * สร้าง Schema ใหม่ พร้อมจัดการ Version, UI Schema และ Virtual Columns\r\n   */\r\n  async create(createDto: CreateJsonSchemaDto): Promise<JsonSchema> {\r\n    // 1. ตรวจสอบความถูกต้องของ JSON Schema Definition (AJV Syntax)\r\n    try {\r\n      this.ajv.compile(createDto.schemaDefinition);\r\n    } catch (error: unknown) {\r\n      throw new BadRequestException(\r\n        `Invalid JSON Schema format: ${error instanceof Error ? error.message : String(error)}`\r\n      );\r\n    }\r\n\r\n    // 2. จัดการ UI Schema\r\n    if (createDto.uiSchema) {\r\n      // ถ้าส่งมา ให้ตรวจสอบความถูกต้องเทียบกับ Data Schema\r\n      this.uiSchemaService.validateUiSchema(\r\n        createDto.uiSchema as unknown as UiSchema,\r\n        createDto.schemaDefinition\r\n      );\r\n    } else {\r\n      // ถ้าไม่ส่งมา ให้สร้าง UI Schema พื้นฐานให้อัตโนมัติ\r\n      createDto.uiSchema = this.uiSchemaService.generateDefaultUiSchema(\r\n        createDto.schemaDefinition\r\n      );\r\n    }\r\n\r\n    // 3. จัดการ Versioning อัตโนมัติ (Auto-increment)\r\n    const latestSchema = await this.jsonSchemaRepository.findOne({\r\n      where: { schemaCode: createDto.schemaCode },\r\n      order: { version: 'DESC' },\r\n    });\r\n\r\n    let newVersion = 1;\r\n    if (latestSchema) {\r\n      // ถ้าผู้ใช้ไม่ระบุ Version หรือระบุมาน้อยกว่าล่าสุด ให้ +1\r\n      if (!createDto.version || createDto.version <= latestSchema.version) {\r\n        newVersion = latestSchema.version + 1;\r\n      } else {\r\n        newVersion = createDto.version;\r\n      }\r\n    } else if (createDto.version) {\r\n      newVersion = createDto.version;\r\n    }\r\n\r\n    // 4. บันทึกลงฐานข้อมูล\r\n    const newSchema = this.jsonSchemaRepository.create({\r\n      ...createDto,\r\n      version: newVersion,\r\n    });\r\n\r\n    const savedSchema = await this.jsonSchemaRepository.save(newSchema);\r\n\r\n    // ล้าง Cache เพื่อให้โหลดตัวใหม่ในครั้งถัดไป\r\n    this.validators.delete(savedSchema.schemaCode);\r\n\r\n    this.logger.log(\r\n      `Schema '${savedSchema.schemaCode}' created (v${savedSchema.version})`\r\n    );\r\n\r\n    // 5. สร้าง/อัปเดต Virtual Columns บน Database จริง (Performance Optimization)\r\n    // Fix TS2345: Add empty array fallback\r\n    if (savedSchema.virtualColumns && savedSchema.virtualColumns.length > 0) {\r\n      await this.virtualColumnService.setupVirtualColumns(\r\n        savedSchema.tableName,\r\n        savedSchema.virtualColumns || []\r\n      );\r\n    }\r\n\r\n    return savedSchema;\r\n  }\r\n\r\n  /**\r\n   * ค้นหา Schema ทั้งหมด (Pagination & Filter)\r\n   */\r\n  async findAll(searchDto: SearchJsonSchemaDto) {\r\n    const { search, isActive, page = 1, limit = 20 } = searchDto;\r\n    const skip = (page - 1) * limit;\r\n\r\n    const query = this.jsonSchemaRepository.createQueryBuilder('schema');\r\n\r\n    if (search) {\r\n      query.andWhere('schema.schemaCode LIKE :search', {\r\n        search: `%${search}%`,\r\n      });\r\n    }\r\n\r\n    if (isActive !== undefined) {\r\n      query.andWhere('schema.isActive = :isActive', { isActive });\r\n    }\r\n\r\n    // เรียงตาม Code ก่อน แล้วตามด้วย Version ล่าสุด\r\n    query.orderBy('schema.schemaCode', 'ASC');\r\n    query.addOrderBy('schema.version', 'DESC');\r\n\r\n    const [items, total] = await query.skip(skip).take(limit).getManyAndCount();\r\n\r\n    return {\r\n      data: items,\r\n      meta: {\r\n        total,\r\n        page,\r\n        limit,\r\n        totalPages: Math.ceil(total / limit),\r\n      },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * ดึงข้อมูล Schema ตาม ID\r\n   */\r\n  async findOne(id: number): Promise<JsonSchema> {\r\n    const schema = await this.jsonSchemaRepository.findOne({ where: { id } });\r\n    if (!schema) {\r\n      throw new NotFoundException(`JsonSchema with ID ${id} not found`);\r\n    }\r\n    return schema;\r\n  }\r\n\r\n  /**\r\n   * ดึงข้อมูล Schema ตาม Code และ Version (สำหรับ Migration)\r\n   */\r\n  async findOneByCodeAndVersion(\r\n    code: string,\r\n    version: number\r\n  ): Promise<JsonSchema> {\r\n    const schema = await this.jsonSchemaRepository.findOne({\r\n      where: { schemaCode: code, version },\r\n    });\r\n\r\n    if (!schema) {\r\n      throw new NotFoundException(\r\n        `JsonSchema '${code}' version ${version} not found`\r\n      );\r\n    }\r\n    return schema;\r\n  }\r\n\r\n  /**\r\n   * ดึง Schema เวอร์ชันล่าสุดที่ Active (สำหรับใช้งานทั่วไป)\r\n   */\r\n  async findLatestByCode(code: string): Promise<JsonSchema> {\r\n    const schema = await this.jsonSchemaRepository.findOne({\r\n      where: { schemaCode: code, isActive: true },\r\n      order: { version: 'DESC' },\r\n    });\r\n\r\n    if (!schema) {\r\n      throw new NotFoundException(\r\n        `Active JsonSchema with code '${code}' not found`\r\n      );\r\n    }\r\n    return schema;\r\n  }\r\n\r\n  /**\r\n   * [CORE FUNCTION] ตรวจสอบข้อมูล (Validate), ทำความสะอาด (Sanitize) และเข้ารหัส (Encrypt)\r\n   * ใช้สำหรับ \"ขาเข้า\" (Write) ก่อนบันทึกลง Database\r\n   */\r\n  async validateData(\r\n    schemaCode: string,\r\n    data: Record<string, unknown>,\r\n    _options: ValidationOptions = {}\r\n  ): Promise<ValidationResult> {\r\n    // 1. ดึงและ Compile Validator\r\n    const validate = await this.getValidator(schemaCode);\r\n    const schema = await this.findLatestByCode(schemaCode); // ดึง Full Schema เพื่อใช้ Config อื่นๆ\r\n\r\n    // 2. สำเนาข้อมูลเพื่อป้องกัน Side Effect และเตรียมสำหรับ AJV Mutation (Sanitization)\r\n    const dataToValidate: Record<string, unknown> = JSON.parse(\r\n      JSON.stringify(data)\r\n    );\r\n\r\n    // 3. เริ่มการตรวจสอบ (AJV จะทำการ Coerce Type และ Remove Additional Properties ให้ด้วย)\r\n    const valid = validate(dataToValidate);\r\n\r\n    // 4. จัดการกรณีข้อมูลไม่ถูกต้อง\r\n    if (!valid) {\r\n      const errors: ValidationErrorDetail[] = (validate.errors || []).map(\r\n        (err) => ({\r\n          field: err.instancePath || 'root',\r\n          message: err.message || 'Validation error',\r\n          value: err.params,\r\n        })\r\n      );\r\n\r\n      return {\r\n        isValid: false,\r\n        errors,\r\n        sanitizedData: null,\r\n      };\r\n    }\r\n\r\n    // 5. เข้ารหัสข้อมูล (Encryption) สำหรับ Field ที่มีความลับ (x-encrypt: true)\r\n    const secureData = this.jsonSecurityService.encryptFields(\r\n      dataToValidate,\r\n      schema.schemaDefinition\r\n    );\r\n\r\n    return {\r\n      isValid: true,\r\n      errors: [],\r\n      sanitizedData: secureData, // ข้อมูลนี้สะอาดและปลอดภัย พร้อมบันทึก\r\n    };\r\n  }\r\n\r\n  /**\r\n   * [CORE FUNCTION] อ่านข้อมูล, ถอดรหัส (Decrypt) และกรองตามสิทธิ์ (Filter)\r\n   * ใช้สำหรับ \"ขาออก\" (Read) ก่อนส่งให้ Frontend\r\n   */\r\n  async processReadData(\r\n    schemaCode: string,\r\n    data: Record<string, unknown>,\r\n    userContext: SecurityContext\r\n  ): Promise<Record<string, unknown>> {\r\n    if (!data) return data;\r\n\r\n    // ดึง Schema เพื่อดู Config การถอดรหัสและการมองเห็น\r\n    const schema = await this.findLatestByCode(schemaCode);\r\n\r\n    return this.jsonSecurityService.decryptAndFilterFields(\r\n      data,\r\n      schema.schemaDefinition,\r\n      userContext\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Helper: ดึงและ Cache AJV Validator Function เพื่อประสิทธิภาพ\r\n   */\r\n  private async getValidator(schemaCode: string): Promise<ValidateFunction> {\r\n    let validate = this.validators.get(schemaCode);\r\n\r\n    if (!validate) {\r\n      const schema = await this.findLatestByCode(schemaCode);\r\n      try {\r\n        validate = this.ajv.compile(schema.schemaDefinition);\r\n        this.validators.set(schemaCode, validate);\r\n      } catch (error: unknown) {\r\n        throw new BadRequestException(\r\n          `Invalid Schema Definition for '${schemaCode}': ${error instanceof Error ? error.message : String(error)}`\r\n        );\r\n      }\r\n    }\r\n    return validate;\r\n  }\r\n\r\n  /**\r\n   * Wrapper เก่าสำหรับ Backward Compatibility (ถ้ามีโค้ดเก่าเรียกใช้)\r\n   */\r\n  async validate(\r\n    schemaCode: string,\r\n    data: Record<string, unknown>\r\n  ): Promise<boolean> {\r\n    const result = await this.validateData(schemaCode, data);\r\n    if (!result.isValid) {\r\n      const errorMsg = result.errors\r\n        .map((e) => `${e.field}: ${e.message}`)\r\n        .join(', ');\r\n      throw new BadRequestException(`JSON Validation Failed: ${errorMsg}`);\r\n    }\r\n    return true;\r\n  }\r\n\r\n  /**\r\n   * อัปเดตข้อมูล Schema และจัดการผลกระทบ (Virtual Columns / UI Schema)\r\n   */\r\n  async update(\r\n    id: number,\r\n    updateDto: UpdateJsonSchemaDto\r\n  ): Promise<JsonSchema> {\r\n    const schema = await this.findOne(id);\r\n\r\n    // ตรวจสอบ JSON Schema\r\n    if (updateDto.schemaDefinition) {\r\n      try {\r\n        this.ajv.compile(updateDto.schemaDefinition);\r\n      } catch (error: unknown) {\r\n        throw new BadRequestException(\r\n          `Invalid JSON Schema format: ${error instanceof Error ? error.message : String(error)}`\r\n        );\r\n      }\r\n      this.validators.delete(schema.schemaCode); // เคลียร์ Cache เก่า\r\n    }\r\n\r\n    // ตรวจสอบ UI Schema\r\n    if (updateDto.uiSchema) {\r\n      this.uiSchemaService.validateUiSchema(\r\n        updateDto.uiSchema as unknown as UiSchema,\r\n        updateDto.schemaDefinition || schema.schemaDefinition\r\n      );\r\n    }\r\n\r\n    const updatedSchema = this.jsonSchemaRepository.merge(schema, updateDto);\r\n    const savedSchema = await this.jsonSchemaRepository.save(updatedSchema);\r\n\r\n    // อัปเดต Virtual Columns ใน Database ถ้ามีการเปลี่ยนแปลง Config\r\n    // Fix TS2345: Add empty array fallback\r\n    if (updateDto.virtualColumns && updatedSchema.virtualColumns) {\r\n      await this.virtualColumnService.setupVirtualColumns(\r\n        savedSchema.tableName,\r\n        savedSchema.virtualColumns || []\r\n      );\r\n    }\r\n\r\n    return savedSchema;\r\n  }\r\n\r\n  /**\r\n   * ลบ Schema (Hard Delete)\r\n   */\r\n  async remove(id: number): Promise<void> {\r\n    const schema = await this.findOne(id);\r\n    this.validators.delete(schema.schemaCode);\r\n    await this.jsonSchemaRepository.remove(schema);\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\services\\json-security.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\services\\schema-migration.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\services\\ui-schema.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\json-schema\\services\\virtual-column.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 101,
        "column": 11,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 104,
        "endColumn": 7
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access [0] on an `any` value.",
        "line": 106,
        "column": 16,
        "nodeType": "Literal",
        "messageId": "unsafeMemberExpression",
        "endLine": 106,
        "endColumn": 17
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/json-schema/services/virtual-column.service.ts\r\nimport { Injectable, Logger } from '@nestjs/common';\r\nimport { DataSource, QueryRunner } from 'typeorm';\r\nimport { VirtualColumnConfig } from '../entities/json-schema.entity';\r\n\r\n@Injectable()\r\nexport class VirtualColumnService {\r\n  private readonly logger = new Logger(VirtualColumnService.name);\r\n\r\n  constructor(private readonly dataSource: DataSource) {}\r\n\r\n  /**\r\n   * สร้าง/อัปเดต Virtual Columns และ Index บน Database จริง\r\n   */\r\n  async setupVirtualColumns(tableName: string, configs: VirtualColumnConfig[]) {\r\n    if (!configs || configs.length === 0) return;\r\n\r\n    // ใช้ QueryRunner เพื่อให้จัดการ Transaction หรือ Connection ได้ละเอียด\r\n    const queryRunner = this.dataSource.createQueryRunner();\r\n    await queryRunner.connect();\r\n\r\n    try {\r\n      this.logger.log(\r\n        `Start setting up virtual columns for table '${tableName}'...`\r\n      );\r\n\r\n      // 1. ตรวจสอบว่าตารางมีอยู่จริงไหม\r\n      const tableExists = await queryRunner.hasTable(tableName);\r\n      if (!tableExists) {\r\n        this.logger.warn(\r\n          `Table '${tableName}' not found. Skipping virtual columns.`\r\n        );\r\n        return;\r\n      }\r\n\r\n      for (const config of configs) {\r\n        await this.ensureVirtualColumn(queryRunner, tableName, config);\r\n\r\n        if (config.index_type) {\r\n          await this.ensureIndex(queryRunner, tableName, config);\r\n        }\r\n      }\r\n\r\n      this.logger.log(\r\n        `Finished setting up virtual columns for '${tableName}'.`\r\n      );\r\n    } catch (err: unknown) {\r\n      this.logger.error(\r\n        `Failed to setup virtual columns: ${err instanceof Error ? err.message : String(err)}`,\r\n        err instanceof Error ? err.stack : undefined\r\n      );\r\n      throw err;\r\n    } finally {\r\n      await queryRunner.release();\r\n    }\r\n  }\r\n\r\n  /**\r\n   * สร้าง Column ถ้ายังไม่มี\r\n   */\r\n  private async ensureVirtualColumn(\r\n    queryRunner: QueryRunner,\r\n    tableName: string,\r\n    config: VirtualColumnConfig\r\n  ) {\r\n    const hasColumn = await queryRunner.hasColumn(\r\n      tableName,\r\n      config.column_name\r\n    );\r\n\r\n    if (!hasColumn) {\r\n      const sql = this.generateAddColumnSql(tableName, config);\r\n      this.logger.log(`Executing: ${sql}`);\r\n      await queryRunner.query(sql);\r\n    } else {\r\n      // TODO: (Advance) ถ้ามี Column แล้ว แต่ Definition เปลี่ยน อาจต้อง ALTER MODIFY\r\n      this.logger.debug(\r\n        `Column '${config.column_name}' already exists in '${tableName}'.`\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * สร้าง Index ถ้ายังไม่มี\r\n   */\r\n  private async ensureIndex(\r\n    queryRunner: QueryRunner,\r\n    tableName: string,\r\n    config: VirtualColumnConfig\r\n  ) {\r\n    const indexName = `idx_${tableName}_${config.column_name}`;\r\n\r\n    // ตรวจสอบว่า Index มีอยู่จริงไหม (Query จาก information_schema เพื่อความชัวร์)\r\n    const checkIndexSql = `\r\n      SELECT COUNT(1) as count\r\n      FROM information_schema.STATISTICS\r\n      WHERE table_schema = DATABASE()\r\n      AND table_name = ?\r\n      AND index_name = ?\r\n    `;\r\n    const result = await queryRunner.query(checkIndexSql, [\r\n      tableName,\r\n      indexName,\r\n    ]);\r\n\r\n    if (result[0].count == 0) {\r\n      const sql = `CREATE ${config.index_type === 'UNIQUE' ? 'UNIQUE' : ''} INDEX ${indexName} ON ${tableName} (${config.column_name})`;\r\n      this.logger.log(`Creating Index: ${sql}`);\r\n      await queryRunner.query(sql);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Generate SQL สำหรับ MariaDB 10.11 Virtual Column\r\n   * Syntax: ADD COLUMN name type GENERATED ALWAYS AS (expr) VIRTUAL\r\n   */\r\n  private generateAddColumnSql(\r\n    tableName: string,\r\n    config: VirtualColumnConfig\r\n  ): string {\r\n    const dbType = this.mapDataTypeToSql(config.data_type);\r\n    // JSON_UNQUOTE(JSON_EXTRACT(details, '$.path'))\r\n    // ใช้ 'details' เป็นชื่อ column JSON หลัก (ต้องตรงกับ Database Schema ที่ออกแบบไว้)\r\n    const expression = `JSON_UNQUOTE(JSON_EXTRACT(details, '${config.json_path}'))`;\r\n\r\n    // Handle Type Casting inside expression if needed,\r\n    // but usually MariaDB handles string return from JSON_EXTRACT.\r\n    // For INT/DATE, virtual column type definition enforces it.\r\n\r\n    return `ALTER TABLE ${tableName} ADD COLUMN ${config.column_name} ${dbType} GENERATED ALWAYS AS (${expression}) VIRTUAL`;\r\n  }\r\n\r\n  private mapDataTypeToSql(type: string): string {\r\n    switch (type) {\r\n      case 'INT':\r\n        return 'INT';\r\n      case 'VARCHAR':\r\n        return 'VARCHAR(255)';\r\n      case 'BOOLEAN':\r\n        return 'TINYINT(1)';\r\n      case 'DATE':\r\n        return 'DATE';\r\n      case 'DATETIME':\r\n        return 'DATETIME';\r\n      case 'DECIMAL':\r\n        return 'DECIMAL(10,2)';\r\n      default:\r\n        return 'VARCHAR(255)';\r\n    }\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\create-discipline.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\create-sub-type.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\create-tag.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\save-number-format.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\search-tag.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\dto\\update-tag.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\entities\\discipline.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\entities\\tag.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\master.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\master.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\master\\master.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\dto\\commit-batch.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\dto\\create-migration-error.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\dto\\enqueue-migration.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\dto\\import-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\dto\\migration-queue-query.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\entities\\import-transaction.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\entities\\migration-error.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\entities\\migration-review-queue.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\migration.controller.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 53,
        "column": 12,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 53,
        "endColumn": 40
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { MigrationController } from './migration.controller';\r\nimport { MigrationService } from './migration.service';\r\nimport { ImportCorrespondenceDto } from './dto/import-correspondence.dto';\r\n\r\ndescribe('MigrationController', () => {\r\n  let controller: MigrationController;\r\n  let service: MigrationService;\r\n\r\n  beforeEach(async () => {\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      controllers: [MigrationController],\r\n      providers: [\r\n        {\r\n          provide: MigrationService,\r\n          useValue: {\r\n            importCorrespondence: jest\r\n              .fn()\r\n              .mockResolvedValue({ message: 'Success' }),\r\n          },\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    controller = module.get<MigrationController>(MigrationController);\r\n    service = module.get<MigrationService>(MigrationService);\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(controller).toBeDefined();\r\n  });\r\n\r\n  it('should call importCorrespondence on service', async () => {\r\n    const dto: ImportCorrespondenceDto = {\r\n      document_number: 'DOC-001',\r\n      subject: 'Legacy Record',\r\n      category: 'Correspondence',\r\n      source_file_path: '/staging_ai/test.pdf',\r\n      migrated_by: 'SYSTEM_IMPORT',\r\n      batch_id: 'batch1',\r\n      project_id: 1,\r\n    };\r\n\r\n    const idempotencyKey = 'key123';\r\n    const user = { userId: 5 };\r\n\r\n    const result = await controller.importCorrespondence(\r\n      dto,\r\n      idempotencyKey,\r\n      user\r\n    );\r\n    expect(result).toEqual({ message: 'Success' });\r\n    expect(service.importCorrespondence).toHaveBeenCalledWith(\r\n      dto,\r\n      idempotencyKey,\r\n      5\r\n    );\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\migration.controller.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/require-await",
        "severity": 2,
        "message": "Async method 'getStagingFile' has no 'await' expression.",
        "line": 166,
        "column": 3,
        "nodeType": "FunctionExpression",
        "messageId": "missingAwait",
        "endLine": 166,
        "endColumn": 23,
        "suggestions": [
          { "messageId": "removeAsync", "fix": { "range": [5026, 5032], "text": "" }, "desc": "Remove 'async'." }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import {\r\n  Controller,\r\n  Post,\r\n  Body,\r\n  Headers,\r\n  UseGuards,\r\n  Get,\r\n  Param,\r\n  Query,\r\n  Res,\r\n  ParseIntPipe,\r\n} from '@nestjs/common';\r\nimport { MigrationService } from './migration.service';\r\nimport { ImportCorrespondenceDto } from './dto/import-correspondence.dto';\r\nimport { EnqueueMigrationDto } from './dto/enqueue-migration.dto';\r\nimport { CommitBatchDto } from './dto/commit-batch.dto';\r\nimport { CreateMigrationErrorDto } from './dto/create-migration-error.dto';\r\nimport { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';\r\nimport { CurrentUser } from '../../common/decorators/current-user.decorator';\r\nimport { User } from '../user/entities/user.entity';\r\nimport {\r\n  ApiTags,\r\n  ApiOperation,\r\n  ApiBearerAuth,\r\n  ApiHeader,\r\n  ApiQuery,\r\n  ApiParam,\r\n} from '@nestjs/swagger';\r\nimport { MigrationQueueQueryDto } from './dto/migration-queue-query.dto';\r\nimport type { Response } from 'express';\r\n\r\n@ApiTags('Migration')\r\n@ApiBearerAuth()\r\n@Controller('migration')\r\nexport class MigrationController {\r\n  constructor(private readonly migrationService: MigrationService) {}\r\n\r\n  @Post('import')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({\r\n    summary: 'Import generic legacy correspondence record via n8n integration',\r\n  })\r\n  @ApiHeader({\r\n    name: 'Idempotency-Key',\r\n    description:\r\n      'Unique key per document and batch to prevent duplicate inserts',\r\n    required: true,\r\n  })\r\n  async importCorrespondence(\r\n    @Body() dto: ImportCorrespondenceDto,\r\n    @Headers('idempotency-key') idempotencyKey: string,\r\n    @CurrentUser() user: User\r\n  ) {\r\n    const userId = user?.user_id || 5;\r\n    return this.migrationService.importCorrespondence(\r\n      dto,\r\n      idempotencyKey,\r\n      userId\r\n    );\r\n  }\r\n\r\n  @Post('commit_batch')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({\r\n    summary: 'Batch approve and import migration review queue items',\r\n  })\r\n  @ApiHeader({\r\n    name: 'Idempotency-Key',\r\n    description:\r\n      'Unique key for the entire batch to prevent duplicate execution',\r\n    required: true,\r\n  })\r\n  async commitBatch(\r\n    @Body() dto: CommitBatchDto,\r\n    @Headers('idempotency-key') idempotencyKey: string,\r\n    @CurrentUser() user: User\r\n  ) {\r\n    const userId = user?.user_id || 5;\r\n    return this.migrationService.commitBatch(dto, idempotencyKey, userId);\r\n  }\r\n\r\n  @Post('queue')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({\r\n    summary: 'Enqueue a record into the staging migration review queue',\r\n  })\r\n  async enqueueRecord(@Body() dto: EnqueueMigrationDto) {\r\n    return this.migrationService.enqueueRecord(dto);\r\n  }\r\n\r\n  @Get('queue')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Get migration review queue' })\r\n  async getReviewQueue(@Query() query: MigrationQueueQueryDto) {\r\n    return this.migrationService.getReviewQueue(query);\r\n  }\r\n\r\n  @Get('queue/:id')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Get a specific queue item by ID' })\r\n  @ApiParam({ name: 'id', type: Number })\r\n  async getQueueItemById(@Param('id', ParseIntPipe) id: number) {\r\n    return this.migrationService.getQueueItemById(id);\r\n  }\r\n\r\n  @Post('errors')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Log a migration error from n8n workflow' })\r\n  async createError(@Body() dto: CreateMigrationErrorDto) {\r\n    return this.migrationService.createError(dto);\r\n  }\r\n\r\n  @Get('errors')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Get migration errors' })\r\n  @ApiQuery({ name: 'page', required: false, type: Number })\r\n  @ApiQuery({ name: 'limit', required: false, type: Number })\r\n  async getErrors(\r\n    @Query('page') page?: number,\r\n    @Query('limit') limit?: number\r\n  ) {\r\n    return this.migrationService.getErrors(page, limit);\r\n  }\r\n\r\n  @Post('queue/:id/approve')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Approve and import a queued migration item' })\r\n  @ApiParam({ name: 'id', type: Number })\r\n  @ApiHeader({\r\n    name: 'Idempotency-Key',\r\n    description:\r\n      'Unique key per document and batch to prevent duplicate inserts',\r\n    required: true,\r\n  })\r\n  async approveQueueItem(\r\n    @Param('id', ParseIntPipe) id: number,\r\n    @Body() dto: ImportCorrespondenceDto,\r\n    @Headers('idempotency-key') idempotencyKey: string,\r\n    @CurrentUser() user: User\r\n  ) {\r\n    const userId = user?.user_id || 5;\r\n    return this.migrationService.approveQueueItem(\r\n      id,\r\n      dto,\r\n      idempotencyKey,\r\n      userId\r\n    );\r\n  }\r\n\r\n  @Post('queue/:id/reject')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Reject a queued migration item' })\r\n  @ApiParam({ name: 'id', type: Number })\r\n  async rejectQueueItem(\r\n    @Param('id', ParseIntPipe) id: number,\r\n    @CurrentUser() user: User\r\n  ) {\r\n    const userId = user?.user_id || 5;\r\n    return this.migrationService.rejectQueueItem(id, userId);\r\n  }\r\n\r\n  @Get('staging-file')\r\n  @UseGuards(JwtAuthGuard)\r\n  @ApiOperation({ summary: 'Stream a file from staging' })\r\n  @ApiQuery({ name: 'path', required: true, type: String })\r\n  async getStagingFile(@Query('path') filePath: string, @Res() res: Response) {\r\n    const stream = this.migrationService.getStagingFileStream(filePath);\r\n    res.set({\r\n      'Content-Type': 'application/pdf',\r\n      'Content-Disposition': 'inline; filename=\"document.pdf\"',\r\n    });\r\n    stream.pipe(res);\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\migration.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\migration.service.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\migration\\migration.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\controllers\\health.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\dto\\set-maintenance.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\logger\\winston.config.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\monitoring.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\monitoring.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\monitoring.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\monitoring\\services\\metrics.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\dto\\create-notification.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\dto\\search-notification.dto.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-return",
        "severity": 2,
        "message": "Unsafe return of a value of type `any`.",
        "line": 20,
        "column": 5,
        "nodeType": "ReturnStatement",
        "messageId": "unsafeReturn",
        "endLine": 20,
        "endColumn": 18
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { IsInt, IsOptional, IsBoolean } from 'class-validator';\r\nimport { Type, Transform } from 'class-transformer';\r\n\r\nexport class SearchNotificationDto {\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  page: number = 1;\r\n\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  limit: number = 20;\r\n\r\n  @IsOptional()\r\n  @IsBoolean()\r\n  @Transform(({ value }) => {\r\n    if (value === 'true') return true;\r\n    if (value === 'false') return false;\r\n    return value;\r\n  })\r\n  isRead?: boolean; // กรอง: อ่านแล้ว/ยังไม่อ่าน\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\entities\\notification.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification-cleanup.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification.gateway.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification.processor.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\notification\\notification.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\dto\\create-organization.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\dto\\search-organization.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\dto\\update-organization.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\entities\\organization-role.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\entities\\organization.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\organization.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\organization.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\organization\\organization.service.ts",
    "messages": [
      {
        "ruleId": "prettier/prettier",
        "severity": 2,
        "message": "Insert `····`",
        "line": 76,
        "column": 1,
        "nodeType": null,
        "messageId": "insert",
        "endLine": 76,
        "endColumn": 1,
        "fix": { "range": [2499, 2499], "text": "    " }
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 1,
    "fixableWarningCount": 0,
    "source": "import {\r\n  Injectable,\r\n  NotFoundException,\r\n  ConflictException,\r\n} from '@nestjs/common';\r\nimport { InjectRepository } from '@nestjs/typeorm';\r\nimport { Repository } from 'typeorm';\r\nimport { Organization } from './entities/organization.entity';\r\nimport { CreateOrganizationDto } from './dto/create-organization.dto.js';\r\nimport { UpdateOrganizationDto } from './dto/update-organization.dto.js';\r\n\r\n@Injectable()\r\nexport class OrganizationService {\r\n  constructor(\r\n    @InjectRepository(Organization)\r\n    private readonly orgRepo: Repository<Organization>\r\n  ) {}\r\n\r\n  async create(dto: CreateOrganizationDto) {\r\n    const existing = await this.orgRepo.findOne({\r\n      where: { organizationCode: dto.organizationCode },\r\n    });\r\n    if (existing) {\r\n      throw new ConflictException(\r\n        `Organization Code \"${dto.organizationCode}\" already exists`\r\n      );\r\n    }\r\n    const org = this.orgRepo.create(dto);\r\n    return this.orgRepo.save(org);\r\n  }\r\n\r\n  async findAll(params?: {\r\n    search?: string;\r\n    roleId?: number;\r\n    projectId?: number;\r\n    page?: number;\r\n    limit?: number;\r\n  }) {\r\n    const { search, roleId, projectId, page = 1, limit = 100 } = params || {};\r\n    const skip = (page - 1) * limit;\r\n\r\n    // Start with a basic query builder to handle dynamic conditions easily\r\n    const queryBuilder = this.orgRepo.createQueryBuilder('org');\r\n\r\n    if (search) {\r\n      queryBuilder.andWhere(\r\n        '(org.organizationCode LIKE :search OR org.organizationName LIKE :search)',\r\n        { search: `%${search}%` }\r\n      );\r\n    }\r\n\r\n    // [Refactor] Support filtering by roleId (e.g., getting all CONTRACTORS)\r\n    if (roleId) {\r\n      // Assuming there is a relation or a way to filter by role.\r\n      // If Organization has a roleId column directly:\r\n      queryBuilder.andWhere('org.roleId = :roleId', { roleId });\r\n    }\r\n\r\n    // [New] Support filtering by projectId (e.g. organizations in a project)\r\n    // Assuming a Many-to-Many or One-to-Many relation exists via ProjectOrganization\r\n    if (projectId) {\r\n      // Use raw join to avoid circular dependency with ProjectOrganization entity\r\n      queryBuilder.innerJoin(\r\n        'project_organizations',\r\n        'po',\r\n        'po.organization_id = org.id AND po.project_id = :projectId',\r\n        { projectId }\r\n      );\r\n    }\r\n\r\n    queryBuilder.orderBy('org.organizationCode', 'ASC').skip(skip).take(limit);\r\n\r\n    const [data, total] = await queryBuilder.getManyAndCount();\r\n\r\n    // Debug logging\r\n//     console.log(`[OrganizationService] Found ${total} organizations`);\r\n\r\n    return {\r\n      data,\r\n      meta: {\r\n        total,\r\n        page,\r\n        limit,\r\n        totalPages: Math.ceil(total / limit),\r\n      },\r\n    };\r\n  }\r\n\r\n  async findOne(id: number) {\r\n    const org = await this.orgRepo.findOne({ where: { id } });\r\n    if (!org) throw new NotFoundException(`Organization ID ${id} not found`);\r\n    return org;\r\n  }\r\n\r\n  async findOneByUuid(uuid: string) {\r\n    const org = await this.orgRepo.findOne({ where: { uuid } });\r\n    if (!org)\r\n      throw new NotFoundException(`Organization UUID ${uuid} not found`);\r\n    return org;\r\n  }\r\n\r\n  async update(uuid: string, dto: UpdateOrganizationDto) {\r\n    const org = await this.findOneByUuid(uuid);\r\n    Object.assign(org, dto);\r\n    return this.orgRepo.save(org);\r\n  }\r\n\r\n  async remove(uuid: string) {\r\n    const org = await this.findOneByUuid(uuid);\r\n    return this.orgRepo.remove(org);\r\n  }\r\n\r\n  async findAllActive() {\r\n    return this.orgRepo.find({\r\n      where: { isActive: true },\r\n      order: { organizationCode: 'ASC' },\r\n    });\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\dto\\create-project.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\dto\\search-project.dto.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-return",
        "severity": 2,
        "message": "Unsafe return of a value of type `any`.",
        "line": 14,
        "column": 5,
        "nodeType": "ReturnStatement",
        "messageId": "unsafeReturn",
        "endLine": 14,
        "endColumn": 18
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { IsString, IsOptional, IsInt, IsBoolean } from 'class-validator';\r\nimport { Type, Transform } from 'class-transformer';\r\n\r\nexport class SearchProjectDto {\r\n  @IsString()\r\n  @IsOptional()\r\n  search?: string; // ค้นหาจาก Project Code หรือ Name\r\n\r\n  @IsOptional()\r\n  @IsBoolean()\r\n  @Transform(({ value }) => {\r\n    if (value === 'true') return true;\r\n    if (value === 'false') return false;\r\n    return value;\r\n  })\r\n  isActive?: boolean; // กรองตามสถานะ Active\r\n\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  page: number = 1;\r\n\r\n  @IsOptional()\r\n  @IsInt()\r\n  @Type(() => Number)\r\n  limit: number = 20;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\dto\\update-project.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\entities\\project-organization.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\entities\\project.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\project.controller.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\project.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\project.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\project.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-call",
        "severity": 2,
        "message": "Unsafe call of an `any` typed value.",
        "line": 64,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "unsafeCall",
        "endLine": 66,
        "endColumn": 43
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .getManyAndCount on an `any` value.",
        "line": 66,
        "column": 10,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 66,
        "endColumn": 25
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { ProjectService } from './project.service';\r\nimport { Project } from './entities/project.entity';\r\nimport { OrganizationService } from '../organization/organization.service';\r\n\r\ndescribe('ProjectService', () => {\r\n  let service: ProjectService;\r\n  let mockProjectRepository: Record<string, jest.Mock>;\r\n  let mockOrganizationService: Record<string, jest.Mock>;\r\n\r\n  beforeEach(async () => {\r\n    mockProjectRepository = {\r\n      find: jest.fn(),\r\n      findOne: jest.fn(),\r\n      create: jest.fn(),\r\n      save: jest.fn(),\r\n      softDelete: jest.fn(),\r\n      createQueryBuilder: jest.fn(() => ({\r\n        leftJoinAndSelect: jest.fn().mockReturnThis(),\r\n        where: jest.fn().mockReturnThis(),\r\n        andWhere: jest.fn().mockReturnThis(),\r\n        orderBy: jest.fn().mockReturnThis(),\r\n        skip: jest.fn().mockReturnThis(),\r\n        take: jest.fn().mockReturnThis(),\r\n        getManyAndCount: jest.fn().mockResolvedValue([[], 0]),\r\n      })),\r\n    };\r\n\r\n    mockOrganizationService = {\r\n      findAllActive: jest.fn(),\r\n    };\r\n\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        ProjectService,\r\n        {\r\n          provide: getRepositoryToken(Project),\r\n          useValue: mockProjectRepository,\r\n        },\r\n        {\r\n          provide: OrganizationService,\r\n          useValue: mockOrganizationService,\r\n        },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<ProjectService>(ProjectService);\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('findAll', () => {\r\n    it('should return paginated projects', async () => {\r\n      const mockProjects = [\r\n        {\r\n          project_id: 1,\r\n          project_code: 'PROJ-001',\r\n          project_name: 'Test Project',\r\n        },\r\n      ];\r\n      mockProjectRepository\r\n        .createQueryBuilder()\r\n        .getManyAndCount.mockResolvedValue([mockProjects, 1]);\r\n\r\n      const result = await service.findAll({ page: 1, limit: 10 });\r\n\r\n      expect(result.data).toBeDefined();\r\n      expect(result.meta).toBeDefined();\r\n    });\r\n  });\r\n\r\n  describe('findAllOrganizations', () => {\r\n    it('should return all organizations', async () => {\r\n      const mockOrgs = [{ organization_id: 1, name: 'Test Org' }];\r\n      mockOrganizationService.findAllActive.mockResolvedValue(mockOrgs);\r\n\r\n      const result = await service.findAllOrganizations();\r\n\r\n      expect(mockOrganizationService.findAllActive).toHaveBeenCalled();\r\n      expect(result).toEqual(mockOrgs);\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\project\\project.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\create-rfa-revision.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\create-rfa-workflow.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\create-rfa.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\search-rfa.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\submit-rfa.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\dto\\update-rfa.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-approve-code.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-item.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-revision.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-status-code.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-type.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-workflow-template-step.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-workflow-template.entity.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 26,
        "column": 35,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 26,
        "endColumn": 38,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [638, 641], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [638, 641], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import {\r\n  Entity,\r\n  PrimaryGeneratedColumn,\r\n  Column,\r\n  CreateDateColumn,\r\n  UpdateDateColumn,\r\n  OneToMany,\r\n} from 'typeorm';\r\nimport { RfaWorkflowTemplateStep } from './rfa-workflow-template-step.entity';\r\n\r\n@Entity('rfa_workflow_templates')\r\nexport class RfaWorkflowTemplate {\r\n  @PrimaryGeneratedColumn()\r\n  id!: number;\r\n\r\n  @Column({ name: 'template_name', length: 100 })\r\n  templateName!: string;\r\n\r\n  @Column({ type: 'text', nullable: true })\r\n  description?: string;\r\n\r\n  @Column({ name: 'is_active', default: true })\r\n  isActive!: boolean;\r\n\r\n  @Column({ type: 'json', nullable: true })\r\n  workflowConfig?: Record<string, any>; // Configuration เพิ่มเติม\r\n\r\n  @CreateDateColumn({ name: 'created_at' })\r\n  createdAt!: Date;\r\n\r\n  @UpdateDateColumn({ name: 'updated_at' })\r\n  updatedAt!: Date;\r\n\r\n  // Relations\r\n  @OneToMany(() => RfaWorkflowTemplateStep, (step) => step.template, {\r\n    cascade: true,\r\n  })\r\n  steps!: RfaWorkflowTemplateStep[];\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa-workflow.entity.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 55,
        "column": 33,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 55,
        "endColumn": 36,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1420, 1423], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1420, 1423], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/rfa/entities/rfa-workflow.entity.ts\r\nimport {\r\n  Column,\r\n  CreateDateColumn,\r\n  Entity,\r\n  JoinColumn,\r\n  ManyToOne,\r\n  PrimaryGeneratedColumn,\r\n  UpdateDateColumn,\r\n} from 'typeorm';\r\nimport { Organization } from '../../organization/entities/organization.entity';\r\nimport { User } from '../../user/entities/user.entity';\r\nimport { RfaRevision } from './rfa-revision.entity';\r\nimport { RfaActionType } from './rfa-workflow-template-step.entity'; // ✅ Import Enum\r\n\r\n@Entity('rfa_workflows')\r\nexport class RfaWorkflow {\r\n  @PrimaryGeneratedColumn()\r\n  id!: number;\r\n\r\n  @Column({ name: 'rfa_revision_id' })\r\n  rfaRevisionId!: number;\r\n\r\n  @Column({ name: 'step_number' })\r\n  stepNumber!: number;\r\n\r\n  @Column({ name: 'organization_id' })\r\n  organizationId!: number;\r\n\r\n  @Column({ name: 'assigned_to', nullable: true })\r\n  assignedTo?: number;\r\n\r\n  @Column({\r\n    name: 'action_type',\r\n    type: 'enum',\r\n    enum: RfaActionType, // ✅ Use Shared Enum\r\n    nullable: true,\r\n  })\r\n  actionType?: RfaActionType;\r\n\r\n  @Column({\r\n    type: 'enum',\r\n    enum: ['PENDING', 'IN_PROGRESS', 'COMPLETED', 'REJECTED'],\r\n    nullable: true,\r\n  })\r\n  status?: string;\r\n\r\n  @Column({ type: 'text', nullable: true })\r\n  comments?: string;\r\n\r\n  @Column({ name: 'completed_at', type: 'datetime', nullable: true })\r\n  completedAt?: Date;\r\n\r\n  @Column({ type: 'json', nullable: true })\r\n  stateContext?: Record<string, any>;\r\n\r\n  @CreateDateColumn({ name: 'created_at' })\r\n  createdAt!: Date;\r\n\r\n  @UpdateDateColumn({ name: 'updated_at' })\r\n  updatedAt!: Date;\r\n\r\n  // Relations\r\n  @ManyToOne(() => RfaRevision, (rev) => rev.workflows, { onDelete: 'CASCADE' })\r\n  @JoinColumn({ name: 'rfa_revision_id' })\r\n  rfaRevision!: RfaRevision;\r\n\r\n  @ManyToOne(() => Organization)\r\n  @JoinColumn({ name: 'organization_id' })\r\n  organization!: Organization;\r\n\r\n  @ManyToOne(() => User)\r\n  @JoinColumn({ name: 'assigned_to' })\r\n  assignee?: User;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\entities\\rfa.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\rfa-workflow.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\rfa.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\rfa.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\rfa\\rfa.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\search\\dto\\search-query.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\search\\search.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\search\\search.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\search\\search.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\dto\\create-transmittal.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\dto\\search-transmittal.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\dto\\update-transmittal.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\entities\\transmittal-item.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\entities\\transmittal.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\transmittal.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\transmittal.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\transmittal\\transmittal.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\assign-role.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\bulk-assignment.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\create-user.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\search-user.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\update-preference.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\dto\\update-user.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\entities\\permission.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\entities\\role.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\entities\\user-assignment.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\entities\\user-preference.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\entities\\user.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user-assignment.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user-preference.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user.controller.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user.service.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\user\\user.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 24,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 24,
        "endColumn": 41
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 185,
        "column": 13,
        "nodeType": "VariableDeclarator",
        "messageId": "anyAssignment",
        "endLine": 185,
        "endColumn": 41
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Injectable, Logger, BadRequestException } from '@nestjs/common';\r\nimport { InjectRepository } from '@nestjs/typeorm';\r\nimport { Repository } from 'typeorm';\r\nimport { WorkflowDslSchema, WorkflowDsl } from './workflow-dsl.schema';\r\nimport { WorkflowDefinition } from '../entities/workflow-definition.entity';\r\n\r\n@Injectable()\r\nexport class WorkflowDslParser {\r\n  private readonly logger = new Logger(WorkflowDslParser.name);\r\n\r\n  constructor(\r\n    @InjectRepository(WorkflowDefinition)\r\n    private workflowDefRepo: Repository<WorkflowDefinition>\r\n  ) {}\r\n\r\n  /**\r\n   * Parse และ Validate Workflow DSL from JSON string\r\n   * @param dslJson JSON string ของ Workflow DSL\r\n   * @returns WorkflowDefinition entity พร้อมบันทึกลง database\r\n   */\r\n  async parse(dslJson: string): Promise<WorkflowDefinition> {\r\n    try {\r\n      // Step 1: Parse JSON\r\n      const rawDsl = JSON.parse(dslJson);\r\n\r\n      // Step 2: Validate with Zod schema\r\n      const dsl = WorkflowDslSchema.parse(rawDsl);\r\n\r\n      // Step 3: Validate state machine integrity\r\n      this.validateStateMachine(dsl);\r\n\r\n      // Step 4: Build WorkflowDefinition entity\r\n      const definition = this.buildDefinition(dsl);\r\n\r\n      // Step 5: Save to database\r\n      return await this.workflowDefRepo.save(definition);\r\n    } catch (error: unknown) {\r\n      if (error instanceof SyntaxError) {\r\n        throw new BadRequestException(`Invalid JSON: ${error.message}`);\r\n      }\r\n      const err = error as {\r\n        name?: string;\r\n        errors?: unknown;\r\n        message?: string;\r\n      };\r\n      if (err.name === 'ZodError') {\r\n        throw new BadRequestException(\r\n          `Invalid workflow DSL: ${JSON.stringify(err.errors)}`\r\n        );\r\n      }\r\n      throw error;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate state machine integrity\r\n   * ตรวจสอบว่า state machine ถูกต้องตามหลักการ:\r\n   * - All states in transitions must exist in states array\r\n   * - Initial state must exist\r\n   * - All final states must exist\r\n   * - No dead-end states (states with no outgoing transitions except final states)\r\n   */\r\n  private validateStateMachine(dsl: WorkflowDsl): void {\r\n    const stateSet = new Set(dsl.states);\r\n    const finalStateSet = new Set(dsl.finalStates);\r\n\r\n    // 1. Validate initial state\r\n    if (!stateSet.has(dsl.initialState)) {\r\n      throw new BadRequestException(\r\n        `Initial state \"${dsl.initialState}\" not found in states array`\r\n      );\r\n    }\r\n\r\n    // 2. Validate final states\r\n    dsl.finalStates.forEach((state) => {\r\n      if (!stateSet.has(state)) {\r\n        throw new BadRequestException(\r\n          `Final state \"${state}\" not found in states array`\r\n        );\r\n      }\r\n    });\r\n\r\n    // 3. Validate transitions\r\n    const statesWithOutgoing = new Set<string>();\r\n\r\n    dsl.transitions.forEach((transition, index) => {\r\n      // Check 'from' state\r\n      if (!stateSet.has(transition.from)) {\r\n        throw new BadRequestException(\r\n          `Transition ${index}: 'from' state \"${transition.from}\" not found in states array`\r\n        );\r\n      }\r\n\r\n      // Check 'to' state\r\n      if (!stateSet.has(transition.to)) {\r\n        throw new BadRequestException(\r\n          `Transition ${index}: 'to' state \"${transition.to}\" not found in states array`\r\n        );\r\n      }\r\n\r\n      // Track states with outgoing transitions\r\n      statesWithOutgoing.add(transition.from);\r\n    });\r\n\r\n    // 4. Check for dead-end states (except final states)\r\n    const nonFinalStates = dsl.states.filter(\r\n      (state) => !finalStateSet.has(state)\r\n    );\r\n\r\n    nonFinalStates.forEach((state) => {\r\n      if (!statesWithOutgoing.has(state) && state !== dsl.initialState) {\r\n        this.logger.warn(\r\n          `Warning: State \"${state}\" has no outgoing transitions (potential dead-end)`\r\n        );\r\n      }\r\n    });\r\n\r\n    // 5. Check for duplicate transitions\r\n    const transitionKeys = new Set<string>();\r\n    dsl.transitions.forEach((transition) => {\r\n      const key = `${transition.from}-${transition.trigger}-${transition.to}`;\r\n      if (transitionKeys.has(key)) {\r\n        throw new BadRequestException(\r\n          `Duplicate transition: ${transition.from} --[${transition.trigger}]--> ${transition.to}`\r\n        );\r\n      }\r\n      transitionKeys.add(key);\r\n    });\r\n\r\n    this.logger.log(\r\n      `Workflow \"${dsl.name}\" v${dsl.version} validated successfully`\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Build WorkflowDefinition entity from validated DSL\r\n   */\r\n  private buildDefinition(dsl: WorkflowDsl): WorkflowDefinition {\r\n    const definition = new WorkflowDefinition();\r\n    definition.workflow_code = dsl.name;\r\n    // Map Semver (1.0.0) to version int (1)\r\n    const majorVersion = Number(dsl.version.split('.')[0], 10);\r\n    definition.version = isNaN(majorVersion) ? 1 : majorVersion;\r\n    definition.description = dsl.description;\r\n    definition.dsl = dsl;\r\n    definition.compiled = dsl;\r\n    definition.is_active = true;\r\n\r\n    return definition;\r\n  }\r\n\r\n  /**\r\n   * Get parsed DSL from WorkflowDefinition\r\n   */\r\n  async getParsedDsl(definitionId: string): Promise<WorkflowDsl> {\r\n    const definition = await this.workflowDefRepo.findOne({\r\n      where: { id: definitionId },\r\n    });\r\n\r\n    if (!definition) {\r\n      throw new BadRequestException(\r\n        `Workflow definition ${definitionId} not found`\r\n      );\r\n    }\r\n\r\n    try {\r\n      const dsl = definition.dsl;\r\n      return WorkflowDslSchema.parse(dsl);\r\n    } catch (error: unknown) {\r\n      this.logger.error(\r\n        `Failed to parse stored DSL for definition ${definitionId}`,\r\n        error\r\n      );\r\n      throw new BadRequestException(\r\n        `Invalid stored DSL: ${error instanceof Error ? error.message : String(error)}`\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate DSL without saving (dry-run)\r\n   */\r\n  validateOnly(dslJson: string): { valid: boolean; errors?: string[] } {\r\n    try {\r\n      const rawDsl = JSON.parse(dslJson);\r\n      const dsl = WorkflowDslSchema.parse(rawDsl);\r\n      this.validateStateMachine(dsl);\r\n      return { valid: true };\r\n    } catch (error: unknown) {\r\n      return {\r\n        valid: false,\r\n        errors: [\r\n          error instanceof Error ? error.message : 'Unknown validation error',\r\n        ],\r\n      };\r\n    }\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\workflow-dsl.schema.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dto\\create-workflow-definition.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dto\\evaluate-workflow.dto.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 24,
        "column": 28,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 24,
        "endColumn": 31,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [777, 780], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [777, 780], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/dto/evaluate-workflow.dto.ts\r\nimport { IsString, IsNotEmpty, IsObject, IsOptional } from 'class-validator';\r\nimport { ApiProperty } from '@nestjs/swagger';\r\n\r\nexport class EvaluateWorkflowDto {\r\n  @ApiProperty({ example: 'RFA', description: 'รหัส Workflow' })\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  workflow_code!: string; // เพิ่ม !\r\n\r\n  @ApiProperty({ example: 'DRAFT', description: 'สถานะปัจจุบัน' })\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  current_state!: string; // เพิ่ม !\r\n\r\n  @ApiProperty({ example: 'SUBMIT', description: 'Action ที่ต้องการทำ' })\r\n  @IsString()\r\n  @IsNotEmpty()\r\n  action!: string; // เพิ่ม !\r\n\r\n  @ApiProperty({ description: 'Context', example: { userId: 1 } })\r\n  @IsObject()\r\n  @IsOptional()\r\n  context?: Record<string, any>;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dto\\get-available-actions.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dto\\update-workflow-definition.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\dto\\workflow-transition.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\entities\\workflow-definition.entity.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\entities\\workflow-history.entity.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 57,
        "column": 29,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 57,
        "endColumn": 32,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1615, 1618], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1615, 1618], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/entities/workflow-history.entity.ts\r\n\r\nimport {\r\n  Column,\r\n  CreateDateColumn,\r\n  Entity,\r\n  Index,\r\n  JoinColumn,\r\n  ManyToOne,\r\n  PrimaryGeneratedColumn,\r\n} from 'typeorm';\r\nimport { WorkflowInstance } from './workflow-instance.entity';\r\n\r\n/**\r\n * เก็บประวัติการเปลี่ยนสถานะ (Audit Trail)\r\n * สำคัญมากสำหรับการตรวจสอบย้อนหลัง (Who did What, When)\r\n */\r\n@Entity('workflow_histories')\r\n@Index(['instanceId']) // ค้นหาประวัติของ Instance นี้\r\n@Index(['actionByUserId']) // ค้นหาว่า User คนนี้ทำอะไรไปบ้าง\r\nexport class WorkflowHistory {\r\n  @PrimaryGeneratedColumn('uuid')\r\n  id!: string;\r\n\r\n  @ManyToOne(() => WorkflowInstance, { onDelete: 'CASCADE' })\r\n  @JoinColumn({ name: 'instance_id' })\r\n  instance!: WorkflowInstance;\r\n\r\n  @Column({ name: 'instance_id' })\r\n  instanceId!: string;\r\n\r\n  @Column({ name: 'from_state', length: 50, comment: 'สถานะต้นทาง' })\r\n  fromState!: string;\r\n\r\n  @Column({ name: 'to_state', length: 50, comment: 'สถานะปลายทาง' })\r\n  toState!: string;\r\n\r\n  @Column({ length: 50, comment: 'Action ที่ User กด (เช่น APPROVE, REJECT)' })\r\n  action!: string;\r\n\r\n  @Column({\r\n    name: 'action_by_user_id',\r\n    nullable: true,\r\n    comment: 'User ID ผู้ดำเนินการ (Nullable กรณี System Auto)',\r\n  })\r\n  actionByUserId?: number;\r\n\r\n  @Column({ type: 'text', nullable: true, comment: 'ความเห็นประกอบการอนุมัติ' })\r\n  comment?: string;\r\n\r\n  // Snapshot ข้อมูล ณ เวลาที่เปลี่ยนสถานะ เพื่อเป็นหลักฐานหาก Context เปลี่ยนในอนาคต\r\n  @Column({\r\n    type: 'json',\r\n    nullable: true,\r\n    comment: 'Snapshot of Context or Metadata',\r\n  })\r\n  metadata?: Record<string, any>;\r\n\r\n  @CreateDateColumn({ name: 'created_at' })\r\n  createdAt!: Date;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\entities\\workflow-instance.entity.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 73,
        "column": 28,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 73,
        "endColumn": 31,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [2108, 2111], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [2108, 2111], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/entities/workflow-instance.entity.ts\r\n\r\nimport {\r\n  Column,\r\n  CreateDateColumn,\r\n  Entity,\r\n  Index,\r\n  JoinColumn,\r\n  ManyToOne,\r\n  PrimaryGeneratedColumn,\r\n  UpdateDateColumn,\r\n} from 'typeorm';\r\nimport { WorkflowDefinition } from './workflow-definition.entity';\r\n\r\nexport enum WorkflowStatus {\r\n  ACTIVE = 'ACTIVE', // กำลังดำเนินการ\r\n  COMPLETED = 'COMPLETED', // จบกระบวนการ (ถึง Terminal State)\r\n  CANCELLED = 'CANCELLED', // ถูกยกเลิกกลางคัน\r\n  TERMINATED = 'TERMINATED', // ถูกบังคับจบโดยระบบ หรือ Error\r\n}\r\n\r\n/**\r\n * เก็บสถานะการเดินเรื่องของเอกสารแต่ละใบ (Runtime State)\r\n */\r\n@Entity('workflow_instances')\r\n@Index(['entityType', 'entityId']) // เพื่อค้นหาว่าเอกสารนี้ (เช่น RFA-001) อยู่ขั้นตอนไหน\r\n@Index(['currentState']) // เพื่อ Dashboard: \"มีงานค้างที่ขั้นตอนไหนบ้าง\"\r\nexport class WorkflowInstance {\r\n  @PrimaryGeneratedColumn('uuid')\r\n  id!: string;\r\n\r\n  // ผูกกับ Definition เพื่อรู้ว่าใช้กฎชุดไหน (Version ไหน)\r\n  @ManyToOne(() => WorkflowDefinition)\r\n  @JoinColumn({ name: 'definition_id' })\r\n  definition!: WorkflowDefinition;\r\n\r\n  @Column({ name: 'definition_id' })\r\n  definitionId!: string;\r\n\r\n  // Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.) โดยไม่ต้อง Foreign Key จริง\r\n  @Column({\r\n    name: 'entity_type',\r\n    length: 50,\r\n    comment: 'ประเภทเอกสาร เช่น rfa, correspondence',\r\n  })\r\n  entityType!: string;\r\n\r\n  @Column({\r\n    name: 'entity_id',\r\n    length: 50,\r\n    comment: 'ID ของเอกสาร (String/UUID)',\r\n  })\r\n  entityId!: string;\r\n\r\n  @Column({\r\n    name: 'current_state',\r\n    length: 50,\r\n    comment: 'ชื่อ State ปัจจุบัน เช่น DRAFT, IN_REVIEW',\r\n  })\r\n  currentState!: string;\r\n\r\n  @Column({\r\n    type: 'enum',\r\n    enum: WorkflowStatus,\r\n    default: WorkflowStatus.ACTIVE,\r\n    comment: 'สถานะภาพรวมของ Instance',\r\n  })\r\n  status!: WorkflowStatus;\r\n\r\n  // Context: เก็บตัวแปรที่จำเป็นสำหรับการตัดสินใจใน Workflow\r\n  // เช่น { \"amount\": 500000, \"requester_role\": \"ENGINEER\", \"approver_ids\": [1, 2] }\r\n  @Column({ type: 'json', nullable: true, comment: 'Runtime Context Data' })\r\n  context?: Record<string, any>;\r\n\r\n  @CreateDateColumn({ name: 'created_at' })\r\n  createdAt!: Date;\r\n\r\n  @UpdateDateColumn({ name: 'updated_at' })\r\n  updatedAt!: Date;\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\interfaces\\workflow.interface.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-dsl.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-implied-eval",
        "severity": 2,
        "message": "Implied eval. Do not use the Function constructor to create functions.",
        "line": 258,
        "column": 20,
        "nodeType": "NewExpression",
        "messageId": "noFunctionConstructor",
        "endLine": 258,
        "endColumn": 68
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-call",
        "severity": 2,
        "message": "Unsafe call of a `Function` typed value.",
        "line": 259,
        "column": 16,
        "nodeType": "Identifier",
        "messageId": "unsafeCall",
        "endLine": 259,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/workflow-dsl.service.ts\r\n\r\nimport { BadRequestException, Injectable, Logger } from '@nestjs/common';\r\n\r\n// ==========================================\r\n// 1. Interfaces for RAW DSL (Input from User)\r\n// ==========================================\r\nexport interface RawWorkflowDSL {\r\n  workflow: string;\r\n  version?: number;\r\n  description?: string;\r\n  states: RawState[];\r\n}\r\n\r\nexport interface RawState {\r\n  name: string;\r\n  initial?: boolean;\r\n  terminal?: boolean;\r\n  on?: Record<string, RawTransition>;\r\n}\r\n\r\nexport interface RawTransition {\r\n  to: string;\r\n  require?: {\r\n    role?: string | string[];\r\n    user?: string;\r\n  };\r\n  condition?: string; // JavaScript Expression string\r\n  events?: RawEvent[];\r\n}\r\n\r\nexport interface RawEvent {\r\n  type: 'notify' | 'webhook' | 'assign' | 'auto_action';\r\n  target?: string;\r\n  template?: string;\r\n  payload?: Record<string, unknown>;\r\n}\r\n\r\n// ==========================================\r\n// 2. Interfaces for COMPILED Schema (Optimized for Runtime)\r\n// ==========================================\r\nexport interface CompiledWorkflow {\r\n  workflow: string;\r\n  version: number;\r\n  initialState: string; // Optimize: เก็บชื่อ Initial State ไว้เลย ไม่ต้อง loop หา\r\n  states: Record<string, CompiledState>;\r\n}\r\n\r\nexport interface CompiledState {\r\n  terminal: boolean;\r\n  transitions: Record<string, CompiledTransition>;\r\n}\r\n\r\nexport interface CompiledTransition {\r\n  to: string;\r\n  requirements: {\r\n    roles: string[];\r\n    userId?: string;\r\n  };\r\n  condition?: string;\r\n  events: RawEvent[];\r\n}\r\n\r\n@Injectable()\r\nexport class WorkflowDslService {\r\n  private readonly logger = new Logger(WorkflowDslService.name);\r\n\r\n  /**\r\n   * [Compile Time]\r\n   * แปลง Raw DSL เป็น Compiled Structure พร้อม Validation\r\n   */\r\n  compile(dsl: RawWorkflowDSL): CompiledWorkflow {\r\n    this.validateSchemaStructure(dsl);\r\n\r\n    const compiled: CompiledWorkflow = {\r\n      workflow: dsl.workflow,\r\n      version: dsl.version || 1,\r\n      initialState: '',\r\n      states: {},\r\n    };\r\n\r\n    const definedStates = new Set<string>(dsl.states.map((s) => s.name));\r\n    let initialFound = false;\r\n\r\n    // 1. Process States\r\n    for (const rawState of dsl.states) {\r\n      if (rawState.initial) {\r\n        if (initialFound) {\r\n          throw new BadRequestException(\r\n            `DSL Error: Multiple initial states found (at \"${rawState.name}\").`\r\n          );\r\n        }\r\n        compiled.initialState = rawState.name;\r\n        initialFound = true;\r\n      }\r\n\r\n      const compiledState: CompiledState = {\r\n        terminal: !!rawState.terminal,\r\n        transitions: {},\r\n      };\r\n\r\n      // 2. Process Transitions\r\n      if (rawState.on) {\r\n        for (const [action, rule] of Object.entries(rawState.on)) {\r\n          // Validation: Target state must exist\r\n          if (!definedStates.has(rule.to)) {\r\n            throw new BadRequestException(\r\n              `DSL Error: State \"${rawState.name}\" transitions via \"${action}\" to unknown state \"${rule.to}\".`\r\n            );\r\n          }\r\n\r\n          compiledState.transitions[action] = {\r\n            to: rule.to,\r\n            requirements: {\r\n              roles: rule.require?.role\r\n                ? Array.isArray(rule.require.role)\r\n                  ? rule.require.role\r\n                  : [rule.require.role]\r\n                : [],\r\n              userId: rule.require?.user,\r\n            },\r\n            condition: rule.condition,\r\n            events: rule.events || [],\r\n          };\r\n        }\r\n      } else if (!rawState.terminal) {\r\n        this.logger.warn(\r\n          `State \"${rawState.name}\" is not terminal but has no transitions.`\r\n        );\r\n      }\r\n\r\n      compiled.states[rawState.name] = compiledState;\r\n    }\r\n\r\n    if (!initialFound) {\r\n      throw new BadRequestException('DSL Error: No initial state defined.');\r\n    }\r\n\r\n    return compiled;\r\n  }\r\n\r\n  /**\r\n   * [Runtime]\r\n   * ประมวลผล Action และคืนค่า State ถัดไป\r\n   */\r\n  evaluate(\r\n    compiled: CompiledWorkflow,\r\n    currentState: string,\r\n    action: string,\r\n    context: Record<string, unknown> = {}\r\n  ): { nextState: string; events: RawEvent[] } {\r\n    const stateConfig = compiled.states[currentState];\r\n\r\n    // 1. Validate State Existence\r\n    if (!stateConfig) {\r\n      throw new BadRequestException(\r\n        `Runtime Error: Current state \"${currentState}\" is invalid.`\r\n      );\r\n    }\r\n\r\n    // 2. Check if terminal\r\n    if (stateConfig.terminal) {\r\n      throw new BadRequestException(\r\n        `Runtime Error: Cannot transition from terminal state \"${currentState}\".`\r\n      );\r\n    }\r\n\r\n    // 3. Find Transition\r\n    const transition = stateConfig.transitions[action];\r\n    if (!transition) {\r\n      const allowed = Object.keys(stateConfig.transitions).join(', ');\r\n      throw new BadRequestException(\r\n        `Invalid Action: \"${action}\" is not allowed from \"${currentState}\". Allowed: [${allowed}]`\r\n      );\r\n    }\r\n\r\n    // 4. Validate Requirements (RBAC)\r\n    this.checkRequirements(transition.requirements, context);\r\n\r\n    // 5. Evaluate Condition (Dynamic Logic)\r\n    if (transition.condition) {\r\n      const isMet = this.evaluateCondition(transition.condition, context);\r\n      if (!isMet) {\r\n        throw new BadRequestException(\r\n          'Condition Failed: The criteria for this transition are not met.'\r\n        );\r\n      }\r\n    }\r\n\r\n    return {\r\n      nextState: transition.to,\r\n      events: transition.events,\r\n    };\r\n  }\r\n\r\n  // --------------------------------------------------------\r\n  // Private Helpers\r\n  // --------------------------------------------------------\r\n\r\n  private validateSchemaStructure(dsl: unknown) {\r\n    if (!dsl || typeof dsl !== 'object') {\r\n      throw new BadRequestException('DSL must be a JSON object.');\r\n    }\r\n    const d = dsl as Record<string, unknown>;\r\n    if (!d.workflow || !d.states || !Array.isArray(d.states)) {\r\n      throw new BadRequestException(\r\n        'DSL Error: Missing required fields (workflow, states).'\r\n      );\r\n    }\r\n  }\r\n\r\n  private checkRequirements(\r\n    req: CompiledTransition['requirements'],\r\n    context: Record<string, unknown>\r\n  ) {\r\n    // [FIX] Early return if no requirements defined\r\n    if (!req) {\r\n      return;\r\n    }\r\n\r\n    const userRoles: string[] = (context.roles as string[]) || [];\r\n    const userId: string | number = context.userId as string | number;\r\n\r\n    // Check Roles (OR logic inside array) - with null-safety\r\n    const requiredRoles = req.roles || [];\r\n    if (requiredRoles.length > 0) {\r\n      const hasRole = requiredRoles.some((r) => userRoles.includes(r));\r\n      if (!hasRole) {\r\n        throw new BadRequestException(\r\n          `Access Denied: Required roles [${requiredRoles.join(', ')}]`\r\n        );\r\n      }\r\n    }\r\n\r\n    // Check Specific User\r\n    if (req.userId && String(req.userId) !== String(userId)) {\r\n      throw new BadRequestException('Access Denied: User mismatch.');\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Evaluate simple JS expression securely\r\n   * NOTE: In production, use a safe parser like 'json-logic-js' or vm2\r\n   * For this phase, we use a simple Function constructor with restricted scope.\r\n   */\r\n  private evaluateCondition(\r\n    expression: string,\r\n    context: Record<string, unknown>\r\n  ): boolean {\r\n    try {\r\n      // Simple guard against malicious code (basic)\r\n      if (expression.includes('process') || expression.includes('require')) {\r\n        throw new Error('Unsafe expression detected');\r\n      }\r\n\r\n      // Create a function that returns the expression result\r\n      // \"context\" is available inside the expression\r\n      const func = new Function('context', `return ${expression};`);\r\n      return !!func(context);\r\n    } catch (error: unknown) {\r\n      this.logger.error(\r\n        `Condition Error: \"${expression}\" -> ${error instanceof Error ? error.message : String(error)}`\r\n      );\r\n      return false; // Fail safe\r\n    }\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.controller.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/require-await",
        "severity": 2,
        "message": "Async method 'getAvailableActions' has no 'await' expression.",
        "line": 118,
        "column": 3,
        "nodeType": "FunctionExpression",
        "messageId": "missingAwait",
        "endLine": 118,
        "endColumn": 28,
        "suggestions": [
          { "messageId": "removeAsync", "fix": { "range": [4233, 4239], "text": "" }, "desc": "Remove 'async'." }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/workflow-engine.controller.ts\r\n\r\nimport {\r\n  Body,\r\n  Controller,\r\n  Get,\r\n  Param,\r\n  Patch,\r\n  Post,\r\n  Request,\r\n  UseGuards,\r\n} from '@nestjs/common';\r\nimport {\r\n  ApiBearerAuth,\r\n  ApiOperation,\r\n  ApiParam,\r\n  ApiResponse,\r\n  ApiTags,\r\n} from '@nestjs/swagger';\r\n\r\n// Services\r\nimport { WorkflowEngineService } from './workflow-engine.service';\r\n\r\n// DTOs\r\nimport { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';\r\nimport { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';\r\nimport { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';\r\nimport { WorkflowTransitionDto } from './dto/workflow-transition.dto';\r\n\r\n// Guards & Decorators (อ้างอิงตามโครงสร้าง src/common ในแผนงาน)\r\nimport { RequirePermission } from '../../common/decorators/require-permission.decorator';\r\nimport { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';\r\nimport { RbacGuard } from '../../common/guards/rbac.guard';\r\nimport type { RequestWithUser } from '../../common/interfaces/request-with-user.interface';\r\n\r\n@ApiTags('Workflow Engine')\r\n@ApiBearerAuth() // ระบุว่าต้องใช้ Token ใน Swagger\r\n@Controller('workflow-engine')\r\n@UseGuards(JwtAuthGuard, RbacGuard) // บังคับ Login และตรวจสอบสิทธิ์ทุก Request\r\nexport class WorkflowEngineController {\r\n  constructor(private readonly workflowService: WorkflowEngineService) {}\r\n\r\n  // =================================================================\r\n  // Definition Management (Admin / Developer)\r\n  // =================================================================\r\n\r\n  @Post('definitions')\r\n  @ApiOperation({ summary: 'สร้าง Workflow Definition ใหม่ (Auto Versioning)' })\r\n  @ApiResponse({ status: 201, description: 'Created successfully' })\r\n  // ใช้ Permission 'system.manage_all' (Admin) หรือสร้าง permission ใหม่ 'workflow.manage' ในอนาคต\r\n  @RequirePermission('system.manage_all')\r\n  async createDefinition(@Body() dto: CreateWorkflowDefinitionDto) {\r\n    return this.workflowService.createDefinition(dto);\r\n  }\r\n\r\n  @Get('definitions')\r\n  @ApiOperation({ summary: 'ดึง Workflow Definition ทั้งหมด' })\r\n  @RequirePermission('system.manage_all')\r\n  async getDefinitions() {\r\n    return this.workflowService.getDefinitions();\r\n  }\r\n\r\n  @Get('definitions/:id')\r\n  @ApiOperation({ summary: 'ดึง Workflow Definition ด้วย ID' })\r\n  @RequirePermission('system.manage_all')\r\n  async getDefinitionById(@Param('id') id: string) {\r\n    return this.workflowService.getDefinitionById(id);\r\n  }\r\n\r\n  @Patch('definitions/:id')\r\n  @ApiOperation({ summary: 'แก้ไข Workflow Definition (Re-compile DSL)' })\r\n  @RequirePermission('system.manage_all')\r\n  async updateDefinition(\r\n    @Param('id') id: string,\r\n    @Body() dto: UpdateWorkflowDefinitionDto\r\n  ) {\r\n    return this.workflowService.update(id, dto);\r\n  }\r\n\r\n  @Post('evaluate')\r\n  @ApiOperation({ summary: 'ทดสอบ Logic Workflow (Dry Run) ไม่บันทึกข้อมูล' })\r\n  @RequirePermission('system.manage_all')\r\n  async evaluate(@Body() dto: EvaluateWorkflowDto) {\r\n    return this.workflowService.evaluate(dto);\r\n  }\r\n\r\n  // =================================================================\r\n  // Runtime Engine (User Actions)\r\n  // =================================================================\r\n\r\n  @Post('instances/:id/transition')\r\n  @ApiOperation({ summary: 'สั่งเปลี่ยนสถานะเอกสาร (User Action)' })\r\n  @ApiParam({ name: 'id', description: 'Workflow Instance ID (UUID)' })\r\n  // Permission จะถูกตรวจสอบ Dynamic ภายใน Service ตาม State ของ Workflow แต่ขั้นต้นต้องมีสิทธิ์ทำงาน Workflow\r\n  @RequirePermission('workflow.action_review')\r\n  async processTransition(\r\n    @Param('id') instanceId: string,\r\n    @Body() dto: WorkflowTransitionDto,\r\n    @Request() req: RequestWithUser\r\n  ) {\r\n    // ดึง User ID จาก Token (req.user มาจาก JwtStrategy)\r\n    const userId = req.user?.user_id;\r\n\r\n    return this.workflowService.processTransition(\r\n      instanceId,\r\n      dto.action,\r\n      userId,\r\n      dto.comment,\r\n      dto.payload\r\n    );\r\n  }\r\n\r\n  @Get('instances/:id/actions')\r\n  @ApiOperation({\r\n    summary: 'ดึงรายการปุ่ม Action ที่สามารถกดได้ ณ สถานะปัจจุบัน',\r\n  })\r\n  @RequirePermission('document.view') // ผู้ที่มีสิทธิ์ดูเอกสาร ควรดู Action ได้\r\n  async getAvailableActions(@Param('id') _instanceId: string) {\r\n    // Note: Logic การดึง Action ตาม Instance ID จะถูก Implement ใน Task ถัดไป\r\n    return { message: 'Pending implementation in Service layer' };\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.module.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 121,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 121,
        "endColumn": 32
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 122,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 122,
        "endColumn": 28
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 145,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 145,
        "endColumn": 33
      },
      {
        "ruleId": "@typescript-eslint/unbound-method",
        "severity": 2,
        "message": "A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.\nConsider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value. \nIf a function does not access `this`, it can be annotated with `this: void`.",
        "line": 188,
        "column": 14,
        "nodeType": "MemberExpression",
        "messageId": "unboundWithoutThisAnnotation",
        "endLine": 188,
        "endColumn": 41
      }
    ],
    "suppressedMessages": [],
    "errorCount": 4,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { WorkflowEngineService } from './workflow-engine.service';\r\nimport { getRepositoryToken } from '@nestjs/typeorm';\r\nimport { DataSource, Repository } from 'typeorm';\r\nimport { WorkflowDefinition } from './entities/workflow-definition.entity';\r\nimport {\r\n  WorkflowInstance,\r\n  WorkflowStatus,\r\n} from './entities/workflow-instance.entity';\r\nimport { WorkflowHistory } from './entities/workflow-history.entity';\r\nimport { WorkflowDslService } from './workflow-dsl.service';\r\nimport { WorkflowEventService } from './workflow-event.service';\r\nimport { NotFoundException } from '@nestjs/common';\r\nimport { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';\r\n\r\ndescribe('WorkflowEngineService', () => {\r\n  let service: WorkflowEngineService;\r\n  let defRepo: Repository<WorkflowDefinition>;\r\n  let instanceRepo: Repository<WorkflowInstance>;\r\n  let dslService: WorkflowDslService;\r\n  let eventService: WorkflowEventService;\r\n\r\n  // Mock Objects\r\n  const mockQueryRunner = {\r\n    connect: jest.fn(),\r\n    startTransaction: jest.fn(),\r\n    commitTransaction: jest.fn(),\r\n    rollbackTransaction: jest.fn(),\r\n    release: jest.fn(),\r\n    manager: {\r\n      findOne: jest.fn(),\r\n      save: jest.fn(),\r\n    },\r\n  };\r\n\r\n  const mockDataSource = {\r\n    createQueryRunner: jest.fn().mockReturnValue(mockQueryRunner),\r\n  };\r\n\r\n  const mockDslService = {\r\n    compile: jest.fn(),\r\n    evaluate: jest.fn(),\r\n  };\r\n\r\n  const mockEventService = {\r\n    dispatchEvents: jest.fn(),\r\n  };\r\n\r\n  const mockCompiledWorkflow = {\r\n    initialState: 'START',\r\n    states: {\r\n      START: { transitions: { SUBMIT: 'PENDING' } },\r\n      PENDING: { transitions: { APPROVE: 'APPROVED', REJECT: 'REJECTED' } },\r\n      APPROVED: { terminal: true },\r\n      REJECTED: { terminal: true },\r\n    },\r\n  };\r\n\r\n  beforeEach(async () => {\r\n    const module: TestingModule = await Test.createTestingModule({\r\n      providers: [\r\n        WorkflowEngineService,\r\n        {\r\n          provide: getRepositoryToken(WorkflowDefinition),\r\n          useValue: {\r\n            findOne: jest.fn(),\r\n            create: jest.fn(),\r\n            save: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(WorkflowInstance),\r\n          useValue: {\r\n            create: jest.fn(),\r\n            save: jest.fn(),\r\n            findOne: jest.fn(),\r\n          },\r\n        },\r\n        {\r\n          provide: getRepositoryToken(WorkflowHistory),\r\n          useValue: {\r\n            create: jest.fn(),\r\n            save: jest.fn(),\r\n          },\r\n        },\r\n        { provide: WorkflowDslService, useValue: mockDslService },\r\n        { provide: WorkflowEventService, useValue: mockEventService },\r\n        { provide: DataSource, useValue: mockDataSource },\r\n      ],\r\n    }).compile();\r\n\r\n    service = module.get<WorkflowEngineService>(WorkflowEngineService);\r\n    defRepo = module.get(getRepositoryToken(WorkflowDefinition));\r\n    instanceRepo = module.get(getRepositoryToken(WorkflowInstance));\r\n    dslService = module.get(WorkflowDslService);\r\n    eventService = module.get(WorkflowEventService);\r\n\r\n    jest.clearAllMocks();\r\n  });\r\n\r\n  it('should be defined', () => {\r\n    expect(service).toBeDefined();\r\n  });\r\n\r\n  describe('createDefinition', () => {\r\n    it('should create a new definition version', async () => {\r\n      const dto = {\r\n        workflow_code: 'WF01',\r\n        dsl: {},\r\n      } as CreateWorkflowDefinitionDto;\r\n      mockDslService.compile.mockReturnValue(mockCompiledWorkflow);\r\n      (defRepo.findOne as jest.Mock).mockResolvedValue({ version: 1 });\r\n      (defRepo.create as jest.Mock).mockReturnValue({ version: 2 });\r\n      (defRepo.save as jest.Mock).mockResolvedValue({\r\n        version: 2,\r\n        workflow_code: 'WF01',\r\n      });\r\n\r\n      const result = await service.createDefinition(dto);\r\n\r\n      expect(dslService.compile).toHaveBeenCalledWith(dto.dsl);\r\n      expect(defRepo.create).toHaveBeenCalledWith(\r\n        expect.objectContaining({ version: 2 })\r\n      );\r\n      expect(result).toEqual(expect.objectContaining({ version: 2 }));\r\n    });\r\n  });\r\n\r\n  describe('createInstance', () => {\r\n    it('should create a new instance with initial state', async () => {\r\n      const mockDef = {\r\n        id: 'def-1',\r\n        compiled: mockCompiledWorkflow,\r\n      };\r\n\r\n      (defRepo.findOne as jest.Mock).mockResolvedValue(mockDef);\r\n      (instanceRepo.create as jest.Mock).mockReturnValue({\r\n        id: 'inst-1',\r\n        currentState: 'START',\r\n      });\r\n      (instanceRepo.save as jest.Mock).mockResolvedValue({ id: 'inst-1' });\r\n\r\n      const result = await service.createInstance('WF01', 'DOC', '101');\r\n\r\n      expect(instanceRepo.create).toHaveBeenCalledWith(\r\n        expect.objectContaining({\r\n          currentState: 'START',\r\n          entityId: '101',\r\n        })\r\n      );\r\n      expect(result).toBeDefined();\r\n    });\r\n\r\n    it('should throw NotFoundException if definition not found', async () => {\r\n      (defRepo.findOne as jest.Mock).mockResolvedValue(null);\r\n      await expect(\r\n        service.createInstance('WF01', 'DOC', '101')\r\n      ).rejects.toThrow(NotFoundException);\r\n    });\r\n  });\r\n\r\n  describe('processTransition', () => {\r\n    it('should process transition successfully and commit transaction', async () => {\r\n      const instanceId = 'inst-1';\r\n      const mockInstance = {\r\n        id: instanceId,\r\n        currentState: 'PENDING',\r\n        status: WorkflowStatus.ACTIVE,\r\n        definition: { compiled: mockCompiledWorkflow },\r\n        context: { some: 'data' },\r\n      };\r\n\r\n      // Mock Pessimistic Lock Find\r\n      mockQueryRunner.manager.findOne.mockResolvedValue(mockInstance);\r\n\r\n      // Mock DSL Evaluation\r\n      mockDslService.evaluate.mockReturnValue({\r\n        nextState: 'APPROVED',\r\n        events: [{ type: 'NOTIFY' }],\r\n      });\r\n\r\n      const result = await service.processTransition(instanceId, 'APPROVE', 1);\r\n\r\n      expect(mockQueryRunner.startTransaction).toHaveBeenCalled();\r\n      expect(mockDslService.evaluate).toHaveBeenCalled();\r\n      expect(mockQueryRunner.manager.save).toHaveBeenCalledTimes(2); // Instance + History\r\n      expect(mockQueryRunner.commitTransaction).toHaveBeenCalled();\r\n      expect(eventService.dispatchEvents).toHaveBeenCalled(); // Should dispatch events\r\n      expect(result.nextState).toBe('APPROVED');\r\n      expect(result.isCompleted).toBe(true);\r\n    });\r\n\r\n    it('should rollback transaction on error', async () => {\r\n      mockQueryRunner.manager.findOne.mockRejectedValue(new Error('DB Error'));\r\n\r\n      await expect(\r\n        service.processTransition('inst-1', 'APPROVE', 1)\r\n      ).rejects.toThrow('DB Error');\r\n\r\n      expect(mockQueryRunner.rollbackTransaction).toHaveBeenCalled();\r\n      expect(mockQueryRunner.release).toHaveBeenCalled();\r\n    });\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-event.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/require-await",
        "severity": 2,
        "message": "Async method 'processSingleEvent' has no 'await' expression.",
        "line": 54,
        "column": 3,
        "nodeType": "FunctionExpression",
        "messageId": "missingAwait",
        "endLine": 54,
        "endColumn": 35,
        "suggestions": [
          { "messageId": "removeAsync", "fix": { "range": [1738, 1744], "text": "" }, "desc": "Remove 'async'." }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: src/modules/workflow-engine/workflow-event.service.ts\r\n\r\nimport { Injectable, Logger } from '@nestjs/common';\r\nimport { RawEvent } from './workflow-dsl.service';\r\n\r\n// Interface สำหรับ External Services ที่จะมารับ Event ต่อ\r\n// (ในอนาคตควรใช้ NestJS Event Emitter เพื่อ Decouple อย่างสมบูรณ์)\r\nexport interface WorkflowEventHandler {\r\n  handleNotification(\r\n    target: string,\r\n    template: string,\r\n    payload: Record<string, unknown>\r\n  ): Promise<void>;\r\n  handleWebhook(url: string, payload: Record<string, unknown>): Promise<void>;\r\n  handleAutoAction(instanceId: string, action: string): Promise<void>;\r\n}\r\n\r\n@Injectable()\r\nexport class WorkflowEventService {\r\n  private readonly logger = new Logger(WorkflowEventService.name);\r\n\r\n  // สามารถ Inject NotificationService หรือ HttpService เข้ามาได้ตรงนี้\r\n  // constructor(private readonly notificationService: NotificationService) {}\r\n\r\n  /**\r\n   * ประมวลผลรายการ Events ที่เกิดจากการเปลี่ยนสถานะ\r\n   */\r\n  dispatchEvents(\r\n    instanceId: string,\r\n    events: RawEvent[],\r\n    context: Record<string, unknown>\r\n  ) {\r\n    if (!events || events.length === 0) return;\r\n\r\n    this.logger.log(\r\n      `Dispatching ${events.length} events for Instance ${instanceId}`\r\n    );\r\n\r\n    // ทำแบบ Async ไม่รอผล (Fire-and-forget) เพื่อไม่ให้กระทบ Response Time ของ User\r\n    void Promise.allSettled(\r\n      events.map((event) => this.processSingleEvent(instanceId, event, context))\r\n    ).then((results) => {\r\n      // Log errors if any\r\n      results.forEach((res, idx) => {\r\n        if (res.status === 'rejected') {\r\n          this.logger.error(\r\n            `Failed to process event [${idx}]: ${String(res.reason)}`\r\n          );\r\n        }\r\n      });\r\n    });\r\n  }\r\n\r\n  private async processSingleEvent(\r\n    instanceId: string,\r\n    event: RawEvent,\r\n    context: Record<string, unknown>\r\n  ) {\r\n    try {\r\n      switch (event.type) {\r\n        case 'notify':\r\n          this.handleNotify(event, context);\r\n          break;\r\n        case 'webhook':\r\n          this.handleWebhook(event, context);\r\n          break;\r\n        case 'auto_action':\r\n          // Logic สำหรับ Auto Transition (เช่น ถ้าผ่านเงื่อนไข ให้ไปต่อเลย)\r\n          this.logger.log(`Auto Action triggered for ${instanceId}`);\r\n          break;\r\n        default:\r\n          this.logger.warn(`Unknown event type: ${event.type}`);\r\n      }\r\n    } catch (error) {\r\n      this.logger.error(\r\n        `Error processing event ${event.type}: ${String(error)}`\r\n      );\r\n      throw error;\r\n    }\r\n  }\r\n\r\n  // --- Handlers ---\r\n\r\n  private handleNotify(event: RawEvent, _context: Record<string, unknown>) {\r\n    // Mockup: ในของจริงจะเรียก NotificationService.send()\r\n    // const recipients = this.resolveRecipients(event.target, context);\r\n    this.logger.log(\r\n      `[EVENT] Notify target: \"${event.target}\" | Template: \"${event.template}\"`\r\n    );\r\n  }\r\n\r\n  private handleWebhook(event: RawEvent, _context: Record<string, unknown>) {\r\n    // Mockup: เรียก HttpService.post()\r\n    this.logger.log(\r\n      `[EVENT] Webhook to: \"${event.target}\" | Payload: ${JSON.stringify(event.payload)}`\r\n    );\r\n  }\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\redlock.d.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\src\\scripts\\migrate-storage-v2.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `DataSourceOptions`.",
        "line": 10,
        "column": 37,
        "nodeType": "TSAsExpression",
        "messageId": "unsafeArgument",
        "endLine": 10,
        "endColumn": 50
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 10,
        "column": 47,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 10,
        "endColumn": 50,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [446, 449], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [446, 449], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 14,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 14,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [502, 553], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 21,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 21,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [766, 832], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 33,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 33,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [1170, 1233], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 36,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 36,
        "endColumn": 19,
        "suggestions": [
          {
            "fix": { "range": [1288, 1384], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "warn" },
            "desc": "Remove the console.warn()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 50,
        "column": 9,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 50,
        "endColumn": 21,
        "suggestions": [
          {
            "fix": { "range": [1652, 1723], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "warn" },
            "desc": "Remove the console.warn()."
          }
        ]
      },
      {
        "ruleId": "no-useless-escape",
        "severity": 2,
        "message": "Unnecessary escape character: \\/.",
        "line": 57,
        "column": 20,
        "nodeType": "Literal",
        "messageId": "unnecessaryEscape",
        "endLine": 57,
        "endColumn": 21,
        "suggestions": [
          {
            "messageId": "removeEscape",
            "fix": { "range": [1913, 1914], "text": "" },
            "desc": "Remove the `\\`. This maintains the current functionality."
          },
          {
            "messageId": "escapeBackslash",
            "fix": { "range": [1913, 1913], "text": "\\" },
            "desc": "Replace the `\\` with `\\\\` to include the actual backslash character."
          }
        ]
      },
      {
        "ruleId": "no-useless-escape",
        "severity": 2,
        "message": "Unnecessary escape character: \\/.",
        "line": 57,
        "column": 78,
        "nodeType": "Literal",
        "messageId": "unnecessaryEscape",
        "endLine": 57,
        "endColumn": 79,
        "suggestions": [
          {
            "messageId": "removeEscape",
            "fix": { "range": [1971, 1972], "text": "" },
            "desc": "Remove the `\\`. This maintains the current functionality."
          },
          {
            "messageId": "escapeBackslash",
            "fix": { "range": [1971, 1971], "text": "\\" },
            "desc": "Replace the `\\` with `\\\\` to include the actual backslash character."
          }
        ]
      },
      {
        "ruleId": "no-useless-escape",
        "severity": 2,
        "message": "Unnecessary escape character: \\/.",
        "line": 57,
        "column": 89,
        "nodeType": "Literal",
        "messageId": "unnecessaryEscape",
        "endLine": 57,
        "endColumn": 90,
        "suggestions": [
          {
            "messageId": "removeEscape",
            "fix": { "range": [1982, 1983], "text": "" },
            "desc": "Remove the `\\`. This maintains the current functionality."
          },
          {
            "messageId": "escapeBackslash",
            "fix": { "range": [1982, 1982], "text": "\\" },
            "desc": "Replace the `\\` with `\\\\` to include the actual backslash character."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 68,
        "column": 9,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 68,
        "endColumn": 21,
        "suggestions": [
          {
            "fix": { "range": [2296, 2353], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "warn" },
            "desc": "Remove the console.warn()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 98,
        "column": 37,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 98,
        "endColumn": 48
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 100,
        "column": 9,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 100,
        "endColumn": 22,
        "suggestions": [
          {
            "fix": { "range": [3366, 3474], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "error" },
            "desc": "Remove the console.error()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 108,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 108,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3521, 3557], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 109,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 109,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3563, 3599], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 110,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 110,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3605, 3645], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 111,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 111,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3651, 3688], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 113,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 113,
        "endColumn": 18,
        "suggestions": [
          {
            "fix": { "range": [3715, 3757], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "error" },
            "desc": "Remove the console.error()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-floating-promises",
        "severity": 2,
        "message": "Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.",
        "line": 119,
        "column": 1,
        "nodeType": "ExpressionStatement",
        "messageId": "floatingVoid",
        "endLine": 119,
        "endColumn": 18,
        "suggestions": [
          {
            "messageId": "floatingFixVoid",
            "fix": { "range": [3817, 3817], "text": "void " },
            "desc": "Add void operator to ignore."
          },
          {
            "messageId": "floatingFixAwait",
            "fix": { "range": [3817, 3817], "text": "await " },
            "desc": "Add await operator."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 19,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { DataSource } from 'typeorm';\r\nimport { databaseConfig } from '../config/database.config';\r\nimport { Attachment } from '../common/file-storage/entities/attachment.entity';\r\nimport * as fs from 'fs-extra';\r\nimport * as path from 'path';\r\n\r\nasync function migrateStorage() {\r\n  // Config override for script execution if needed\r\n  const config = { ...databaseConfig, entities: [Attachment] };\r\n  const dataSource = new DataSource(config as any);\r\n  await dataSource.initialize();\r\n\r\n  try {\r\n    console.log('🚀 Starting Storage Migration v2...');\r\n    const attachmentRepo = dataSource.getRepository(Attachment);\r\n\r\n    // Find all permanent attachments\r\n    const attachments = await attachmentRepo.find({\r\n      where: { isTemporary: false },\r\n    });\r\n    console.log(`Found ${attachments.length} permanent attachments.`);\r\n\r\n    let movedCount = 0;\r\n    let errorCount = 0;\r\n    let skippedCount = 0;\r\n\r\n    // Define base permanent directory\r\n    // Note: Adjust path based on execution context (e.g., from backend root)\r\n    const permanentBaseDir =\r\n      process.env.UPLOAD_PERMANENT_DIR ||\r\n      path.join(process.cwd(), 'uploads', 'permanent');\r\n\r\n    console.log(`Target Permanent Directory: ${permanentBaseDir}`);\r\n\r\n    if (!fs.existsSync(permanentBaseDir)) {\r\n      console.warn(\r\n        `Base directory not found: ${permanentBaseDir}. Creating it...`\r\n      );\r\n      await fs.ensureDir(permanentBaseDir);\r\n    }\r\n\r\n    for (const att of attachments) {\r\n      if (!att.filePath) {\r\n        skippedCount++;\r\n        continue;\r\n      }\r\n\r\n      const currentPath = att.filePath;\r\n      if (!fs.existsSync(currentPath)) {\r\n        console.warn(`File not found on disk: ${currentPath} (ID: ${att.id})`);\r\n        errorCount++;\r\n        continue;\r\n      }\r\n\r\n      // Check if already in new structure (contains /General/YYYY/MM or similar)\r\n      const newStructureRegex =\r\n        /permanent[\\/\\\\](ContractDrawing|ShopDrawing|AsBuiltDrawing|General)[\\/\\\\]\\d{4}[\\/\\\\]\\d{2}/;\r\n      if (newStructureRegex.test(currentPath)) {\r\n        skippedCount++;\r\n        continue;\r\n      }\r\n\r\n      // Determine target date\r\n      const refDate = att.referenceDate\r\n        ? new Date(att.referenceDate)\r\n        : new Date(att.createdAt);\r\n      if (isNaN(refDate.getTime())) {\r\n        console.warn(`Invalid date for ID ${att.id}, skipping.`);\r\n        errorCount++;\r\n        continue;\r\n      }\r\n\r\n      const year = refDate.getFullYear().toString();\r\n      const month = (refDate.getMonth() + 1).toString().padStart(2, '0');\r\n\r\n      // Determine Doc Type (Default 'General' as we don't know easily without joins)\r\n      const docType = 'General';\r\n\r\n      const newDir = path.join(permanentBaseDir, docType, year, month);\r\n      const newPath = path.join(newDir, att.storedFilename);\r\n\r\n      if (path.resolve(currentPath) === path.resolve(newPath)) {\r\n        skippedCount++;\r\n        continue;\r\n      }\r\n\r\n      try {\r\n        await fs.ensureDir(newDir);\r\n        await fs.move(currentPath, newPath, { overwrite: true });\r\n\r\n        // Update DB\r\n        att.filePath = newPath;\r\n        if (!att.referenceDate) {\r\n          att.referenceDate = refDate;\r\n        }\r\n        await attachmentRepo.save(att);\r\n        movedCount++;\r\n        if (movedCount % 100 === 0) console.log(`Moved ${movedCount} files...`);\r\n      } catch (err: unknown) {\r\n        console.error(\r\n          `Failed to move file ID ${att.id}:`,\r\n          (err as Error).message\r\n        );\r\n        errorCount++;\r\n      }\r\n    }\r\n\r\n    console.log(`Migration completed.`);\r\n    console.log(`Moved: ${movedCount}`);\r\n    console.log(`Skipped: ${skippedCount}`);\r\n    console.log(`Errors: ${errorCount}`);\r\n  } catch (error) {\r\n    console.error('Migration failed:', error);\r\n  } finally {\r\n    await dataSource.destroy();\r\n  }\r\n}\r\n\r\nmigrateStorage();\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\test\\app.e2e-spec.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\test\\phase3-workflow.e2e-spec.ts",
    "messages": [
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 57,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 57,
        "endColumn": 19,
        "suggestions": [
          {
            "fix": { "range": [1934, 2037], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "warn" },
            "desc": "Remove the console.warn()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `App`.",
        "line": 70,
        "column": 36,
        "nodeType": "CallExpression",
        "messageId": "unsafeArgument",
        "endLine": 70,
        "endColumn": 55
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 83,
        "column": 5,
        "nodeType": "AssignmentExpression",
        "messageId": "anyAssignment",
        "endLine": 83,
        "endColumn": 40
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .id on an `any` value.",
        "line": 83,
        "column": 38,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 83,
        "endColumn": 40
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 84,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 84,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [2707, 2767], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `App`.",
        "line": 88,
        "column": 36,
        "nodeType": "CallExpression",
        "messageId": "unsafeArgument",
        "endLine": 88,
        "endColumn": 55
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-assignment",
        "severity": 2,
        "message": "Unsafe assignment of an `any` value.",
        "line": 98,
        "column": 5,
        "nodeType": "AssignmentExpression",
        "messageId": "anyAssignment",
        "endLine": 98,
        "endColumn": 50
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .instanceId on an `any` value.",
        "line": 98,
        "column": 40,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 98,
        "endColumn": 50
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 99,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 99,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3290, 3347], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 100,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 100,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [3353, 3411], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-member-access",
        "severity": 2,
        "message": "Unsafe member access .currentState on an `any` value.",
        "line": 100,
        "column": 49,
        "nodeType": "Identifier",
        "messageId": "unsafeMemberExpression",
        "endLine": 100,
        "endColumn": 61
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 106,
        "column": 7,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 106,
        "endColumn": 19,
        "suggestions": [
          {
            "fix": { "range": [3592, 3657], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "warn" },
            "desc": "Remove the console.warn()."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unsafe-argument",
        "severity": 2,
        "message": "Unsafe argument of type `any` assigned to a parameter of type `App`.",
        "line": 110,
        "column": 36,
        "nodeType": "CallExpression",
        "messageId": "unsafeArgument",
        "endLine": 110,
        "endColumn": 55
      },
      {
        "ruleId": "no-console",
        "severity": 2,
        "message": "Unexpected console statement.",
        "line": 122,
        "column": 5,
        "nodeType": "MemberExpression",
        "messageId": "unexpected",
        "endLine": 122,
        "endColumn": 16,
        "suggestions": [
          {
            "fix": { "range": [4216, 4261], "text": "" },
            "messageId": "removeConsole",
            "data": { "propertyName": "log" },
            "desc": "Remove the console.log()."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 14,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import { Test, TestingModule } from '@nestjs/testing';\r\nimport { INestApplication } from '@nestjs/common';\r\nimport request from 'supertest';\r\nimport { AppModule } from '../src/app.module';\r\nimport { JwtService } from '@nestjs/jwt';\r\nimport { DataSource } from 'typeorm';\r\nimport { WorkflowDefinition } from '../src/modules/workflow-engine/entities/workflow-definition.entity';\r\n\r\n/**\r\n * Phase 3 Workflow (E2E) - Unified Workflow Engine\r\n *\r\n * Tests the correspondence workflow using the Unified Workflow Engine\r\n * instead of the deprecated RoutingTemplate system.\r\n */\r\ndescribe('Phase 3 Workflow (E2E)', () => {\r\n  let app: INestApplication;\r\n  let jwtService: JwtService;\r\n  let dataSource: DataSource;\r\n  let correspondenceId: number;\r\n  let workflowInstanceId: string;\r\n\r\n  // Test Users (must exist in seed data)\r\n  const editorUser = { user_id: 3, username: 'editor01', organization_id: 41 };\r\n  const adminUser = { user_id: 2, username: 'admin', organization_id: 1 };\r\n\r\n  let editorToken: string;\r\n  let _adminToken: string;\r\n\r\n  beforeAll(async () => {\r\n    const moduleFixture: TestingModule = await Test.createTestingModule({\r\n      imports: [AppModule],\r\n    }).compile();\r\n\r\n    app = moduleFixture.createNestApplication();\r\n    await app.init();\r\n\r\n    jwtService = moduleFixture.get<JwtService>(JwtService);\r\n    dataSource = moduleFixture.get<DataSource>(DataSource);\r\n\r\n    // Generate Tokens\r\n    editorToken = jwtService.sign({\r\n      username: editorUser.username,\r\n      sub: editorUser.user_id,\r\n    });\r\n    adminToken = jwtService.sign({\r\n      username: adminUser.username,\r\n      sub: adminUser.user_id,\r\n    });\r\n\r\n    // Ensure workflow definition exists (should be seeded)\r\n    const defRepo = dataSource.getRepository(WorkflowDefinition);\r\n    const existing = await defRepo.findOne({\r\n      where: { workflow_code: 'CORRESPONDENCE_FLOW_V1', is_active: true },\r\n    });\r\n\r\n    if (!existing) {\r\n      console.warn(\r\n        'WorkflowDefinition CORRESPONDENCE_FLOW_V1 not found. Tests may fail.'\r\n      );\r\n    }\r\n  });\r\n\r\n  afterAll(async () => {\r\n    if (app) {\r\n      await app.close();\r\n    }\r\n  });\r\n\r\n  it('/correspondences (POST) - Create Document', async () => {\r\n    const response = await request(app.getHttpServer())\r\n      .post('/correspondences')\r\n      .set('Authorization', `Bearer ${editorToken}`)\r\n      .send({\r\n        projectId: 1,\r\n        typeId: 1,\r\n        title: 'E2E Workflow Test Document',\r\n        details: { question: 'Testing Unified Workflow' },\r\n      })\r\n      .expect(201);\r\n\r\n    expect(response.body).toHaveProperty('id');\r\n    expect(response.body).toHaveProperty('correspondenceNumber');\r\n    correspondenceId = response.body.id;\r\n    console.log('Created Correspondence ID:', correspondenceId);\r\n  });\r\n\r\n  it('/correspondences/:id/submit (POST) - Submit to Workflow', async () => {\r\n    const response = await request(app.getHttpServer())\r\n      .post(`/correspondences/${correspondenceId}/submit`)\r\n      .set('Authorization', `Bearer ${editorToken}`)\r\n      .send({\r\n        note: 'Submitting for E2E test',\r\n      })\r\n      .expect(201);\r\n\r\n    expect(response.body).toHaveProperty('instanceId');\r\n    expect(response.body).toHaveProperty('currentState');\r\n    workflowInstanceId = response.body.instanceId;\r\n    console.log('Workflow Instance ID:', workflowInstanceId);\r\n    console.log('Current State:', response.body.currentState);\r\n  });\r\n\r\n  it('/correspondences/:id/workflow/action (POST) - Process Action', async () => {\r\n    // Skip if submit failed to get instanceId\r\n    if (!workflowInstanceId) {\r\n      console.warn('Skipping action test - no instanceId from submit');\r\n      return;\r\n    }\r\n\r\n    const response = await request(app.getHttpServer())\r\n      .post(`/correspondences/${correspondenceId}/workflow/action`)\r\n      .set('Authorization', `Bearer ${editorToken}`) // Use editor - has workflow.action_review permission\r\n      .send({\r\n        instanceId: workflowInstanceId,\r\n        action: 'APPROVE',\r\n        comment: 'E2E Approved via Unified Workflow Engine',\r\n      })\r\n      .expect(201);\r\n\r\n    expect(response.body).toHaveProperty('success', true);\r\n    expect(response.body).toHaveProperty('nextState');\r\n    console.log('Action Result:', response.body);\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\backend\\test\\simple.e2e-spec.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'request' is defined but never used. Allowed unused vars must match /^_/u.",
        "line": 1,
        "column": 8,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 1,
        "endColumn": 15,
        "suggestions": [
          {
            "messageId": "removeUnusedImportDeclaration",
            "data": { "varName": "request" },
            "fix": { "range": [0, 34], "text": "" },
            "desc": "Remove unused import declaration."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import request from 'supertest';\r\nimport { AppModule } from '../src/app.module';\r\nimport { _RoutingTemplate } from '../src/modules/correspondence/entities/routing-template.entity';\r\n\r\nimport { Test, TestingModule } from '@nestjs/testing';\r\n\r\ndescribe('Simple Test', () => {\r\n  it('should pass', async () => {\r\n    const moduleFixture: TestingModule = await Test.createTestingModule({\r\n      imports: [AppModule],\r\n    }).compile();\r\n    expect(moduleFixture).toBeDefined();\r\n  });\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\eslint.config.mjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\fix-console.cjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\fix-lint-automated.cjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\fix-parseInt.cjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\access-control\\organizations\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\access-control\\roles\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\access-control\\users\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\audit-logs\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\contracts\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\contract\\categories\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\contract\\sub-categories\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\contract\\volumes\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\shop\\main-categories\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\drawings\\shop\\sub-categories\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\numbering\\[id]\\edit\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\numbering\\new\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 28,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 28,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\r\n\r\nimport { TemplateEditor } from \"@/components/numbering/template-editor\";\r\nimport { numberingApi, NumberingTemplate } from \"@/lib/api/numbering\";\r\nimport { useRouter } from \"next/navigation\";\r\nimport { useCorrespondenceTypes, useContracts, useDisciplines } from \"@/hooks/use-master-data\";\r\nimport { useProjects } from \"@/hooks/use-projects\";\r\nimport { toast } from 'sonner';\r\n\r\nexport default function NewTemplatePage() {\r\n  const router = useRouter();\r\n\r\n  // Master Data\r\n  const { data: correspondenceTypes = [] } = useCorrespondenceTypes();\r\n  const { data: projects = [] } = useProjects();\r\n  const projectId = 1; // Default or sync with selection\r\n  const { data: contracts = [] } = useContracts(projectId);\r\n  const contractId = contracts[0]?.id;\r\n  const { data: disciplines = [] } = useDisciplines(contractId);\r\n\r\n  const selectedProjectName =\r\n    projects.find((p: { id: number; projectName: string }) => p.id === projectId)?.projectName || 'LCBP3';\r\n\r\n  const handleSave = async (data: Partial<NumberingTemplate>) => {\r\n    try {\r\n      await numberingApi.saveTemplate(data);\r\n      router.push(\"/admin/numbering\");\r\n    } catch (_error) {\r\n      toast.error('Failed to create template');\r\n    }\r\n  };\r\n\r\n  const handleCancel = () => {\r\n    router.push('/admin/doc-control/numbering');\r\n  };\r\n\r\n  return (\r\n    <div className=\"p-6 space-y-6\">\r\n      <h1 className=\"text-3xl font-bold\">New Numbering Template</h1>\r\n      <TemplateEditor\r\n        projectId={projectId}\r\n        projectName={selectedProjectName}\r\n        correspondenceTypes={correspondenceTypes}\r\n        disciplines={disciplines}\r\n        onSave={handleSave}\r\n        onCancel={handleCancel}\r\n      />\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\numbering\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\projects\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\correspondence-types\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\disciplines\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 17,
        "column": 28,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 17,
        "endColumn": 31,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [742, 745], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [742, 745], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 46,
        "column": 45,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 46,
        "endColumn": 48,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1497, 1500], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1497, 1500], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'id' is defined but never used. Allowed unused args must match /^_/u.",
        "line": 70,
        "column": 20,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 70,
        "endColumn": 22
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 84,
        "column": 36,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 84,
        "endColumn": 39,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [3233, 3236], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [3233, 3236], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 4,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "'use client';\n\nimport { GenericCrudTable } from '@/components/admin/reference/generic-crud-table';\nimport { masterDataService } from '@/lib/services/master-data.service';\nimport { useContracts } from '@/hooks/use-master-data';\nimport { ColumnDef } from '@tanstack/react-table';\nimport { useState } from 'react';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';\n\nexport default function DisciplinesPage() {\n  const [selectedContractId, setSelectedContractId] = useState<string | null>(null);\n\n  const { data: contractsData = [] } = useContracts();\n  // Ensure we consistently use an array\n  const contracts = Array.isArray(contractsData) ? contractsData : [];\n\n  const columns: ColumnDef<any>[] = [\n    {\n      accessorKey: 'disciplineCode',\n      header: 'Code',\n      cell: ({ row }) => <span className=\"font-mono font-bold\">{row.getValue('disciplineCode')}</span>,\n    },\n    {\n      accessorKey: 'codeNameTh',\n      header: 'Name (TH)',\n    },\n    {\n      accessorKey: 'codeNameEn',\n      header: 'Name (EN)',\n    },\n    {\n      accessorKey: 'isActive',\n      header: 'Status',\n      cell: ({ row }) => (\n        <span\n          className={`px-2 py-1 rounded-full text-xs ${\n            row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'\n          }`}\n        >\n          {row.getValue('isActive') ? 'Active' : 'Inactive'}\n        </span>\n      ),\n    },\n  ];\n\n  const contractOptions = contracts.map((c: any) => ({\n    label: `${c.contractName} (${c.contractCode})`,\n    value: String(c.id),\n  }));\n\n  return (\n    <div className=\"p-6\">\n      <GenericCrudTable\n        entityName=\"Discipline\"\n        title=\"Disciplines Management\"\n        description=\"Manage system disciplines (e.g., ARCH, STR, MEC)\"\n        queryKey={['disciplines', selectedContractId ?? 'all']}\n        fetchFn={async () => {\n          const items = await masterDataService.getDisciplines(selectedContractId ? selectedContractId : undefined);\n          // ADR-019: Map contractId INT → contract UUID for edit mode select matching\n          return (items as Record<string, unknown>[]).map((item) => {\n            const rec = item as { contract?: { id?: number; uuid?: string }; contractId?: number };\n            return {\n              ...item,\n              contractId: rec.contract?.id || rec.contract?.uuid || String(rec.contractId),\n            };\n          });\n        }}\n        createFn={(data) => masterDataService.createDiscipline(data as unknown as Parameters<typeof masterDataService.createDiscipline>[0])}\n        updateFn={(id, _data) => Promise.reject('Not implemented yet')}\n        deleteFn={(id) => masterDataService.deleteDiscipline(id)}\n        columns={columns}\n        filters={\n          <div className=\"w-[300px]\">\n            <Select\n              value={selectedContractId || 'all'}\n              onValueChange={(val) => setSelectedContractId(val === 'all' ? null : val)}\n            >\n              <SelectTrigger>\n                <SelectValue placeholder=\"Filter by Contract\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"all\">All Contracts</SelectItem>\n                {contracts.map((c: any) => (\n                  <SelectItem key={c.id} value={String(c.id)}>\n                    {c.contractName} ({c.contractCode})\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n        }\n        fields={[\n          {\n            name: 'contractId',\n            label: 'Contract',\n            type: 'select',\n            required: true,\n            options: contractOptions,\n          },\n          {\n            name: 'disciplineCode',\n            label: 'Code',\n            type: 'text',\n            required: true,\n          },\n          {\n            name: 'codeNameTh',\n            label: 'Name (TH)',\n            type: 'text',\n            required: true,\n          },\n          { name: 'codeNameEn', label: 'Name (EN)', type: 'text' },\n          { name: 'isActive', label: 'Active', type: 'checkbox' },\n        ]}\n      />\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\drawing-categories\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\rfa-types\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\reference\\tags\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\workflows\\[id]\\edit\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 72,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 72,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "'use client';\r\n\r\nimport { useState, useEffect } from 'react';\r\nimport { useRouter, useParams } from 'next/navigation';\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';\r\nimport { DSLEditor } from '@/components/workflows/dsl-editor';\r\nimport { VisualWorkflowBuilder } from '@/components/workflows/visual-builder';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { Textarea } from '@/components/ui/textarea';\r\nimport { Card } from '@/components/ui/card';\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';\r\nimport { useWorkflowDefinition, useCreateWorkflowDefinition, useUpdateWorkflowDefinition } from '@/hooks/use-workflows';\r\nimport { Workflow } from '@/types/workflow';\r\nimport { CreateWorkflowDefinitionDto } from '@/types/dto/workflow-engine/workflow-engine.dto';\r\nimport { toast } from 'sonner';\r\nimport { Save, ArrowLeft, Loader2 } from 'lucide-react';\r\nimport Link from 'next/link';\r\n\r\nexport default function WorkflowEditPage() {\r\n  const params = useParams();\r\n  const router = useRouter();\r\n  const id = params?.id === 'new' ? null : params?.id as string;\r\n\r\n  const [workflowData, setWorkflowData] = useState<Partial<Workflow>>({\r\n    workflowName: '',\r\n    description: '',\r\n    workflowType: 'CORRESPONDENCE',\r\n    dslDefinition: 'name: New Workflow\\nversion: 1.0\\nsteps: []',\r\n    isActive: true,\r\n  });\r\n\r\n  const { data: fetchedWorkflow, isLoading: loadingWorkflow } = useWorkflowDefinition(id as string);\r\n  const createMutation = useCreateWorkflowDefinition();\r\n  const updateMutation = useUpdateWorkflowDefinition();\r\n\r\n  useEffect(() => {\r\n    if (fetchedWorkflow) {\r\n      setWorkflowData(fetchedWorkflow);\r\n    }\r\n  }, [fetchedWorkflow]);\r\n\r\n  const loading = (!!id && loadingWorkflow) || createMutation.isPending || updateMutation.isPending;\r\n  const saving = createMutation.isPending || updateMutation.isPending;\r\n\r\n  const handleSave = async () => {\r\n    if (!workflowData.workflowName) {\r\n      toast.error('Workflow name is required');\r\n      return;\r\n    }\r\n\r\n    try {\r\n      const dto: CreateWorkflowDefinitionDto = {\r\n        workflow_code: workflowData.workflowType || 'CORRESPONDENCE',\r\n        dsl: {\r\n          workflowName: workflowData.workflowName,\r\n          description: workflowData.description,\r\n          dslDefinition: workflowData.dslDefinition,\r\n        },\r\n        is_active: workflowData.isActive,\r\n      };\r\n\r\n      if (id) {\r\n        await updateMutation.mutateAsync({ id, data: dto });\r\n        toast.success('Workflow updated successfully');\r\n      } else {\r\n        await createMutation.mutateAsync(dto);\r\n        toast.success('Workflow created successfully');\r\n        router.push('/admin/doc-control/workflows');\r\n      }\r\n    } catch (_error) {\r\n      toast.error('Failed to save workflow');\r\n    }\r\n  };\r\n\r\n  if (loading) {\r\n    return (\r\n      <div className=\"flex items-center justify-center h-screen\">\r\n        <Loader2 className=\"h-8 w-8 animate-spin\" />\r\n      </div>\r\n    );\r\n  }\r\n\r\n  return (\r\n    <div className=\"p-6 space-y-6 max-w-7xl mx-auto\">\r\n      <div className=\"flex justify-between items-center\">\r\n        <div className=\"flex items-center gap-4\">\r\n          <Link href=\"/admin/doc-control/workflows\">\r\n            <Button variant=\"ghost\" size=\"icon\">\r\n              <ArrowLeft className=\"h-5 w-5\" />\r\n            </Button>\r\n          </Link>\r\n          <div>\r\n            <h1 className=\"text-3xl font-bold\">{id ? 'Edit Workflow' : 'New Workflow'}</h1>\r\n            <p className=\"text-muted-foreground\">\r\n              {id ? `Version ${workflowData.version}` : 'Create a new workflow definition'}\r\n            </p>\r\n          </div>\r\n        </div>\r\n        <div className=\"flex gap-2\">\r\n          <Link href=\"/admin/doc-control/workflows\">\r\n            <Button variant=\"outline\">Cancel</Button>\r\n          </Link>\r\n          <Button onClick={handleSave} disabled={saving}>\r\n            {saving && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n            <Save className=\"mr-2 h-4 w-4\" />\r\n            {id ? 'Save Changes' : 'Create Workflow'}\r\n          </Button>\r\n        </div>\r\n      </div>\r\n\r\n      <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\r\n        <div className=\"lg:col-span-1 space-y-6\">\r\n          <Card className=\"p-6\">\r\n            <div className=\"grid gap-4\">\r\n              <div>\r\n                <Label htmlFor=\"name\">Workflow Name *</Label>\r\n                <Input\r\n                  id=\"name\"\r\n                  value={workflowData.workflowName}\r\n                  onChange={(e) =>\r\n                    setWorkflowData({\r\n                      ...workflowData,\r\n                      workflowName: e.target.value,\r\n                    })\r\n                  }\r\n                  placeholder=\"e.g. Standard RFA Workflow\"\r\n                />\r\n              </div>\r\n\r\n              <div>\r\n                <Label htmlFor=\"desc\">Description</Label>\r\n                <Textarea\r\n                  id=\"desc\"\r\n                  value={workflowData.description}\r\n                  onChange={(e) =>\r\n                    setWorkflowData({\r\n                      ...workflowData,\r\n                      description: e.target.value,\r\n                    })\r\n                  }\r\n                  placeholder=\"Describe the purpose of this workflow\"\r\n                />\r\n              </div>\r\n\r\n              <div>\r\n                <Label htmlFor=\"type\">Workflow Type</Label>\r\n                <Select\r\n                  value={workflowData.workflowType}\r\n                  onValueChange={(value: Workflow['workflowType']) =>\r\n                    setWorkflowData({ ...workflowData, workflowType: value })\r\n                  }\r\n                >\r\n                  <SelectTrigger>\r\n                    <SelectValue placeholder=\"Select type\" />\r\n                  </SelectTrigger>\r\n                  <SelectContent>\r\n                    <SelectItem value=\"CORRESPONDENCE\">Correspondence</SelectItem>\r\n                    <SelectItem value=\"RFA\">RFA</SelectItem>\r\n                    <SelectItem value=\"DRAWING\">Drawing</SelectItem>\r\n                  </SelectContent>\r\n                </Select>\r\n              </div>\r\n            </div>\r\n          </Card>\r\n        </div>\r\n\r\n        <div className=\"lg:col-span-2\">\r\n          <Tabs defaultValue=\"dsl\" className=\"w-full\">\r\n            <TabsList className=\"w-full justify-start\">\r\n              <TabsTrigger value=\"dsl\">DSL Editor</TabsTrigger>\r\n              <TabsTrigger value=\"visual\">Visual Builder</TabsTrigger>\r\n            </TabsList>\r\n\r\n            <TabsContent value=\"dsl\" className=\"mt-4\">\r\n              <DSLEditor\r\n                initialValue={workflowData.dslDefinition}\r\n                onChange={(value) => setWorkflowData({ ...workflowData, dslDefinition: value })}\r\n              />\r\n            </TabsContent>\r\n\r\n            <TabsContent value=\"visual\" className=\"mt-4 h-[600px]\">\r\n              <VisualWorkflowBuilder\r\n                dslString={workflowData.dslDefinition}\r\n                onDslChange={(newDsl) => setWorkflowData({ ...workflowData, dslDefinition: newDsl })}\r\n                onSave={() => toast.info('Visual state saving not implemented in this demo')}\r\n              />\r\n            </TabsContent>\r\n          </Tabs>\r\n        </div>\r\n      </div>\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\workflows\\new\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 34,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 34,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "'use client';\r\n\r\nimport { useState } from 'react';\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';\r\nimport { DSLEditor } from '@/components/workflows/dsl-editor';\r\nimport { VisualWorkflowBuilder } from '@/components/workflows/visual-builder';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { Textarea } from '@/components/ui/textarea';\r\nimport { Card } from '@/components/ui/card';\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';\r\nimport { workflowApi } from '@/lib/api/workflows';\r\nimport { WorkflowType } from '@/types/workflow';\r\nimport { useRouter } from 'next/navigation';\r\nimport { Loader2 } from 'lucide-react';\r\nimport { toast } from 'sonner';\r\n\r\nexport default function NewWorkflowPage() {\r\n  const router = useRouter();\r\n  const [saving, setSaving] = useState(false);\r\n  const [workflowData, setWorkflowData] = useState({\r\n    workflowName: '',\r\n    description: '',\r\n    workflowType: 'CORRESPONDENCE' as WorkflowType,\r\n    dslDefinition: 'name: New Workflow\\nversion: 1.0\\nsteps: []',\r\n  });\r\n\r\n  const handleSave = async () => {\r\n    setSaving(true);\r\n    try {\r\n      await workflowApi.createWorkflow(workflowData);\r\n      router.push('/admin/doc-control/workflows');\r\n    } catch (_error) {\r\n      toast.error('Failed to create workflow');\r\n    } finally {\r\n      setSaving(false);\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div className=\"p-6 space-y-6\">\r\n      <div className=\"flex justify-between items-center\">\r\n        <h1 className=\"text-3xl font-bold\">New Workflow</h1>\r\n        <div className=\"flex gap-2\">\r\n          <Button variant=\"outline\" onClick={() => router.back()}>\r\n            Cancel\r\n          </Button>\r\n          <Button onClick={handleSave} disabled={saving}>\r\n            {saving && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n            Create Workflow\r\n          </Button>\r\n        </div>\r\n      </div>\r\n\r\n      <Card className=\"p-6\">\r\n        <div className=\"grid gap-4\">\r\n          <div>\r\n            <Label htmlFor=\"workflow_name\">Workflow Name *</Label>\r\n            <Input\r\n              id=\"workflow_name\"\r\n              value={workflowData.workflowName}\r\n              onChange={(e) =>\r\n                setWorkflowData({\r\n                  ...workflowData,\r\n                  workflowName: e.target.value,\r\n                })\r\n              }\r\n              placeholder=\"e.g., Special RFA Approval\"\r\n            />\r\n          </div>\r\n\r\n          <div>\r\n            <Label htmlFor=\"description\">Description</Label>\r\n            <Textarea\r\n              id=\"description\"\r\n              value={workflowData.description}\r\n              onChange={(e) =>\r\n                setWorkflowData({\r\n                  ...workflowData,\r\n                  description: e.target.value,\r\n                })\r\n              }\r\n              placeholder=\"Describe the purpose of this workflow\"\r\n            />\r\n          </div>\r\n\r\n          <div>\r\n            <Label htmlFor=\"workflow_type\">Workflow Type</Label>\r\n            <Select\r\n              value={workflowData.workflowType}\r\n              onValueChange={(value) => setWorkflowData({ ...workflowData, workflowType: value as WorkflowType })}\r\n            >\r\n              <SelectTrigger id=\"workflow_type\">\r\n                <SelectValue />\r\n              </SelectTrigger>\r\n              <SelectContent>\r\n                <SelectItem value=\"CORRESPONDENCE\">Correspondence</SelectItem>\r\n                <SelectItem value=\"RFA\">RFA</SelectItem>\r\n                <SelectItem value=\"DRAWING\">Drawing</SelectItem>\r\n              </SelectContent>\r\n            </Select>\r\n          </div>\r\n        </div>\r\n      </Card>\r\n\r\n      <Tabs defaultValue=\"dsl\">\r\n        <TabsList>\r\n          <TabsTrigger value=\"dsl\">DSL Editor</TabsTrigger>\r\n          <TabsTrigger value=\"visual\">Visual Builder</TabsTrigger>\r\n        </TabsList>\r\n\r\n        <TabsContent value=\"dsl\" className=\"mt-4\">\r\n          <DSLEditor\r\n            initialValue={workflowData.dslDefinition}\r\n            onChange={(value) => setWorkflowData({ ...workflowData, dslDefinition: value })}\r\n          />\r\n        </TabsContent>\r\n\r\n        <TabsContent value=\"visual\" className=\"mt-4\">\r\n          <VisualWorkflowBuilder />\r\n        </TabsContent>\r\n      </Tabs>\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\doc-control\\workflows\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\migration\\errors\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\migration\\page.tsx",
    "messages": [
      {
        "ruleId": "react-hooks/exhaustive-deps",
        "severity": 1,
        "message": "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array.",
        "line": 35,
        "column": 6,
        "nodeType": "ArrayExpression",
        "endLine": 35,
        "endColumn": 20,
        "suggestions": [
          {
            "desc": "Update the dependencies array to be: [fetchData, statusFilter]",
            "fix": { "range": [1363, 1377], "text": "[fetchData, statusFilter]" }
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 104,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 104,
        "endColumn": 19
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 1,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { migrationService } from \"@/lib/services/migration.service\";\nimport { MigrationReviewQueueItem, MigrationReviewStatus } from \"@/types/migration\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { toast } from \"sonner\";\nimport { Button } from \"@/components/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { format } from \"date-fns\";\nimport { EyeIcon, FileXIcon, CheckSquareIcon } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { getApiErrorMessage } from \"@/types/api-error\";\n\nexport default function MigrationReviewQueuePage() {\n  const [items, setItems] = useState<MigrationReviewQueueItem[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [submitting, setSubmitting] = useState(false);\n  const [statusFilter, setStatusFilter] = useState<string>(\"PENDING\");\n  const [selectedIds, setSelectedIds] = useState<number[]>([]);\n  const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n  useEffect(() => {\n    fetchData();\n  }, [statusFilter]);\n\n  const fetchData = async () => {\n    try {\n      setLoading(true);\n      setErrorMessage(null);\n      const res = await migrationService.getReviewQueue({\n        status: statusFilter === \"ALL\" ? undefined : (statusFilter as MigrationReviewStatus),\n        limit: 50,\n      });\n      setItems(Array.isArray(res.items) ? res.items : []);\n      setSelectedIds([]); // reset selection on fetch\n    } catch (error: unknown) {\n      setItems([]);\n      setErrorMessage(getApiErrorMessage(error, \"Failed to load queue\"));\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handleToggleSelectAll = () => {\n    if (selectedIds.length === items.length) {\n      setSelectedIds([]);\n    } else {\n      setSelectedIds(items.map((i) => i.id));\n    }\n  };\n\n  const handleToggleSelect = (id: number) => {\n    setSelectedIds((prev) =>\n      prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]\n    );\n  };\n\n  const handleBatchApprove = async () => {\n    if (selectedIds.length === 0) return;\n    try {\n      setSubmitting(true);\n\n      const batchItems = items\n        .filter((i) => selectedIds.includes(i.id))\n        .map((item) => ({\n          queueId: item.id,\n          dto: {\n            document_number: item.documentNumber,\n            subject: item.title || item.originalTitle || 'Untitled',\n            category: item.aiSuggestedCategory || 'Correspondence',\n            project_id: item.projectId || 1,\n            migrated_by: 'SYSTEM_IMPORT',\n            temp_attachment_id: item.tempAttachmentId,\n            ai_confidence: item.aiConfidence,\n            ai_issues: item.aiIssues,\n            issued_date: item.issuedDate,\n            received_date: item.receivedDate,\n            sender_id: item.senderOrganizationId,\n            receiver_id: item.receiverOrganizationId,\n            details: {\n              tags: item.extractedTags\n            }\n          }\n        }));\n\n      const batchId = `BATCH_UI_${Date.now()}`;\n      await migrationService.commitBatch(\n        { items: batchItems, batchId },\n        batchId\n      );\n\n      fetchData();\n    } catch (error) {\n      toast.error(\"Batch commit failed.\");\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  return (\n    <div className=\"space-y-6\">\n      <div className=\"flex justify-between flex-wrap gap-4 items-center\">\n        <div>\n          <h1 className=\"text-3xl font-bold tracking-tight\">Migration Review Queue</h1>\n          <p className=\"text-muted-foreground mt-1\">\n            Review and correct documents that AI flagged as low confidence.\n          </p>\n        </div>\n        <div className=\"flex items-center gap-4\">\n          {selectedIds.length > 0 && (\n            <Button\n              variant=\"default\"\n              onClick={handleBatchApprove}\n              disabled={submitting}\n            >\n              <CheckSquareIcon className=\"mr-2 h-4 w-4\" />\n              {submitting ? \"Processing...\" : `Batch Approve (${selectedIds.length})`}\n            </Button>\n          )}\n          <Link href=\"/admin/migration/errors\">\n            <Button variant=\"outline\">\n              <FileXIcon className=\"mr-2 h-4 w-4\" /> View Error Logs\n            </Button>\n          </Link>\n          <Select value={statusFilter} onValueChange={setStatusFilter}>\n            <SelectTrigger className=\"w-[180px]\">\n              <SelectValue placeholder=\"Status\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"ALL\">All Status</SelectItem>\n              <SelectItem value=\"PENDING\">Pending</SelectItem>\n              <SelectItem value=\"APPROVED\">Approved</SelectItem>\n              <SelectItem value=\"REJECTED\">Rejected</SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n\n      <Card>\n        <CardHeader>\n          <CardTitle>Queue Items - {statusFilter}</CardTitle>\n        </CardHeader>\n        <CardContent>\n          {errorMessage && (\n            <div className=\"mb-4 rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive\">\n              {errorMessage}\n            </div>\n          )}\n          {loading ? (\n            <div className=\"py-10 text-center\">Loading queue...</div>\n          ) : items.length === 0 ? (\n            <div className=\"py-10 text-center text-muted-foreground\">No items in the queue.</div>\n          ) : (\n            <div className=\"rounded-md border\">\n              <Table>\n                <TableHeader>\n                  <TableRow>\n                    <TableHead className=\"w-[50px]\">\n                      <Checkbox\n                        checked={items.length > 0 && selectedIds.length === items.length}\n                        onCheckedChange={handleToggleSelectAll}\n                        aria-label=\"Select all\"\n                      />\n                    </TableHead>\n                    <TableHead>Document No.</TableHead>\n                    <TableHead>Suggested Category</TableHead>\n                    <TableHead>Confidence</TableHead>\n                    <TableHead>Status</TableHead>\n                    <TableHead>Created At</TableHead>\n                    <TableHead className=\"text-right\">Action</TableHead>\n                  </TableRow>\n                </TableHeader>\n                <TableBody>\n                  {items.map((item) => (\n                    <TableRow key={item.id}>\n                      <TableCell>\n                        <Checkbox\n                          checked={selectedIds.includes(item.id)}\n                          onCheckedChange={() => handleToggleSelect(item.id)}\n                          aria-label={`Select item ${item.id}`}\n                        />\n                      </TableCell>\n                      <TableCell className=\"font-medium\">{item.documentNumber}</TableCell>\n                      <TableCell>{item.aiSuggestedCategory || \"Unknown\"}</TableCell>\n                      <TableCell>\n                        <Badge\n                          variant={\n                            !item.aiConfidence\n                              ? \"destructive\"\n                              : item.aiConfidence > 0.8\n                                ? \"default\"\n                                : item.aiConfidence > 0.5\n                                  ? \"secondary\"\n                                  : \"destructive\"\n                          }\n                        >\n                          {item.aiConfidence ? (item.aiConfidence * 100).toFixed(1) + \"%\" : \"N/A\"}\n                        </Badge>\n                      </TableCell>\n                      <TableCell>\n                        <Badge variant={item.status === 'PENDING' ? 'outline' : item.status === 'APPROVED' ? 'default' : 'destructive'}>\n                          {item.status}\n                        </Badge>\n                      </TableCell>\n                      <TableCell>{format(new Date(item.createdAt), \"dd MMM yyyy, HH:mm\")}</TableCell>\n                      <TableCell className=\"text-right\">\n                        <Link href={`/admin/migration/review/${item.id}`}>\n                          <Button size=\"sm\" variant=\"ghost\">\n                            <EyeIcon className=\"h-4 w-4 mr-2\" /> Review\n                          </Button>\n                        </Link>\n                      </TableCell>\n                    </TableRow>\n                  ))}\n                </TableBody>\n              </Table>\n            </div>\n          )}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\migration\\review\\[id]\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'FormDescription' is defined but never used. Allowed unused vars must match /^_/u.",
        "line": 14,
        "column": 3,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 14,
        "endColumn": 18,
        "suggestions": [
          {
            "messageId": "removeUnusedVar",
            "data": { "varName": "FormDescription" },
            "fix": { "range": [450, 469], "text": "" },
            "desc": "Remove unused variable \"FormDescription\"."
          }
        ]
      },
      {
        "ruleId": "react-hooks/exhaustive-deps",
        "severity": 1,
        "message": "React Hook useEffect has a missing dependency: 'fetchItem'. Either include it or remove the dependency array.",
        "line": 67,
        "column": 6,
        "nodeType": "ArrayExpression",
        "endLine": 67,
        "endColumn": 10,
        "suggestions": [
          {
            "desc": "Update the dependencies array to be: [fetchItem, id]",
            "fix": { "range": [2120, 2124], "text": "[fetchItem, id]" }
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 89,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 89,
        "endColumn": 19
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 144,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 144,
        "endColumn": 19
      }
    ],
    "suppressedMessages": [],
    "errorCount": 3,
    "fatalErrorCount": 0,
    "warningCount": 1,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useParams, useRouter } from \"next/navigation\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport * as z from \"zod\";\nimport { migrationService } from \"@/lib/services/migration.service\";\nimport { MigrationReviewQueueItem } from \"@/types/migration\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { ArrowLeftIcon, CheckCircleIcon, XCircleIcon } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { toast } from \"sonner\";\nimport { Card, CardContent } from \"@/components/ui/card\";\n\nconst reviewFormSchema = z.object({\n  document_number: z.string().min(1, \"Document number is required\"),\n  subject: z.string().min(1, \"Subject is required\"),\n  category: z.string().min(1, \"Category is required\"),\n  document_date: z.string().optional(),\n  issued_date: z.string().optional(),\n  received_date: z.string().optional(),\n  sender_id: z.string().optional(),\n  discipline_id: z.string().optional(),\n});\n\ntype ReviewFormValues = z.infer<typeof reviewFormSchema>;\n\nexport default function MigrationReviewPage() {\n  const params = useParams();\n  const router = useRouter();\n  const id = Number(params.id);\n\n  const [item, setItem] = useState<MigrationReviewQueueItem | null>(null);\n  const [loading, setLoading] = useState(true);\n  const [submitting, setSubmitting] = useState(false);\n\n  const form = useForm<ReviewFormValues>({\n    resolver: zodResolver(reviewFormSchema),\n    defaultValues: {\n      document_number: \"\",\n      subject: \"\",\n      category: \"\",\n      document_date: \"\",\n      issued_date: \"\",\n      received_date: \"\",\n      sender_id: \"\",\n      discipline_id: \"\",\n    },\n  });\n\n  useEffect(() => {\n    if (!id) return;\n    fetchItem(id);\n  }, [id]);\n\n  const fetchItem = async (itemId: number) => {\n    try {\n      setLoading(true);\n      const res = await migrationService.getQueueItem(itemId);\n      setItem(res);\n\n      if (res) {\n        // Pre-fill form from database item and aiIssues payload\n        const issues = res.aiIssues || {};\n        form.reset({\n          document_number: res.documentNumber || \"\",\n          subject: res.title || res.originalTitle || \"\",\n          category: res.aiSuggestedCategory || \"\",\n          document_date: issues.document_date || \"\",\n          issued_date: issues.issued_date || \"\",\n          received_date: issues.received_date || \"\",\n          sender_id: issues.sender_id ? String(issues.sender_id) : \"\",\n          discipline_id: issues.discipline_id ? String(issues.discipline_id) : \"\",\n        });\n      }\n    } catch (error) {\n      toast.error(\"Failed to load queue item\");\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const onSubmit = async (values: ReviewFormValues) => {\n    if (!item) return;\n\n    try {\n      setSubmitting(true);\n      const issues = item.aiIssues || {};\n\n      const payload = {\n        document_number: values.document_number,\n        subject: values.subject,\n        category: values.category,\n        source_file_path: issues.source_file_path || \"\",\n        migrated_by: \"SYSTEM_IMPORT\",\n        batch_id: \"MANUAL_REVIEW_BATCH\",\n        project_id: 1, // Assumption or pulled from store\n        document_date: values.document_date,\n        issued_date: values.issued_date,\n        received_date: values.received_date,\n        sender_id: values.sender_id ? Number(values.sender_id) : undefined,\n        discipline_id: values.discipline_id ? Number(values.discipline_id) : undefined,\n        details: {\n          tags: issues.tags || [],\n          ai_confidence: item.aiConfidence,\n        }\n      };\n\n      // Mock idempotency key based on timestamp to ensure uniqueness per approval retry\n      const idempotencyKey = `review-${item.id}-${Date.now()}`;\n      await migrationService.approveQueueItem(item.id, payload, idempotencyKey);\n\n      toast.success(\"Document approved and imported successfully\");\n      router.push(\"/admin/migration\");\n    } catch (error: unknown) {\n      const err = error as { response?: { data?: { message?: string } } };\n      toast.error(err?.response?.data?.message || \"Failed to approve and import\");\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  const onReject = async () => {\n    if (!item || !confirm(\"Are you sure you want to REJECT this document? It will not be imported.\")) return;\n\n    try {\n      setSubmitting(true);\n      await migrationService.rejectQueueItem(item.id);\n      toast.success(\"Document rejected\");\n      router.push(\"/admin/migration\");\n    } catch (error: unknown) {\n      toast.error(\"Failed to reject document\");\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  if (loading) {\n    return <div className=\"py-10 text-center\">Loading document data...</div>;\n  }\n\n  if (!item) {\n    return <div className=\"py-10 text-center text-red-500\">Document not found</div>;\n  }\n\n  const pdfUrl = item.aiIssues?.source_file_path\n    ? migrationService.getStagingFileUrl(item.aiIssues.source_file_path)\n    : null;\n\n  return (\n    <div className=\"flex flex-col h-[calc(100vh-6rem)] space-y-4\">\n      <div className=\"flex justify-between items-center shrink-0\">\n        <div className=\"flex items-center gap-4\">\n          <Link href=\"/admin/migration\">\n            <Button variant=\"outline\" size=\"icon\">\n              <ArrowLeftIcon className=\"h-4 w-4\" />\n            </Button>\n          </Link>\n          <div>\n            <h1 className=\"text-2xl font-bold tracking-tight\">Review Document: {item.documentNumber}</h1>\n            <p className=\"text-sm text-muted-foreground flex items-center gap-2\">\n              Status: <span className=\"font-semibold text-primary\">{item.status}</span>\n              {' | '} Confidence: <span className={item.aiConfidence && item.aiConfidence < 0.8 ? \"text-red-500\" : \"text-green-500\"}>{item.aiConfidence ? (item.aiConfidence * 100).toFixed(1) + \"%\" : \"N/A\"}</span>\n            </p>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex flex-1 gap-6 overflow-hidden\">\n        {/* Left Side: PDF Viewer */}\n        <Card className=\"flex-1 hidden md:flex flex-col overflow-hidden border-2 border-primary/10 shadow-md\">\n          <CardContent className=\"p-0 flex-1 relative bg-slate-100\">\n            {pdfUrl ? (\n              <iframe\n                src={`${pdfUrl}#toolbar=0&navpanes=0`}\n                className=\"absolute inset-0 w-full h-full\"\n                title=\"Document Viewer\"\n              />\n            ) : (\n              <div className=\"absolute inset-0 flex items-center justify-center text-muted-foreground\">\n                <p>No Source File Path found for this document</p>\n              </div>\n            )}\n          </CardContent>\n        </Card>\n\n        {/* Right Side: Form */}\n        <Card className=\"w-full md:w-[450px] lg:w-[500px] flex-shrink-0 flex flex-col overflow-hidden border-2 border-primary/10 shadow-md\">\n          <div className=\"p-4 border-b bg-muted/30\">\n            <h2 className=\"font-semibold text-lg flex items-center gap-2\">\n              Extracted Information\n            </h2>\n            {item.reviewReason && (\n              <p className=\"text-sm text-red-500 mt-1 font-medium bg-red-50 p-2 rounded border border-red-100\">\n                Reason: {item.reviewReason}\n              </p>\n            )}\n          </div>\n          <CardContent className=\"flex-1 overflow-y-auto p-4 custom-scrollbar\">\n            <Form {...form}>\n              <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n                <FormField\n                  control={form.control}\n                  name=\"document_number\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Document Number</FormLabel>\n                      <FormControl>\n                        <Input {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n                <FormField\n                  control={form.control}\n                  name=\"subject\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Subject</FormLabel>\n                      <FormControl>\n                        <Textarea {...field} rows={3} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <div className=\"grid grid-cols-2 gap-4\">\n                  <FormField\n                    control={form.control}\n                    name=\"category\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Category</FormLabel>\n                        <Select onValueChange={field.onChange} defaultValue={field.value}>\n                          <FormControl>\n                            <SelectTrigger>\n                              <SelectValue placeholder=\"Select type\" />\n                            </SelectTrigger>\n                          </FormControl>\n                          <SelectContent>\n                            <SelectItem value=\"CORR\">CORR</SelectItem>\n                            <SelectItem value=\"RFA\">RFA</SelectItem>\n                            <SelectItem value=\"LETTER\">LETTER</SelectItem>\n                            <SelectItem value=\"MEMO\">MEMO</SelectItem>\n                          </SelectContent>\n                        </Select>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n                  <FormField\n                    control={form.control}\n                    name=\"discipline_id\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Discipline ID</FormLabel>\n                        <FormControl>\n                          <Input {...field} type=\"number\" placeholder=\"Optional\" />\n                        </FormControl>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n                </div>\n\n                <div className=\"grid grid-cols-2 gap-4\">\n                  <FormField\n                    control={form.control}\n                    name=\"document_date\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Doc Date</FormLabel>\n                        <FormControl>\n                          <Input {...field} type=\"date\" />\n                        </FormControl>\n                      </FormItem>\n                    )}\n                  />\n                  <FormField\n                    control={form.control}\n                    name=\"issued_date\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Issued Date</FormLabel>\n                        <FormControl>\n                          <Input {...field} type=\"date\" />\n                        </FormControl>\n                      </FormItem>\n                    )}\n                  />\n                </div>\n\n                <FormField\n                  control={form.control}\n                  name=\"sender_id\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Sender Org ID</FormLabel>\n                      <FormControl>\n                        <Input {...field} type=\"number\" placeholder=\"Optional\" />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                {item.aiIssues?.key_points && item.aiIssues.key_points.length > 0 && (\n                  <div className=\"mt-6 border-t pt-4\">\n                    <h3 className=\"font-semibold text-sm mb-2 text-muted-foreground\">AI Extracted Key Points</h3>\n                    <ul className=\"text-sm space-y-1 list-disc pl-4 text-muted-foreground\">\n                      {item.aiIssues.key_points.map((point: string, i: number) => (\n                        <li key={i}>{point}</li>\n                      ))}\n                    </ul>\n                  </div>\n                )}\n\n                <div className=\"flex gap-4 pt-6 mt-4 border-t sticky bottom-0 bg-background/95 backdrop-blur z-10\">\n                  <Button\n                    type=\"button\"\n                    variant=\"destructive\"\n                    className=\"flex-1\"\n                    disabled={submitting || item.status !== 'PENDING'}\n                    onClick={onReject}\n                  >\n                    <XCircleIcon className=\"w-4 h-4 mr-2\" />\n                    Reject\n                  </Button>\n                  <Button\n                    type=\"submit\"\n                    className=\"flex-1 bg-green-600 hover:bg-green-700 text-white\"\n                    disabled={submitting || item.status !== 'PENDING'}\n                  >\n                    <CheckCircleIcon className=\"w-4 h-4 mr-2\" />\n                    {submitting ? \"Processing...\" : \"Approve & Import\"}\n                  </Button>\n                </div>\n              </form>\n            </Form>\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\monitoring\\audit-logs\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\monitoring\\sessions\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\monitoring\\system-logs\\numbering\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\numbering\\[id]\\edit\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\numbering\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\numbering\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\organizations\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\settings\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\users\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\workflows\\[id]\\edit\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\workflows\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\admin\\workflows\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\error.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(admin)\\layout.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(auth)\\layout.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(auth)\\login\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 78,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 78,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: app/(auth)/login/page.tsx\r\n\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { useForm } from \"react-hook-form\";\r\nimport { zodResolver } from \"@hookform/resolvers/zod\";\r\nimport * as z from \"zod\";\r\nimport { signIn } from \"next-auth/react\";\r\nimport { useRouter } from \"next/navigation\";\r\nimport { Eye, EyeOff, Loader2 } from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\n\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n  Card,\r\n  CardContent,\r\n  CardDescription,\r\n  CardFooter,\r\n  CardHeader,\r\n  CardTitle,\r\n} from \"@/components/ui/card\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\n\r\n// กำหนด Schema สำหรับตรวจสอบข้อมูลฟอร์ม\r\nconst loginSchema = z.object({\r\n  username: z.string().min(1, \"กรุณาระบุชื่อผู้ใช้งาน\"),\r\n  password: z.string().min(1, \"กรุณาระบุรหัสผ่าน\"),\r\n});\r\n\r\ntype LoginValues = z.infer<typeof loginSchema>;\r\n\r\nexport default function LoginPage() {\r\n  const router = useRouter();\r\n  const [isLoading, setIsLoading] = useState(false);\r\n  const [showPassword, setShowPassword] = useState(false);\r\n  // Removed local errorMessage state in favor of toast\r\n\r\n  // ตั้งค่า React Hook Form\r\n  const {\r\n    register,\r\n    handleSubmit,\r\n    formState: { errors },\r\n  } = useForm<LoginValues>({\r\n    resolver: zodResolver(loginSchema),\r\n    defaultValues: {\r\n      username: \"\",\r\n      password: \"\",\r\n    },\r\n  });\r\n\r\n  // ฟังก์ชันเมื่อกด Submit\r\n  async function onSubmit(data: LoginValues) {\r\n    setIsLoading(true);\r\n\r\n    try {\r\n      // เรียกใช้ NextAuth signIn (Credential Provider)\r\n      const result = await signIn(\"credentials\", {\r\n        username: data.username,\r\n        password: data.password,\r\n        redirect: false, // เราจะจัดการ Redirect เอง\r\n      });\r\n\r\n      if (result?.error) {\r\n        // กรณี Login ไม่สำเร็จ\r\n        toast.error(\"เข้าสู่ระบบไม่สำเร็จ\", {\r\n          description: \"ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง กรุณาลองใหม่\",\r\n        });\r\n        return;\r\n      }\r\n\r\n      // Login สำเร็จ -> ไปหน้า Dashboard\r\n      toast.success(\"เข้าสู่ระบบสำเร็จ\", {\r\n        description: \"กำลังพาท่านไปยังหน้า Dashboard...\",\r\n      });\r\n      router.push(\"/dashboard\");\r\n      router.refresh(); // Refresh เพื่อให้ Server Component รับรู้ Session ใหม่\r\n    } catch (_error) {\r\n      toast.error(\"เกิดข้อผิดพลาด\", {\r\n        description: \"ระบบขัดข้อง กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ\",\r\n      });\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }\r\n\r\n  return (\r\n    <Card className=\"w-full max-w-sm shadow-lg\">\r\n      <CardHeader className=\"space-y-1 text-center\">\r\n        <CardTitle className=\"text-2xl font-bold text-primary\">\r\n          LCBP3 DMS\r\n        </CardTitle>\r\n        <CardDescription>\r\n          กรอกชื่อผู้ใช้งานและรหัสผ่านเพื่อเข้าสู่ระบบ\r\n        </CardDescription>\r\n      </CardHeader>\r\n\r\n      <form onSubmit={handleSubmit(onSubmit)}>\r\n        <CardContent className=\"grid gap-4\">\r\n          {/* Username Field */}\r\n          <div className=\"grid gap-2\">\r\n            <Label htmlFor=\"username\">ชื่อผู้ใช้งาน</Label>\r\n            <Input\r\n              id=\"username\"\r\n              placeholder=\"username\"\r\n              type=\"text\"\r\n              autoCapitalize=\"none\"\r\n              autoComplete=\"username\"\r\n              autoCorrect=\"off\"\r\n              disabled={isLoading}\r\n              className={errors.username ? \"border-destructive\" : \"\"}\r\n              {...register(\"username\")}\r\n            />\r\n            {errors.username && (\r\n              <p className=\"text-xs text-destructive\">\r\n                {errors.username.message}\r\n              </p>\r\n            )}\r\n          </div>\r\n\r\n          {/* Password Field */}\r\n          <div className=\"grid gap-2\">\r\n            <Label htmlFor=\"password\">รหัสผ่าน</Label>\r\n            <div className=\"relative\">\r\n              <Input\r\n                id=\"password\"\r\n                placeholder=\"••••••••\"\r\n                type={showPassword ? \"text\" : \"password\"}\r\n                autoComplete=\"current-password\"\r\n                disabled={isLoading}\r\n                className={errors.password ? \"border-destructive pr-10\" : \"pr-10\"}\r\n                {...register(\"password\")}\r\n              />\r\n              {/* ปุ่ม Show/Hide Password */}\r\n              <Button\r\n                type=\"button\"\r\n                variant=\"ghost\"\r\n                size=\"sm\"\r\n                className=\"absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent\"\r\n                onClick={() => setShowPassword(!showPassword)}\r\n                disabled={isLoading}\r\n              >\r\n                {showPassword ? (\r\n                  <EyeOff className=\"h-4 w-4 text-muted-foreground\" />\r\n                ) : (\r\n                  <Eye className=\"h-4 w-4 text-muted-foreground\" />\r\n                )}\r\n                <span className=\"sr-only\">\r\n                  {showPassword ? \"Hide password\" : \"Show password\"}\r\n                </span>\r\n              </Button>\r\n            </div>\r\n            {errors.password && (\r\n              <p className=\"text-xs text-destructive\">\r\n                {errors.password.message}\r\n              </p>\r\n            )}\r\n          </div>\r\n        </CardContent>\r\n\r\n        <CardFooter>\r\n          <Button className=\"w-full\" type=\"submit\" disabled={isLoading}>\r\n            {isLoading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n            เข้าสู่ระบบ\r\n          </Button>\r\n        </CardFooter>\r\n      </form>\r\n    </Card>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\circulation\\[uuid]\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\circulation\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\circulation\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\correspondences\\[uuid]\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\correspondences\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\correspondences\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\dashboard\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\drawings\\[uuid]\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\drawings\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\drawings\\upload\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\error.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\layout.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\profile\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 70,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 70,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: app/(dashboard)/profile/page.tsx\r\n\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { useSession } from \"next-auth/react\";\r\nimport { useForm } from \"react-hook-form\";\r\nimport { zodResolver } from \"@hookform/resolvers/zod\";\r\nimport * as z from \"zod\";\r\nimport { Loader2, User, Shield, Bell } from \"lucide-react\";\r\n\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\r\nimport {\r\n  Card,\r\n  CardContent,\r\n  CardDescription,\r\n  CardFooter,\r\n  CardHeader,\r\n  CardTitle,\r\n} from \"@/components/ui/card\";\r\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\r\nimport { Switch } from \"@/components/ui/switch\";\r\nimport apiClient from \"@/lib/api/client\";\r\nimport { toast } from \"sonner\";\r\n\r\n// -----------------------------------------------------------------------------\r\n// Schemas\r\n// -----------------------------------------------------------------------------\r\n\r\nconst passwordSchema = z\r\n  .object({\r\n    currentPassword: z.string().min(1, \"กรุณาระบุรหัสผ่านปัจจุบัน\"),\r\n    newPassword: z.string().min(8, \"รหัสผ่านใหม่ต้องมีอย่างน้อย 8 ตัวอักษร\"),\r\n    confirmPassword: z.string().min(1, \"กรุณายืนยันรหัสผ่านใหม่\"),\r\n  })\r\n  .refine((data) => data.newPassword === data.confirmPassword, {\r\n    message: \"รหัสผ่านใหม่ไม่ตรงกัน\",\r\n    path: [\"confirmPassword\"],\r\n  });\r\n\r\ntype PasswordValues = z.infer<typeof passwordSchema>;\r\n\r\nexport default function ProfilePage() {\r\n  const { data: session } = useSession();\r\n  const [isLoading, setIsLoading] = useState(false);\r\n\r\n  // --- Password Form Handling ---\r\n  const {\r\n    register,\r\n    handleSubmit,\r\n    reset,\r\n    formState: { errors },\r\n  } = useForm<PasswordValues>({\r\n    resolver: zodResolver(passwordSchema),\r\n  });\r\n\r\n  const onPasswordSubmit = async (data: PasswordValues) => {\r\n    setIsLoading(true);\r\n    try {\r\n      // เรียก API เปลี่ยนรหัสผ่าน\r\n      await apiClient.put(\"/users/change-password\", {\r\n        currentPassword: data.currentPassword,\r\n        newPassword: data.newPassword,\r\n      });\r\n\r\n      toast.success('เปลี่ยนรหัสผ่านสำเร็จ');\r\n      reset();\r\n    } catch (_error) {\r\n      toast.error('ไม่สามารถเปลี่ยนรหัสผ่านได้: รหัสผ่านปัจจุบันไม่ถูกต้อง');\r\n      // Password change failed - toast shown\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  };\r\n\r\n  // --- Notification State (Mockup) ---\r\n  // ในการใช้งานจริง ควรดึงค่าจาก API /users/preferences หรือ UserPreferenceService\r\n  const [notifyEmail, setNotifyEmail] = useState(true);\r\n  const [notifyLine, setNotifyLine] = useState(true);\r\n  const [digestMode, setDigestMode] = useState(false);\r\n\r\n  // Helper to get initials\r\n  const userName = session?.user?.name || \"User\";\r\n  const userInitials = userName\r\n    .split(\" \")\r\n    .map((n) => n[0])\r\n    .join(\"\")\r\n    .toUpperCase()\r\n    .substring(0, 2);\r\n\r\n  return (\r\n    <div className=\"space-y-6\">\r\n      <div>\r\n        <h3 className=\"text-lg font-medium\">Profile & Settings</h3>\r\n        <p className=\"text-sm text-muted-foreground\">\r\n          จัดการข้อมูลส่วนตัวและการตั้งค่าระบบของคุณ\r\n        </p>\r\n      </div>\r\n\r\n      <Tabs defaultValue=\"general\" className=\"space-y-4\">\r\n        <TabsList>\r\n          <TabsTrigger value=\"general\" className=\"flex items-center gap-2\">\r\n            <User className=\"h-4 w-4\" />\r\n            General\r\n          </TabsTrigger>\r\n          <TabsTrigger value=\"security\" className=\"flex items-center gap-2\">\r\n            <Shield className=\"h-4 w-4\" />\r\n            Security\r\n          </TabsTrigger>\r\n          <TabsTrigger value=\"notifications\" className=\"flex items-center gap-2\">\r\n            <Bell className=\"h-4 w-4\" />\r\n            Notifications\r\n          </TabsTrigger>\r\n        </TabsList>\r\n\r\n        {/* 1. General Tab */}\r\n        <TabsContent value=\"general\">\r\n          <Card>\r\n            <CardHeader>\r\n              <CardTitle>ข้อมูลทั่วไป</CardTitle>\r\n              <CardDescription>\r\n                ข้อมูลพื้นฐานของคุณที่แสดงในระบบ\r\n              </CardDescription>\r\n            </CardHeader>\r\n            <CardContent className=\"space-y-6\">\r\n              <div className=\"flex items-center gap-4\">\r\n                <Avatar className=\"h-20 w-20\">\r\n                  <AvatarImage src={session?.user?.image || \"\"} />\r\n                  <AvatarFallback className=\"text-lg\">{userInitials}</AvatarFallback>\r\n                </Avatar>\r\n                <div>\r\n                  <h4 className=\"text-lg font-semibold\">{userName}</h4>\r\n                  <p className=\"text-sm text-muted-foreground\">{session?.user?.email}</p>\r\n                  <div className=\"mt-2 inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80\">\r\n                    {session?.user?.role || \"Member\"}\r\n                  </div>\r\n                </div>\r\n              </div>\r\n\r\n              <div className=\"grid gap-4 md:grid-cols-2\">\r\n                <div className=\"space-y-2\">\r\n                  <Label>ชื่อจริง</Label>\r\n                  <Input defaultValue={userName.split(\" \")[0]} disabled />\r\n                </div>\r\n                <div className=\"space-y-2\">\r\n                  <Label>นามสกุล</Label>\r\n                  <Input defaultValue={userName.split(\" \")[1] || \"\"} disabled />\r\n                </div>\r\n                <div className=\"space-y-2\">\r\n                  <Label>อีเมล</Label>\r\n                  <Input defaultValue={session?.user?.email || \"\"} disabled />\r\n                </div>\r\n                <div className=\"space-y-2\">\r\n                  <Label>หน่วยงาน / องค์กร</Label>\r\n                  {/* ในอนาคตดึงจาก Organization ID */}\r\n                  <Input defaultValue={`Organization ID: ${session?.user?.organizationId || \"-\"}`} disabled />\r\n                </div>\r\n              </div>\r\n            </CardContent>\r\n            {/* <CardFooter>\r\n              <Button>บันทึกการเปลี่ยนแปลง</Button>\r\n            </CardFooter>\r\n            */}\r\n          </Card>\r\n        </TabsContent>\r\n\r\n        {/* 2. Security Tab */}\r\n        <TabsContent value=\"security\">\r\n          <Card>\r\n            <CardHeader>\r\n              <CardTitle>รหัสผ่าน</CardTitle>\r\n              <CardDescription>\r\n                เปลี่ยนรหัสผ่านเพื่อความปลอดภัยของบัญชี (ต้องมีอย่างน้อย 8 ตัวอักษร)\r\n              </CardDescription>\r\n            </CardHeader>\r\n            <form onSubmit={handleSubmit(onPasswordSubmit)}>\r\n              <CardContent className=\"space-y-4\">\r\n                <div className=\"space-y-2\">\r\n                  <Label htmlFor=\"currentPassword\">รหัสผ่านปัจจุบัน</Label>\r\n                  <Input\r\n                    id=\"currentPassword\"\r\n                    type=\"password\"\r\n                    {...register(\"currentPassword\")}\r\n                  />\r\n                  {errors.currentPassword && (\r\n                    <p className=\"text-xs text-destructive\">{errors.currentPassword.message}</p>\r\n                  )}\r\n                </div>\r\n                <div className=\"space-y-2\">\r\n                  <Label htmlFor=\"newPassword\">รหัสผ่านใหม่</Label>\r\n                  <Input\r\n                    id=\"newPassword\"\r\n                    type=\"password\"\r\n                    {...register(\"newPassword\")}\r\n                  />\r\n                  {errors.newPassword && (\r\n                    <p className=\"text-xs text-destructive\">{errors.newPassword.message}</p>\r\n                  )}\r\n                </div>\r\n                <div className=\"space-y-2\">\r\n                  <Label htmlFor=\"confirmPassword\">ยืนยันรหัสผ่านใหม่</Label>\r\n                  <Input\r\n                    id=\"confirmPassword\"\r\n                    type=\"password\"\r\n                    {...register(\"confirmPassword\")}\r\n                  />\r\n                  {errors.confirmPassword && (\r\n                    <p className=\"text-xs text-destructive\">{errors.confirmPassword.message}</p>\r\n                  )}\r\n                </div>\r\n              </CardContent>\r\n              <CardFooter>\r\n                <Button type=\"submit\" disabled={isLoading}>\r\n                  {isLoading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n                  เปลี่ยนรหัสผ่าน\r\n                </Button>\r\n              </CardFooter>\r\n            </form>\r\n          </Card>\r\n        </TabsContent>\r\n\r\n        {/* 3. Notifications Tab */}\r\n        <TabsContent value=\"notifications\">\r\n          <Card>\r\n            <CardHeader>\r\n              <CardTitle>การแจ้งเตือน</CardTitle>\r\n              <CardDescription>\r\n                กำหนดช่องทางที่คุณต้องการรับข้อมูลข่าวสารจากระบบ\r\n              </CardDescription>\r\n            </CardHeader>\r\n            <CardContent className=\"space-y-6\">\r\n              <div className=\"flex items-center justify-between space-x-2\">\r\n                <Label htmlFor=\"notify-email\" className=\"flex flex-col space-y-1\">\r\n                  <span>Email Notifications</span>\r\n                  <span className=\"font-normal text-xs text-muted-foreground\">\r\n                    รับแจ้งเตือนงานใหม่และการอนุมัติผ่านทางอีเมล\r\n                  </span>\r\n                </Label>\r\n                <Switch\r\n                  id=\"notify-email\"\r\n                  checked={notifyEmail}\r\n                  onCheckedChange={setNotifyEmail}\r\n                />\r\n              </div>\r\n\r\n              <div className=\"flex items-center justify-between space-x-2\">\r\n                <Label htmlFor=\"notify-line\" className=\"flex flex-col space-y-1\">\r\n                  <span>LINE Notifications</span>\r\n                  <span className=\"font-normal text-xs text-muted-foreground\">\r\n                    รับแจ้งเตือนด่วนผ่าน LINE Official Account\r\n                  </span>\r\n                </Label>\r\n                <Switch\r\n                  id=\"notify-line\"\r\n                  checked={notifyLine}\r\n                  onCheckedChange={setNotifyLine}\r\n                />\r\n              </div>\r\n\r\n              <div className=\"flex items-center justify-between space-x-2\">\r\n                <Label htmlFor=\"digest-mode\" className=\"flex flex-col space-y-1\">\r\n                  <span>Digest Mode (รวมแจ้งเตือน)</span>\r\n                  <span className=\"font-normal text-xs text-muted-foreground\">\r\n                    รับสรุปแจ้งเตือนวันละครั้ง แทนการแจ้งเตือนทันที (ลด Spam)\r\n                  </span>\r\n                </Label>\r\n                <Switch\r\n                  id=\"digest-mode\"\r\n                  checked={digestMode}\r\n                  onCheckedChange={setDigestMode}\r\n                />\r\n              </div>\r\n            </CardContent>\r\n            <CardFooter>\r\n              <Button variant=\"outline\" onClick={() => toast.success('บันทึกการตั้งค่าแจ้งเตือนแล้ว')}>\r\n                บันทึกการตั้งค่า\r\n              </Button>\r\n            </CardFooter>\r\n          </Card>\r\n        </TabsContent>\r\n      </Tabs>\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\projects\\new\\page.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 92,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 92,
        "endColumn": 19
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: app/(dashboard)/projects/new/page.tsx\r\n\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { useRouter } from \"next/navigation\";\r\nimport { useForm } from \"react-hook-form\";\r\nimport { zodResolver } from \"@hookform/resolvers/zod\";\r\nimport * as z from \"zod\";\r\nimport { Loader2, ChevronLeft, Save } from \"lucide-react\";\r\n\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport {\r\n  Select,\r\n  SelectContent,\r\n  SelectItem,\r\n  SelectTrigger,\r\n  SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport {\r\n  Card,\r\n  CardContent,\r\n  CardDescription,\r\n  CardFooter,\r\n  CardHeader,\r\n  CardTitle,\r\n} from \"@/components/ui/card\";\r\nimport { toast } from \"sonner\";\r\n\r\n// Force dynamic rendering to prevent build-time prerendering issues\r\nexport const dynamic = 'force-dynamic';\r\n\r\n// Ensure this page is never statically generated\r\nexport const fetchCache = 'force-no-store';\r\n\r\n// 1. กำหนด Schema สำหรับตรวจสอบข้อมูล (Validation)\r\n// อ้างอิงจาก Data Dictionary ตาราง projects\r\nconst projectSchema = z.object({\r\n  projectCode: z\r\n    .string()\r\n    .min(1, \"กรุณาระบุรหัสโครงการ\")\r\n    .max(50, \"รหัสโครงการต้องไม่เกิน 50 ตัวอักษร\")\r\n    .regex(/^[A-Z0-9-]+$/, \"รหัสโครงการควรประกอบด้วยตัวอักษรภาษาอังกฤษตัวใหญ่ ตัวเลข หรือขีด (-) เท่านั้น\"),\r\n  projectName: z\r\n    .string()\r\n    .min(1, \"กรุณาระบุชื่อโครงการ\")\r\n    .max(255, \"ชื่อโครงการต้องไม่เกิน 255 ตัวอักษร\"),\r\n  description: z.string().optional(),\r\n  status: z.enum([\"Active\", \"Inactive\", \"On Hold\"]),\r\n  startDate: z.string().optional(),\r\n  endDate: z.string().optional(),\r\n});\r\n\r\ntype ProjectValues = z.infer<typeof projectSchema>;\r\n\r\nexport default function CreateProjectPage() {\r\n  const router = useRouter();\r\n  const [isLoading, setIsLoading] = useState(false);\r\n\r\n  // 2. ตั้งค่า React Hook Form\r\n  const {\r\n    register,\r\n    handleSubmit,\r\n    setValue, // ใช้สำหรับ manual set value (เช่น Select)\r\n    formState: { errors },\r\n  } = useForm<ProjectValues>({\r\n    resolver: zodResolver(projectSchema),\r\n    defaultValues: {\r\n      projectCode: \"\",\r\n      projectName: \"\",\r\n      status: \"Active\",\r\n    },\r\n  });\r\n\r\n  // 3. ฟังก์ชัน Submit\r\n  async function onSubmit(_data: ProjectValues) {\r\n    setIsLoading(true);\r\n    try {\r\n      // เรียก API สร้างโครงการ (Mockup URL)\r\n      // ใน Phase หลัง Backend จะเตรียม Endpoint POST /projects ไว้ให้\r\n\r\n      // จำลองการส่งข้อมูล (Artificial Delay)\r\n      await new Promise((resolve) => setTimeout(resolve, 1000));\r\n\r\n      // await apiClient.post(\"/projects\", data);\r\n\r\n      toast.success('สร้างโครงการสำเร็จ');\r\n      router.push('/projects');\r\n      router.refresh();\r\n    } catch (error) {\r\n      toast.error('เกิดข้อผิดพลาดในการสร้างโครงการ');\r\n      // Project creation failed - toast shown\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }\r\n\r\n  return (\r\n    <div className=\"max-w-2xl mx-auto space-y-6\">\r\n      {/* Header with Back Button */}\r\n      <div className=\"flex items-center gap-4\">\r\n        <Button\r\n          variant=\"outline\"\r\n          size=\"icon\"\r\n          onClick={() => router.back()}\r\n          className=\"h-9 w-9\"\r\n        >\r\n          <ChevronLeft className=\"h-5 w-5\" />\r\n          <span className=\"sr-only\">Back</span>\r\n        </Button>\r\n        <div>\r\n          <h2 className=\"text-2xl font-bold tracking-tight\">Create New Project</h2>\r\n          <p className=\"text-muted-foreground\">\r\n            สร้างโครงการใหม่เพื่อเริ่มบริหารจัดการสัญญาและเอกสาร\r\n          </p>\r\n        </div>\r\n      </div>\r\n\r\n      <form onSubmit={handleSubmit(onSubmit)}>\r\n        <Card>\r\n          <CardHeader>\r\n            <CardTitle>ข้อมูลโครงการ</CardTitle>\r\n            <CardDescription>\r\n              กรอกรายละเอียดสำคัญของโครงการ รหัสโครงการควรไม่ซ้ำกับที่มีอยู่\r\n            </CardDescription>\r\n          </CardHeader>\r\n\r\n          <CardContent className=\"space-y-4\">\r\n            {/* Project Code */}\r\n            <div className=\"space-y-2\">\r\n              <Label htmlFor=\"project_code\" className=\"after:content-['*'] after:ml-0.5 after:text-red-500\">\r\n                รหัสโครงการ (Project Code)\r\n              </Label>\r\n              <Input\r\n                id=\"project_code\"\r\n                placeholder=\"e.g. LCBP3-C1\"\r\n                className={errors.projectCode ? \"border-destructive\" : \"\"}\r\n                {...register(\"projectCode\")}\r\n                onChange={(e) => {\r\n                  e.target.value = e.target.value.toUpperCase();\r\n                  register(\"projectCode\").onChange(e);\r\n                }}\r\n              />\r\n              {errors.projectCode ? (\r\n                <p className=\"text-xs text-destructive\">{errors.projectCode.message}</p>\r\n              ) : (\r\n                <p className=\"text-xs text-muted-foreground\">\r\n                  ใช้ภาษาอังกฤษตัวพิมพ์ใหญ่ ตัวเลข และขีด (-) เท่านั้น\r\n                </p>\r\n              )}\r\n            </div>\r\n\r\n            {/* Project Name */}\r\n            <div className=\"space-y-2\">\r\n              <Label htmlFor=\"project_name\" className=\"after:content-['*'] after:ml-0.5 after:text-red-500\">\r\n                ชื่อโครงการ (Project Name)\r\n              </Label>\r\n              <Input\r\n                id=\"project_name\"\r\n                placeholder=\"ระบุชื่อโครงการฉบับเต็ม...\"\r\n                className={errors.projectName ? \"border-destructive\" : \"\"}\r\n                {...register(\"projectName\")}\r\n              />\r\n              {errors.projectName && (\r\n                <p className=\"text-xs text-destructive\">{errors.projectName.message}</p>\r\n              )}\r\n            </div>\r\n\r\n            {/* Description */}\r\n            <div className=\"space-y-2\">\r\n              <Label htmlFor=\"description\">รายละเอียดเพิ่มเติม</Label>\r\n              <Textarea\r\n                id=\"description\"\r\n                placeholder=\"คำอธิบายเกี่ยวกับขอบเขตงานของโครงการ...\"\r\n                className=\"min-h-[100px]\"\r\n                {...register(\"description\")}\r\n              />\r\n            </div>\r\n\r\n            {/* Dates Row */}\r\n            <div className=\"grid gap-4 md:grid-cols-2\">\r\n              <div className=\"space-y-2\">\r\n                <Label htmlFor=\"start_date\">วันที่เริ่มต้นสัญญา</Label>\r\n                <Input\r\n                  id=\"start_date\"\r\n                  type=\"date\"\r\n                  {...register(\"startDate\")}\r\n                />\r\n              </div>\r\n              <div className=\"space-y-2\">\r\n                <Label htmlFor=\"end_date\">วันที่สิ้นสุดสัญญา</Label>\r\n                <Input\r\n                  id=\"end_date\"\r\n                  type=\"date\"\r\n                  {...register(\"endDate\")}\r\n                />\r\n              </div>\r\n            </div>\r\n\r\n            {/* Status Select */}\r\n            <div className=\"space-y-2\">\r\n              <Label htmlFor=\"status\">สถานะโครงการ</Label>\r\n              {/* เนื่องจาก Select ของ Shadcn เป็น Custom UI\r\n                เราต้องใช้ onValueChange เพื่อเชื่อมกับ React Hook Form\r\n              */}\r\n              <Select\r\n                onValueChange={(value: 'Active' | 'Inactive' | 'On Hold') => setValue('status', value)}\r\n                defaultValue=\"Active\"\r\n              >\r\n                <SelectTrigger>\r\n                  <SelectValue placeholder=\"เลือกสถานะ\" />\r\n                </SelectTrigger>\r\n                <SelectContent>\r\n                  <SelectItem value=\"Active\">Active (กำลังดำเนินงาน)</SelectItem>\r\n                  <SelectItem value=\"On Hold\">On Hold (ระงับชั่วคราว)</SelectItem>\r\n                  <SelectItem value=\"Inactive\">Inactive (ปิดโครงการ)</SelectItem>\r\n                </SelectContent>\r\n              </Select>\r\n            </div>\r\n          </CardContent>\r\n\r\n          <CardFooter className=\"flex justify-end gap-2 border-t p-4 bg-muted/50\">\r\n            <Button\r\n              type=\"button\"\r\n              variant=\"ghost\"\r\n              onClick={() => router.back()}\r\n              disabled={isLoading}\r\n            >\r\n              ยกเลิก\r\n            </Button>\r\n            <Button type=\"submit\" disabled={isLoading}>\r\n              {isLoading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n              <Save className=\"mr-2 h-4 w-4\" />\r\n              บันทึกข้อมูล\r\n            </Button>\r\n          </CardFooter>\r\n        </Card>\r\n      </form>\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\projects\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\projects\\page_backup.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\rfas\\[uuid]\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\rfas\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\rfas\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\search\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\transmittals\\[uuid]\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\transmittals\\new\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\(dashboard)\\transmittals\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\api\\auth\\[...nextauth]\\route.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\error.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\global-error.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\layout.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\app\\page.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\admin\\organization-dialog.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\admin\\reference\\generic-crud-table.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\admin\\security\\rbac-matrix.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\admin\\sidebar.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\admin\\user-dialog.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\auth\\auth-sync.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\circulation\\circulation-list.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'StatusBadge' is defined but never used. Allowed unused vars must match /^_/u.",
        "line": 6,
        "column": 10,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 6,
        "endColumn": 21,
        "suggestions": [
          {
            "messageId": "removeUnusedImportDeclaration",
            "data": { "varName": "StatusBadge" },
            "fix": { "range": [202, 266], "text": "" },
            "desc": "Remove unused import declaration."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { Circulation, CirculationListResponse } from \"@/types/circulation\";\nimport { DataTable } from \"@/components/common/data-table\";\nimport { ColumnDef } from \"@tanstack/react-table\";\nimport { StatusBadge } from \"@/components/common/status-badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Eye, _CheckCircle2 } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { format } from \"date-fns\";\nimport { Badge } from \"@/components/ui/badge\";\n\ninterface CirculationListProps {\n  data: CirculationListResponse;\n}\n\n/**\n * Calculate progress of circulation routings\n */\nfunction getProgress(routings?: Circulation[\"routings\"]) {\n  if (!routings || routings.length === 0) return { completed: 0, total: 0 };\n  const completed = routings.filter((r) => r.status === \"COMPLETED\").length;\n  return { completed, total: routings.length };\n}\n\n/**\n * Get status color variant for circulation status\n */\nfunction getStatusVariant(\n  statusCode: string\n): \"default\" | \"secondary\" | \"destructive\" | \"outline\" {\n  switch (statusCode?.toUpperCase()) {\n    case \"DRAFT\":\n      return \"outline\";\n    case \"ACTIVE\":\n    case \"IN_PROGRESS\":\n      return \"default\";\n    case \"COMPLETED\":\n    case \"CLOSED\":\n      return \"secondary\";\n    default:\n      return \"outline\";\n  }\n}\n\nexport function CirculationList({ data }: CirculationListProps) {\n  if (!data) return null;\n\n  const columns: ColumnDef<Circulation>[] = [\n    {\n      accessorKey: \"circulationNo\",\n      header: \"Circulation No.\",\n      cell: ({ row }) => (\n        <span className=\"font-medium\">{row.getValue(\"circulationNo\")}</span>\n      ),\n    },\n    {\n      accessorKey: \"subject\",\n      header: \"Subject\",\n      cell: ({ row }) => (\n        <div className=\"max-w-[250px] truncate\" title={row.getValue(\"subject\")}>\n          {row.getValue(\"subject\")}\n        </div>\n      ),\n    },\n    {\n      accessorKey: \"organization\",\n      header: \"Organization\",\n      cell: ({ row }) => {\n        const org = row.original.organization;\n        return org?.organization_name || \"-\";\n      },\n    },\n    {\n      accessorKey: \"statusCode\",\n      header: \"Status\",\n      cell: ({ row }) => {\n        const status = row.getValue(\"statusCode\") as string;\n        return <Badge variant={getStatusVariant(status)}>{status}</Badge>;\n      },\n    },\n    {\n      id: \"progress\",\n      header: \"Progress\",\n      cell: ({ row }) => {\n        const { completed, total } = getProgress(row.original.routings);\n        if (total === 0) return \"-\";\n        const percent = Math.round((completed / total) * 100);\n        return (\n          <div className=\"flex items-center gap-2\">\n            <div className=\"w-20 h-2 bg-muted rounded-full overflow-hidden\">\n              <div\n                className=\"h-full bg-primary transition-all\"\n                style={{ width: `${percent}%` }}\n              />\n            </div>\n            <span className=\"text-xs text-muted-foreground\">\n              {completed}/{total}\n            </span>\n          </div>\n        );\n      },\n    },\n    {\n      accessorKey: \"createdAt\",\n      header: \"Created\",\n      cell: ({ row }) =>\n        format(new Date(row.getValue(\"createdAt\")), \"dd MMM yyyy\"),\n    },\n    {\n      id: \"actions\",\n      cell: ({ row }) => {\n        const item = row.original;\n        return (\n          <div className=\"flex gap-1\">\n            <Link href={`/circulation/${item.uuid}`}>\n              <Button variant=\"ghost\" size=\"icon\" title=\"View Details\">\n                <Eye className=\"h-4 w-4\" />\n              </Button>\n            </Link>\n          </div>\n        );\n      },\n    },\n  ];\n\n  return (\n    <div>\n      <DataTable columns={columns} data={data.data || []} />\n      {data.meta && (\n        <div className=\"mt-4 text-sm text-muted-foreground text-center\">\n          Showing {data.data?.length || 0} of {data.meta.total} circulations\n        </div>\n      )}\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\common\\can.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\common\\confirm-dialog.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\common\\data-table.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\common\\pagination.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\common\\status-badge.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\correspondences\\correspondences-content.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\correspondences\\detail.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\correspondences\\form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 86,
        "column": 75,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 86,
        "endColumn": 78,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [2912, 2915], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [2912, 2915], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 102,
        "column": 55,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 102,
        "endColumn": 58,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [3877, 3880], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [3877, 3880], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 117,
        "column": 57,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 117,
        "endColumn": 60,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [5082, 5085], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [5082, 5085], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 118,
        "column": 48,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 118,
        "endColumn": 51,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [5189, 5192], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [5189, 5192], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 131,
        "column": 52,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 131,
        "endColumn": 55,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [5570, 5573], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [5570, 5573], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ]
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'err' is defined but never used.",
        "line": 198,
        "column": 17,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 198,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 6,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\r\n\r\nimport { useForm } from \"react-hook-form\";\r\nimport { zodResolver } from \"@hookform/resolvers/zod\";\r\nimport { z } from \"zod\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\r\n  Select,\r\n  SelectContent,\r\n  SelectItem,\r\n  SelectTrigger,\r\n  SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { FileUploadZone } from \"@/components/custom/file-upload-zone\";\r\nimport { useRouter } from \"next/navigation\";\r\nimport { Loader2 } from \"lucide-react\";\r\nimport { useCreateCorrespondence, useUpdateCorrespondence } from \"@/hooks/use-correspondence\";\r\nimport { Organization } from \"@/types/organization\";\r\nimport { useOrganizations, useProjects, useCorrespondenceTypes, useDisciplines } from \"@/hooks/use-master-data\";\r\nimport { CreateCorrespondenceDto } from \"@/types/dto/correspondence/create-correspondence.dto\";\r\nimport { useState, useEffect } from \"react\";\r\nimport { _correspondenceService } from \"@/lib/services/correspondence.service\";\r\nimport { numberingApi } from \"@/lib/api/numbering\";\r\n\r\n// Updated Zod Schema with all required fields\r\nconst correspondenceSchema = z.object({\r\n  projectId: z.string().min(1, \"Please select a Project\"),\r\n  documentTypeId: z.number().min(1, \"Please select a Document Type\"),\r\n  disciplineId: z.number().optional(),\r\n  subject: z.string().min(5, \"Subject must be at least 5 characters\"),\r\n  description: z.string().optional(),\r\n  body: z.string().optional(),\r\n  remarks: z.string().optional(),\r\n  dueDate: z.string().optional(), // ISO Date string\r\n  documentDate: z.string().optional(),\r\n  issuedDate: z.string().optional(),\r\n  receivedDate: z.string().optional(),\r\n  fromOrganizationId: z.string().min(1, \"Please select From Organization\"),\r\n  toOrganizationId: z.string().min(1, \"Please select To Organization\"),\r\n  importance: z.enum([\"NORMAL\", \"HIGH\", \"URGENT\"]),\r\n  attachments: z.array(z.instanceof(File)).optional(),\r\n});\r\n\r\ntype FormData = z.infer<typeof correspondenceSchema>;\r\n\r\ntype ProjectOption = {\r\n  uuid?: string;\r\n  id?: number;\r\n  projectName: string;\r\n  projectCode: string;\r\n};\r\n\r\ntype CorrespondenceTypeOption = {\r\n  id: number;\r\n  typeName: string;\r\n  typeCode: string;\r\n};\r\n\r\ntype DisciplineOption = {\r\n  id: number;\r\n  disciplineCode: string;\r\n  codeNameEn?: string;\r\n};\r\n\r\nconst extractArrayData = <T,>(value: unknown): T[] => {\r\n  let current: unknown = value;\r\n\r\n  for (let i = 0; i < 5; i += 1) {\r\n    if (Array.isArray(current)) {\r\n      return current as T[];\r\n    }\r\n\r\n    if (!current || typeof current !== \"object\" || !(\"data\" in current)) {\r\n      return [];\r\n    }\r\n\r\n    current = (current as { data?: unknown }).data;\r\n  }\r\n\r\n  return Array.isArray(current) ? (current as T[]) : [];\r\n};\r\n\r\nexport function CorrespondenceForm({ initialData, uuid }: { initialData?: any, uuid?: string }) {\r\n  const router = useRouter();\r\n  const createMutation = useCreateCorrespondence();\r\n  const updateMutation = useUpdateCorrespondence();\r\n\r\n  // Fetch master data for dropdowns\r\n  const { data: projectsData, isLoading: isLoadingProjects } = useProjects();\r\n  const { data: organizations, isLoading: isLoadingOrgs } = useOrganizations();\r\n  const { data: correspondenceTypesData, isLoading: isLoadingTypes } = useCorrespondenceTypes();\r\n  const { data: disciplinesData, isLoading: isLoadingDisciplines } = useDisciplines();\r\n  const projects = extractArrayData<ProjectOption>(projectsData);\r\n  const organizationOptions = extractArrayData<Organization>(organizations);\r\n  const correspondenceTypes = extractArrayData<CorrespondenceTypeOption>(correspondenceTypesData);\r\n  const disciplines = extractArrayData<DisciplineOption>(disciplinesData);\r\n\r\n  // Extract initial values if editing\r\n  const currentRev = initialData?.revisions?.find((r: any) => r.isCurrent) || initialData?.revisions?.[0];\r\n  const defaultValues: Partial<FormData> = {\r\n    projectId: initialData?.project?.uuid || (initialData?.projectId ? String(initialData.projectId) : undefined),\r\n    documentTypeId: initialData?.correspondenceTypeId || undefined,\r\n    disciplineId: initialData?.disciplineId || undefined,\r\n    subject: currentRev?.subject || currentRev?.title || \"\",\r\n    description: currentRev?.description || \"\",\r\n    body: currentRev?.body || \"\",\r\n    remarks: currentRev?.remarks || \"\",\r\n    dueDate: currentRev?.dueDate ? new Date(currentRev.dueDate).toISOString().split('T')[0] : undefined,\r\n    documentDate: currentRev?.documentDate ? new Date(currentRev.documentDate).toISOString().split('T')[0] : undefined,\r\n    issuedDate: currentRev?.issuedDate ? new Date(currentRev.issuedDate).toISOString().split('T')[0] : undefined,\r\n    receivedDate: currentRev?.receivedDate ? new Date(currentRev.receivedDate).toISOString().split('T')[0] : undefined,\r\n    fromOrganizationId: initialData?.originatorId ? String(initialData.originatorId) : undefined,\r\n    // Map initial recipient (TO) - Simplified for now\r\n    toOrganizationId: initialData?.recipients?.find((r: any) => r.recipientType === 'TO')?.recipientOrganizationId\r\n      ? String(initialData.recipients.find((r: any) => r.recipientType === 'TO').recipientOrganizationId)\r\n      : undefined,\r\n    importance: currentRev?.details?.importance || \"NORMAL\",\r\n  };\r\n\r\n  const {\r\n    register,\r\n    handleSubmit,\r\n    setValue,\r\n    watch,\r\n    formState: { errors },\r\n  } = useForm<FormData>({\r\n    // @ts-ignore: Zod version mismatch in monorepo\r\n    resolver: zodResolver(correspondenceSchema) as any,\r\n    defaultValues: defaultValues as FormData,\r\n  });\r\n\r\n  // Watch for controlled inputs\r\n  const projectId = watch(\"projectId\");\r\n  const documentTypeId = watch(\"documentTypeId\");\r\n  const disciplineId = watch(\"disciplineId\");\r\n  const fromOrgId = watch(\"fromOrganizationId\");\r\n  const toOrgId = watch(\"toOrganizationId\");\r\n\r\n  const onSubmit = (data: FormData) => {\r\n    const payload: CreateCorrespondenceDto = {\r\n      projectId: data.projectId,\r\n      typeId: data.documentTypeId,\r\n      disciplineId: data.disciplineId,\r\n      subject: data.subject,\r\n      description: data.description,\r\n      body: data.body,\r\n      remarks: data.remarks,\r\n      dueDate: data.dueDate ? new Date(data.dueDate).toISOString() : undefined,\r\n      documentDate: data.documentDate ? new Date(data.documentDate).toISOString() : undefined,\r\n      issuedDate: data.issuedDate ? new Date(data.issuedDate).toISOString() : undefined,\r\n      receivedDate: data.receivedDate ? new Date(data.receivedDate).toISOString() : undefined,\r\n      originatorId: data.fromOrganizationId,\r\n      recipients: [\r\n        { organizationId: data.toOrganizationId, type: 'TO' }\r\n      ],\r\n      details: {\r\n        importance: data.importance\r\n      },\r\n    };\r\n\r\n    if (uuid && initialData) {\r\n       // UPDATE Mode\r\n       updateMutation.mutate({ uuid, data: payload }, {\r\n         onSuccess: () => router.push(`/correspondences/${uuid}`)\r\n       });\r\n    } else {\r\n       // CREATE Mode\r\n       createMutation.mutate(payload, {\r\n         onSuccess: () => router.push(\"/correspondences\"),\r\n       });\r\n    }\r\n  };\r\n\r\n  const isPending = createMutation.isPending || updateMutation.isPending;\r\n\r\n  // -- Preview Logic --\r\n  const [preview, setPreview] = useState<{ number: string; isDefaultTemplate: boolean } | null>(null);\r\n\r\n  useEffect(() => {\r\n    if (!projectId || !documentTypeId || !fromOrgId || !toOrgId) {\r\n       setPreview(null);\r\n       return;\r\n    }\r\n\r\n    const fetchPreview = async () => {\r\n       try {\r\n         const res = await numberingApi.previewNumber({\r\n             projectId,\r\n             correspondenceTypeId: documentTypeId,\r\n             disciplineId,\r\n             originatorOrganizationId: fromOrgId,\r\n             recipientOrganizationId: toOrgId\r\n         });\r\n         setPreview({ number: res.previewNumber, isDefaultTemplate: res.isDefault });\r\n       } catch (err) {\r\n         setPreview(null);\r\n       }\r\n    };\r\n\r\n    const timer = setTimeout(fetchPreview, 500);\r\n    return () => clearTimeout(timer);\r\n  }, [projectId, documentTypeId, disciplineId, fromOrgId, toOrgId]);\r\n\r\n\r\n\r\n  return (\r\n    <form onSubmit={handleSubmit(onSubmit)} className=\"max-w-3xl space-y-6\">\r\n      {/* Existing Document Number (Read Only) */}\r\n      {initialData?.correspondenceNumber && (\r\n        <div className=\"space-y-2\">\r\n          <Label>Current Document Number</Label>\r\n          <div className=\"flex items-center gap-2\">\r\n             <Input value={initialData.correspondenceNumber} disabled readOnly className=\"bg-muted font-mono font-bold text-lg w-full\" />\r\n             {preview && preview.number !== initialData.correspondenceNumber && (\r\n                 <span className=\"text-xs text-amber-600 font-semibold whitespace-nowrap px-2\">\r\n                    Start Change Detected\r\n                 </span>\r\n             )}\r\n          </div>\r\n        </div>\r\n      )}\r\n\r\n      {/* Preview Section */}\r\n      {preview && (\r\n        <div className={`p-4 rounded-md border ${preview.number !== initialData?.correspondenceNumber ? 'bg-amber-50 border-amber-200' : 'bg-muted border-border'}`}>\r\n           <p className=\"text-sm font-semibold mb-1 flex items-center gap-2\">\r\n              {initialData?.correspondenceNumber ? \"New Document Number (Preview)\" : \"Document Number Preview\"}\r\n              {preview.number !== initialData?.correspondenceNumber && initialData?.correspondenceNumber && (\r\n                  <span className=\"text-[10px] bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full border border-amber-200\">\r\n                      Will Update\r\n                  </span>\r\n              )}\r\n           </p>\r\n           <div className=\"flex items-center gap-3\">\r\n             <span className={`text-xl font-bold font-mono tracking-wide ${preview.number !== initialData?.correspondenceNumber ? 'text-amber-700' : 'text-primary'}`}>\r\n                {preview.number}\r\n             </span>\r\n             {preview.isDefaultTemplate && (\r\n                <span className=\"text-[10px] uppercase font-semibold px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800 border border-yellow-200\">\r\n                   Default Template\r\n                </span>\r\n             )}\r\n           </div>\r\n           {preview.number !== initialData?.correspondenceNumber && initialData?.correspondenceNumber && (\r\n              <p className=\"text-xs text-muted-foreground mt-2\">\r\n                 * The document number will be regenerated because critical fields were changed.\r\n              </p>\r\n           )}\r\n        </div>\r\n      )}\r\n\r\n      {/* Document Metadata Section */}\r\n      <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\r\n        {/* Project Dropdown */}\r\n        <div className=\"space-y-2\">\r\n          <Label>Project *</Label>\r\n          <Select\r\n            onValueChange={(v) => setValue(\"projectId\", v)}\r\n            value={projectId || undefined}\r\n            disabled={isLoadingProjects}\r\n          >\r\n            <SelectTrigger>\r\n              <SelectValue placeholder={isLoadingProjects ? \"Loading...\" : \"Select Project\"} />\r\n            </SelectTrigger>\r\n            <SelectContent>\r\n              {projects.map((p) => (\r\n                <SelectItem key={p.uuid || String(p.id)} value={p.uuid || String(p.id)}>\r\n                  {p.projectName} ({p.projectCode})\r\n                </SelectItem>\r\n              ))}\r\n            </SelectContent>\r\n          </Select>\r\n          {errors.projectId && (\r\n            <p className=\"text-sm text-destructive\">{errors.projectId.message}</p>\r\n          )}\r\n        </div>\r\n\r\n        {/* Document Type Dropdown */}\r\n        <div className=\"space-y-2\">\r\n          <Label>Document Type *</Label>\r\n          <Select\r\n            onValueChange={(v) => setValue(\"documentTypeId\", Number(v))}\r\n            value={documentTypeId ? String(documentTypeId) : undefined}\r\n            disabled={isLoadingTypes}\r\n          >\r\n            <SelectTrigger>\r\n              <SelectValue placeholder={isLoadingTypes ? \"Loading...\" : \"Select Type\"} />\r\n            </SelectTrigger>\r\n            <SelectContent>\r\n              {correspondenceTypes.map((t) => (\r\n                <SelectItem key={t.id} value={String(t.id)}>\r\n                  {t.typeName} ({t.typeCode})\r\n                </SelectItem>\r\n              ))}\r\n            </SelectContent>\r\n          </Select>\r\n          {errors.documentTypeId && (\r\n            <p className=\"text-sm text-destructive\">{errors.documentTypeId.message}</p>\r\n          )}\r\n        </div>\r\n\r\n        {/* Discipline Dropdown (Optional) */}\r\n        <div className=\"space-y-2\">\r\n          <Label>Discipline</Label>\r\n          <Select\r\n            onValueChange={(v) => setValue(\"disciplineId\", v ? Number(v) : undefined)}\r\n            value={disciplineId ? String(disciplineId) : undefined}\r\n            disabled={isLoadingDisciplines}\r\n          >\r\n            <SelectTrigger>\r\n              <SelectValue placeholder={isLoadingDisciplines ? \"Loading...\" : \"Select Discipline (Optional)\"} />\r\n            </SelectTrigger>\r\n            <SelectContent>\r\n              {disciplines.map((d) => (\r\n                <SelectItem key={d.id} value={String(d.id)}>\r\n                  {d.codeNameEn || d.disciplineCode}\r\n                </SelectItem>\r\n              ))}\r\n            </SelectContent>\r\n          </Select>\r\n        </div>\r\n      </div>\r\n\r\n      {/* Subject */}\r\n      <div className=\"space-y-2\">\r\n        <Label htmlFor=\"subject\">Subject *</Label>\r\n        <Input id=\"subject\" {...register(\"subject\")} placeholder=\"Enter subject\" />\r\n        {errors.subject && (\r\n          <p className=\"text-sm text-destructive\">{errors.subject.message}</p>\r\n        )}\r\n      </div>\r\n\r\n       {/* Body */}\r\n       <div className=\"space-y-2\">\r\n        <Label htmlFor=\"body\">Body (Content)</Label>\r\n        <Textarea\r\n          id=\"body\"\r\n          {...register(\"body\")}\r\n          rows={6}\r\n          placeholder=\"Enter letter content...\"\r\n        />\r\n      </div>\r\n\r\n      {/* Date Fields */}\r\n      <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4\">\r\n          <div className=\"space-y-2\">\r\n            <Label htmlFor=\"documentDate\">Document Date</Label>\r\n            <Input \r\n               id=\"documentDate\" \r\n               type=\"date\" \r\n               {...register(\"documentDate\")} \r\n               onChange={(e) => {\r\n                 const val = e.target.value;\r\n                 setValue(\"documentDate\", val, { shouldValidate: true, shouldDirty: true });\r\n                 if (val) {\r\n                   setValue(\"issuedDate\", val, { shouldValidate: true, shouldDirty: true });\r\n                   setValue(\"receivedDate\", val, { shouldValidate: true, shouldDirty: true });\r\n                   const d = new Date(val);\r\n                   d.setDate(d.getDate() + 7);\r\n                   setValue(\"dueDate\", d.toISOString().split('T')[0], { shouldValidate: true, shouldDirty: true });\r\n                 }\r\n               }}\r\n            />\r\n          </div>\r\n          <div className=\"space-y-2\">\r\n            <Label htmlFor=\"issuedDate\">Issued Date</Label>\r\n            <Input id=\"issuedDate\" type=\"date\" {...register(\"issuedDate\")} />\r\n          </div>\r\n          <div className=\"space-y-2\">\r\n            <Label htmlFor=\"receivedDate\">Received Date</Label>\r\n            <Input \r\n               id=\"receivedDate\" \r\n               type=\"date\" \r\n               {...register(\"receivedDate\")} \r\n               onChange={(e) => {\r\n                 const val = e.target.value;\r\n                 setValue(\"receivedDate\", val, { shouldValidate: true, shouldDirty: true });\r\n                 if (val) {\r\n                   const d = new Date(val);\r\n                   d.setDate(d.getDate() + 7);\r\n                   setValue(\"dueDate\", d.toISOString().split('T')[0], { shouldValidate: true, shouldDirty: true });\r\n                 }\r\n               }}\r\n            />\r\n          </div>\r\n          <div className=\"space-y-2\">\r\n            <Label htmlFor=\"dueDate\">Due Date</Label>\r\n            <Input id=\"dueDate\" type=\"date\" {...register(\"dueDate\")} />\r\n          </div>\r\n      </div>\r\n\r\n      {/* Remarks */}\r\n      <div className=\"grid grid-cols-1 gap-4\">\r\n          <div className=\"space-y-2\">\r\n            <Label htmlFor=\"remarks\">Remarks</Label>\r\n            <Input id=\"remarks\" {...register(\"remarks\")} placeholder=\"Optional remarks\" />\r\n          </div>\r\n      </div>\r\n\r\n      {/* Description */}\r\n      <div className=\"space-y-2\">\r\n        <Label htmlFor=\"description\">Description (Internal Note)</Label>\r\n        <Textarea\r\n          id=\"description\"\r\n          {...register(\"description\")}\r\n          rows={2}\r\n          placeholder=\"Enter description...\"\r\n        />\r\n      </div>\r\n\r\n      {/* Organizations */}\r\n      <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n        <div className=\"space-y-2\">\r\n          <Label>From Organization *</Label>\r\n          <Select\r\n            onValueChange={(v) => setValue(\"fromOrganizationId\", v)}\r\n            value={fromOrgId || undefined}\r\n            disabled={isLoadingOrgs}\r\n          >\r\n            <SelectTrigger>\r\n              <SelectValue placeholder={isLoadingOrgs ? \"Loading...\" : \"Select Organization\"} />\r\n            </SelectTrigger>\r\n            <SelectContent>\r\n              {organizationOptions.map((org) => (\r\n                <SelectItem key={org.uuid} value={org.uuid}>\r\n                  {org.organizationName} ({org.organizationCode})\r\n                </SelectItem>\r\n              ))}\r\n            </SelectContent>\r\n          </Select>\r\n          {errors.fromOrganizationId && (\r\n            <p className=\"text-sm text-destructive\">{errors.fromOrganizationId.message}</p>\r\n          )}\r\n        </div>\r\n\r\n        <div className=\"space-y-2\">\r\n          <Label>To Organization *</Label>\r\n          <Select\r\n            onValueChange={(v) => setValue(\"toOrganizationId\", v)}\r\n            value={toOrgId || undefined}\r\n            disabled={isLoadingOrgs}\r\n          >\r\n            <SelectTrigger>\r\n              <SelectValue placeholder={isLoadingOrgs ? \"Loading...\" : \"Select Organization\"} />\r\n            </SelectTrigger>\r\n            <SelectContent>\r\n              {organizationOptions.map((org) => (\r\n                <SelectItem key={org.uuid} value={org.uuid}>\r\n                  {org.organizationName} ({org.organizationCode})\r\n                </SelectItem>\r\n              ))}\r\n            </SelectContent>\r\n          </Select>\r\n          {errors.toOrganizationId && (\r\n            <p className=\"text-sm text-destructive\">{errors.toOrganizationId.message}</p>\r\n          )}\r\n        </div>\r\n      </div>\r\n\r\n      {/* Importance */}\r\n      <div className=\"space-y-2\">\r\n        <Label>Importance</Label>\r\n        <div className=\"flex gap-6 mt-2\">\r\n          <label className=\"flex items-center space-x-2 cursor-pointer\">\r\n            <input\r\n              type=\"radio\"\r\n              value=\"NORMAL\"\r\n              {...register(\"importance\")}\r\n              className=\"accent-primary\"\r\n            />\r\n            <span>Normal</span>\r\n          </label>\r\n          <label className=\"flex items-center space-x-2 cursor-pointer\">\r\n            <input\r\n              type=\"radio\"\r\n              value=\"HIGH\"\r\n              {...register(\"importance\")}\r\n              className=\"accent-primary\"\r\n            />\r\n            <span>High</span>\r\n          </label>\r\n          <label className=\"flex items-center space-x-2 cursor-pointer\">\r\n            <input\r\n              type=\"radio\"\r\n              value=\"URGENT\"\r\n              {...register(\"importance\")}\r\n              className=\"accent-primary\"\r\n            />\r\n            <span>Urgent</span>\r\n          </label>\r\n        </div>\r\n      </div>\r\n\r\n      {/* Attachments (only for new documents) */}\r\n      {!initialData && (\r\n        <div className=\"space-y-2\">\r\n          <Label>Attachments</Label>\r\n          <FileUploadZone\r\n            onFilesChanged={(files) => setValue(\"attachments\", files)}\r\n            multiple\r\n            accept={[\".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\", \".jpg\", \".png\"]}\r\n          />\r\n        </div>\r\n      )}\r\n\r\n      {/* Actions */}\r\n      <div className=\"flex justify-end gap-4 pt-6 border-t\">\r\n        <Button type=\"button\" variant=\"outline\" onClick={() => router.back()}>\r\n          Cancel\r\n        </Button>\r\n        <Button type=\"submit\" disabled={isPending}>\r\n          {isPending && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n          {uuid ? \"Update Correspondence\" : \"Create Correspondence\"}\r\n        </Button>\r\n      </div>\r\n    </form>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\correspondences\\list.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\custom\\file-upload-zone.tsx",
    "messages": [
      {
        "ruleId": "react-hooks/exhaustive-deps",
        "severity": 1,
        "message": "React Hook useCallback has a missing dependency: 'validateFile'. Either include it or remove the dependency array.",
        "line": 88,
        "column": 5,
        "nodeType": "ArrayExpression",
        "endLine": 88,
        "endColumn": 48,
        "suggestions": [
          {
            "desc": "Update the dependencies array to be: [validateFile, multiple, onFilesChanged]",
            "fix": { "range": [2956, 2999], "text": "[validateFile, multiple, onFilesChanged]" }
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 1,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: components/custom/file-upload-zone.tsx\r\n\r\n\"use client\";\r\n\r\nimport React, { useCallback, useState } from \"react\";\r\nimport { UploadCloud, File as FileIcon, X, AlertTriangle, CheckCircle } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\r\n\r\nexport interface FileWithMeta extends File {\r\n  preview?: string;\r\n  validationError?: string;\r\n}\r\n\r\ninterface FileUploadZoneProps {\r\n  /** Callback เมื่อไฟล์มีการเปลี่ยนแปลง */\r\n  onFilesChanged: (files: FileWithMeta[]) => void;\r\n  /** ประเภทไฟล์ที่ยอมรับ (เช่น .pdf, .dwg) */\r\n  accept?: string[];\r\n  /** ขนาดไฟล์สูงสุด (Bytes) Default: 50MB */\r\n  maxSize?: number;\r\n  /** อนุญาตให้อัปโหลดหลายไฟล์หรือไม่ */\r\n  multiple?: boolean;\r\n  /** ไฟล์ที่มีอยู่เดิม (ถ้ามี) */\r\n  initialFiles?: FileWithMeta[];\r\n  className?: string;\r\n}\r\n\r\n/**\r\n * Helper: แปลง Bytes เป็นหน่วยที่อ่านง่าย\r\n */\r\nconst formatBytes = (bytes: number, decimals = 2) => {\r\n  if (!Number(bytes)) return \"0 Bytes\";\r\n  const k = 1024;\r\n  const dm = decimals < 0 ? 0 : decimals;\r\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\r\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\r\n};\r\n\r\n/**\r\n * FileUploadZone Component\r\n * รองรับ Drag & Drop, Validation และแสดงรายการไฟล์\r\n */\r\nexport function FileUploadZone({\r\n  onFilesChanged,\r\n  accept = [\".pdf\", \".dwg\", \".docx\", \".xlsx\", \".zip\"],\r\n  maxSize = 50 * 1024 * 1024, // 50MB Default\r\n  multiple = true,\r\n  initialFiles = [],\r\n  className,\r\n}: FileUploadZoneProps) {\r\n  const [files, setFiles] = useState<FileWithMeta[]>(initialFiles);\r\n  const [isDragging, setIsDragging] = useState(false);\r\n\r\n  // ตรวจสอบไฟล์\r\n  const validateFile = (file: File): string | undefined => {\r\n    // 1. Check Size\r\n    if (file.size > maxSize) {\r\n      return `ขนาดไฟล์เกินกำหนด (${formatBytes(maxSize)})`;\r\n    }\r\n    // 2. Check Type (Extension based validation for simplicity on client)\r\n    const fileExtension = \".\" + file.name.split(\".\").pop()?.toLowerCase();\r\n    if (accept.length > 0 && !accept.includes(fileExtension)) {\r\n      return `ประเภทไฟล์ไม่รองรับ (อนุญาต: ${accept.join(\", \")})`;\r\n    }\r\n    return undefined;\r\n  };\r\n\r\n  const handleFileSelect = useCallback(\r\n    (newFiles: File[]) => {\r\n      const processedFiles: FileWithMeta[] = newFiles.map((file) => {\r\n        const error = validateFile(file);\r\n        // สร้าง Object ใหม่เพื่อไม่ให้กระทบ File object เดิม\r\n        const fileWithMeta = new File([file], file.name, { type: file.type }) as FileWithMeta;\r\n        fileWithMeta.validationError = error;\r\n        return fileWithMeta;\r\n      });\r\n\r\n      setFiles((prev) => {\r\n        const updated = multiple ? [...prev, ...processedFiles] : processedFiles;\r\n        onFilesChanged(updated);\r\n        return updated;\r\n      });\r\n    },\r\n    [maxSize, accept, multiple, onFilesChanged]\r\n  );\r\n\r\n  // Drag Events\r\n  const onDragOver = (e: React.DragEvent) => {\r\n    e.preventDefault();\r\n    setIsDragging(true);\r\n  };\r\n  const onDragLeave = (e: React.DragEvent) => {\r\n    e.preventDefault();\r\n    setIsDragging(false);\r\n  };\r\n  const onDrop = (e: React.DragEvent) => {\r\n    e.preventDefault();\r\n    setIsDragging(false);\r\n    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\r\n      handleFileSelect(Array.from(e.dataTransfer.files));\r\n    }\r\n  };\r\n\r\n  const removeFile = (indexToRemove: number) => {\r\n    setFiles((prev) => {\r\n      const updated = prev.filter((_, index) => index !== indexToRemove);\r\n      onFilesChanged(updated);\r\n      return updated;\r\n    });\r\n  };\r\n\r\n  return (\r\n    <div className={cn(\"w-full space-y-4\", className)}>\r\n      {/* Drop Zone */}\r\n      <div\r\n        className={cn(\r\n          \"border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer flex flex-col items-center justify-center gap-2\",\r\n          isDragging\r\n            ? \"border-primary bg-primary/10\"\r\n            : \"border-muted-foreground/25 hover:border-primary/50\",\r\n          \"h-48\"\r\n        )}\r\n        onDragOver={onDragOver}\r\n        onDragLeave={onDragLeave}\r\n        onDrop={onDrop}\r\n        onClick={() => document.getElementById(\"file-input\")?.click()}\r\n      >\r\n        <input\r\n          id=\"file-input\"\r\n          type=\"file\"\r\n          className=\"hidden\"\r\n          multiple={multiple}\r\n          accept={accept.join(\",\")}\r\n          onChange={(e) => {\r\n            if (e.target.files) handleFileSelect(Array.from(e.target.files));\r\n          }}\r\n        />\r\n        <div className=\"p-3 bg-muted rounded-full\">\r\n          <UploadCloud className=\"w-8 h-8 text-muted-foreground\" />\r\n        </div>\r\n        <div className=\"space-y-1\">\r\n          <p className=\"text-sm font-medium\">\r\n            คลิกเพื่อเลือกไฟล์ หรือ ลากไฟล์มาวางที่นี่\r\n          </p>\r\n          <p className=\"text-xs text-muted-foreground\">\r\n            รองรับ: {accept.join(\", \")} (สูงสุด {formatBytes(maxSize)})\r\n          </p>\r\n        </div>\r\n      </div>\r\n\r\n      {/* File List */}\r\n      {files.length > 0 && (\r\n        <ScrollArea className=\"h-[200px] w-full rounded-md border p-4\">\r\n          <div className=\"space-y-3\">\r\n            {files.map((file, index) => (\r\n              <div\r\n                key={`${file.name}-${index}`}\r\n                className=\"flex items-center justify-between p-3 bg-card rounded-md border shadow-sm group\"\r\n              >\r\n                <div className=\"flex items-center gap-3 overflow-hidden\">\r\n                  <div className=\"p-2 bg-primary/10 rounded-md shrink-0\">\r\n                    <FileIcon className=\"w-5 h-5 text-primary\" />\r\n                  </div>\r\n                  <div className=\"min-w-0\">\r\n                    <p className=\"text-sm font-medium truncate max-w-[200px] sm:max-w-md\">\r\n                      {file.name}\r\n                    </p>\r\n                    <div className=\"flex items-center gap-2\">\r\n                        <span className=\"text-xs text-muted-foreground\">\r\n                        {formatBytes(file.size)}\r\n                        </span>\r\n                        {file.validationError ? (\r\n                             <Badge variant=\"destructive\" className=\"text-[10px] px-1 h-5 flex gap-1\">\r\n                                <AlertTriangle className=\"w-3 h-3\" /> {file.validationError}\r\n                             </Badge>\r\n                        ) : (\r\n                            <Badge variant=\"outline\" className=\"text-[10px] px-1 h-5 text-green-600 bg-green-50 border-green-200 flex gap-1\">\r\n                                <CheckCircle className=\"w-3 h-3\" /> Ready\r\n                            </Badge>\r\n                        )}\r\n                    </div>\r\n                  </div>\r\n                </div>\r\n                <Button\r\n                  variant=\"ghost\"\r\n                  size=\"icon\"\r\n                  className=\"shrink-0 text-muted-foreground hover:text-destructive\"\r\n                  onClick={() => removeFile(index)}\r\n                >\r\n                  <X className=\"w-4 h-4\" />\r\n                </Button>\r\n              </div>\r\n            ))}\r\n          </div>\r\n        </ScrollArea>\r\n      )}\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\custom\\workflow-visualizer.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\dashboard\\pending-tasks.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\dashboard\\quick-actions.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\dashboard\\recent-activity.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\dashboard\\stats-cards.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\documents\\common\\server-data-table.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\drawings\\card.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\drawings\\columns.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\drawings\\list.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\drawings\\revision-history.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\drawings\\upload-form.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\dashboard-shell.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\global-search.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\header.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\navbar.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\notifications-dropdown.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\sidebar.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\user-menu.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\layout\\user-nav.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\audit-logs-table.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 26,
        "column": 16,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 26,
        "endColumn": 21
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\nimport { format } from \"date-fns\";\n\nexport function AuditLogsTable() {\n  const [logs, setLogs] = useState<unknown[]>([]); // Replace with AuditLog type\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    async function fetchLogs() {\n      try {\n        const data = await documentNumberingService.getMetrics(); // Using metrics endpoint for now as it contains logs\n        if (data && data.audit) {\n           setLogs(data.audit);\n        }\n      } catch (error) {\n        // Failed to fetch audit logs - empty state shown\n      } finally {\n        setLoading(false);\n      }\n    }\n    fetchLogs();\n  }, []);\n\n  if (loading) return <div>Loading logs...</div>;\n\n  return (\n    <div className=\"rounded-md border\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>Time</TableHead>\n            <TableHead>Operation</TableHead>\n            <TableHead>Number</TableHead>\n            <TableHead>User</TableHead>\n            <TableHead>Status</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {logs.length === 0 ? (\n            <TableRow>\n              <TableCell colSpan={5} className=\"text-center\">No logs found.</TableCell>\n            </TableRow>\n          ) : (\n            logs.map((log) => (\n              <TableRow key={log.id}>\n                <TableCell>{format(new Date(log.createdAt), \"yyyy-MM-dd HH:mm:ss\")}</TableCell>\n                <TableCell>{log.operation}</TableCell>\n                <TableCell>{log.generatedNumber}</TableCell>\n                <TableCell>{log.createdBy || \"System\"}</TableCell>\n                <TableCell>{log.status}</TableCell>\n              </TableRow>\n            ))\n          )}\n        </TableBody>\n      </Table>\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\bulk-import-form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 31,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 31,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { toast } from \"sonner\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\n\nexport function BulkImportForm({ projectId = 1 }: { projectId?: number | string }) {\n  const [file, setFile] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    if (e.target.files) {\n      setFile(e.target.files[0]);\n    }\n  };\n\n  const handleUpload = async () => {\n    if (!file) return;\n    setLoading(true);\n    try {\n      const formData = new FormData();\n      formData.append(\"file\", file);\n      formData.append(\"projectId\", projectId.toString());\n\n      await documentNumberingService.bulkImport(formData);\n      toast.success(\"Bulk import initiated. Check audit logs for progress.\");\n      setFile(null);\n    } catch (_error) {\n      toast.error(\"Failed to import numbers.\");\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"border p-4 rounded-md space-y-4\">\n       <h3 className=\"text-lg font-medium\">Bulk Import Numbers</h3>\n       <p className=\"text-sm text-gray-500\">Import legacy numbers via CSV to reserve them in the system.</p>\n\n       <div className=\"grid w-full max-w-sm items-center gap-1.5\">\n          <Label htmlFor=\"csv-file\">CSV File</Label>\n          <Input id=\"csv-file\" type=\"file\" accept=\".csv,.xlsx\" onChange={handleFileChange} />\n       </div>\n\n       <Button onClick={handleUpload} disabled={!file || loading}>\n         {loading ? \"Importing...\" : \"Upload & Import\"}\n       </Button>\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\cancel-number-form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 46,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 46,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 32,
        "column": 42,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 32,
        "endColumn": 45,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [968, 971], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [968, 971], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ],
        "suppressions": [{ "kind": "directive", "justification": "zod 4 + @hookform/resolvers compat" }]
      }
    ],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport * as z from \"zod\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { toast } from \"sonner\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\nimport { CancelNumberDto } from \"@/types/dto/numbering.dto\";\nimport { useState } from \"react\";\n\nconst formSchema = z.object({\n  documentNumber: z.string().min(3, \"Document Number is required\"),\n  reason: z.string().min(5, \"Reason must be at least 5 characters\"),\n});\n\ntype CancelNumberFormData = z.infer<typeof formSchema>;\n\nexport function CancelNumberForm() {\n  const [loading, setLoading] = useState(false);\n\n  const form = useForm<CancelNumberFormData>({\n    resolver: zodResolver(formSchema) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- zod 4 + @hookform/resolvers compat\n    defaultValues: {\n      documentNumber: \"\",\n      reason: \"\",\n    },\n  });\n\n  async function onSubmit(values: CancelNumberFormData) {\n    setLoading(true);\n    try {\n      const dto: CancelNumberDto = values;\n      await documentNumberingService.cancelNumber(dto);\n      toast.success(\"Number cancelled successfully.\");\n      form.reset();\n    } catch (_error) {\n      toast.error(\"Failed to cancel number. It may not exist or is already cancelled.\");\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4 border p-4 rounded-md\">\n        <h3 className=\"text-lg font-medium\">Cancel Number</h3>\n        <p className=\"text-sm text-gray-500\">Permanently cancel a number (e.g. if generated by mistake). It cannot be reused.</p>\n\n        <FormField control={form.control} name=\"documentNumber\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Document Number</FormLabel>\n            <FormControl>\n              <Input placeholder=\"e.g. LCB3-COR-GGL-2025-0001\" {...field} />\n            </FormControl>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <FormField control={form.control} name=\"reason\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Reason</FormLabel>\n            <FormControl>\n              <Input placeholder=\"Reason for cancellation...\" {...field} />\n            </FormControl>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <Button type=\"submit\" variant=\"destructive\" disabled={loading}>\n          {loading ? \"Cancelling...\" : \"Cancel Number\"}\n        </Button>\n      </form>\n    </Form>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\manual-override-form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 58,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 58,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 36,
        "column": 42,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 36,
        "endColumn": 45,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1317, 1320], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1317, 1320], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ],
        "suppressions": [{ "kind": "directive", "justification": "zod 4 + @hookform/resolvers compat" }]
      }
    ],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport * as z from \"zod\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n  FormDescription\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { toast } from \"sonner\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\nimport { ManualOverrideDto } from \"@/types/dto/numbering.dto\";\nimport { useState } from \"react\";\n\nconst formSchema = z.object({\n  projectId: z.coerce.number().min(1, \"Project is required\"),\n  originatorOrganizationId: z.coerce.number().min(1, \"Originator is required\"),\n  recipientOrganizationId: z.coerce.number().min(1, \"Recipient is required\"),\n  correspondenceTypeId: z.coerce.number().min(1, \"Type is required\"),\n  newLastNumber: z.coerce.number().min(1, \"New number is required\"),\n  reason: z.string().min(5, \"Reason must be at least 5 characters\"),\n  resetScope: z.string().optional()\n});\n\nexport function ManualOverrideForm({ projectId = 1 }: { projectId?: number | string }) {\n  const [loading, setLoading] = useState(false);\n\n  const form = useForm<z.infer<typeof formSchema>>({\n    resolver: zodResolver(formSchema) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- zod 4 + @hookform/resolvers compat\n    defaultValues: {\n      projectId: Number(projectId),\n      originatorOrganizationId: 0,\n      recipientOrganizationId: 0,\n      correspondenceTypeId: 0,\n      newLastNumber: 0,\n      reason: \"\",\n      resetScope: \"YEAR_2025\" // Example, should be dynamic or selected\n    },\n  });\n\n  async function onSubmit(values: z.infer<typeof formSchema>) {\n    setLoading(true);\n    try {\n      const dto: ManualOverrideDto = {\n        ...values,\n        resetScope: values.resetScope || \"YEAR_\" + new Date().getFullYear()\n      };\n      await documentNumberingService.manualOverride(dto);\n      toast.success(\"Manual override applied successfully.\");\n      form.reset();\n    } catch (_error) {\n      toast.error(\"Failed to apply override.\");\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4 border p-4 rounded-md mt-4\">\n        <h3 className=\"text-lg font-medium\">Manual Override Sequence</h3>\n        <p className=\"text-sm text-gray-500\">Careful: This updates the LAST generated number. Next number will receive +1.</p>\n\n        <div className=\"grid grid-cols-2 gap-4\">\n           {/* Allow simple text input for IDs for now, ideally Selects from Master Data */}\n           <FormField control={form.control} name=\"projectId\" render={({ field }) => (\n             <FormItem>\n               <FormLabel>Project ID</FormLabel>\n               <FormControl><Input type=\"number\" {...field} /></FormControl>\n               <FormMessage />\n             </FormItem>\n           )} />\n           <FormField control={form.control} name=\"correspondenceTypeId\" render={({ field }) => (\n             <FormItem>\n               <FormLabel>Type ID</FormLabel>\n               <FormControl><Input type=\"number\" {...field} /></FormControl>\n               <FormMessage />\n             </FormItem>\n           )} />\n           <FormField control={form.control} name=\"originatorOrganizationId\" render={({ field }) => (\n             <FormItem>\n               <FormLabel>Originator Org ID</FormLabel>\n               <FormControl><Input type=\"number\" {...field} /></FormControl>\n               <FormMessage />\n             </FormItem>\n           )} />\n           <FormField control={form.control} name=\"recipientOrganizationId\" render={({ field }) => (\n             <FormItem>\n               <FormLabel>Recipient Org ID</FormLabel>\n               <FormControl><Input type=\"number\" {...field} /></FormControl>\n               <FormMessage />\n             </FormItem>\n           )} />\n        </div>\n\n        <FormField control={form.control} name=\"newLastNumber\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Set Last Number To</FormLabel>\n            <FormControl>\n              <Input type=\"number\" {...field} />\n            </FormControl>\n            <FormDescription>\n                If you set 99, the next auto-generated number will be 100.\n            </FormDescription>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <FormField control={form.control} name=\"reason\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Reason</FormLabel>\n            <FormControl>\n              <Input placeholder=\"Why are you overriding?\" {...field} />\n            </FormControl>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <Button type=\"submit\" disabled={loading}>\n          {loading ? \"Applying...\" : \"Apply Override\"}\n        </Button>\n      </form>\n    </Form>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\metrics-dashboard.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 18,
        "column": 16,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 18,
        "endColumn": 21
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Card, CardContent, CardHeader, CardTitle, _CardDescription } from \"@/components/ui/card\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\nimport { NumberingMetrics } from \"@/types/dto/numbering.dto\";\n\nexport function MetricsDashboard() {\n  const [metrics, setMetrics] = useState<Partial<NumberingMetrics>>({});\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    async function fetchMetrics() {\n      try {\n        const data = await documentNumberingService.getMetrics();\n        setMetrics(data);\n      } catch (error) {\n        // Failed to fetch metrics - handled by loading state\n      } finally {\n        setLoading(false);\n      }\n    }\n    fetchMetrics();\n    const interval = setInterval(fetchMetrics, 30000); // Poll every 30s\n    return () => clearInterval(interval);\n  }, []);\n\n  if (loading) return <div>Loading metrics...</div>;\n  if (!metrics) return <div>No metrics available.</div>;\n\n  // Mock data mapping if real data is missing from backend stub\n  const utilization = metrics.audit ? 45 : 0; // Placeholder until backend returns specific metric\n  const generationRate = 120; // Placeholder\n  const lockWaitP95 = 0.05; // Placeholder\n\n  return (\n    <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n      <Card>\n        <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n          <CardTitle className=\"text-sm font-medium\">Generation Rate</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <div className=\"text-2xl font-bold\">{generationRate} /Hr</div>\n          <p className=\"text-xs text-muted-foreground\">+20.1% from last hour</p>\n        </CardContent>\n      </Card>\n\n      <Card>\n        <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n           <CardTitle className=\"text-sm font-medium\">Sequence Utilization</CardTitle>\n        </CardHeader>\n        <CardContent>\n           <div className=\"text-2xl font-bold\">{utilization}%</div>\n           <Progress value={utilization} className=\"mt-2\" />\n           <p className=\"text-xs text-muted-foreground mt-1\">Average capacity used</p>\n        </CardContent>\n      </Card>\n\n      <Card>\n        <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n          <CardTitle className=\"text-sm font-medium\">Lock Wait Time (P95)</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <div className=\"text-2xl font-bold\">{lockWaitP95}s</div>\n          <p className=\"text-xs text-muted-foreground\">Redis distributed lock latency</p>\n        </CardContent>\n      </Card>\n\n      <Card>\n         <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n           <CardTitle className=\"text-sm font-medium\">Recent Errors</CardTitle>\n         </CardHeader>\n         <CardContent>\n           <div className=\"text-2xl font-bold\">{metrics.errors?.length || 0}</div>\n           <p className=\"text-xs text-muted-foreground\">In the last 24 hours</p>\n         </CardContent>\n      </Card>\n    </div>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\sequence-viewer.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\template-editor.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\template-tester.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\numbering\\void-replace-form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 54,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 54,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 36,
        "column": 42,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 36,
        "endColumn": 45,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [1134, 1137], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [1134, 1137], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ],
        "suppressions": [{ "kind": "directive", "justification": "zod 4 + @hookform/resolvers compat" }]
      }
    ],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\n\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport * as z from \"zod\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n  FormDescription\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { toast } from \"sonner\";\nimport { documentNumberingService } from \"@/lib/services/document-numbering.service\";\nimport { VoidReplaceDto } from \"@/types/dto/numbering.dto\";\nimport { useState } from \"react\";\n\nconst formSchema = z.object({\n  documentNumber: z.string().min(3, \"Document Number is required\"),\n  reason: z.string().min(5, \"Reason must be at least 5 characters\"),\n  replace: z.boolean(),\n  projectId: z.number()\n});\n\ntype VoidReplaceFormData = z.infer<typeof formSchema>;\n\nexport function VoidReplaceForm({ projectId = 1 }: { projectId?: number | string }) {\n  const [loading, setLoading] = useState(false);\n\n  const form = useForm<VoidReplaceFormData>({\n    resolver: zodResolver(formSchema) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- zod 4 + @hookform/resolvers compat\n    defaultValues: {\n      documentNumber: \"\",\n      reason: \"\",\n      replace: false,\n      projectId: Number(projectId)\n    },\n  });\n\n  async function onSubmit(values: z.infer<typeof formSchema>) {\n    setLoading(true);\n    try {\n      const dto: VoidReplaceDto = {\n        ...values,\n      };\n      await documentNumberingService.voidAndReplace(dto);\n      toast.success(\"Number voided successfully. \" + (values.replace ? \"Replacement generated.\" : \"\"));\n      form.reset();\n    } catch (_error) {\n      toast.error(\"Failed to void number. Check if it exists.\");\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4 border p-4 rounded-md\">\n        <h3 className=\"text-lg font-medium\">Void & Replace Number</h3>\n        <p className=\"text-sm text-gray-500\">Void a generated number. Useful for skipped numbers or errors.</p>\n\n        <FormField control={form.control} name=\"documentNumber\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Document Number</FormLabel>\n            <FormControl>\n              <Input placeholder=\"e.g. LCB3-COR-GGL-2025-0001\" {...field} />\n            </FormControl>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <FormField control={form.control} name=\"reason\" render={({ field }) => (\n          <FormItem>\n            <FormLabel>Reason</FormLabel>\n            <FormControl>\n              <Input placeholder=\"Reason for voiding...\" {...field} />\n            </FormControl>\n            <FormMessage />\n          </FormItem>\n        )} />\n\n        <FormField control={form.control} name=\"replace\" render={({ field }) => (\n          <FormItem className=\"flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4\">\n            <FormControl>\n              <Checkbox\n                checked={field.value}\n                onCheckedChange={field.onChange}\n              />\n            </FormControl>\n            <div className=\"space-y-1 leading-none\">\n              <FormLabel>\n                Generate Replacement?\n              </FormLabel>\n              <FormDescription>\n                If checked, a new number will be reserved immediately.\n              </FormDescription>\n            </div>\n          </FormItem>\n        )} />\n\n        <Button type=\"submit\" variant=\"destructive\" disabled={loading}>\n          {loading ? \"Processing...\" : \"Void Number\"}\n        </Button>\n      </form>\n    </Form>\n  );\n}\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\rfas\\detail.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\rfas\\form.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_err' is defined but never used.",
        "line": 285,
        "column": 17,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 285,
        "endColumn": 21
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "\"use client\";\r\n\r\nimport { useForm, type SubmitErrorHandler } from \"react-hook-form\";\r\nimport { zodResolver } from \"@hookform/resolvers/zod\";\r\nimport { z } from \"zod\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card } from \"@/components/ui/card\";\r\nimport { Loader2 } from \"lucide-react\";\r\nimport {\r\n  Select,\r\n  SelectContent,\r\n  SelectItem,\r\n  SelectTrigger,\r\n  SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { useRouter } from \"next/navigation\";\r\nimport { useCreateRFA } from \"@/hooks/use-rfa\";\r\nimport { useDrawings } from \"@/hooks/use-drawing\";\r\nimport { useDisciplines, useContracts, useOrganizations } from \"@/hooks/use-master-data\";\r\nimport { useCorrespondenceTypes, useRfaTypes } from \"@/hooks/use-reference-data\";\r\nimport { useProjects } from \"@/hooks/use-projects\";\r\nimport { CreateRfaDto } from \"@/types/dto/rfa/rfa.dto\";\r\nimport { useState, useEffect, type FormEvent } from \"react\";\r\nimport { correspondenceService } from \"@/lib/services/correspondence.service\";\r\n\r\nconst rfaSchema = z.object({\r\n  projectId: z.string().min(1, \"Project is required\"), // ADR-019: UUID\r\n  contractId: z.string().min(1, \"Contract is required\"),\r\n  disciplineId: z.number().min(1, \"Discipline is required\"),\r\n  rfaTypeId: z.number().min(1, \"Type is required\"),\r\n  subject: z.string().min(5, \"Subject must be at least 5 characters\"),\r\n  description: z.string().optional(),\r\n  body: z.string().optional(),\r\n  remarks: z.string().optional(),\r\n  toOrganizationId: z.string().min(1, \"Please select To Organization\"),\r\n  dueDate: z.string().optional(),\r\n  shopDrawingRevisionIds: z.array(z.string()).optional(),\r\n  asBuiltDrawingRevisionIds: z.array(z.string()).optional(),\r\n});\r\n\r\ntype RFAFormData = z.infer<typeof rfaSchema>;\r\n\r\ntype ProjectOption = {\r\n  uuid?: string;\r\n  id?: number;\r\n  projectName?: string;\r\n  projectCode?: string;\r\n};\r\n\r\ntype ContractOption = {\r\n  uuid?: string;\r\n  id?: number;\r\n  contractName?: string;\r\n  name?: string;\r\n  contractCode?: string;\r\n};\r\n\r\ntype DisciplineOption = {\r\n  id: number;\r\n  disciplineCode: string;\r\n  codeNameEn?: string;\r\n  codeNameTh?: string;\r\n};\r\n\r\ntype RfaTypeOption = {\r\n  id: number;\r\n  typeCode?: string;\r\n  typeName?: string;\r\n  typeNameEn?: string;\r\n  typeNameTh?: string;\r\n};\r\n\r\ntype CorrespondenceTypeOption = {\r\n  id: number;\r\n  typeCode?: string;\r\n  typeName?: string;\r\n};\r\n\r\ntype OrganizationOption = {\r\n  uuid?: string;\r\n  id?: number;\r\n  organizationCode?: string;\r\n  organizationName?: string;\r\n};\r\n\r\ntype SelectableDrawingOption = {\r\n  uuid?: string;\r\n  drawingNumber?: string;\r\n  title?: string;\r\n  legacyDrawingNumber?: string;\r\n  currentRevisionUuid?: string;\r\n  currentRevision?: {\r\n    uuid?: string;\r\n    revisionLabel?: string;\r\n    revisionNumber?: number | string;\r\n    title?: string;\r\n    legacyDrawingNumber?: string;\r\n  };\r\n};\r\n\r\nconst extractArrayData = <T,>(value: unknown): T[] => {\r\n  let current: unknown = value;\r\n\r\n  for (let i = 0; i < 5; i += 1) {\r\n    if (Array.isArray(current)) {\r\n      return current as T[];\r\n    }\r\n\r\n    if (!current || typeof current !== \"object\" || !(\"data\" in current)) {\r\n      return [];\r\n    }\r\n\r\n    current = (current as { data?: unknown }).data;\r\n  }\r\n\r\n  return Array.isArray(current) ? (current as T[]) : [];\r\n};\r\n\r\nconst dedupeByKey = <T,>(items: T[], getKey: (item: T) => string | number | undefined): T[] => {\r\n  const seen = new Set<string | number>();\r\n\r\n  return items.filter((item) => {\r\n    const key = getKey(item);\r\n\r\n    if (key === undefined || key === \"\" || seen.has(key)) {\r\n      return false;\r\n    }\r\n\r\n    seen.add(key);\r\n    return true;\r\n  });\r\n};\r\n\r\nconst getOptionValue = (value?: string | number): string | undefined => {\r\n  if (value === undefined || value === null || value === \"\") {\r\n    return undefined;\r\n  }\r\n\r\n  return String(value);\r\n};\r\n\r\nexport function RFAForm() {\r\n  const router = useRouter();\r\n  const createMutation = useCreateRFA();\r\n\r\n  const { data: projectsData, isLoading: isLoadingProjects } = useProjects();\r\n  const projects = dedupeByKey(\r\n    extractArrayData<ProjectOption>(projectsData),\r\n    (project) => project.uuid ?? project.id\r\n  );\r\n  const { data: organizationsData, isLoading: isLoadingOrganizations } = useOrganizations({ isActive: true });\r\n  const organizations = dedupeByKey(\r\n    extractArrayData<OrganizationOption>(organizationsData),\r\n    (organization) => organization.uuid ?? organization.id\r\n  );\r\n  const { data: correspondenceTypesData } = useCorrespondenceTypes();\r\n  const correspondenceTypes = extractArrayData<CorrespondenceTypeOption>(correspondenceTypesData);\r\n  const rfaCorrespondenceType = correspondenceTypes.find(\r\n    (type) => type.typeCode?.toUpperCase() === \"RFA\"\r\n  );\r\n\r\n  const {\r\n    register,\r\n    handleSubmit,\r\n    setValue,\r\n    setError,\r\n    clearErrors,\r\n    watch,\r\n    formState: { errors },\r\n  } = useForm<RFAFormData>({\r\n    resolver: zodResolver(rfaSchema),\r\n    defaultValues: {\r\n      projectId: \"\",\r\n      contractId: \"\",\r\n      disciplineId: 0,\r\n      rfaTypeId: 0,\r\n      subject: \"\",\r\n      description: \"\",\r\n      body: \"\",\r\n      remarks: \"\",\r\n      toOrganizationId: \"\",\r\n      dueDate: \"\",\r\n      shopDrawingRevisionIds: [],\r\n      asBuiltDrawingRevisionIds: [],\r\n    },\r\n  });\r\n\r\n  const selectedProjectId = watch(\"projectId\");\r\n  const { data: contractsData, isLoading: isLoadingContracts } = useContracts(selectedProjectId);\r\n  const contracts = dedupeByKey(\r\n    extractArrayData<ContractOption>(contractsData),\r\n    (contract) => contract.uuid ?? contract.id\r\n  );\r\n\r\n  const selectedContractId = watch(\"contractId\");\r\n  const { data: disciplinesData, isLoading: isLoadingDisciplines } = useDisciplines(selectedContractId);\r\n  const disciplines = dedupeByKey(extractArrayData<DisciplineOption>(disciplinesData), (discipline) => discipline.id);\r\n  const { data: rfaTypesData, isLoading: isLoadingRfaTypes } = useRfaTypes(selectedContractId);\r\n  const rfaTypes = dedupeByKey(extractArrayData<RfaTypeOption>(rfaTypesData), (rfaType) => rfaType.id);\r\n  const [shopDrawingSearch, setShopDrawingSearch] = useState(\"\");\r\n  const [shopDrawingPage, setShopDrawingPage] = useState(1);\r\n  const { data: shopDrawingsData, isLoading: isLoadingShopDrawings } = useDrawings(\"SHOP\", {\r\n    projectUuid: selectedProjectId || \"\",\r\n    search: shopDrawingSearch,\r\n    page: shopDrawingPage,\r\n    limit: 10,\r\n  });\r\n  const shopDrawings = dedupeByKey(\r\n    extractArrayData<SelectableDrawingOption>(shopDrawingsData),\r\n    (drawing) => drawing.currentRevisionUuid ?? drawing.currentRevision?.uuid ?? drawing.uuid\r\n  );\r\n\r\n  const [asBuiltDrawingSearch, setAsBuiltDrawingSearch] = useState(\"\");\r\n  const [asBuiltDrawingPage, setAsBuiltDrawingPage] = useState(1);\r\n  const { data: asBuiltDrawingsData, isLoading: isLoadingAsBuiltDrawings } = useDrawings(\"AS_BUILT\", {\r\n    projectUuid: selectedProjectId || \"\",\r\n    search: asBuiltDrawingSearch,\r\n    page: asBuiltDrawingPage,\r\n    limit: 10,\r\n  });\r\n  const asBuiltDrawings = dedupeByKey(\r\n    extractArrayData<SelectableDrawingOption>(asBuiltDrawingsData),\r\n    (drawing) => drawing.currentRevisionUuid ?? drawing.currentRevision?.uuid ?? drawing.uuid\r\n  );\r\n  const selectedDisciplineId = watch(\"disciplineId\");\r\n\r\n  const rfaTypeId = watch(\"rfaTypeId\");\r\n  const disciplineId = watch(\"disciplineId\");\r\n  const toOrganizationId = watch(\"toOrganizationId\");\r\n  const selectedShopDrawingRevisionIds = watch(\"shopDrawingRevisionIds\") ?? [];\r\n  const selectedAsBuiltDrawingRevisionIds = watch(\"asBuiltDrawingRevisionIds\") ?? [];\r\n  const selectedRfaType = rfaTypes.find((rfaType) => rfaType.id === rfaTypeId);\r\n  const selectedRfaTypeCode = selectedRfaType?.typeCode?.toUpperCase();\r\n  const requiresShopDrawings = selectedRfaTypeCode === \"DDW\" || selectedRfaTypeCode === \"SDW\";\r\n  const requiresAsBuiltDrawings = selectedRfaTypeCode === \"ADW\";\r\n\r\n  useEffect(() => {\r\n    // Reset page and search when project changes\r\n    setShopDrawingPage(1);\r\n    setShopDrawingSearch(\"\");\r\n    setAsBuiltDrawingPage(1);\r\n    setAsBuiltDrawingSearch(\"\");\r\n\r\n    if (requiresShopDrawings) {\r\n      setValue(\"asBuiltDrawingRevisionIds\", []);\r\n      clearErrors(\"asBuiltDrawingRevisionIds\");\r\n      return;\r\n    }\r\n\r\n    if (requiresAsBuiltDrawings) {\r\n      setValue(\"shopDrawingRevisionIds\", []);\r\n      clearErrors(\"shopDrawingRevisionIds\");\r\n      return;\r\n    }\r\n\r\n    setValue(\"shopDrawingRevisionIds\", []);\r\n    setValue(\"asBuiltDrawingRevisionIds\", []);\r\n    clearErrors(\"shopDrawingRevisionIds\");\r\n    clearErrors(\"asBuiltDrawingRevisionIds\");\r\n  }, [requiresShopDrawings, requiresAsBuiltDrawings, selectedProjectId, setValue, clearErrors]);\r\n\r\n  // -- Preview Logic --\r\n  const [preview, setPreview] = useState<{ number: string; isDefaultTemplate: boolean } | null>(null);\r\n\r\n  useEffect(() => {\r\n    if (!selectedProjectId || !rfaCorrespondenceType?.id || !rfaTypeId || !disciplineId || !toOrganizationId) {\r\n       setPreview(null);\r\n       return;\r\n    }\r\n\r\n    const fetchPreview = async () => {\r\n       try {\r\n         const res = await correspondenceService.previewNumber({\r\n             projectId: selectedProjectId,\r\n             typeId: rfaCorrespondenceType.id,\r\n             disciplineId,\r\n             recipients: [{ organizationId: toOrganizationId, type: 'TO' }],\r\n             subject: watch(\"subject\") || \"Preview Subject\"\r\n         });\r\n         setPreview(res);\r\n       } catch (_err) {\r\n         setPreview(null);\r\n       }\r\n    };\r\n\r\n    const timer = setTimeout(fetchPreview, 500);\r\n    return () => clearTimeout(timer);\r\n  }, [rfaTypeId, disciplineId, toOrganizationId, selectedProjectId, rfaCorrespondenceType?.id, watch]);\r\n\r\n  const onSubmit = (data: RFAFormData) => {\r\n    if (requiresShopDrawings && data.shopDrawingRevisionIds?.length === 0) {\r\n      setError(\"shopDrawingRevisionIds\", {\r\n        type: \"manual\",\r\n        message: \"Please select at least one Shop Drawing Revision\",\r\n      });\r\n      return;\r\n    }\r\n\r\n    if (requiresAsBuiltDrawings && data.asBuiltDrawingRevisionIds?.length === 0) {\r\n      setError(\"asBuiltDrawingRevisionIds\", {\r\n        type: \"manual\",\r\n        message: \"Please select at least one As-Built Drawing Revision\",\r\n      });\r\n      return;\r\n    }\r\n\r\n    clearErrors(\"shopDrawingRevisionIds\");\r\n    clearErrors(\"asBuiltDrawingRevisionIds\");\r\n\r\n    const payload: CreateRfaDto = {\r\n      ...data,\r\n      shopDrawingRevisionIds: requiresShopDrawings ? data.shopDrawingRevisionIds : undefined,\r\n      asBuiltDrawingRevisionIds: requiresAsBuiltDrawings ? data.asBuiltDrawingRevisionIds : undefined,\r\n    };\r\n    createMutation.mutate(payload, {\r\n      onSuccess: () => {\r\n        router.push(\"/rfas\");\r\n      },\r\n    });\r\n  };\r\n\r\n  const onInvalidSubmit: SubmitErrorHandler<RFAFormData> = () => undefined;\r\n  const submitForm = handleSubmit(onSubmit, onInvalidSubmit);\r\n  const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {\r\n    void submitForm(event).catch(() => undefined);\r\n  };\r\n\r\n  return (\r\n    <form onSubmit={handleFormSubmit} className=\"max-w-4xl space-y-6\">\r\n      {preview && (\r\n        <Card className=\"p-4 bg-muted border-l-4 border-l-primary\">\r\n           <p className=\"text-sm text-muted-foreground mb-1\">Document Number Preview</p>\r\n           <div className=\"flex items-center gap-3\">\r\n             <span className=\"text-xl font-bold font-mono text-primary tracking-wide\">{preview.number}</span>\r\n             {preview.isDefaultTemplate && (\r\n                <span className=\"text-[10px] uppercase font-semibold px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800 border border-yellow-200\">\r\n                   Default Template\r\n                </span>\r\n             )}\r\n           </div>\r\n        </Card>\r\n      )}\r\n\r\n      <Card className=\"p-6\">\r\n        <h3 className=\"text-lg font-semibold mb-4\">RFA Information</h3>\r\n\r\n        <div className=\"space-y-4\">\r\n          <div>\r\n            <Label htmlFor=\"subject\">Subject *</Label>\r\n            <Input id=\"subject\" {...register(\"subject\")} placeholder=\"Enter subject\" />\r\n            {errors.subject && (\r\n              <p className=\"text-sm text-destructive mt-1\">\r\n                {errors.subject.message}\r\n              </p>\r\n            )}\r\n          </div>\r\n\r\n          <div>\r\n             <Label htmlFor=\"body\">Body (Content)</Label>\r\n             <Textarea id=\"body\" {...register(\"body\")} rows={4} placeholder=\"Enter content...\" />\r\n          </div>\r\n\r\n           <div>\r\n             <Label htmlFor=\"remarks\">Remarks</Label>\r\n             <Input id=\"remarks\" {...register(\"remarks\")} placeholder=\"Optional remarks\" />\r\n          </div>\r\n\r\n          <div>\r\n            <Label htmlFor=\"description\">Description</Label>\r\n            <Input id=\"description\" {...register(\"description\")} placeholder=\"Enter key description\" />\r\n          </div>\r\n\r\n          <div>\r\n            <Label>Project *</Label>\r\n            <Select\r\n              value={selectedProjectId || undefined}\r\n              onValueChange={(val) => {\r\n                setValue(\"projectId\", val);\r\n                setValue(\"contractId\", \"\");\r\n                setValue(\"disciplineId\", 0);\r\n                setValue(\"rfaTypeId\", 0);\r\n                setValue(\"shopDrawingRevisionIds\", []);\r\n                setValue(\"asBuiltDrawingRevisionIds\", []);\r\n              }}\r\n              disabled={isLoadingProjects}\r\n            >\r\n              <SelectTrigger>\r\n                <SelectValue placeholder={isLoadingProjects ? \"Loading...\" : \"Select Project\"} />\r\n              </SelectTrigger>\r\n              <SelectContent>\r\n                {projects.map((p) => {\r\n                  const projectValue = getOptionValue(p.uuid ?? p.id);\r\n\r\n                  if (!projectValue) {\r\n                    return null;\r\n                  }\r\n\r\n                  return (\r\n                  <SelectItem key={projectValue} value={projectValue}>\r\n                    {p.projectName || p.projectCode}\r\n                  </SelectItem>\r\n                  );\r\n                })}\r\n              </SelectContent>\r\n            </Select>\r\n            {errors.projectId && (\r\n              <p className=\"text-sm text-destructive mt-1\">{errors.projectId.message}</p>\r\n            )}\r\n          </div>\r\n\r\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n            <div>\r\n              <Label>Contract *</Label>\r\n              <Select\r\n                value={selectedContractId || undefined}\r\n                onValueChange={(val) => {\r\n                  setValue(\"contractId\", val);\r\n                  setValue(\"disciplineId\", 0);\r\n                  setValue(\"rfaTypeId\", 0);\r\n                  setValue(\"shopDrawingRevisionIds\", []);\r\n                  setValue(\"asBuiltDrawingRevisionIds\", []);\r\n                }}\r\n                disabled={!selectedProjectId || isLoadingContracts}\r\n              >\r\n                <SelectTrigger>\r\n                  <SelectValue placeholder={isLoadingContracts ? \"Loading...\" : \"Select Contract\"} />\r\n                </SelectTrigger>\r\n                <SelectContent>\r\n                  {contracts.map((c) => {\r\n                    const contractValue = getOptionValue(c.uuid ?? c.id);\r\n\r\n                    if (!contractValue) {\r\n                      return null;\r\n                    }\r\n\r\n                    return (\r\n                    <SelectItem key={contractValue} value={contractValue}>\r\n                      {c.contractName || c.name || c.contractCode}\r\n                    </SelectItem>\r\n                    );\r\n                  })}\r\n                </SelectContent>\r\n              </Select>\r\n              {errors.contractId && (\r\n                <p className=\"text-sm text-destructive mt-1\">{errors.contractId.message}</p>\r\n              )}\r\n            </div>\r\n\r\n            <div>\r\n              <Label>Discipline *</Label>\r\n              <Select\r\n                value={selectedDisciplineId > 0 ? String(selectedDisciplineId) : undefined}\r\n                onValueChange={(val) => setValue(\"disciplineId\", Number(val))}\r\n                disabled={!selectedContractId || isLoadingDisciplines}\r\n              >\r\n                <SelectTrigger>\r\n                  <SelectValue placeholder={isLoadingDisciplines ? \"Loading...\" : \"Select Discipline\"} />\r\n                </SelectTrigger>\r\n                <SelectContent>\r\n                  {disciplines.map((d) => (\r\n                    <SelectItem key={d.id} value={String(d.id)}>\r\n                      {`${d.codeNameEn || d.codeNameTh || d.disciplineCode} (${d.disciplineCode})`}\r\n                    </SelectItem>\r\n                  ))}\r\n                  {!isLoadingDisciplines && disciplines.length === 0 && (\r\n                     <SelectItem value=\"0\" disabled>No disciplines found</SelectItem>\r\n                  )}\r\n                </SelectContent>\r\n              </Select>\r\n              {errors.disciplineId && (\r\n                <p className=\"text-sm text-destructive mt-1\">{errors.disciplineId.message}</p>\r\n              )}\r\n            </div>\r\n          </div>\r\n\r\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n            <div>\r\n              <Label>RFA Type *</Label>\r\n              <Select\r\n                value={rfaTypeId > 0 ? String(rfaTypeId) : undefined}\r\n                onValueChange={(val) => {\r\n                  setValue(\"rfaTypeId\", Number(val));\r\n                  setValue(\"shopDrawingRevisionIds\", []);\r\n                  setValue(\"asBuiltDrawingRevisionIds\", []);\r\n                }}\r\n                disabled={!selectedContractId || isLoadingRfaTypes}\r\n              >\r\n                <SelectTrigger>\r\n                  <SelectValue placeholder={isLoadingRfaTypes ? \"Loading...\" : \"Select RFA Type\"} />\r\n                </SelectTrigger>\r\n                <SelectContent>\r\n                  {rfaTypes.map((rfaType) => (\r\n                    <SelectItem key={rfaType.id} value={String(rfaType.id)}>\r\n                      {`${rfaType.typeCode || \"RFA\"} - ${rfaType.typeName || rfaType.typeNameEn || rfaType.typeNameTh || \"Unnamed Type\"}`}\r\n                    </SelectItem>\r\n                  ))}\r\n                </SelectContent>\r\n              </Select>\r\n              {errors.rfaTypeId && (\r\n                <p className=\"text-sm text-destructive mt-1\">{errors.rfaTypeId.message}</p>\r\n              )}\r\n            </div>\r\n\r\n            <div>\r\n              <Label>To Organization *</Label>\r\n              <Select\r\n                value={toOrganizationId || undefined}\r\n                onValueChange={(val) => setValue(\"toOrganizationId\", val)}\r\n                disabled={isLoadingOrganizations}\r\n              >\r\n                <SelectTrigger>\r\n                  <SelectValue placeholder={isLoadingOrganizations ? \"Loading...\" : \"Select To Organization\"} />\r\n                </SelectTrigger>\r\n                <SelectContent>\r\n                  {organizations.map((organization) => {\r\n                    const organizationValue = getOptionValue(organization.uuid ?? organization.id);\r\n\r\n                    if (!organizationValue) {\r\n                      return null;\r\n                    }\r\n\r\n                    return (\r\n                    <SelectItem key={organizationValue} value={organizationValue}>\r\n                      {`${organization.organizationCode || \"ORG\"} - ${organization.organizationName || \"Unnamed Organization\"}`}\r\n                    </SelectItem>\r\n                    );\r\n                  })}\r\n                </SelectContent>\r\n              </Select>\r\n              {errors.toOrganizationId && (\r\n                <p className=\"text-sm text-destructive mt-1\">{errors.toOrganizationId.message}</p>\r\n              )}\r\n            </div>\r\n          </div>\r\n        </div>\r\n      </Card>\r\n\r\n      {(requiresShopDrawings || requiresAsBuiltDrawings) && (\r\n        <Card className=\"p-6\">\r\n          <h3 className=\"text-lg font-semibold mb-4\">New Item</h3>\r\n          <div className=\"space-y-3\">\r\n            <p className=\"text-sm text-muted-foreground\">\r\n              {requiresShopDrawings\r\n                ? \"RFA Type นี้ต้องอ้างอิง Shop Drawing Revision อย่างน้อย 1 รายการ\"\r\n                : \"RFA Type นี้ต้องอ้างอิง As-Built Drawing Revision อย่างน้อย 1 รายการ\"}\r\n            </p>\r\n\r\n            {requiresShopDrawings && (\r\n              <div className=\"space-y-3\">\r\n                <div className=\"flex items-center gap-2 mb-2\">\r\n                  <Input\r\n                    placeholder=\"ค้นหาตาม Drawing Number...\"\r\n                    value={shopDrawingSearch}\r\n                    onChange={(e) => {\r\n                      setShopDrawingSearch(e.target.value);\r\n                      setShopDrawingPage(1);\r\n                    }}\r\n                    className=\"max-w-xs\"\r\n                  />\r\n                </div>\r\n\r\n                {isLoadingShopDrawings && (\r\n                  <p className=\"text-sm text-muted-foreground\">Loading Shop Drawings...</p>\r\n                )}\r\n                {!isLoadingShopDrawings && shopDrawings.length === 0 && (\r\n                  <p className=\"text-sm text-muted-foreground\">No Shop Drawings found for the selected project.</p>\r\n                )}\r\n                <div className=\"grid grid-cols-1 gap-3\">\r\n                  {shopDrawings.map((drawing) => {\r\n                    const revisionUuid = drawing.currentRevisionUuid ?? drawing.currentRevision?.uuid;\r\n\r\n                    if (!revisionUuid) {\r\n                      return null;\r\n                    }\r\n\r\n                    return (\r\n                      <label\r\n                        key={revisionUuid}\r\n                        className=\"flex items-start gap-3 rounded-lg border p-3 cursor-pointer hover:bg-muted/50 transition-colors\"\r\n                      >\r\n                        <Checkbox\r\n                          checked={selectedShopDrawingRevisionIds.includes(revisionUuid)}\r\n                          onCheckedChange={(checked) => {\r\n                            const nextValues = checked === true\r\n                              ? [...selectedShopDrawingRevisionIds, revisionUuid]\r\n                              : selectedShopDrawingRevisionIds.filter((value) => value !== revisionUuid);\r\n                            setValue(\"shopDrawingRevisionIds\", nextValues, { shouldDirty: true, shouldValidate: true });\r\n                            clearErrors(\"shopDrawingRevisionIds\");\r\n                          }}\r\n                        />\r\n                        <div className=\"space-y-1\">\r\n                          <p className=\"font-medium\">{drawing.drawingNumber || \"Unnamed Shop Drawing\"}</p>\r\n                          <p className=\"text-sm text-muted-foreground\">{drawing.currentRevision?.title || drawing.title || \"Untitled Revision\"}</p>\r\n                          <p className=\"text-xs text-muted-foreground\">\r\n                            Revision {drawing.currentRevision?.revisionLabel || drawing.currentRevision?.revisionNumber || \"-\"}\r\n                            {drawing.currentRevision?.legacyDrawingNumber ? ` • Legacy ${drawing.currentRevision.legacyDrawingNumber}` : \"\"}\r\n                          </p>\r\n                        </div>\r\n                      </label>\r\n                    );\r\n                  })}\r\n                </div>\r\n\r\n                {shopDrawingsData?.meta && shopDrawingsData.meta.totalPages > 1 && (\r\n                  <div className=\"flex items-center justify-between mt-4\">\r\n                    <p className=\"text-xs text-muted-foreground\">\r\n                      Page {shopDrawingPage} of {shopDrawingsData.meta.totalPages}\r\n                    </p>\r\n                    <div className=\"flex gap-2\">\r\n                      <Button\r\n                        type=\"button\"\r\n                        variant=\"outline\"\r\n                        size=\"sm\"\r\n                        disabled={shopDrawingPage === 1 || isLoadingShopDrawings}\r\n                        onClick={() => setShopDrawingPage((p) => Math.max(1, p - 1))}\r\n                      >\r\n                        Previous\r\n                      </Button>\r\n                      <Button\r\n                        type=\"button\"\r\n                        variant=\"outline\"\r\n                        size=\"sm\"\r\n                        disabled={shopDrawingPage >= shopDrawingsData.meta.totalPages || isLoadingShopDrawings}\r\n                        onClick={() => setShopDrawingPage((p) => p + 1)}\r\n                      >\r\n                        Next\r\n                      </Button>\r\n                    </div>\r\n                  </div>\r\n                )}\r\n\r\n                {errors.shopDrawingRevisionIds && (\r\n                  <p className=\"text-sm text-destructive mt-2\">{errors.shopDrawingRevisionIds.message}</p>\r\n                )}\r\n              </div>\r\n            )}\r\n\r\n            {requiresAsBuiltDrawings && (\r\n              <div className=\"space-y-3\">\r\n                <div className=\"flex items-center gap-2 mb-2\">\r\n                  <Input\r\n                    placeholder=\"ค้นหาตาม Drawing Number...\"\r\n                    value={asBuiltDrawingSearch}\r\n                    onChange={(e) => {\r\n                      setAsBuiltDrawingSearch(e.target.value);\r\n                      setAsBuiltDrawingPage(1);\r\n                    }}\r\n                    className=\"max-w-xs\"\r\n                  />\r\n                </div>\r\n\r\n                {isLoadingAsBuiltDrawings && (\r\n                  <p className=\"text-sm text-muted-foreground\">Loading As-Built Drawings...</p>\r\n                )}\r\n                {!isLoadingAsBuiltDrawings && asBuiltDrawings.length === 0 && (\r\n                  <p className=\"text-sm text-muted-foreground\">No As-Built Drawings found for the selected project.</p>\r\n                )}\r\n                <div className=\"grid grid-cols-1 gap-3\">\r\n                  {asBuiltDrawings.map((drawing) => {\r\n                    const revisionUuid = drawing.currentRevisionUuid ?? drawing.currentRevision?.uuid;\r\n\r\n                    if (!revisionUuid) {\r\n                      return null;\r\n                    }\r\n\r\n                    return (\r\n                      <label\r\n                        key={revisionUuid}\r\n                        className=\"flex items-start gap-3 rounded-lg border p-3 cursor-pointer hover:bg-muted/50 transition-colors\"\r\n                      >\r\n                        <Checkbox\r\n                          checked={selectedAsBuiltDrawingRevisionIds.includes(revisionUuid)}\r\n                          onCheckedChange={(checked) => {\r\n                            const nextValues = checked === true\r\n                              ? [...selectedAsBuiltDrawingRevisionIds, revisionUuid]\r\n                              : selectedAsBuiltDrawingRevisionIds.filter((value) => value !== revisionUuid);\r\n                            setValue(\"asBuiltDrawingRevisionIds\", nextValues, { shouldDirty: true, shouldValidate: true });\r\n                            clearErrors(\"asBuiltDrawingRevisionIds\");\r\n                          }}\r\n                        />\r\n                        <div className=\"space-y-1\">\r\n                          <p className=\"font-medium\">{drawing.drawingNumber || \"Unnamed As-Built Drawing\"}</p>\r\n                          <p className=\"text-sm text-muted-foreground\">{drawing.currentRevision?.title || drawing.title || \"Untitled Revision\"}</p>\r\n                          <p className=\"text-xs text-muted-foreground\">\r\n                            Revision {drawing.currentRevision?.revisionLabel || drawing.currentRevision?.revisionNumber || \"-\"}\r\n                            {drawing.currentRevision?.legacyDrawingNumber ? ` • Legacy ${drawing.currentRevision.legacyDrawingNumber}` : \"\"}\r\n                          </p>\r\n                        </div>\r\n                      </label>\r\n                    );\r\n                  })}\r\n                </div>\r\n\r\n                {asBuiltDrawingsData?.meta && asBuiltDrawingsData.meta.totalPages > 1 && (\r\n                  <div className=\"flex items-center justify-between mt-4\">\r\n                    <p className=\"text-xs text-muted-foreground\">\r\n                      Page {asBuiltDrawingPage} of {asBuiltDrawingsData.meta.totalPages}\r\n                    </p>\r\n                    <div className=\"flex gap-2\">\r\n                      <Button\r\n                        type=\"button\"\r\n                        variant=\"outline\"\r\n                        size=\"sm\"\r\n                        disabled={asBuiltDrawingPage === 1 || isLoadingAsBuiltDrawings}\r\n                        onClick={() => setAsBuiltDrawingPage((p) => Math.max(1, p - 1))}\r\n                      >\r\n                        Previous\r\n                      </Button>\r\n                      <Button\r\n                        type=\"button\"\r\n                        variant=\"outline\"\r\n                        size=\"sm\"\r\n                        disabled={asBuiltDrawingPage >= asBuiltDrawingsData.meta.totalPages || isLoadingAsBuiltDrawings}\r\n                        onClick={() => setAsBuiltDrawingPage((p) => p + 1)}\r\n                      >\r\n                        Next\r\n                      </Button>\r\n                    </div>\r\n                  </div>\r\n                )}\r\n\r\n                {errors.asBuiltDrawingRevisionIds && (\r\n                  <p className=\"text-sm text-destructive mt-2\">{errors.asBuiltDrawingRevisionIds.message}</p>\r\n                )}\r\n              </div>\r\n            )}\r\n          </div>\r\n        </Card>\r\n      )}\r\n\r\n      <div className=\"flex justify-end gap-3\">\r\n        <Button type=\"button\" variant=\"outline\" onClick={() => router.back()}>\r\n          Cancel\r\n        </Button>\r\n        <Button type=\"submit\" disabled={createMutation.isPending}>\r\n          {createMutation.isPending && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\r\n          Create RFA\r\n        </Button>\r\n      </div>\r\n    </form>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\rfas\\list.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\search\\filters.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\search\\results.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\transmittal\\transmittal-form.tsx",
    "messages": [],
    "suppressedMessages": [
      {
        "ruleId": "@typescript-eslint/no-explicit-any",
        "severity": 2,
        "message": "Unexpected any. Specify a different type.",
        "line": 80,
        "column": 42,
        "nodeType": "TSAnyKeyword",
        "messageId": "unexpectedAny",
        "endLine": 80,
        "endColumn": 45,
        "suggestions": [
          {
            "messageId": "suggestUnknown",
            "fix": { "range": [2584, 2587], "text": "unknown" },
            "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
          },
          {
            "messageId": "suggestNever",
            "fix": { "range": [2584, 2587], "text": "never" },
            "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."
          }
        ],
        "suppressions": [{ "kind": "directive", "justification": "zod 4 + @hookform/resolvers compat" }]
      }
    ],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\transmittal\\transmittal-list.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\__tests__\\button.test.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\alert-dialog.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\alert.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\avatar.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\badge.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\button.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\calendar.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\card.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\checkbox.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\command.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\dialog.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\dropdown-menu.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'Check' is defined but never used. Allowed unused vars must match /^_/u.",
        "line": 6,
        "column": 10,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 6,
        "endColumn": 15,
        "suggestions": [
          {
            "messageId": "removeUnusedVar",
            "data": { "varName": "Check" },
            "fix": { "range": [171, 177], "text": "" },
            "desc": "Remove unused variable \"Check\"."
          }
        ]
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: components/ui/dropdown-menu.tsx\r\n\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\r\nimport { Check, ChevronRight, _Circle } from \"lucide-react\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst DropdownMenu = DropdownMenuPrimitive.Root\r\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\r\nconst DropdownMenuContent = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\r\n>(({ className, sideOffset = 4, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.Portal>\r\n    <DropdownMenuPrimitive.Content\r\n      ref={ref}\r\n      sideOffset={sideOffset}\r\n      className={cn(\r\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\r\n        className\r\n      )}\r\n      {...props}\r\n    />\r\n  </DropdownMenuPrimitive.Portal>\r\n))\r\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\r\n\r\nconst DropdownMenuItem = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\r\n    inset?: boolean\r\n  }\r\n>(({ className, inset, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.Item\r\n    ref={ref}\r\n    className={cn(\r\n      \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\r\n      inset && \"pl-8\",\r\n      className\r\n    )}\r\n    {...props}\r\n  />\r\n))\r\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\r\n\r\nconst DropdownMenuLabel = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\r\n    inset?: boolean\r\n  }\r\n>(({ className, inset, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.Label\r\n    ref={ref}\r\n    className={cn(\r\n      \"px-2 py-1.5 text-sm font-semibold\",\r\n      inset && \"pl-8\",\r\n      className\r\n    )}\r\n    {...props}\r\n  />\r\n))\r\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\r\n\r\nconst DropdownMenuSeparator = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\r\n>(({ className, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.Separator\r\n    ref={ref}\r\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\r\n    {...props}\r\n  />\r\n))\r\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\r\n\r\n// Export ส่วนประกอบอื่นๆ ที่อาจใช้ (Shortcut, Group, Sub) เพื่อความครบถ้วน\r\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\r\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\r\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\r\nconst DropdownMenuSubTrigger = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\r\n    inset?: boolean\r\n  }\r\n>(({ className, inset, children, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.SubTrigger\r\n    ref={ref}\r\n    className={cn(\r\n      \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent\",\r\n      inset && \"pl-8\",\r\n      className\r\n    )}\r\n    {...props}\r\n  >\r\n    {children}\r\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\r\n  </DropdownMenuPrimitive.SubTrigger>\r\n))\r\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName\r\n\r\nconst DropdownMenuSubContent = React.forwardRef<\r\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\r\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\r\n>(({ className, ...props }, ref) => (\r\n  <DropdownMenuPrimitive.SubContent\r\n    ref={ref}\r\n    className={cn(\r\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\r\n      className\r\n    )}\r\n    {...props}\r\n  />\r\n))\r\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName\r\n\r\nconst DropdownMenuShortcut = ({\r\n  className,\r\n  ...props\r\n}: React.HTMLAttributes<HTMLSpanElement>) => {\r\n  return (\r\n    <span\r\n      className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\r\n      {...props}\r\n    />\r\n  )\r\n}\r\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\r\n\r\nexport {\r\n  DropdownMenu,\r\n  DropdownMenuTrigger,\r\n  DropdownMenuContent,\r\n  DropdownMenuItem,\r\n  DropdownMenuLabel,\r\n  DropdownMenuSeparator,\r\n  DropdownMenuGroup,\r\n  DropdownMenuPortal,\r\n  DropdownMenuSub,\r\n  DropdownMenuSubContent,\r\n  DropdownMenuSubTrigger,\r\n  DropdownMenuShortcut,\r\n}",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\form.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\hover-card.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\input.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\label.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\popover.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\progress.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\scroll-area.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\select.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\separator.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\sheet.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\skeleton.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\sonner.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\switch.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\table.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\tabs.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\ui\\textarea.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\workflows\\dsl-editor.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 50,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 50,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "'use client';\r\n\r\nimport { useState, useRef, useEffect } from 'react';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Card } from '@/components/ui/card';\r\nimport { Alert, AlertDescription } from '@/components/ui/alert';\r\nimport { CheckCircle, AlertCircle, Play, Loader2 } from 'lucide-react';\r\nimport Editor, { OnMount } from '@monaco-editor/react';\r\nimport { workflowApi } from '@/lib/api/workflows';\r\nimport { ValidationResult } from '@/types/workflow';\r\nimport { useTheme } from 'next-themes';\r\n\r\ninterface DSLEditorProps {\r\n  initialValue?: string;\r\n  onChange?: (value: string) => void;\r\n  readOnly?: boolean;\r\n}\r\n\r\nexport function DSLEditor({ initialValue = '', onChange, readOnly = false }: DSLEditorProps) {\r\n  const [dsl, setDsl] = useState(initialValue);\r\n  const [validationResult, setValidationResult] = useState<ValidationResult | null>(null);\r\n  const [isValidating, setIsValidating] = useState(false);\r\n  const editorRef = useRef<unknown>(null);\r\n  const { theme } = useTheme();\r\n\r\n  // Update internal state if initialValue changes (e.g. loaded from API)\r\n  useEffect(() => {\r\n    setDsl(initialValue);\r\n  }, [initialValue]);\r\n\r\n  const handleEditorChange = (value: string | undefined) => {\r\n    const newValue = value || '';\r\n    setDsl(newValue);\r\n    onChange?.(newValue);\r\n    // Clear previous validation result on edit to avoid stale state\r\n    if (validationResult) {\r\n      setValidationResult(null);\r\n    }\r\n  };\r\n\r\n  const handleEditorDidMount: OnMount = (editor) => {\r\n    editorRef.current = editor;\r\n  };\r\n\r\n  const validateDSL = async () => {\r\n    setIsValidating(true);\r\n    try {\r\n      const result = await workflowApi.validateDSL(dsl);\r\n      setValidationResult(result);\r\n    } catch (_error) {\r\n      // Validation failed - error state shown in UI\r\n      setValidationResult({ valid: false, errors: ['Validation failed due to server error'] });\r\n    } finally {\r\n      setIsValidating(false);\r\n    }\r\n  };\r\n\r\n  interface TestResult {\r\n    success: boolean;\r\n    message: string;\r\n  }\r\n\r\n  const [testResult, setTestResult] = useState<TestResult | null>(null);\r\n  const [isTesting, setIsTesting] = useState(false);\r\n\r\n  const testWorkflow = async () => {\r\n    setIsTesting(true);\r\n    setTestResult(null);\r\n    try {\r\n        // Mock test execution\r\n        await new Promise(resolve => setTimeout(resolve, 1000));\r\n        setTestResult({ success: true, message: \"Workflow simulation completed successfully.\" });\r\n    } catch {\r\n        setTestResult({ success: false, message: \"Workflow simulation failed.\" });\r\n    } finally {\r\n        setIsTesting(false);\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div className=\"space-y-4\">\r\n      <div className=\"flex justify-between items-center\">\r\n        <h3 className=\"text-lg font-semibold\">Workflow DSL</h3>\r\n        <div className=\"flex gap-2\">\r\n          <Button\r\n            variant=\"outline\"\r\n            onClick={validateDSL}\r\n            disabled={isValidating || readOnly}\r\n          >\r\n            {isValidating ? (\r\n                <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\r\n            ) : (\r\n                <CheckCircle className=\"mr-2 h-4 w-4\" />\r\n            )}\r\n            Validate\r\n          </Button>\r\n          <Button variant=\"outline\" onClick={testWorkflow} disabled={isTesting || readOnly}>\r\n             {isTesting ? (\r\n                <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\r\n            ) : (\r\n                <Play className=\"mr-2 h-4 w-4\" />\r\n            )}\r\n            Test\r\n          </Button>\r\n        </div>\r\n      </div>\r\n\r\n      <Card className=\"overflow-hidden border-2\">\r\n        <Editor\r\n          height=\"500px\"\r\n          defaultLanguage=\"yaml\"\r\n          value={dsl}\r\n          onChange={handleEditorChange}\r\n          onMount={handleEditorDidMount}\r\n          theme={theme === 'dark' ? 'vs-dark' : 'light'}\r\n          options={{\r\n            readOnly: readOnly,\r\n            minimap: { enabled: false },\r\n            fontSize: 14,\r\n            lineNumbers: 'on',\r\n            rulers: [80],\r\n            wordWrap: 'on',\r\n            scrollBeyondLastLine: false,\r\n            automaticLayout: true,\r\n          }}\r\n        />\r\n      </Card>\r\n\r\n      {validationResult && (\r\n        <Alert variant={validationResult.valid ? 'default' : 'destructive'} className={validationResult.valid ? \"border-green-500 text-green-700 dark:text-green-400\" : \"\"}>\r\n          {validationResult.valid ? (\r\n            <CheckCircle className=\"h-4 w-4\" />\r\n          ) : (\r\n            <AlertCircle className=\"h-4 w-4\" />\r\n          )}\r\n          <AlertDescription>\r\n            {validationResult.valid ? (\r\n              <span className=\"font-semibold\">DSL is valid and ready to deploy.</span>\r\n            ) : (\r\n              <div>\r\n                <p className=\"font-medium mb-2\">Validation Errors:</p>\r\n                <ul className=\"list-disc list-inside space-y-1\">\r\n                  {validationResult.errors?.map((error: string, i: number) => (\r\n                    <li key={i} className=\"text-sm\">\r\n                      {error}\r\n                    </li>\r\n                  ))}\r\n                </ul>\r\n              </div>\r\n            )}\r\n          </AlertDescription>\r\n        </Alert>\r\n      )}\r\n\r\n       {testResult && (\r\n        <Alert variant={testResult.success ? 'default' : 'destructive'}  className={testResult.success ? \"border-blue-500 text-blue-700 dark:text-blue-400\" : \"\"}>\r\n             {testResult.success ? <CheckCircle className=\"h-4 w-4\"/> : <AlertCircle className=\"h-4 w-4\"/>}\r\n            <AlertDescription>\r\n                {testResult.message}\r\n            </AlertDescription>\r\n        </Alert>\r\n      )}\r\n    </div>\r\n  );\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\components\\workflows\\visual-builder.tsx",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_e' is defined but never used.",
        "line": 238,
        "column": 12,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 238,
        "endColumn": 14
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "'use client';\r\n\r\nimport { useCallback, useEffect } from 'react';\r\nimport ReactFlow, {\r\n  Node,\r\n  Edge,\r\n  Controls,\r\n  Background,\r\n  useNodesState,\r\n  useEdgesState,\r\n  addEdge,\r\n  Connection,\r\n  ReactFlowProvider,\r\n  Panel,\r\n  MarkerType,\r\n  useReactFlow,\r\n} from 'reactflow';\r\nimport 'reactflow/dist/style.css';\r\n\r\nimport { Button } from '@/components/ui/button';\r\nimport { Plus, Download, Save, Layout } from 'lucide-react';\r\n\r\ninterface WorkflowStateNodeData {\r\n  label?: string;\r\n  name?: string;\r\n  role?: string;\r\n  type?: string;\r\n}\r\n\r\ninterface RawTransitionShape {\r\n  to?: string;\r\n  target?: string;\r\n  require?: {\r\n    role?: string | string[];\r\n  };\r\n}\r\n\r\ninterface RawStateShape {\r\n  id?: string;\r\n  name: string;\r\n  type?: string;\r\n  role?: string;\r\n  initial?: boolean;\r\n  terminal?: boolean;\r\n  on?: Record<string, RawTransitionShape>;\r\n}\r\n\r\ninterface CompiledTransitionShape {\r\n  to?: string;\r\n  target?: string;\r\n  requirements?: {\r\n    roles?: string[];\r\n  };\r\n}\r\n\r\ninterface CompiledStateShape {\r\n  initial?: boolean;\r\n  terminal?: boolean;\r\n  transitions?: Record<string, CompiledTransitionShape>;\r\n}\r\n\r\ninterface ParsedDslShape {\r\n  workflow?: string;\r\n  initialState?: string;\r\n  states?: RawStateShape[] | Record<string, CompiledStateShape>;\r\n  dslDefinition?: string;\r\n}\r\n\r\n// Define custom node styles (simplified for now)\r\nconst nodeStyle = {\r\n    padding: '10px 20px',\r\n    borderRadius: '8px',\r\n    border: '1px solid #ddd',\r\n    fontSize: '14px',\r\n    fontWeight: 500,\r\n    background: 'white',\r\n    color: '#333',\r\n    width: 180, // Increased width for role display\r\n    textAlign: 'center' as const,\r\n    whiteSpace: 'pre-wrap' as const, // Allow multiline\r\n};\r\n\r\nconst conditionNodeStyle = {\r\n    ...nodeStyle,\r\n    background: '#fef3c7', // Amber-100\r\n    borderColor: '#d97706', // Amber-600\r\n    borderStyle: 'dashed',\r\n    borderRadius: '24px', // More rounded\r\n};\r\n\r\nconst initialNodes: Node[] = [\r\n  {\r\n    id: '1',\r\n    type: 'input',\r\n    data: { label: 'Start' },\r\n    position: { x: 250, y: 5 },\r\n    style: { ...nodeStyle, background: '#10b981', color: 'white', border: 'none' },\r\n  },\r\n];\r\n\r\ninterface VisualWorkflowBuilderProps {\r\n    initialNodes?: Node[];\r\n    initialEdges?: Edge[];\r\n    dslString?: string;\r\n    onSave?: (nodes: Node[], edges: Edge[]) => void;\r\n    onDslChange?: (dsl: string) => void;\r\n}\r\n\r\nconst createNode = (\r\n  name: string,\r\n  yOffset: number,\r\n  options?: {\r\n    isCondition?: boolean;\r\n    isStart?: boolean;\r\n    isEnd?: boolean;\r\n    role?: string;\r\n    type?: string;\r\n  }\r\n): Node<WorkflowStateNodeData> => {\r\n  const isCondition = options?.isCondition === true;\r\n  const isStart = options?.isStart === true;\r\n  const isEnd = options?.isEnd === true;\r\n\r\n  let nodeType: Node['type'] = 'default';\r\n  let style = { ...nodeStyle };\r\n\r\n  if (isStart) {\r\n    nodeType = 'input';\r\n    style = { ...nodeStyle, background: '#10b981', color: 'white', border: 'none' };\r\n  } else if (isEnd) {\r\n    nodeType = 'output';\r\n    style = { ...nodeStyle, background: '#ef4444', color: 'white', border: 'none' };\r\n  } else if (isCondition) {\r\n    style = conditionNodeStyle;\r\n  }\r\n\r\n  return {\r\n    id: name,\r\n    type: nodeType,\r\n    data: {\r\n      label: isStart || isEnd ? name : `${name}\\n(${options?.role || 'No Role'})`,\r\n      name,\r\n      role: options?.role,\r\n      type: options?.type || (isStart ? 'START' : isEnd ? 'END' : 'TASK')\r\n    },\r\n    position: { x: 250, y: yOffset },\r\n    style\r\n  };\r\n};\r\n\r\nconst createEdge = (source: string, target: string, label: string): Edge => ({\r\n  id: `e-${source}-${label}-${target}`,\r\n  source,\r\n  target,\r\n  label,\r\n  markerEnd: { type: MarkerType.ArrowClosed }\r\n});\r\n\r\nfunction parseDSL(dsl: string): { nodes: Node[], edges: Edge[] } {\r\n  const nodes: Node[] = [];\r\n  const edges: Edge[] = [];\r\n  let yOffset = 50;\r\n\r\n  try {\r\n    const parsedDsl = JSON.parse(dsl) as ParsedDslShape;\r\n\r\n    if (typeof parsedDsl.dslDefinition === 'string') {\r\n      return parseDSL(parsedDsl.dslDefinition);\r\n    }\r\n\r\n    if (Array.isArray(parsedDsl.states)) {\r\n      parsedDsl.states.forEach((state) => {\r\n        const stateName = state.name || state.id || `node-${Date.now()}`;\r\n        const role =\r\n          state.role ||\r\n          (Array.isArray(state.on?.SUBMIT?.require?.role)\r\n            ? state.on?.SUBMIT?.require?.role.join(', ')\r\n            : state.on?.SUBMIT?.require?.role);\r\n        const isCondition = state.type === 'CONDITION';\r\n        const isStart = state.initial === true || state.type === 'START';\r\n        const isEnd = state.terminal === true || state.type === 'END';\r\n\r\n        nodes.push(\r\n          createNode(stateName, yOffset, {\r\n            isCondition,\r\n            isStart,\r\n            isEnd,\r\n            role,\r\n            type: state.type\r\n          })\r\n        );\r\n\r\n        if (state.on) {\r\n          Object.entries(state.on).forEach(([eventName, transition]) => {\r\n            const target = transition?.to || transition?.target;\r\n            if (target) {\r\n              edges.push(createEdge(stateName, target, eventName));\r\n            }\r\n          });\r\n        }\r\n\r\n        yOffset += 120;\r\n      });\r\n\r\n      return { nodes, edges };\r\n    }\r\n\r\n    if (parsedDsl.states && typeof parsedDsl.states === 'object') {\r\n      Object.entries(parsedDsl.states).forEach(([stateName, state]) => {\r\n        const roles = state.transitions\r\n          ? Object.values(state.transitions)\r\n              .flatMap((transition) => transition.requirements?.roles || [])\r\n              .filter((role, index, array) => array.indexOf(role) === index)\r\n          : [];\r\n        const isStart = parsedDsl.initialState === stateName || state.initial === true;\r\n        const isEnd = state.terminal === true;\r\n\r\n        nodes.push(\r\n          createNode(stateName, yOffset, {\r\n            isStart,\r\n            isEnd,\r\n            role: roles.join(', ')\r\n          })\r\n        );\r\n\r\n        if (state.transitions) {\r\n          Object.entries(state.transitions).forEach(([eventName, transition]) => {\r\n            const target = transition?.to || transition?.target;\r\n            if (target) {\r\n              edges.push(createEdge(stateName, target, eventName));\r\n            }\r\n          });\r\n        }\r\n\r\n        yOffset += 120;\r\n      });\r\n    }\r\n  } catch (_e) {\r\n    // Failed to parse DSL as JSON - nodes/edges remain empty\r\n  }\r\n\r\n  return { nodes, edges };\r\n}\r\n\r\nfunction VisualWorkflowBuilderContent({ initialNodes: propNodes, initialEdges: propEdges, dslString, onSave, onDslChange }: VisualWorkflowBuilderProps) {\r\n  const [nodes, setNodes, onNodesChange] = useNodesState(propNodes || initialNodes);\r\n  const [edges, setEdges, onEdgesChange] = useEdgesState(propEdges || []);\r\n  const { fitView } = useReactFlow();\r\n\r\n  // Sync DSL to nodes when dslString changes\r\n  useEffect(() => {\r\n      if (dslString) {\r\n          const { nodes: newNodes, edges: newEdges } = parseDSL(dslString);\r\n          setNodes(newNodes.length > 0 ? newNodes : propNodes || initialNodes);\r\n          setEdges(newNodes.length > 0 ? newEdges : propEdges || []);\r\n          setTimeout(() => fitView(), 100);\r\n      }\r\n  }, [dslString, fitView, propEdges, propNodes, setEdges, setNodes]);\r\n\r\n  const onConnect = useCallback(\r\n    (params: Connection) => setEdges((eds) => addEdge({ ...params, markerEnd: { type: MarkerType.ArrowClosed } }, eds)),\r\n    [setEdges]\r\n  );\r\n\r\n  const addNode = (type: string, label: string) => {\r\n    const id = `${type}-${Date.now()}`;\r\n    const nodeType = type === 'condition' ? 'CONDITION' : type === 'end' ? 'END' : type === 'start' ? 'START' : 'TASK';\r\n\r\n    const newNode: Node<WorkflowStateNodeData> = {\r\n      id,\r\n      position: { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 },\r\n      data: { label: label, name: label, role: 'User', type: nodeType },\r\n      style: { ...nodeStyle },\r\n    };\r\n\r\n    if (type === 'end') {\r\n        newNode.style = { ...nodeStyle, background: '#ef4444', color: 'white', border: 'none' };\r\n        newNode.type = 'output';\r\n    } else if (type === 'start') {\r\n         newNode.style = { ...nodeStyle, background: '#10b981', color: 'white', border: 'none' };\r\n         newNode.type = 'input';\r\n    } else if (type === 'condition') {\r\n         newNode.style = conditionNodeStyle;\r\n    }\r\n\r\n    setNodes((nds) => nds.concat(newNode));\r\n  };\r\n\r\n  const handleSave = () => {\r\n      onSave?.(nodes, edges);\r\n  };\r\n\r\n  // Generate JSON DSL\r\n  const generateDSL = () => {\r\n    const states = nodes.map(n => {\r\n        const outgoingEdges = edges.filter(e => e.source === n.id);\r\n        const onConfig: Record<string, { to: string }> = {};\r\n\r\n        outgoingEdges.forEach(e => {\r\n            const eventName = e.label || 'PROCEED';\r\n            onConfig[eventName as string] = { to: e.target };\r\n        });\r\n\r\n        const isStartNode = n.type === 'input';\r\n        const isEndNode = n.type === 'output';\r\n        const nodeData = n.data as WorkflowStateNodeData;\r\n\r\n        const stateObj: { name: string; type?: string; role?: string; initial?: boolean; terminal?: boolean; on?: Record<string, { to: string }> } = {\r\n            name: nodeData.name || nodeData.label?.split('\\n')[0] || n.id,\r\n        };\r\n\r\n        if (nodeData.type && nodeData.type !== 'START' && nodeData.type !== 'END' && nodeData.type !== 'TASK') {\r\n            stateObj.type = nodeData.type;\r\n        }\r\n\r\n        if (nodeData.role && !isStartNode && !isEndNode) {\r\n            stateObj.role = nodeData.role;\r\n        }\r\n\r\n        if (isStartNode) {\r\n            stateObj.initial = true;\r\n        }\r\n        if (isEndNode) {\r\n            stateObj.terminal = true;\r\n        }\r\n        if (Object.keys(onConfig).length > 0) {\r\n            stateObj.on = onConfig;\r\n        }\r\n\r\n        return stateObj;\r\n    });\r\n\r\n    const dslObj = {\r\n        workflow: \"VISUAL_WORKFLOW\",\r\n        version: 1,\r\n        states\r\n    };\r\n    const dsl = JSON.stringify(dslObj, null, 2);\r\n\r\n    // DSL generated from visual builder\r\n    onDslChange?.(dsl);\r\n    alert(\"DSL Updated from Visual Builder!\");\r\n  };\r\n\r\n  return (\r\n    <div className=\"space-y-4 h-full flex flex-col\">\r\n      <div className=\"h-[600px] border rounded-lg overflow-hidden relative bg-slate-50 dark:bg-slate-950\">\r\n        <ReactFlow\r\n          nodes={nodes}\r\n          edges={edges}\r\n          onNodesChange={onNodesChange}\r\n          onEdgesChange={onEdgesChange}\r\n          onConnect={onConnect}\r\n          fitView\r\n          attributionPosition=\"bottom-right\"\r\n        >\r\n          <Controls />\r\n          <Background color=\"#aaa\" gap={16} />\r\n\r\n          <Panel position=\"top-right\" className=\"flex gap-2 p-2 bg-white/80 dark:bg-black/50 rounded-lg backdrop-blur-sm border shadow-sm\">\r\n             <Button size=\"sm\" variant=\"secondary\" onClick={() => addNode('step', 'New Step')}>\r\n                <Plus className=\"mr-2 h-4 w-4\" /> Add Step\r\n             </Button>\r\n             <Button size=\"sm\" variant=\"secondary\" onClick={() => addNode('condition', 'Condition')}>\r\n                <Layout className=\"mr-2 h-4 w-4\" /> Condition\r\n             </Button>\r\n             <Button size=\"sm\" variant=\"secondary\" onClick={() => addNode('end', 'End')}>\r\n                <Plus className=\"mr-2 h-4 w-4\" /> Add End\r\n             </Button>\r\n          </Panel>\r\n\r\n           <Panel position=\"bottom-left\" className=\"flex gap-2\">\r\n             <Button size=\"sm\" onClick={handleSave}>\r\n                <Save className=\"mr-2 h-4 w-4\" /> Save Visual State\r\n             </Button>\r\n             <Button size=\"sm\" variant=\"outline\" onClick={generateDSL}>\r\n                <Download className=\"mr-2 h-4 w-4\" /> Generate DSL\r\n             </Button>\r\n           </Panel>\r\n        </ReactFlow>\r\n      </div>\r\n       <div className=\"text-sm text-muted-foreground\">\r\n        <p>Tip: Drag to connect nodes. Use backspace to delete selected nodes.</p>\r\n      </div>\r\n    </div>\r\n  );\r\n}\r\n\r\nexport function VisualWorkflowBuilder(props: VisualWorkflowBuilderProps) {\r\n    return (\r\n        <ReactFlowProvider>\r\n            <VisualWorkflowBuilderContent {...props} />\r\n        </ReactFlowProvider>\r\n    )\r\n}\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\eslint.config.mjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\__tests__\\use-correspondence.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\__tests__\\use-drawing.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\__tests__\\use-projects.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\__tests__\\use-rfa.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\__tests__\\use-users.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-audit-logs.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-correspondence.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-dashboard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-drawing.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-master-data.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-notification.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-numbering.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-projects.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-reference-data.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-rfa.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-search.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-users.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\hooks\\use-workflows.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\admin.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\client.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 43,
        "column": 16,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 43,
        "endColumn": 22
      }
    ],
    "suppressedMessages": [],
    "errorCount": 1,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: lib/api/client.ts\r\nimport axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosError } from \"axios\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\n\r\n// อ่านค่า Base URL จาก Environment Variable\r\nconst baseURL = process.env.NEXT_PUBLIC_API_URL || \"http://localhost:3001/api\";\r\n\r\n// สร้าง Axios Instance หลัก\r\nconst apiClient: AxiosInstance = axios.create({\r\n  baseURL,\r\n  headers: {\r\n    \"Content-Type\": \"application/json\",\r\n  },\r\n  timeout: 15000, // Timeout 15 วินาที\r\n});\r\n\r\n// ---------------------------------------------------------------------------\r\n// Request Interceptors\r\n// ---------------------------------------------------------------------------\r\n\r\napiClient.interceptors.request.use(\r\n  async (config: InternalAxiosRequestConfig) => {\r\n    // 1. Idempotency Key Injection\r\n    // ป้องกันการทำรายการซ้ำสำหรับ Method ที่เปลี่ยนแปลงข้อมูล\r\n    const method = config.method?.toLowerCase();\r\n    if (method && [\"post\", \"put\", \"delete\", \"patch\"].includes(method)) {\r\n      config.headers[\"Idempotency-Key\"] = uuidv4();\r\n    }\r\n\r\n    // 2. Authentication Token Injection\r\n    // ดึง Token จาก Zustand persist store (localStorage)\r\n    if (typeof window !== \"undefined\") {\r\n      try {\r\n        const authStorage = localStorage.getItem('auth-storage');\r\n        if (authStorage) {\r\n          const parsed = JSON.parse(authStorage);\r\n          const token = parsed?.state?.token;\r\n\r\n          if (token) {\r\n            config.headers[\"Authorization\"] = `Bearer ${token}`;\r\n          }\r\n        }\r\n      } catch (_error) {\r\n        // Auth token retrieval failed - request will proceed without token\r\n      }\r\n    }\r\n\r\n    return config;\r\n  },\r\n  (error) => {\r\n    return Promise.reject(error);\r\n  }\r\n);\r\n\r\n// ---------------------------------------------------------------------------\r\n// Response Interceptors\r\n// ---------------------------------------------------------------------------\r\n\r\napiClient.interceptors.response.use(\r\n  (response) => {\r\n    return response;\r\n  },\r\n  (error: AxiosError) => {\r\n    if (error.response) {\r\n      const { status } = error.response;\r\n\r\n      // กรณี Token หมดอายุ หรือ ไม่มีสิทธิ์\r\n      if (status === 401) {\r\n        // Unauthorized: redirect handled by auth interceptor\r\n        // สามารถเพิ่ม Logic Redirect ไปหน้า Login ได้ถ้าต้องการ\r\n      }\r\n    }\r\n    return Promise.reject(error);\r\n  }\r\n);\r\n\r\nexport default apiClient;\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\dashboard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\drawings.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\notifications.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\numbering.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\api\\workflows.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\auth.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 106,
        "column": 12,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 106,
        "endColumn": 17
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'errorBody' is assigned a value but never used. Allowed unused vars must match /^_/u.",
        "line": 154,
        "column": 19,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 154,
        "endColumn": 28
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'error' is defined but never used.",
        "line": 180,
        "column": 18,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 180,
        "endColumn": 23
      }
    ],
    "suppressedMessages": [],
    "errorCount": 3,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "// File: lib/auth.ts\r\nimport NextAuth from \"next-auth\";\r\nimport Credentials from \"next-auth/providers/credentials\";\r\nimport { z } from \"zod\";\r\nimport type { User } from \"next-auth\";\r\nimport type { JWT } from \"next-auth/jwt\";\r\n\r\n// Schema for input validation\r\nconst _loginSchema = z.object({\r\n  username: z.string().min(1),\r\n  password: z.string().min(1),\r\n});\r\n\r\nconst baseUrl = (typeof window === \"undefined\" ? process.env.INTERNAL_API_URL : null) || process.env.NEXT_PUBLIC_API_URL || \"http://localhost:3001/api\";\r\n\r\n// Helper to parse JWT expiry\r\nfunction getJwtExpiry(token: string): number {\r\n  try {\r\n    const payload = JSON.parse(atob(token.split('.')[1]));\r\n    return payload.exp * 1000; // Convert to ms\r\n  } catch {\r\n    return Date.now(); // If invalid, treat as expired\r\n  }\r\n}\r\n\r\ninterface TokenPayload {\r\n  access_token: string;\r\n  refresh_token?: string;\r\n}\r\n\r\ninterface LoginPayload extends TokenPayload {\r\n  user: {\r\n    user_id: number;\r\n    username: string;\r\n    email?: string;\r\n    firstName?: string;\r\n    lastName?: string;\r\n    role?: string;\r\n    primaryOrganizationId?: number;\r\n  };\r\n}\r\n\r\nfunction unwrapApiResponse(value: unknown): unknown {\r\n  let current = value;\r\n\r\n  for (let i = 0; i < 5; i += 1) {\r\n    if (!current || typeof current !== \"object\") {\r\n      return current;\r\n    }\r\n\r\n    const record = current as Record<string, unknown>;\r\n    if (typeof record.access_token === \"string\") {\r\n      return current;\r\n    }\r\n\r\n    if (!(\"data\" in record)) {\r\n      return current;\r\n    }\r\n\r\n    current = record.data;\r\n  }\r\n\r\n  return current;\r\n}\r\n\r\nfunction isTokenPayload(value: unknown): value is TokenPayload {\r\n  return !!value && typeof value === \"object\" && typeof (value as Record<string, unknown>).access_token === \"string\";\r\n}\r\n\r\nfunction isLoginPayload(value: unknown): value is LoginPayload {\r\n  if (!isTokenPayload(value)) {\r\n    return false;\r\n  }\r\n\r\n  const user = (value as unknown as { user?: unknown }).user;\r\n  return !!user && typeof user === \"object\" && typeof (user as Record<string, unknown>).username === \"string\";\r\n}\r\n\r\nasync function refreshAccessToken(token: JWT) {\r\n  try {\r\n    const response = await fetch(`${baseUrl}/auth/refresh`, {\r\n      method: \"POST\",\r\n      headers: {\r\n        Authorization: `Bearer ${token.refreshToken}`,\r\n      },\r\n    });\r\n\r\n    const refreshedTokens = await response.json();\r\n\r\n    if (!response.ok) {\r\n      throw refreshedTokens;\r\n    }\r\n\r\n    const data = unwrapApiResponse(refreshedTokens);\r\n\r\n    if (!isTokenPayload(data)) {\r\n      throw new Error(\"Invalid refresh response format\");\r\n    }\r\n\r\n    return {\r\n      ...token,\r\n      accessToken: data.access_token,\r\n      accessTokenExpires: getJwtExpiry(data.access_token),\r\n      refreshToken: data.refresh_token ?? token.refreshToken,\r\n    };\r\n  } catch (error) {\r\n    // RefreshAccessTokenError - token will be invalidated\r\n\r\n    return {\r\n      ...token,\r\n      error: \"RefreshAccessTokenError\",\r\n    };\r\n  }\r\n}\r\n\r\nexport const {\r\n  handlers: { GET, POST },\r\n  auth,\r\n  signIn,\r\n  signOut,\r\n} = NextAuth({\r\n  providers: [\r\n    Credentials({\r\n      name: \"Credentials\",\r\n      credentials: {\r\n        username: { label: \"Username\", type: \"text\" },\r\n        password: { label: \"Password\", type: \"password\" },\r\n      },\r\n      authorize: async (credentials) => {\r\n        if (!credentials?.username || !credentials?.password) return null;\r\n\r\n        try {\r\n          // 1. Sanitize payload (Only send username and password)\r\n          const payload = {\r\n            username: credentials.username as string,\r\n            password: credentials.password as string,\r\n          };\r\n\r\n          // console.log(`[AUTH] Attempting login at: ${baseUrl}/auth/login`); /* TODO: Remove before prod */\r\n          // console.log(`[AUTH] Current process.env.INTERNAL_API_URL: ${process.env.INTERNAL_API_URL}`); /* TODO: Remove before prod */\r\n          // console.log(`[AUTH] Current process.env.NEXT_PUBLIC_API_URL: ${process.env.NEXT_PUBLIC_API_URL}`); /* TODO: Remove before prod */\r\n\r\n          const res = await fetch(`${baseUrl}/auth/login`, {\r\n            method: \"POST\",\r\n            body: JSON.stringify(payload),\r\n            headers: {\r\n              \"Content-Type\": \"application/json\",\r\n            },\r\n            cache: 'no-store', // Disable caching for auth requests\r\n          });\r\n\r\n          if (!res.ok) {\r\n            // console.error(`[AUTH] Login Failed: status ${res.status}`); /* TODO: Remove before prod */\r\n            const errorBody = await res.text().catch(() => \"No error body\");\r\n            // console.error(`[AUTH] Error details: ${errorBody}`); /* TODO: Remove before prod */\r\n            return null;\r\n          }\r\n\r\n          const data = await res.json();\r\n          const backendData = unwrapApiResponse(data);\r\n\r\n          if (!isLoginPayload(backendData)) {\r\n            // console.error(\"[AUTH] Login failed: Invalid response format from backend (missing access_token)\"); /* TODO: Remove before prod */\r\n            return null;\r\n          }\r\n\r\n          // console.log(`[AUTH] Login Successful for user: ${backendData.user?.username || 'unknown'}`); /* TODO: Remove before prod */\r\n\r\n          return {\r\n            id: backendData.user.user_id.toString(),\r\n            name: `${backendData.user.firstName ?? \"\"} ${backendData.user.lastName ?? \"\"}`.trim(),\r\n            email: backendData.user.email,\r\n            username: backendData.user.username,\r\n            role: backendData.user.role || \"User\",\r\n            organizationId: backendData.user.primaryOrganizationId,\r\n            accessToken: backendData.access_token,\r\n            refreshToken: backendData.refresh_token,\r\n          } as User;\r\n\r\n        } catch (error) {\r\n          // console.error(\"[AUTH] Network/Fetch Error during authorize:\", error); /* TODO: Remove before prod */\r\n          return null;\r\n        }\r\n      },\r\n    }),\r\n  ],\r\n  pages: {\r\n    signIn: \"/login\",\r\n    error: \"/login\",\r\n  },\r\n  callbacks: {\r\n    async jwt({ token, user }) {\r\n      if (user) {\r\n        return {\r\n          ...token,\r\n          id: user.id,\r\n          username: user.username, // ✅ Save username\r\n          role: user.role,\r\n          organizationId: user.organizationId,\r\n          accessToken: user.accessToken,\r\n          refreshToken: user.refreshToken,\r\n          accessTokenExpires: getJwtExpiry(user.accessToken!),\r\n        };\r\n      }\r\n\r\n      // Return previous token if valid (minus 10s buffer)\r\n      if (Date.now() < (token.accessTokenExpires as number) - 10000) {\r\n        return token;\r\n      }\r\n\r\n      // If existing token has an error, do not retry refresh (prevents infinite loop)\r\n      if (token.error) {\r\n        return token;\r\n      }\r\n\r\n      // Token expired, refresh it\r\n      return refreshAccessToken(token);\r\n    },\r\n    async session({ session, token }) {\r\n      if (token && session.user) {\r\n        session.user.id = token.id as string;\r\n        session.user.username = token.username as string; // ✅ Restore username\r\n        session.user.role = token.role as string;\r\n        session.user.organizationId = token.organizationId as number;\r\n\r\n        session.accessToken = token.accessToken as string;\r\n        session.refreshToken = token.refreshToken as string;\r\n        session.error = token.error as string;\r\n      }\r\n      return session;\r\n    },\r\n  },\r\n  session: {\r\n    strategy: \"jwt\",\r\n    maxAge: 24 * 60 * 60, // 24 hours\r\n  },\r\n  secret: process.env.AUTH_SECRET,\r\n  debug: process.env.NODE_ENV === \"development\",\r\n});\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\__tests__\\correspondence.service.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\__tests__\\master-data.service.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\__tests__\\project.service.test.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\asbuilt-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\audit-log.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\circulation.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\contract-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\contract.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\correspondence.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\dashboard.service.ts",
    "messages": [
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 18,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 18,
        "endColumn": 20
      },
      {
        "ruleId": "@typescript-eslint/no-unused-vars",
        "severity": 2,
        "message": "'_error' is defined but never used.",
        "line": 34,
        "column": 14,
        "nodeType": "Identifier",
        "messageId": "unusedVar",
        "endLine": 34,
        "endColumn": 20
      }
    ],
    "suppressedMessages": [],
    "errorCount": 2,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "source": "import apiClient from \"@/lib/api/client\";\r\nimport { DashboardStats, ActivityLog, PendingTask } from \"@/types/dashboard\";\r\n\r\nexport const dashboardService = {\r\n  getStats: async (): Promise<DashboardStats> => {\r\n    const response = await apiClient.get(\"/dashboard/stats\");\r\n    return response.data;\r\n  },\r\n\r\n  getRecentActivity: async (): Promise<ActivityLog[]> => {\r\n    try {\r\n      const response = await apiClient.get(\"/dashboard/activity\");\r\n      // ตรวจสอบว่า response.data เป็น array จริงๆ\r\n      if (Array.isArray(response.data)) {\r\n        return response.data;\r\n      }\r\n      return [];\r\n    } catch (_error) {\r\n      return [];\r\n    }\r\n  },\r\n\r\n  getPendingTasks: async (): Promise<PendingTask[]> => {\r\n    try {\r\n      const response = await apiClient.get(\"/dashboard/pending\");\r\n      // Backend คืน { data: [], meta: {} } ต้องดึง data ออกมา\r\n      if (response.data?.data && Array.isArray(response.data.data)) {\r\n        return response.data.data;\r\n      }\r\n      if (Array.isArray(response.data)) {\r\n        return response.data;\r\n      }\r\n      return [];\r\n    } catch (_error) {\r\n      return [];\r\n    }\r\n  },\r\n};\r\n",
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\document-numbering.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\drawing-master-data.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\index.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\json-schema.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\master-data.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\migration.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\monitoring.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\notification.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\organization.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\project.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\rfa.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\search.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\session.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\shop-drawing.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\transmittal.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\user.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\services\\workflow-engine.service.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\stores\\auth-store.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\stores\\draft-store.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\stores\\ui-store.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\test-utils.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\utils.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\lib\\utils\\uuid-guard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\next-env.d.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\next.config.mjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\postcss.config.mjs",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\providers\\query-provider.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\providers\\session-provider.tsx",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\proxy.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\tailwind.config.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\admin.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\api-error.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\circulation.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\correspondence.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dashboard.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\drawing.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\circulation\\create-circulation.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\circulation\\search-circulation.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\circulation\\update-circulation-routing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\contract\\contract.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\correspondence\\add-reference.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\correspondence\\create-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\correspondence\\search-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\correspondence\\submit-correspondence.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\correspondence\\workflow-action.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\drawing\\asbuilt-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\drawing\\contract-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\drawing\\shop-drawing.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\json-schema\\json-schema.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\correspondence-type.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\discipline.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\number-format.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\rfa-type.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\sub-type.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\master\\tag.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\monitoring\\set-maintenance.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\notification\\notification.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\numbering.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\organization\\organization.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\project\\project.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\rfa\\rfa.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\search\\search-query.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\transmittal\\transmittal.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\user\\user.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\dto\\workflow-engine\\workflow-engine.dto.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\migration.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\next-auth.d.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\notification.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\numbering.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\organization.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\react-day-picker.d.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\rfa.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\search.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\transmittal.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\user.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\types\\workflow.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\vitest.config.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\frontend\\vitest.setup.ts",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  },
  {
    "filePath": "D:\\nap-dms.lcbp3\\test_preview.js",
    "messages": [],
    "suppressedMessages": [],
    "errorCount": 0,
    "fatalErrorCount": 0,
    "warningCount": 0,
    "fixableErrorCount": 0,
    "fixableWarningCount": 0,
    "usedDeprecatedRules": []
  }
]
