# 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: src/manager/tests/e2e/invoice/invoice-issue-credit-note.spec.ts >> issues a partial credit note for an open invoice with multiple items by unselecting items
- Location: src/manager/tests/e2e/invoice/invoice-issue-credit-note.spec.ts:28:7

# Error details

```
Error: Failed to create invoice via API: 500 {"type":"about:blank","title":"Server Error","status":500,"detail":"An unexpected error occurred."}
```

# Test source

```ts
  1   | import { APIRequestContext } from '@playwright/test';
  2   | import {
  3   |   fromInvoiceFetchApiResponse,
  4   |   toInvoiceCreateApiPayload,
  5   |   toInvoiceIssueCreditNoteApiPayload,
  6   | } from '@/manager/modules/invoice/invoice-mappers';
  7   | import {
  8   |   InvoiceCreateApiResponse,
  9   |   InvoiceCreateData,
  10  |   InvoiceFetchApiResponse,
  11  |   InvoiceIssueCreditNoteData,
  12  | } from '@/manager/modules/invoice/invoice-types';
  13  | import { Invoice } from '@/manager/modules/invoice/invoice-types';
  14  | import { fetchLocationDefaultPaymentMethodIdsViaApi } from '@/manager/modules/location/location-api';
  15  | import { isModernBillingMode } from '@/shared/utils/billing-utils';
  16  | import dayjs from 'dayjs';
  17  | 
  18  | // The manager API serves invoice writes under /v1; the billing API (modern Kinnovis billing) serves them under
  19  | // /api/v1, and in modern mode the manager API creates invoices against Stripe prices the tenant no longer has.
  20  | // Mirrors the manager SPA's kbInvoiceService: create, credit notes and mark-as-paid go through `request`, while
  21  | // reads (invoice fetch, location payment methods) stay on `managerRequest` in both modes.
  22  | export interface InvoiceApiClient {
  23  |   request: APIRequestContext;
  24  |   basePath: string;
  25  |   managerRequest: APIRequestContext;
  26  | }
  27  | 
  28  | const managerInvoiceBasePath = '/v1';
  29  | const billingInvoiceBasePath = '/api/v1';
  30  | 
  31  | export function resolveInvoiceApiClient(
  32  |   managerApiRequest: APIRequestContext,
  33  |   billingApiRequest: APIRequestContext
  34  | ): InvoiceApiClient {
  35  |   return isModernBillingMode()
  36  |     ? { request: billingApiRequest, basePath: billingInvoiceBasePath, managerRequest: managerApiRequest }
  37  |     : { request: managerApiRequest, basePath: managerInvoiceBasePath, managerRequest: managerApiRequest };
  38  | }
  39  | 
  40  | export async function createInvoiceViaApi(client: InvoiceApiClient, data: InvoiceCreateData): Promise<Invoice> {
  41  |   const defaultPaymentMethodIds = await fetchLocationDefaultPaymentMethodIdsViaApi(
  42  |     client.managerRequest,
  43  |     data.location.id
  44  |   );
  45  | 
  46  |   const response = await client.request.post(`${client.basePath}/invoice`, {
  47  |     data: toInvoiceCreateApiPayload(data, defaultPaymentMethodIds),
  48  |   });
  49  | 
  50  |   if (!response.ok()) {
> 51  |     throw new Error(`Failed to create invoice via API: ${response.status()} ${await response.text()}`);
      |           ^ Error: Failed to create invoice via API: 500 {"type":"about:blank","title":"Server Error","status":500,"detail":"An unexpected error occurred."}
  52  |   }
  53  | 
  54  |   const { id } = ((await response.json()) as InvoiceCreateApiResponse).data;
  55  |   const body = await fetchInvoiceViaApi(client.managerRequest, id);
  56  | 
  57  |   return fromInvoiceFetchApiResponse(id, body, data);
  58  | }
  59  | 
  60  | async function fetchInvoiceViaApi(request: APIRequestContext, id: number): Promise<InvoiceFetchApiResponse> {
  61  |   const response = await request.get(`/v1/invoice/${id}`);
  62  | 
  63  |   if (!response.ok()) {
  64  |     throw new Error(`Failed to fetch invoice ${id} via API: ${response.status()} ${await response.text()}`);
  65  |   }
  66  | 
  67  |   return (await response.json()) as InvoiceFetchApiResponse;
  68  | }
  69  | 
  70  | // Whether a credit note can be issued for the invoice right now — false while the invoice's charge is still
  71  | // settling (pending payment) or the API forbids crediting it for any other reason.
  72  | export async function fetchInvoiceCanBeCreditedViaApi(request: APIRequestContext, id: number): Promise<boolean> {
  73  |   const body = await fetchInvoiceViaApi(request, id);
  74  | 
  75  |   return !body.data.has_pending_payment && body.data.actions.can_issue_credit_note;
  76  | }
  77  | 
  78  | // The credit endpoint identifies what to credit by invoice line-item ids, which only exist server-side — so
  79  | // they are fetched here rather than carried on the Invoice domain type. The response is the updated invoice,
  80  | // not the credit note — the credit note itself has no read endpoint, and its number is derivable via
  81  | // toInvoiceCreditNoteNumber, so nothing from the response is needed.
  82  | export async function issueInvoiceCreditNoteViaApi(
  83  |   client: InvoiceApiClient,
  84  |   id: number,
  85  |   data: InvoiceIssueCreditNoteData
  86  | ): Promise<void> {
  87  |   const body = await fetchInvoiceViaApi(client.managerRequest, id);
  88  | 
  89  |   const response = await client.request.post(`${client.basePath}/credit-note`, {
  90  |     data: toInvoiceIssueCreditNoteApiPayload(data, id, body.data.line_items),
  91  |   });
  92  | 
  93  |   if (!response.ok()) {
  94  |     throw new Error(
  95  |       `Failed to issue credit note for invoice ${id} via API: ${response.status()} ${await response.text()}`
  96  |     );
  97  |   }
  98  | }
  99  | 
  100 | // Marks an open invoice paid with an out-of-band cash payment — purely internal, no Stripe charge or email.
  101 | // Used to stand up the 'paid' invoices the credit-note specs need without a real card charge.
  102 | export async function markInvoiceAsPaidViaApi(client: InvoiceApiClient, id: number): Promise<void> {
  103 |   const response = await client.request.put(`${client.basePath}/invoice/${id}/status-paid`, {
  104 |     data: { payment_date: dayjs().format('YYYY-MM-DD'), payment_type: 'cash' },
  105 |   });
  106 | 
  107 |   if (!response.ok()) {
  108 |     throw new Error(`Failed to mark invoice ${id} as paid via API: ${response.status()} ${await response.text()}`);
  109 |   }
  110 | }
  111 | 
```