← 모든 레시피

대변전표 · 환불 명세

이미 발행한 청구서를 되돌리는 대변전표를 만듭니다. 원 문서를 가리키는 표기, 취소 도장 느낌의 투명도, 그리고 URL 대신 PDF 바이트를 그대로 받는 법을 배웁니다.

이 템플릿 가져오기

여기서 배우는 것

  • style-opacity
  • expr-string
  • api-export-file
  • expr-format-currency

편집기로 만들기

  1. 청구서 템플릿을 복제해 시작합니다 — 대변전표는 같은 표에 부호만 반대인 문서입니다.
  2. 제목을 대변전표로 바꾸고, 그 아래에 원 청구서 번호를 적습니다. {{ upper(data.origin) }} 처럼 대문자로 통일해 두면 눈으로 대조하기 쉽습니다.
  3. 라벨을 하나 더 놓아 "취소" 를 큼직하게 적고, 투명도를 0.12 로 내립니다. 글자 위에 겹쳐도 본문을 가리지 않습니다.
  4. 겹치는 순서는 객체 트리의 위아래가 정합니다. 도장이 본문 위에 오도록 맨 앞으로 보냅니다.
  5. 금액은 음수로 들어옵니다. 통화 서식은 음수를 그 로케일 규칙대로 표시하므로 따로 손댈 것이 없습니다.

스키마 · API 로 만들기

  1. 회계 시스템이 파일을 바로 저장해야 한다면 export_type 을 "file" 로 보내십시오. 응답 본문이 곧 PDF 이고 content-type 은 application/pdf 입니다.
  2. "file" 은 POST /v1/create 에서만 씁니다. 비동기와 배치는 대답할 시점에 파일이 아직 없으므로 거절합니다.
  3. 그 밖의 값은 조용히 기본값으로 떨어뜨리지 않고 거절합니다 — 조용히 무시된 옵션은 오타를 영원히 감춥니다.
  4. upper · lower · trim · sub · replace · pad · split 같은 문자열 함수를 표현식에서 쓸 수 있습니다. replace 는 리터럴만 바꿉니다 — 정규식은 받지 않습니다.

완성 문서

아래 JSON 이 위 두 순서가 만드는 바로 그 문서입니다. 저희 테스트가 이 문서를 검증하고 경고 없이 렌더되는지 확인하므로, 그대로 붙여 넣어 쓰셔도 됩니다.

예시
{
  "v": 1,
  "unit": "px",
  "page": {
    "size": "a4",
    "orientation": "portrait",
    "margin": {
      "t": 48,
      "r": 48,
      "b": 56,
      "l": 48
    },
    "header": "",
    "footer": "{{ data.company }} · [[page]] / [[pages]]"
  },
  "fonts": [],
  "styles": [
    {
      "id": "s_h1",
      "fontFamily": "Liberation Sans",
      "fontSize": 26,
      "fontWeight": 700,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_l",
      "fontFamily": "Liberation Sans",
      "fontSize": 13,
      "fontWeight": 400,
      "italic": false,
      "color": "#333333",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_tbl",
      "fontFamily": "Liberation Sans",
      "fontSize": 12,
      "fontWeight": 400,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "middle",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_total",
      "fontFamily": "Liberation Sans",
      "fontSize": 18,
      "fontWeight": 700,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "right",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_void",
      "fontFamily": "Liberation Sans",
      "fontSize": 96,
      "fontWeight": 700,
      "italic": false,
      "color": "#d92d20",
      "bg": "transparent",
      "align": "center",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 0.12
    }
  ],
  "nodes": [
    {
      "id": "n_title",
      "parent": "",
      "order": 0,
      "type": "label",
      "x": 0,
      "y": 0,
      "w": 460,
      "h": 34,
      "show": "",
      "styleId": "s_h1",
      "props": {
        "text": "Credit note",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Title"
    },
    {
      "id": "n_origin",
      "parent": "",
      "order": 1,
      "type": "label",
      "x": 0,
      "y": 42,
      "w": 460,
      "h": 20,
      "show": "",
      "styleId": "s_l",
      "props": {
        "text": "Refund against invoice {{ upper(data.origin) }}",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Original"
    },
    {
      "id": "n_items",
      "parent": "",
      "order": 2,
      "type": "table",
      "x": 0,
      "y": 96,
      "w": 697,
      "h": 130,
      "show": "",
      "styleId": "s_tbl",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#c9ced6",
        "fill": "#f3f5f7",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 6,
        "padX": 8,
        "align": "",
        "valign": "middle",
        "wrap": "",
        "bind": "data.items",
        "headerHeight": 28,
        "rowHeight": 24,
        "max": 100,
        "columns": [
          {
            "header": "Refunded item",
            "cell": "{{ item.name }}",
            "width": 497,
            "align": "left"
          },
          {
            "header": "Amount",
            "cell": "{{ format(item.amount, \"currency\", \"USD\", \"en-US\") }}",
            "width": 200,
            "align": "right"
          }
        ],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Refunded items"
    },
    {
      "id": "n_total",
      "parent": "",
      "order": 3,
      "type": "label",
      "x": 0,
      "y": 240,
      "w": 697,
      "h": 26,
      "show": "",
      "styleId": "s_total",
      "props": {
        "text": "Refund total {{ format(sum(data.items, \"amount\"), \"currency\", \"USD\", \"en-US\") }}",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Total"
    },
    {
      "id": "n_void",
      "parent": "",
      "order": 4,
      "type": "label",
      "x": 0,
      "y": 300,
      "w": 697,
      "h": 120,
      "show": "",
      "styleId": "s_void",
      "props": {
        "text": "VOID",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Void stamp"
    }
  ]
}

요청 본문입니다. template 자리에 위 문서를, data 자리에 아래 데이터를 넣습니다.

요청
{
  "template": "(the document above)",
  "data": {
    "company": "brewmypdf",
    "origin": "inv-2026-0042",
    "items": [
      {
        "name": "Maintenance (3 months) refund",
        "amount": -840
      },
      {
        "name": "Over-billing adjustment",
        "amount": -120
      }
    ]
  },
  "export_type": "file"
}