import { TIMEOUTS } from '../../utils/timeouts'; import { test, expect } from '../../utils/test-setup'; import type { APIRequestContext } from '@playwright/test'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const testUsers = JSON.parse(readFileSync(join(__dirname, '../../fixtures/test-users.json'), 'utf-8')); async function getAuthToken(request: APIRequestContext): Promise { const loginResponse = await request.post('http://localhost:8080/api/v1/auth/login', { data: { username: testUsers.admin.username, password: testUsers.admin.password } }); if (!loginResponse.ok()) { console.debug('API 登录失败:', await loginResponse.text()); return null; } const loginData = await loginResponse.json(); return loginData.data?.token || loginData.token || null; } async function createDisabledPersonViaAPI( request: APIRequestContext, personData: { name: string; gender: string; idCard: string; disabilityId: string; disabilityType: string; disabilityLevel: string; idAddress: string; phone: string; province: string; city: string; } ): Promise<{ id: number; name: string } | null> { try { const token = await getAuthToken(request); if (!token) return null; const createResponse = await request.post('http://localhost:8080/api/v1/disability/createDisabledPerson', { headers: { 'Authorization': 'Bearer ' + String(token), 'Content-Type': 'application/json' }, data: personData }); if (!createResponse.ok()) { const errorText = await createResponse.text(); console.debug('API 创建残疾人失败:', createResponse.status(), errorText); return null; } const result = await createResponse.json(); console.debug('API 创建残疾人成功:', result.name); return { id: result.id, name: result.name }; } catch (error) { console.debug('API 调用出错:', error); return null; } } async function createPlatformViaAPI( request: APIRequestContext ): Promise<{ id: number; name: string } | null> { try { const token = await getAuthToken(request); if (!token) return null; const timestamp = Date.now(); const platformData = { platformName: '测试平台_' + String(timestamp), contactPerson: '测试联系人', contactPhone: '13800138000', contactEmail: 'test@example.com' }; const createResponse = await request.post('http://localhost:8080/api/v1/platform/createPlatform', { headers: { 'Authorization': 'Bearer ' + String(token), 'Content-Type': 'application/json' }, data: platformData }); if (!createResponse.ok()) { const errorText = await createResponse.text(); console.debug('API 创建平台失败:', createResponse.status(), errorText); return null; } const result = await createResponse.json(); console.debug('API 创建平台成功:', result.id, result.platformName); return { id: result.id, name: result.platformName }; } catch (error) { console.debug('创建平台 API 调用出错:', error); return null; } } async function createCompanyViaAPI( request: APIRequestContext, platformId: number ): Promise<{ id: number; name: string } | null> { try { const token = await getAuthToken(request); if (!token) return null; const timestamp = Date.now(); const companyName = '测试公司_' + String(timestamp); const companyData = { companyName: companyName, platformId: platformId, contactPerson: '测试联系人', contactPhone: '13900139000', contactEmail: 'company@example.com' }; const createResponse = await request.post('http://localhost:8080/api/v1/company/createCompany', { headers: { 'Authorization': 'Bearer ' + String(token), 'Content-Type': 'application/json' }, data: companyData }); if (!createResponse.ok()) { const errorText = await createResponse.text(); console.debug('API 创建公司失败:', createResponse.status(), errorText); return null; } const createResult = await createResponse.json(); if (!createResult.success) { console.debug('API 创建公司返回 success=false'); return null; } const listResponse = await request.get('http://localhost:8080/api/v1/company/getCompaniesByPlatform/' + String(platformId), { headers: { 'Authorization': 'Bearer ' + String(token) } }); if (!listResponse.ok()) { console.debug('API 获取公司列表失败'); return null; } const companies = await listResponse.json(); const createdCompany = companies.find((c: { companyName: string }) => c.companyName === companyName); if (createdCompany) { console.debug('API 创建公司成功:', createdCompany.id, createdCompany.companyName); return { id: createdCompany.id, name: createdCompany.companyName }; } console.debug('未找到创建的公司'); return null; } catch (error) { console.debug('创建公司 API 调用出错:', error); return null; } } async function createOrderViaAPI( request: APIRequestContext, orderData: { orderName: string; platformId: number; companyId: number; expectedStartDate: string; } ): Promise<{ id: number; name: string } | null> { try { const token = await getAuthToken(request); if (!token) return null; const createResponse = await request.post('http://localhost:8080/api/v1/order/create', { headers: { 'Authorization': 'Bearer ' + String(token), 'Content-Type': 'application/json' }, data: orderData }); if (!createResponse.ok()) { const errorText = await createResponse.text(); console.debug('API 创建订单失败:', createResponse.status(), errorText); return null; } const result = await createResponse.json(); console.debug('API 创建订单成功:', result.id, result.orderName); return { id: result.id, name: result.orderName }; } catch (error) { console.debug('创建订单 API 调用出错:', error); return null; } } async function bindPersonToOrderViaAPI( request: APIRequestContext, orderId: number, personId: number, joinDate: string ): Promise { try { const token = await getAuthToken(request); if (!token) return false; const url = 'http://localhost:8080/api/v1/order/' + String(orderId) + '/persons/batch'; const createResponse = await request.post(url, { headers: { 'Authorization': 'Bearer ' + String(token), 'Content-Type': 'application/json' }, data: { persons: [ { personId: personId, joinDate: joinDate, salaryDetail: 5000 } ] } }); if (!createResponse.ok()) { const errorText = await createResponse.text(); console.debug('API 绑定人员失败:', createResponse.status(), errorText); return false; } const result = await createResponse.json(); if (result.success) { console.debug('API 绑定人员成功:', personId); return true; } return false; } catch (error) { console.debug('绑定人员 API 调用出错:', error); return false; } } function generateUniqueTestData() { const timestamp = Date.now(); const counter = Math.floor(Math.random() * 10000); return { orderName: '日期测试订单_' + String(timestamp), personName: '日期测试残疾人_' + String(timestamp), gender: '男', idCard: '110101' + String(timestamp).slice(-8) + String(counter).slice(-4), disabilityId: '残疾证' + String(timestamp).slice(-6) + String(counter), disabilityType: '视力残疾', disabilityLevel: '一级', idAddress: '北京市东城区测试地址' + String(timestamp), phone: '138' + String(counter).padStart(8, '0'), province: '北京市', city: '北京市', joinDate: '2026-01-15', newJoinDate: '2026-01-10', }; } test.describe.serial('订单管理 - 人员入职/离职日期行内编辑功能 (Story 15.5)', () => { test.beforeEach(async ({ adminLoginPage }) => { await adminLoginPage.goto(); await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password); }); test('AC1: 订单人员详情页中入职日期和离职日期可点击编辑(行内编辑模式)', async ({ page, request }) => { console.debug('========== Story 15.5 AC1: 入职/离职日期行内编辑 =========='); const testData = generateUniqueTestData(); console.debug('测试数据已生成:', testData.personName); const platform = await createPlatformViaAPI(request); expect(platform).not.toBeNull(); const company = await createCompanyViaAPI(request, platform!.id); expect(company).not.toBeNull(); const order = await createOrderViaAPI(request, { orderName: testData.orderName, platformId: platform!.id, companyId: company!.id, expectedStartDate: '2026-01-01' }); expect(order).not.toBeNull(); const person = await createDisabledPersonViaAPI(request, { name: testData.personName, gender: testData.gender, idCard: testData.idCard, disabilityId: testData.disabilityId, disabilityType: testData.disabilityType, disabilityLevel: testData.disabilityLevel, idAddress: testData.idAddress, phone: testData.phone, province: testData.province, city: testData.city, }); expect(person).not.toBeNull(); const bound = await bindPersonToOrderViaAPI(request, order!.id, person!.id, testData.joinDate); expect(bound).toBe(true); await page.goto('/admin/orders'); await page.waitForLoadState('networkidle'); await page.fill('input[placeholder*="搜索"]', testData.orderName); await page.waitForTimeout(TIMEOUTS.MEDIUM); const orderRow = page.locator('tbody tr').filter({ hasText: testData.orderName }); await orderRow.waitFor({ state: 'visible', timeout: TIMEOUTS.TABLE_LOAD }); // 点击"打开菜单"按钮 const menuTrigger = orderRow.getByRole('button', { name: /打开菜单/ }); await menuTrigger.click(); await page.waitForTimeout(TIMEOUTS.VERY_SHORT); // 点击"查看详情"菜单项 const detailButton = page.getByRole('menuitem', { name: /查看详情/ }); await detailButton.click(); await page.waitForSelector('[data-testid="order-detail-dialog"]', { state: 'visible', timeout: TIMEOUTS.DIALOG }); const joinDateButton = page.locator('[data-testid="edit-join-date-' + String(person!.id) + '"]'); await expect(joinDateButton).toBeVisible(); console.debug('入职日期按钮可见'); const leaveDateButton = page.locator('[data-testid="edit-leave-date-' + String(person!.id) + '"]'); await expect(leaveDateButton).toBeVisible(); console.debug('离职日期按钮可见'); // 点击入职日期按钮,应该弹出日历选择器 await joinDateButton.click(); // 等待日历选择器出现 const calendar = page.locator('[data-slot="calendar"]'); await expect(calendar).toBeVisible({ timeout: TIMEOUTS.DIALOG }); console.debug('日历选择器已打开'); // 验证初始日期被选中 // 验证初始日期被选中 - 查找包含"15"文本的按钮 const selectedDate = calendar.locator('button').filter({ hasText: '15' }); await expect(selectedDate.first()).toBeVisible(); console.debug('初始日期(15日)被正确选中'); console.debug('初始日期(15日)被正确选中'); // 点击新日期(10日) const newDateButton = calendar.locator('button').filter({ hasText: '10' }).first(); // 增加等待时间确保动画完成 await page.waitForTimeout(TIMEOUTS.SHORT); // 监听网络请求 let apiCalled = false; let apiResponse = null; page.on('response', async (response) => { if (response.url().includes('/persons/dates')) { apiCalled = true; apiResponse = await response.text(); console.debug('API 调用已捕获:', response.status(), apiResponse); } }); // 点击新日期 await newDateButton.click({ force: true }); await page.waitForTimeout(TIMEOUTS.MEDIUM); console.debug('API 是否被调用:', apiCalled); if (apiCalled) { console.debug('API 响应:', apiResponse); } const toast = page.locator('[data-sonner-toast]'); await expect(toast).toBeVisible(); await expect(toast).toContainText('入职日期更新成功'); console.debug('显示入职日期更新成功 toast'); // 验证日期已更新 await expect(joinDateButton).toContainText(testData.newJoinDate); console.debug('入职日期已更新为:', testData.newJoinDate); // 验证日历选择器已关闭 await expect(calendar).not.toBeVisible(); console.debug('日历选择器已自动关闭'); // 测试离职日期编辑 await leaveDateButton.click(); // 等待日历选择器出现 await expect(calendar).toBeVisible({ timeout: TIMEOUTS.DIALOG }); console.debug('离职日期日历选择器已打开'); // 按 ESC 键关闭日历 await page.keyboard.press('Escape'); await expect(calendar).not.toBeVisible(); console.debug('按 ESC 键关闭了日历选择器'); console.debug('========== AC1 测试完成 =========='); }); });