# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: 3485/dev/company-profit-aggregate.spec.ts >> [3485][dev] [QA] 업체별 수익집계 마감/수수료 계산 검증 >> 재마감 버튼 클릭 후 원장 확인 상태로 전환되면 거래, 이체 원장으로 화면이 전환되고 플랫폼 수수료율 입력 항목을 수정할 수 있는지 확인
- Location: src/qa/scenarios/companyProfitAggregate.ts:326:5

# Error details

```
Error: 재마감 원장 확인 상태에서는 플랫폼 수수료율 입력을 수정할 수 있어야 합니다.

expect(received).toBeGreaterThan(expected)

Expected: > 0
Received:   0
```

# Test source

```ts
  235 |         await openTargetPage(page, runtimeEnv, profitPage);
  236 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, OPEN_MONTH);
  237 |         expect(snapshot.status.payload?.closed, "2026년 4월은 마감 전이어야 합니다.").toBe(false);
  238 |         expect(snapshot.ledger.payload?.items?.length ?? 0, "2026년 4월 원장 기준 데이터가 있어야 합니다.").toBeGreaterThan(0);
  239 |         expect(snapshot.closing.status, "2026년 4월은 저장된 마감 데이터가 없어야 합니다.").toBe(400);
  240 |
  241 |         await attachProfitEvidence(testInfo, profitPage.name, ROLE, "april-open-ledger", snapshot, [
  242 |           "2026년 4월 마감 상태와 원장 데이터를 API로 확인했습니다.",
  243 |           "결론: 2026년 4월은 마감 전 상태이며 원장 기준 데이터가 조회됩니다."
  244 |         ]);
  245 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "april-open-ledger");
  246 |       });
  247 |     });
  248 |
  249 |     test(checklist.checklist[6], async ({ browser }, testInfo) => {
  250 |       test.setTimeout(70_000);
  251 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "may-closed-snapshot", async (page) => {
  252 |         const snapshot = await loadProfitSnapshotFromOpenedPage(page, runtimeEnv, profitPage, CLOSED_MONTH);
  253 |         expect(snapshot.status.payload?.closed, "2026년 5월은 마감완료여야 합니다.").toBe(true);
  254 |         expect(closingReport(snapshot)?.items?.length ?? 0, "2026년 5월 저장 마감 데이터가 있어야 합니다.").toBeGreaterThan(0);
  255 |
  256 |         await attachProfitEvidence(testInfo, profitPage.name, ROLE, "may-closed-snapshot", snapshot, [
  257 |           "2026년 5월 마감 상태와 저장된 마감 스냅샷을 확인했습니다.",
  258 |           "결론: 2026년 5월은 마감완료 상태이며 저장된 마감 데이터가 조회됩니다."
  259 |         ]);
  260 |       });
  261 |     });
  262 |
  263 |     test(checklist.checklist[7], async ({ browser }, testInfo) => {
  264 |       test.setTimeout(70_000);
  265 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "june-closed-snapshot", async (page) => {
  266 |         const snapshot = await loadProfitSnapshotFromOpenedPage(page, runtimeEnv, profitPage, CURRENT_CLOSED_MONTH);
  267 |         const bodyText = await visibleText(page);
  268 |         expect(snapshot.status.payload?.closed, "2026년 6월은 마감완료여야 합니다.").toBe(true);
  269 |         expect(closingReport(snapshot)?.items?.length ?? 0, "2026년 6월 저장 마감 데이터가 있어야 합니다.").toBeGreaterThan(0);
  270 |         expect(bodyText, "현재 6월 화면에 마감 완료 문구가 보여야 합니다.").toContain("마감 완료");
  271 |
  272 |         await attachProfitEvidence(testInfo, profitPage.name, ROLE, "june-closed-snapshot", snapshot, [
  273 |           "2026년 6월 마감 상태와 화면 문구를 확인했습니다.",
  274 |           `화면 일부: ${bodyText.slice(0, 1600)}`,
  275 |           "결론: 2026년 6월은 마감완료 상태이며 저장된 마감 데이터가 조회됩니다."
  276 |         ]);
  277 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "june-closed-snapshot");
  278 |       });
  279 |     });
  280 |
  281 |     test(checklist.checklist[8], async ({ browser }, testInfo) => {
  282 |       test.setTimeout(70_000);
  283 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "status-labels", async (page) => {
  284 |         await openTargetPage(page, runtimeEnv, profitPage);
  285 |         const bodyText = await visibleText(page);
  286 |         const april = await loadProfitSnapshot(page, runtimeEnv, OPEN_MONTH);
  287 |         const june = await loadProfitSnapshot(page, runtimeEnv, CURRENT_CLOSED_MONTH);
  288 |
  289 |         expect(april.status.payload?.closed, "API 기준 4월은 마감 전이어야 합니다.").toBe(false);
  290 |         expect(june.status.payload?.closed, "API 기준 6월은 마감 완료여야 합니다.").toBe(true);
  291 |         expect(bodyText, "마감완료 화면 문구가 보여야 합니다.").toContain("마감 완료");
  292 |         expect(bodyText, "재마감 버튼이 보여야 합니다.").toContain("재마감");
  293 |         expect(bodyText, "저장된 마감 스냅샷 문구가 보여야 합니다.").toContain("저장된 마감 스냅샷");
  294 |
  295 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "status-labels", [
  296 |           "마감 전/마감완료/재마감 관련 상태 문구를 확인했습니다.",
  297 |           `4월 API closed=${april.status.payload?.closed}`,
  298 |           `6월 API closed=${june.status.payload?.closed}`,
  299 |           `6월 화면 일부: ${bodyText.slice(0, 1600)}`,
  300 |           "결론: 마감 전, 마감완료, 재마감 원장 확인 상태 문구가 확인됩니다."
  301 |         ]);
  302 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "status-labels");
  303 |       });
  304 |     });
  305 |
  306 |     test(checklist.checklist[9], async ({ browser }, testInfo) => {
  307 |       test.setTimeout(70_000);
  308 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "closed-rate-frozen", async (page) => {
  309 |         await openTargetPage(page, runtimeEnv, profitPage);
  310 |         const bodyText = await visibleText(page);
  311 |         const editableRateInputs = await countEditablePlatformRateInputs(page);
  312 |
  313 |         expect(bodyText, "마감완료 데이터 고정 문구가 보여야 합니다.").toContain("마감완료 데이터는 저장 금액 고정");
  314 |         expect(editableRateInputs, "마감완료 상태에서는 플랫폼 수수료율 입력 항목이 수정 가능하면 안 됩니다.").toBe(0);
  315 |
  316 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "closed-rate-frozen", [
  317 |           "6월 마감완료 상태에서 플랫폼 수수료율 입력 항목의 수정 가능 여부를 확인했습니다.",
  318 |           `수정 가능한 플랫폼 수수료율 입력 수: ${editableRateInputs}`,
  319 |           `화면 일부: ${bodyText.slice(0, 1600)}`,
  320 |           "결론: 마감완료 상태에서는 플랫폼 수수료율 입력 항목이 동결되어 있습니다."
  321 |         ]);
  322 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "closed-rate-frozen");
  323 |       });
  324 |     });
  325 |
  326 |     test(checklist.checklist[10], async ({ browser }, testInfo) => {
  327 |       test.setTimeout(90_000);
  328 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "reclose-ledger-state", async (page) => {
  329 |         await openTargetPage(page, runtimeEnv, profitPage);
  330 |         await clickReclose(page);
  331 |         const bodyText = await visibleText(page);
  332 |         const editableRateInputs = await countEditablePlatformRateInputs(page);
  333 |
  334 |         expect(bodyText, "재마감 후에는 원장 기준 집계 문구가 보여야 합니다.").toMatch(/원장 기준|마감 버튼/);
> 335 |         expect(editableRateInputs, "재마감 원장 확인 상태에서는 플랫폼 수수료율 입력을 수정할 수 있어야 합니다.").toBeGreaterThan(0);
      |                                                                                   ^ Error: 재마감 원장 확인 상태에서는 플랫폼 수수료율 입력을 수정할 수 있어야 합니다.
  336 |
  337 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "reclose-ledger-state", [
  338 |           "재마감 버튼을 클릭해 원장 확인 상태로 전환되는지 확인했습니다.",
  339 |           `수정 가능한 플랫폼 수수료율 입력 수: ${editableRateInputs}`,
  340 |           `화면 일부: ${bodyText.slice(0, 1800)}`,
  341 |           "결론: 재마감 후 거래/이체 원장 기준 화면으로 전환되고 플랫폼 수수료율 입력 항목이 수정 가능해집니다."
  342 |         ]);
  343 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "reclose-ledger-state");
  344 |       });
  345 |     });
  346 |
  347 |     test(checklist.checklist[11], async ({ browser }, testInfo) => {
  348 |       test.setTimeout(70_000);
  349 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "period-data", async (page) => {
  350 |         await openTargetPage(page, runtimeEnv, profitPage);
  351 |         const snapshots = await Promise.all([OPEN_MONTH, CLOSED_MONTH, CURRENT_CLOSED_MONTH].map((month) => loadProfitSnapshot(page, runtimeEnv, month)));
  352 |         for (const snapshot of snapshots) {
  353 |           expect(reportForDisplay(snapshot)?.items?.length ?? 0, `2026년 ${snapshot.month}월 업체별 데이터가 있어야 합니다.`).toBeGreaterThan(0);
  354 |         }
  355 |
  356 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "period-data", [
  357 |           "업체별 수익집계의 기간별 데이터 조회를 API로 확인했습니다.",
  358 |           ...snapshots.map((snapshot) => summarizeSnapshot(snapshot)),
  359 |           "결론: 기간 선택 대상 월별 업체 데이터가 조회됩니다."
  360 |         ]);
  361 |       });
  362 |     });
  363 |
  364 |     test(checklist.checklist[12], async ({ browser }, testInfo) => {
  365 |       test.setTimeout(70_000);
  366 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "summary-matches-rows", async (page) => {
  367 |         await openTargetPage(page, runtimeEnv, profitPage);
  368 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, CURRENT_CLOSED_MONTH);
  369 |         const report = closingReport(snapshot);
  370 |         const rowTotals = computeReportTotals(report);
  371 |         const summary = snapshot.closing.payload?.summary ?? {};
  372 |
  373 |         expect(summary.transferFeeAmount, "상단 실제 이체수수료 합계가 하단 업체별 합계와 일치해야 합니다.").toBe(rowTotals.transferFeeAmount);
  374 |         expect(summary.completedTransferCount, "상단 완료 이체건수가 하단 업체별 합계와 일치해야 합니다.").toBe(rowTotals.completedTransferCount);
  375 |         expect(summary.transferFeeBaseAmount, "상단 이체수수료 기준금액이 하단 업체별 합계와 일치해야 합니다.").toBe(rowTotals.transferFeeBaseAmount);
  376 |         expect(summary.transferFeeAdditionalChargeAmount, "상단 이체수수료 차액이 하단 업체별 합계와 일치해야 합니다.").toBe(rowTotals.transferFeeAdditionalChargeAmount);
  377 |         expect(summary.platformFeeAmount, "상단 플랫폼수수료가 하단 업체별 합계와 일치해야 합니다.").toBe(rowTotals.platformFeeAmount);
  378 |
  379 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "summary-matches-rows", [
  380 |           "2026년 6월 마감 스냅샷의 상단 요약과 하단 업체별 합계를 비교했습니다.",
  381 |           `상단 요약: ${formatTotals(summary)}`,
  382 |           `하단 업체 합계: ${formatTotals(rowTotals)}`,
  383 |           "결론: 화면 상단 요약 금액/건수가 하단 표의 업체별 합계와 일치합니다."
  384 |         ]);
  385 |       });
  386 |     });
  387 |
  388 |     test(checklist.checklist[13], async ({ browser }, testInfo) => {
  389 |       test.setTimeout(80_000);
  390 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "may-sales-transaction-match", async (page) => {
  391 |         await openTargetPage(page, runtimeEnv, profitPage);
  392 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, CLOSED_MONTH);
  393 |         const bigs = requireTenantItem(reportForDisplay(snapshot), TARGET_TENANT_NAME);
  394 |         const profitSales = computeItemTotals(bigs).salesAmount;
  395 |         const transactionSales = await loadMayTransactionSalesAmount(page, runtimeEnv);
  396 |
  397 |         expect(transactionSales, "2026년 5월 거래내역 합계가 업체별 수익집계 매출금과 일치해야 합니다.").toBe(profitSales);
  398 |
  399 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "may-sales-transaction-match", [
  400 |           "2026년 5월 빅스페이먼츠 기준 매출금을 거래내역 API 합계와 대조했습니다.",
  401 |           `업체별 수익집계 매출금: ${formatNumber(profitSales)}원`,
  402 |           `거래내역 금액 합계: ${formatNumber(transactionSales)}원`,
  403 |           "결론: 2026년 5월 매출금이 같은 기간/업체 거래내역 금액 합계와 일치합니다."
  404 |         ]);
  405 |       });
  406 |     });
  407 |
  408 |     test(checklist.checklist[14], async ({ browser }, testInfo) => {
  409 |       test.setTimeout(80_000);
  410 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "may-transfer-fee-match", async (page) => {
  411 |         await openTargetPage(page, runtimeEnv, profitPage);
  412 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, CLOSED_MONTH);
  413 |         const bigs = requireTenantItem(reportForDisplay(snapshot), TARGET_TENANT_NAME);
  414 |         const profitTransferFee = computeItemTotals(bigs).transferFeeAmount;
  415 |         const transferTotal = await loadMayTransferTotal(page, runtimeEnv);
  416 |
  417 |         expect(transferTotal.commissionAmount, "이체내역 수수료 합계가 업체별 수익집계 실제 이체수수료와 일치해야 합니다.").toBe(profitTransferFee);
  418 |
  419 |         await attachEvidenceLog(testInfo, profitPage.name, ROLE, "may-transfer-fee-match", [
  420 |           "2026년 5월 빅스페이먼츠 기준 이체수수료를 이체내역 API 합계와 대조했습니다.",
  421 |           `업체별 수익집계 실제 이체수수료: ${formatNumber(profitTransferFee)}원`,
  422 |           `이체내역 이체 수수료 합계: ${formatNumber(transferTotal.commissionAmount)}원`,
  423 |           "결론: 이체수수료가 같은 기간/업체 이체내역 수수료 합계와 일치합니다."
  424 |         ]);
  425 |       });
  426 |     });
  427 |
  428 |     test(checklist.checklist[15], async ({ browser }, testInfo) => {
  429 |       test.setTimeout(80_000);
  430 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "may-completed-transfer-count", async (page) => {
  431 |         await openTargetPage(page, runtimeEnv, profitPage);
  432 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, CLOSED_MONTH);
  433 |         const bigs = requireTenantItem(reportForDisplay(snapshot), TARGET_TENANT_NAME);
  434 |         const completedCount = computeItemTotals(bigs).completedTransferCount;
  435 |         const transferTotal = await loadMayTransferTotal(page, runtimeEnv);
```