워드프레스 거래명세표 플러그인 개발 기록

2026.04.28

# 거래명세표 플러그인 개발 문서


> **플러그인명**: 웰컴스 거래명세표 (`wellcoms-invoice`)

> **버전**: 1.6.0

> **경로**: `wp-content/plugins/wellcoms-invoice/`


---


# # 1. 개요


거래명세서/견적서 발행·인쇄 및 매입매출 관리를 위한 WordPress 관리자 플러그인.

관리화면에서 CRUD 형태로 거래명세서, 견적서, 매입매출 항목을 입력하고, 인쇄 및 분석 기능을 제공합니다.


## # 기능 아키텍처


```mermaid

flowchart LR

    A[wellcoms-invoice] --> B[거래명세서]

    A --> C[견적서]

    A --> D[매입매출 관리]

    A --> E[설정]

    B --> B1[CRUD + 인쇄]

    B --> B2[품목 20개]

    B --> B3[도장 표시]

    C --> C1[CRUD + 인쇄]

    C --> C2[번호 자동생성]

    C --> C3[참고사항]

    D --> D1[수입/지출 기록]

    D --> D2[카테고리 분석]

    D --> D3[바 차트]

    E --> E1[공급자 정보]

```


---


# # 2. 기능 목록


## # 2.1 거래명세서


| 기능 | 설명 |

|------|------|

| CRUD | 거래명세서 생성/조회/수정/삭제 (AJAX) |

| 인쇄 | A4 용지 기준, 엑셀 샘플 양식과 유사한 레이아웃 |

| 품목 | 최대 20개 품목, 균일 행 높이 |

| 자동 계산 | 공급가액 = round(단가 / 1.1 × 수량), 세액 = 합계 - 공급가액 |

| 도장 | 공급자 영역에 이미지 도장 표시 |

| 검색 | 거래명세서 번호, 공급받는자 상호 검색 |


## # 2.2 견적서


| 기능 | 설명 |

|------|------|

| CRUD | 견적서 생성/조회/수정/삭제 (AJAX) |

| 인쇄 | 거래명세서와 동일 레이아웃, 일부 수정 |

| 품목 | 최대 20개 품목, 균일 행 높이 |

| 자동 계산 | 공급가액 = round(단가 / 1.1 × 수량), 세액 = 합계 - 공급가액 |

| 도장 | 공급자 영역에 이미지 도장 표시 |

| 번호 생성 | `WC-EST-YYYYMMDD-NNN` 형식 |

| 참고사항 | 품목 하단에 memo 필드 출력 |


### # 견적서 인쇄 레이아웃


```mermaid

flowchart TD

    subgraph 견적서인쇄

        A["견 적 서 (20pt, 전체 폭)"] --> B

        subgraph B["정보 영역 flex 50/50"]

            B1["공급받는자<br/>상호 / 견적일 / 담당 / 제목"]

            B2["공급자<br/>등록번호 / 상호 / 성명 / 사업장 + 도장"]

        end

        B --> C["아래와 같이 견적합니다"]

        C --> D["품목 테이블 20행<br/>월/일/품목/규격/수량/단가/공급가액/세액/비고"]

        D --> E["하단 요약<br/>공급가액 / 세액 / 합계금액"]

        E --> F["참고사항 memo 내용"]

    end

```


> 거래명세서와의 차이: 타이틀만 표시, 견적일 추가, 사업장/합계금액 제거, 안내 텍스트 추가, 참고사항 추가


## # 2.3 매입매출 관리


| 기능 | 설명 |

|------|------|

| CRUD | 수입/지출 기록 생성/조회/수정/삭제 (AJAX) |

| 카테고리 | 컴퓨터 관련 수입, 일반 수입, 컴퓨터 관련 지출, 관리비 지출, 업무 지출, 기타 지출 |

| 결제 방법 | 현금, 카드, 외상 |

| 분석 | 월별/년별 카테고리별 수입/지출 요약, 바 차트 |

| 보고서 출력 | 요약 + 카테고리 + 내역 테이블을 새 창에 인쇄용 HTML로 출력 |

| 검색 | 내용/비고 검색, 카테고리 필터 |


## # 2.4 설정


| 항목 | 설명 |

|------|------|

| 공급자 정보 | 상호, 등록번호, 대표자, 주소, 전화, 팩스 |

| 기본값 | 견적서 플러그인(wellcoms-estimate) 설정 연동 |


---


# # 3. 파일 구조


```

wellcoms-invoice/

├── wellcoms-invoice.php          # 메인 bootstrap (AJAX handlers, 메뉴 등록)

├── includes/

│   ├── class-activator.php       # DB 스키마 정의 (activate 시 테이블 생성)

│   └── class-database.php        # CRUD 로직 (invoices, estimates, accounting)

├── admin/

│   ├── assets/

│   │   ├── admin.js              # 프론트엔드 JS (IIFE 패턴, WCI 네임스페이스)

│   │   └── admin.css             # 관리자 UI 스타일

│   └── views/

│       ├── page-invoices.php     # 거래명세서 관리 페이지

│       ├── page-estimates.php    # 견적서 관리 페이지

│       ├── page-accounting.php   # 매입매출 관리 페이지

│       └── page-settings.php     # 설정 페이지

```


---


# # 4. 데이터베이스


## # ER 다이어그램


```mermaid

erDiagram

    INVOICES ||--o{ INVOICE_ITEMS : has

    INV_ESTIMATES ||--o{ INV_ESTIMATE_ITEMS : has


    INVOICES {

        bigint id PK

        varchar invoice_no UK

        date issue_date

        varchar buyer_name

        varchar buyer_address

        varchar buyer_phone

        varchar buyer_receiver

        bigint buyer_unpaid

        int total_qty

        bigint total_supply

        bigint total_tax

        bigint total_amount

        text memo

    }


    INVOICE_ITEMS {

        bigint id PK

        bigint invoice_id FK

        int sort_order

        varchar category

        varchar name

        varchar spec

        int qty

        bigint unit_price

        bigint supply_amount

        bigint tax_amount

        bigint total_amount

        text memo

    }


    INV_ESTIMATES {

        bigint id PK

        varchar estimate_no UK

        date estimate_date

        varchar buyer_name

        varchar buyer_address

        varchar buyer_phone

        varchar manager

        varchar title

        int total_qty

        bigint total_supply

        bigint total_tax

        bigint total_amount

        text memo

    }


    INV_ESTIMATE_ITEMS {

        bigint id PK

        bigint estimate_id FK

        int sort_order

        varchar category

        varchar name

        varchar spec

        int qty

        bigint unit_price

        bigint supply_amount

        bigint tax_amount

        bigint total_amount

        text memo

    }


    ACCOUNTING {

        bigint id PK

        date record_date

        varchar category

        varchar payment_method

        tinyint is_expense

        varchar description

        bigint amount

        text memo

    }

```


## # 테이블 접두사 규칙


| 테이블 | 접두사 | 설명 |

|--------|--------|------|

| wp_wellcoms_invoices | 없음 | 거래명세서 메인 |

| wp_wellcoms_invoice_items | 없음 | 거래명세서 품목 |

| wp_wellcoms_inv_estimates | inv_ | 기존 wellcoms-estimate 플러그인과 충돌 방지 |

| wp_wellcoms_inv_estimate_items | inv_ | 견적서 품목 |

| wp_wellcoms_accounting | 없음 | 매입매출 기록 |


## # 4.1 wp_wellcoms_invoices


거래명세서 메인 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| invoice_no | varchar(30) UNIQUE | 명세서 번호 (예: WC-INV-20260401-001) |

| issue_date | date | 발행일자 |

| buyer_name | varchar(200) | 공급받는자 상호 |

| buyer_address | varchar(500) | 공급받는자 주소 |

| buyer_phone | varchar(50) | 공급받는자 전화 |

| buyer_receiver | varchar(100) | 인수자 |

| buyer_unpaid | bigint(20) | 미수금 |

| total_qty | int(11) | 총 수량 (자동 계산) |

| total_supply | bigint(20) | 총 공급가액 (자동 계산) |

| total_tax | bigint(20) | 총 세액 (자동 계산) |

| total_amount | bigint(20) | 총 합계금액 (자동 계산) |

| memo | text | 비고 |


## # 4.2 wp_wellcoms_invoice_items


거래명세서 품목 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| invoice_id | bigint(20) FK | 거래명세서 ID |

| sort_order | int(11) | 정렬 순서 |

| category | varchar(50) | 품목 분류 (월/일 입력용) |

| name | varchar(200) | 품목명 |

| spec | varchar(200) | 규격 |

| qty | int(11) | 수량 |

| unit_price | bigint(20) | 단가 |

| supply_amount | bigint(20) | 공급가액 (자동 계산) |

| tax_amount | bigint(20) | 세액 (자동 계산) |

| total_amount | bigint(20) | 합계 (자동 계산) |

| memo | text | 비고 |


## # 4.3 wp_wellcoms_inv_estimates


견적서 메인 테이블. 기존 견적서 플러그인(wellcoms-estimate)의 `wp_wellcoms_estimates`와 충돌을 피하기 위해 `inv_` 접두사 사용.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| estimate_no | varchar(30) UNIQUE | 견적서 번호 (예: WC-EST-20260409-001) |

| estimate_date | date | 견적일 |

| buyer_name | varchar(200) | 공급받는자 상호 |

| buyer_address | varchar(500) | 공급받는자 주소 |

| buyer_phone | varchar(50) | 공급받는자 전화 |

| manager | varchar(100) | 담당자 |

| title | varchar(200) | 견적 제목 |

| total_qty | int(11) | 총 수량 (자동 계산) |

| total_supply | bigint(20) | 총 공급가액 (자동 계산) |

| total_tax | bigint(20) | 총 세액 (자동 계산) |

| total_amount | bigint(20) | 총 합계금액 (자동 계산) |

| memo | text | 참고사항 |


## # 4.4 wp_wellcoms_inv_estimate_items


견적서 품목 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| estimate_id | bigint(20) FK | 견적서 ID |

| sort_order | int(11) | 정렬 순서 |

| category | varchar(50) | 품목 분류 |

| name | varchar(200) | 품목명 |

| spec | varchar(200) | 규격 |

| qty | int(11) | 수량 |

| unit_price | bigint(20) | 단가 |

| supply_amount | bigint(20) | 공급가액 (자동 계산) |

| tax_amount | bigint(20) | 세액 (자동 계산) |

| total_amount | bigint(20) | 합계 (자동 계산) |

| memo | text | 비고 |


## # 4.5 wp_wellcoms_accounting


매입매출 기록 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| record_date | date | 기록일자 |

| category | varchar(50) | 카테고리 |

| payment_method | varchar(20) | 결제 방법 (현금/카드/외상) |

| is_expense | tinyint(1) | 지출 여부 (카테고리명에서 자동 판별) |

| description | varchar(500) | 내용 |

| amount | bigint(20) | 금액 |

| memo | text | 비고 |


---


# # 5. 관리자 메뉴


```mermaid

flowchart LR

    A[거래명세표] --> B[거래명세서]

    A --> C[견적서]

    A --> D[매입매출]

    A --> E[설정]

    B --> B1["admin.php?page=wellcoms-invoice"]

    C --> C1["admin.php?page=wellcoms-invoice-estimates"]

    D --> D1["admin.php?page=wellcoms-invoice-accounting"]

    E --> E1["admin.php?page=wellcoms-invoice-settings"]

```


---


# # 6. AJAX 엔드포인트


모든 엔드포인트는 `wp_ajax_` 훅으로 등록되며, 관리자 인증 + nonce 검증을 사용합니다.


## # 요청 흐름


```mermaid

sequenceDiagram

    participant Browser as 관리자 브라우저

    participant JS as admin.js WCI

    participant WP as WordPress AJAX

    participant DB as class-database.php


    Browser->>JS: 사용자 액션 저장/조회/삭제

    JS->>WP: wp_ajax_wci_xxx + nonce

    WP->>WP: 관리자 인증 + nonce 검증

    WP->>DB: CRUD 메서드 호출

    DB-->>WP: 결과 반환

    WP-->>JS: JSON 응답

    JS-->>Browser: 화면 갱신

```


## # 거래명세서


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_invoices` | GET | 목록 조회 (검색, 날짜, 상태 필터, 페이지네이션) |

| `wci_get_invoice` | GET | 단건 조회 (품목 포함) |

| `wci_save_invoice` | POST | 생성/수정 (JSON data) |

| `wci_delete_invoice` | POST | 삭제 |


## # 견적서


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_estimates` | GET | 목록 조회 (검색, 날짜 필터, 페이지네이션) |

| `wci_get_estimate` | GET | 단건 조회 (품목 포함) |

| `wci_save_estimate` | POST | 생성/수정 (JSON data) |

| `wci_delete_estimate` | POST | 삭제 |


## # 매입매출


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_accounting` | GET | 목록 조회 (검색, 카테고리, 날짜 필터, 페이지네이션) |

| `wci_save_accounting` | POST | 생성/수정 (JSON data) |

| `wci_delete_accounting` | POST | 삭제 |

| `wci_get_accounting_summary` | GET | 월별/년별 요약 + 카테고리별 집계 + 월별 트렌드 |


## # 설정


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_settings` | GET | 공급자 설정 조회 |

| `wci_save_settings` | POST | 공급자 설정 저장 |


---


# # 7. 인쇄 양식 사양


## # 용지 및 여백

- `@page { size: A4; margin: 20mm 12mm; }`

- 상하 20mm, 좌우 12mm

- 화면 모드: `.page`에 inline `style="padding:15mm 0"` (거래명세서) / `10mm 0` (견적서)

- 인쇄 모드: `@media print { .page { padding: 0; } }` 로 화면 padding 제거


## # 거래명세서 레이아웃


```mermaid

flowchart TD

    subgraph 거래명세서인쇄

        A["상단 테이블<br/>거래일자 / 거 래 명 세 표 / 공급받는자용"]

        A --> B

        subgraph B["정보 영역 flex 50/50"]

            B1["공급받는자<br/>4행 세로 상호/주소/전화/인수자"]

            B2["공급자<br/>5행 등록번호/상호/대표자/주소/전화 + 도장"]

        end

        B --> C["품목 테이블 20행 균일 높이 26px<br/>월/일/품목/규격/수량/단가/공급가액/세액/비고<br/>+ 합계행"]

        C --> D["하단 테이블<br/>공급가액 / 세액 / 합계금액 / 미수금 / 인수자"]

    end

```


## # 견적서 레이아웃 (거래명세서와의 차이)

- 상단: "견  적  서" 타이틀만 표시 (20pt, 전체 폭)

- 공급받는자: 상호, 견적일, 담당, 제목 (4행, rowspan=4)

  - 사업장/합계금액 행 제거, 견적일 추가

- 안내 텍스트: "아래와 같이 견적합니다" (품목 테이블 상단)

- 하단: 공급가액/세액/합계금액 + 참고사항 (memo)


## # 스타일 규칙

- 모든 보더: `1px solid #999` (통일, 등록번호 셀 포함)

- 폰트: 9pt 기본 (거래명세서 제목 20pt, 견적서 제목 20pt)

- 품목 행 높이: 거래명세서 `26px`, 견적서 `25px`

- 품목 셀 padding: `3px` 대칭 + `vertical-align:middle` (수직 중앙 정렬)

- 금액 셀 padding: `2px`, `font-size:8pt` (줄바꿈 방지)

- 하단 요약 `.bv`: `padding:2px 4px`, `font-size:8pt`

- 공급받는자/공급자 섹션: `.info-wrap{height:132px}` 고정 + `table{height:100%}` (하단 정렬 동일)

- 세로 라벨: `writing-mode: vertical-rl`

- 도장: 85px, 공급자 영역 우측 중앙

- 배경 라벨: `#f0f0f0`

- 이중 보더 방지: `.stack + .stack { margin-top: -1px; }`

- 우측 라인 보호: `.iw { overflow: visible; }` (hidden 시 우측 보더 잘림)

- 타이틀 스타일: inline style로 직접 적용 (CSS specificity 문제 우회)

- 견적서 타이틀: `<div>` 사용 (보더 없음), 거래명세서 타이틀: `<td>` 사용 (보더 있음)


## # 공식 계산

- 공급가액 = `round(단가 / 1.1 × 수량)`

- 세액 = `수량 × 단가 - 공급가액`


---


# # 8. 매입매출 분석


## # 카테고리 구조


```mermaid

flowchart LR

    A[매입매출] --> B[수입]

    A --> C[지출]

    B --> B1[컴퓨터 관련 수입]

    B --> B2[일반 수입]

    C --> C1[컴퓨터 관련 지출]

    C --> C2[관리비 지출]

    C --> C3[업무 지출]

    C --> C4[기타 지출]

```


수입/지출 판별은 카테고리명에 "지출"이 포함되어 있는지로 자동 처리 (`isExpenseCategory()`).


## # 대시보드 요약 카드

- 총 수입, 총 지출, 순이익

- 카테고리별 카드 (수입/지출 색상 구분)

- 월별 바 차트 (수입/지출 비교)


---


# # 9. 보고서 출력


매입매출 관리 페이지에서 "보고서 출력" 버튼 클릭 시:


```mermaid

flowchart TD

    A["보고서 출력 버튼 클릭"] --> B["선택된 기간 데이터 조회<br/>월별 또는 년별"]

    B --> C["새 창에 인쇄용 HTML 생성"]

    C --> D["제목 + 기간 + 요약 카드"]

    D --> E["카테고리별 카드"]

    E --> F["내역 테이블"]

    F --> G["인쇄 버튼"]

    G --> H["page: size A4, margin 15mm"]

```


---


# # 10. 기술적 참고사항


## # JS 아키텍처

- IIFE 패턴으로 `WCI` 네임스페이스 노출

- **반드시 return 객체에 새 함수 추가** — 누락 시 `WCI.funcName()`이 조용히 실패

- jQuery AJAX 사용 (`wciData.ajaxUrl`, `wciData.nonce`)


## # 주의사항

- `is_expense` 비교는 반드시 `parseInt(r.is_expense) === 1` 사용 (문자열 "0"이 truthy)

- `fmt()` 함수는 이미 '원'을 반환하므로 중복 '+원' 금지

- `dbDelta()`는 기존 테이블에 컬럼 추가 실패 가능 — 수동 ALTER 필요

- 컨테이너에 WP-CLI, mysql CLI 없음 — DB 작업은 `php -r` + `$wpdb` 사용

- **테이블명 충돌 주의**: 기존 `wp_wellcoms_estimates`는 견적서 설정 플러그인이 사용 중 → 견적서 테이블은 `wp_wellcoms_inv_estimates` 사용


## # 개발 환경


```

컨테이너: wellcoms_wp

파일 경로: /var/www/html/wp-content/plugins/wellcoms-invoice/

권한: docker exec -u root wellcoms_wp chmod -R 777 ...

OPcache: docker exec wellcoms_wp php -r "opcache_reset();"

DB 작업: docker cp script.php wellcoms_wp:/tmp/ && docker exec wellcoms_wp php /tmp/script.php

DB 확인: docker exec wellcoms_wp bash -c "php -d error_reporting=0 -r '...$wpdb...'"

```


---


# # 11. 버그 수정 이력


| 이슈 | 원인 | 해결 |

|------|------|------|

| 빈화면 (3회) | JS 문자열 `+` 연결 누락, `</style>` 누락, `<table class="main">` 누락 | 누락된 코드 추가 |

| "원원" 중복 | `fmt()`가 이미 '원' 반환 | 중복 '+원' 제거 |

| 구분 오류 | `r.is_expense`가 문자열 "0"일 때 truthy | `parseInt() === 1` 사용 |

| 목록 로드 실패 | `loadAcctList` 대신 `loadList` 호출 | 올바른 함수명으로 수정 |

| 기간 라벨 미갱신 | `updatePeriodLabel`이 return 객체에 누락 | return 객체에 추가 |

| 컬럼 추가 실패 | `dbDelta()`가 ALTER 미지원 | 수동 ALTER 쿼리 실행 |

| `$wpdb->get_var()` 버그 | 2번째 인자를 prepare 파라미터로 오해 | `$wpdb->prepare()` 래핑 |

| status 필드 불필요 | 사용자 요청으로 기능 제거 | DB/PHP/JS/HTML에서 status 관련 코드 전부 제거 |

| 견적서 저장 오류 | `wp_wellcoms_estimates` 테이블이 기존 견적서 플러그인과 충돌 | 테이블명을 `wp_wellcoms_inv_estimates`/`wp_wellcoms_inv_estimate_items`로 변경 |

| 이중 보더 | 개별 박스로 구성하면서 인접 보더가 2px로 보임 | `.stack + .stack { margin-top: -1px; }` + 단일 테이블 구조로 변경 |

| 타이틀 폭 미변경 | CSS class `td.top-title{padding:35px 0}`이 `table.m td{padding:0}`과 specificity 동등 | inline style로 직접 `padding` 적용 |

| 상하단 여백 불균형 | `.page{padding}`이 `*{padding:0}`에 의해 덮임 | `.page` div에 inline `style="padding:15mm 0"` 적용 |

| 공급받는자/공급자 하단 위치 차이 | 4행×33px=132px vs 5행×26px=130px 불일치 | `.info-wrap{height:132px}` 고정 + `table{height:100%}` |

| 등록번호 보더 색상 차이 | `.reg-wrap table td`가 `#888`, 나머지 `#999` | 전체 `#999`로 통일 |

| 품목 글자 위 정렬 | 비대칭 padding (`1px 2px 8px`) 사용 | 대칭 `padding:3px 4px` + `vertical-align:middle` 명시 |

| 우측 라인 얇음 | `.iw{overflow:hidden}`이 테이블 우측 보더 절반 잘라냄 | `overflow:visible`로 변경 |

| 금액 줄바꿈 | 셀 padding이 커서 금액 텍스트가 두 줄로 나뉨 | 금액 셀 `padding:2px`, `font-size:8pt`로 축소 |


---


# # 12. 커밋 이력


| 커밋 | 내용 |

|------|------|

| 93bd653 | 초기 커밋: 플러그인 전체 구조, 거래명세서/매입매출/설정 |

| 382a9ad | status 필드 제거 |

| c26a470 | 견적서 기능 추가 및 테이블명 충돌 해결 |

| 0e754b5 | docs: README.md 추가 |

| (v1.6.0) | 인쇄 양식 전면 개편: 단일 표 구조, 보더 통일, 타이틀 20pt, 품목/규격 184:80, 금액 8pt, 여백 균형 |


▣ 마크 다운(Markdown) 문서(Mermaid 포함) 지원합니다.
글보기
제목워드프레스 거래명세표 플러그인 개발 기록2026-04-28 18:32
카테고리 기술노트
작성자 Level 10

# 거래명세표 플러그인 개발 문서


> **플러그인명**: 웰컴스 거래명세표 (`wellcoms-invoice`)

> **버전**: 1.6.0

> **경로**: `wp-content/plugins/wellcoms-invoice/`


---


## 1. 개요


거래명세서/견적서 발행·인쇄 및 매입매출 관리를 위한 WordPress 관리자 플러그인.

관리화면에서 CRUD 형태로 거래명세서, 견적서, 매입매출 항목을 입력하고, 인쇄 및 분석 기능을 제공합니다.


### 기능 아키텍처


```mermaid

flowchart LR

    A[wellcoms-invoice] --> B[거래명세서]

    A --> C[견적서]

    A --> D[매입매출 관리]

    A --> E[설정]

    B --> B1[CRUD + 인쇄]

    B --> B2[품목 20개]

    B --> B3[도장 표시]

    C --> C1[CRUD + 인쇄]

    C --> C2[번호 자동생성]

    C --> C3[참고사항]

    D --> D1[수입/지출 기록]

    D --> D2[카테고리 분석]

    D --> D3[바 차트]

    E --> E1[공급자 정보]

```


---


## 2. 기능 목록


### 2.1 거래명세서


| 기능 | 설명 |

|------|------|

| CRUD | 거래명세서 생성/조회/수정/삭제 (AJAX) |

| 인쇄 | A4 용지 기준, 엑셀 샘플 양식과 유사한 레이아웃 |

| 품목 | 최대 20개 품목, 균일 행 높이 |

| 자동 계산 | 공급가액 = round(단가 / 1.1 × 수량), 세액 = 합계 - 공급가액 |

| 도장 | 공급자 영역에 이미지 도장 표시 |

| 검색 | 거래명세서 번호, 공급받는자 상호 검색 |


### 2.2 견적서


| 기능 | 설명 |

|------|------|

| CRUD | 견적서 생성/조회/수정/삭제 (AJAX) |

| 인쇄 | 거래명세서와 동일 레이아웃, 일부 수정 |

| 품목 | 최대 20개 품목, 균일 행 높이 |

| 자동 계산 | 공급가액 = round(단가 / 1.1 × 수량), 세액 = 합계 - 공급가액 |

| 도장 | 공급자 영역에 이미지 도장 표시 |

| 번호 생성 | `WC-EST-YYYYMMDD-NNN` 형식 |

| 참고사항 | 품목 하단에 memo 필드 출력 |


#### 견적서 인쇄 레이아웃


```mermaid

flowchart TD

    subgraph 견적서인쇄

        A["견 적 서 (20pt, 전체 폭)"] --> B

        subgraph B["정보 영역 flex 50/50"]

            B1["공급받는자<br/>상호 / 견적일 / 담당 / 제목"]

            B2["공급자<br/>등록번호 / 상호 / 성명 / 사업장 + 도장"]

        end

        B --> C["아래와 같이 견적합니다"]

        C --> D["품목 테이블 20행<br/>월/일/품목/규격/수량/단가/공급가액/세액/비고"]

        D --> E["하단 요약<br/>공급가액 / 세액 / 합계금액"]

        E --> F["참고사항 memo 내용"]

    end

```


> 거래명세서와의 차이: 타이틀만 표시, 견적일 추가, 사업장/합계금액 제거, 안내 텍스트 추가, 참고사항 추가


### 2.3 매입매출 관리


| 기능 | 설명 |

|------|------|

| CRUD | 수입/지출 기록 생성/조회/수정/삭제 (AJAX) |

| 카테고리 | 컴퓨터 관련 수입, 일반 수입, 컴퓨터 관련 지출, 관리비 지출, 업무 지출, 기타 지출 |

| 결제 방법 | 현금, 카드, 외상 |

| 분석 | 월별/년별 카테고리별 수입/지출 요약, 바 차트 |

| 보고서 출력 | 요약 + 카테고리 + 내역 테이블을 새 창에 인쇄용 HTML로 출력 |

| 검색 | 내용/비고 검색, 카테고리 필터 |


### 2.4 설정


| 항목 | 설명 |

|------|------|

| 공급자 정보 | 상호, 등록번호, 대표자, 주소, 전화, 팩스 |

| 기본값 | 견적서 플러그인(wellcoms-estimate) 설정 연동 |


---


## 3. 파일 구조


```

wellcoms-invoice/

├── wellcoms-invoice.php          # 메인 bootstrap (AJAX handlers, 메뉴 등록)

├── includes/

│   ├── class-activator.php       # DB 스키마 정의 (activate 시 테이블 생성)

│   └── class-database.php        # CRUD 로직 (invoices, estimates, accounting)

├── admin/

│   ├── assets/

│   │   ├── admin.js              # 프론트엔드 JS (IIFE 패턴, WCI 네임스페이스)

│   │   └── admin.css             # 관리자 UI 스타일

│   └── views/

│       ├── page-invoices.php     # 거래명세서 관리 페이지

│       ├── page-estimates.php    # 견적서 관리 페이지

│       ├── page-accounting.php   # 매입매출 관리 페이지

│       └── page-settings.php     # 설정 페이지

```


---


## 4. 데이터베이스


### ER 다이어그램


```mermaid

erDiagram

    INVOICES ||--o{ INVOICE_ITEMS : has

    INV_ESTIMATES ||--o{ INV_ESTIMATE_ITEMS : has


    INVOICES {

        bigint id PK

        varchar invoice_no UK

        date issue_date

        varchar buyer_name

        varchar buyer_address

        varchar buyer_phone

        varchar buyer_receiver

        bigint buyer_unpaid

        int total_qty

        bigint total_supply

        bigint total_tax

        bigint total_amount

        text memo

    }


    INVOICE_ITEMS {

        bigint id PK

        bigint invoice_id FK

        int sort_order

        varchar category

        varchar name

        varchar spec

        int qty

        bigint unit_price

        bigint supply_amount

        bigint tax_amount

        bigint total_amount

        text memo

    }


    INV_ESTIMATES {

        bigint id PK

        varchar estimate_no UK

        date estimate_date

        varchar buyer_name

        varchar buyer_address

        varchar buyer_phone

        varchar manager

        varchar title

        int total_qty

        bigint total_supply

        bigint total_tax

        bigint total_amount

        text memo

    }


    INV_ESTIMATE_ITEMS {

        bigint id PK

        bigint estimate_id FK

        int sort_order

        varchar category

        varchar name

        varchar spec

        int qty

        bigint unit_price

        bigint supply_amount

        bigint tax_amount

        bigint total_amount

        text memo

    }


    ACCOUNTING {

        bigint id PK

        date record_date

        varchar category

        varchar payment_method

        tinyint is_expense

        varchar description

        bigint amount

        text memo

    }

```


### 테이블 접두사 규칙


| 테이블 | 접두사 | 설명 |

|--------|--------|------|

| wp_wellcoms_invoices | 없음 | 거래명세서 메인 |

| wp_wellcoms_invoice_items | 없음 | 거래명세서 품목 |

| wp_wellcoms_inv_estimates | inv_ | 기존 wellcoms-estimate 플러그인과 충돌 방지 |

| wp_wellcoms_inv_estimate_items | inv_ | 견적서 품목 |

| wp_wellcoms_accounting | 없음 | 매입매출 기록 |


### 4.1 wp_wellcoms_invoices


거래명세서 메인 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| invoice_no | varchar(30) UNIQUE | 명세서 번호 (예: WC-INV-20260401-001) |

| issue_date | date | 발행일자 |

| buyer_name | varchar(200) | 공급받는자 상호 |

| buyer_address | varchar(500) | 공급받는자 주소 |

| buyer_phone | varchar(50) | 공급받는자 전화 |

| buyer_receiver | varchar(100) | 인수자 |

| buyer_unpaid | bigint(20) | 미수금 |

| total_qty | int(11) | 총 수량 (자동 계산) |

| total_supply | bigint(20) | 총 공급가액 (자동 계산) |

| total_tax | bigint(20) | 총 세액 (자동 계산) |

| total_amount | bigint(20) | 총 합계금액 (자동 계산) |

| memo | text | 비고 |


### 4.2 wp_wellcoms_invoice_items


거래명세서 품목 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| invoice_id | bigint(20) FK | 거래명세서 ID |

| sort_order | int(11) | 정렬 순서 |

| category | varchar(50) | 품목 분류 (월/일 입력용) |

| name | varchar(200) | 품목명 |

| spec | varchar(200) | 규격 |

| qty | int(11) | 수량 |

| unit_price | bigint(20) | 단가 |

| supply_amount | bigint(20) | 공급가액 (자동 계산) |

| tax_amount | bigint(20) | 세액 (자동 계산) |

| total_amount | bigint(20) | 합계 (자동 계산) |

| memo | text | 비고 |


### 4.3 wp_wellcoms_inv_estimates


견적서 메인 테이블. 기존 견적서 플러그인(wellcoms-estimate)의 `wp_wellcoms_estimates`와 충돌을 피하기 위해 `inv_` 접두사 사용.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| estimate_no | varchar(30) UNIQUE | 견적서 번호 (예: WC-EST-20260409-001) |

| estimate_date | date | 견적일 |

| buyer_name | varchar(200) | 공급받는자 상호 |

| buyer_address | varchar(500) | 공급받는자 주소 |

| buyer_phone | varchar(50) | 공급받는자 전화 |

| manager | varchar(100) | 담당자 |

| title | varchar(200) | 견적 제목 |

| total_qty | int(11) | 총 수량 (자동 계산) |

| total_supply | bigint(20) | 총 공급가액 (자동 계산) |

| total_tax | bigint(20) | 총 세액 (자동 계산) |

| total_amount | bigint(20) | 총 합계금액 (자동 계산) |

| memo | text | 참고사항 |


### 4.4 wp_wellcoms_inv_estimate_items


견적서 품목 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| estimate_id | bigint(20) FK | 견적서 ID |

| sort_order | int(11) | 정렬 순서 |

| category | varchar(50) | 품목 분류 |

| name | varchar(200) | 품목명 |

| spec | varchar(200) | 규격 |

| qty | int(11) | 수량 |

| unit_price | bigint(20) | 단가 |

| supply_amount | bigint(20) | 공급가액 (자동 계산) |

| tax_amount | bigint(20) | 세액 (자동 계산) |

| total_amount | bigint(20) | 합계 (자동 계산) |

| memo | text | 비고 |


### 4.5 wp_wellcoms_accounting


매입매출 기록 테이블.


| 컬럼 | 타입 | 설명 |

|------|------|------|

| id | bigint(20) PK | 고유 ID |

| record_date | date | 기록일자 |

| category | varchar(50) | 카테고리 |

| payment_method | varchar(20) | 결제 방법 (현금/카드/외상) |

| is_expense | tinyint(1) | 지출 여부 (카테고리명에서 자동 판별) |

| description | varchar(500) | 내용 |

| amount | bigint(20) | 금액 |

| memo | text | 비고 |


---


## 5. 관리자 메뉴


```mermaid

flowchart LR

    A[거래명세표] --> B[거래명세서]

    A --> C[견적서]

    A --> D[매입매출]

    A --> E[설정]

    B --> B1["admin.php?page=wellcoms-invoice"]

    C --> C1["admin.php?page=wellcoms-invoice-estimates"]

    D --> D1["admin.php?page=wellcoms-invoice-accounting"]

    E --> E1["admin.php?page=wellcoms-invoice-settings"]

```


---


## 6. AJAX 엔드포인트


모든 엔드포인트는 `wp_ajax_` 훅으로 등록되며, 관리자 인증 + nonce 검증을 사용합니다.


### 요청 흐름


```mermaid

sequenceDiagram

    participant Browser as 관리자 브라우저

    participant JS as admin.js WCI

    participant WP as WordPress AJAX

    participant DB as class-database.php


    Browser->>JS: 사용자 액션 저장/조회/삭제

    JS->>WP: wp_ajax_wci_xxx + nonce

    WP->>WP: 관리자 인증 + nonce 검증

    WP->>DB: CRUD 메서드 호출

    DB-->>WP: 결과 반환

    WP-->>JS: JSON 응답

    JS-->>Browser: 화면 갱신

```


### 거래명세서


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_invoices` | GET | 목록 조회 (검색, 날짜, 상태 필터, 페이지네이션) |

| `wci_get_invoice` | GET | 단건 조회 (품목 포함) |

| `wci_save_invoice` | POST | 생성/수정 (JSON data) |

| `wci_delete_invoice` | POST | 삭제 |


### 견적서


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_estimates` | GET | 목록 조회 (검색, 날짜 필터, 페이지네이션) |

| `wci_get_estimate` | GET | 단건 조회 (품목 포함) |

| `wci_save_estimate` | POST | 생성/수정 (JSON data) |

| `wci_delete_estimate` | POST | 삭제 |


### 매입매출


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_accounting` | GET | 목록 조회 (검색, 카테고리, 날짜 필터, 페이지네이션) |

| `wci_save_accounting` | POST | 생성/수정 (JSON data) |

| `wci_delete_accounting` | POST | 삭제 |

| `wci_get_accounting_summary` | GET | 월별/년별 요약 + 카테고리별 집계 + 월별 트렌드 |


### 설정


| Action | 메서드 | 설명 |

|--------|--------|------|

| `wci_get_settings` | GET | 공급자 설정 조회 |

| `wci_save_settings` | POST | 공급자 설정 저장 |


---


## 7. 인쇄 양식 사양


### 용지 및 여백

- `@page { size: A4; margin: 20mm 12mm; }`

- 상하 20mm, 좌우 12mm

- 화면 모드: `.page`에 inline `style="padding:15mm 0"` (거래명세서) / `10mm 0` (견적서)

- 인쇄 모드: `@media print { .page { padding: 0; } }` 로 화면 padding 제거


### 거래명세서 레이아웃


```mermaid

flowchart TD

    subgraph 거래명세서인쇄

        A["상단 테이블<br/>거래일자 / 거 래 명 세 표 / 공급받는자용"]

        A --> B

        subgraph B["정보 영역 flex 50/50"]

            B1["공급받는자<br/>4행 세로 상호/주소/전화/인수자"]

            B2["공급자<br/>5행 등록번호/상호/대표자/주소/전화 + 도장"]

        end

        B --> C["품목 테이블 20행 균일 높이 26px<br/>월/일/품목/규격/수량/단가/공급가액/세액/비고<br/>+ 합계행"]

        C --> D["하단 테이블<br/>공급가액 / 세액 / 합계금액 / 미수금 / 인수자"]

    end

```


### 견적서 레이아웃 (거래명세서와의 차이)

- 상단: "견  적  서" 타이틀만 표시 (20pt, 전체 폭)

- 공급받는자: 상호, 견적일, 담당, 제목 (4행, rowspan=4)

  - 사업장/합계금액 행 제거, 견적일 추가

- 안내 텍스트: "아래와 같이 견적합니다" (품목 테이블 상단)

- 하단: 공급가액/세액/합계금액 + 참고사항 (memo)


### 스타일 규칙

- 모든 보더: `1px solid #999` (통일, 등록번호 셀 포함)

- 폰트: 9pt 기본 (거래명세서 제목 20pt, 견적서 제목 20pt)

- 품목 행 높이: 거래명세서 `26px`, 견적서 `25px`

- 품목 셀 padding: `3px` 대칭 + `vertical-align:middle` (수직 중앙 정렬)

- 금액 셀 padding: `2px`, `font-size:8pt` (줄바꿈 방지)

- 하단 요약 `.bv`: `padding:2px 4px`, `font-size:8pt`

- 공급받는자/공급자 섹션: `.info-wrap{height:132px}` 고정 + `table{height:100%}` (하단 정렬 동일)

- 세로 라벨: `writing-mode: vertical-rl`

- 도장: 85px, 공급자 영역 우측 중앙

- 배경 라벨: `#f0f0f0`

- 이중 보더 방지: `.stack + .stack { margin-top: -1px; }`

- 우측 라인 보호: `.iw { overflow: visible; }` (hidden 시 우측 보더 잘림)

- 타이틀 스타일: inline style로 직접 적용 (CSS specificity 문제 우회)

- 견적서 타이틀: `<div>` 사용 (보더 없음), 거래명세서 타이틀: `<td>` 사용 (보더 있음)


### 공식 계산

- 공급가액 = `round(단가 / 1.1 × 수량)`

- 세액 = `수량 × 단가 - 공급가액`


---


## 8. 매입매출 분석


### 카테고리 구조


```mermaid

flowchart LR

    A[매입매출] --> B[수입]

    A --> C[지출]

    B --> B1[컴퓨터 관련 수입]

    B --> B2[일반 수입]

    C --> C1[컴퓨터 관련 지출]

    C --> C2[관리비 지출]

    C --> C3[업무 지출]

    C --> C4[기타 지출]

```


수입/지출 판별은 카테고리명에 "지출"이 포함되어 있는지로 자동 처리 (`isExpenseCategory()`).


### 대시보드 요약 카드

- 총 수입, 총 지출, 순이익

- 카테고리별 카드 (수입/지출 색상 구분)

- 월별 바 차트 (수입/지출 비교)


---


## 9. 보고서 출력


매입매출 관리 페이지에서 "보고서 출력" 버튼 클릭 시:


```mermaid

flowchart TD

    A["보고서 출력 버튼 클릭"] --> B["선택된 기간 데이터 조회<br/>월별 또는 년별"]

    B --> C["새 창에 인쇄용 HTML 생성"]

    C --> D["제목 + 기간 + 요약 카드"]

    D --> E["카테고리별 카드"]

    E --> F["내역 테이블"]

    F --> G["인쇄 버튼"]

    G --> H["page: size A4, margin 15mm"]

```


---


## 10. 기술적 참고사항


### JS 아키텍처

- IIFE 패턴으로 `WCI` 네임스페이스 노출

- **반드시 return 객체에 새 함수 추가** — 누락 시 `WCI.funcName()`이 조용히 실패

- jQuery AJAX 사용 (`wciData.ajaxUrl`, `wciData.nonce`)


### 주의사항

- `is_expense` 비교는 반드시 `parseInt(r.is_expense) === 1` 사용 (문자열 "0"이 truthy)

- `fmt()` 함수는 이미 '원'을 반환하므로 중복 '+원' 금지

- `dbDelta()`는 기존 테이블에 컬럼 추가 실패 가능 — 수동 ALTER 필요

- 컨테이너에 WP-CLI, mysql CLI 없음 — DB 작업은 `php -r` + `$wpdb` 사용

- **테이블명 충돌 주의**: 기존 `wp_wellcoms_estimates`는 견적서 설정 플러그인이 사용 중 → 견적서 테이블은 `wp_wellcoms_inv_estimates` 사용


### 개발 환경


```

컨테이너: wellcoms_wp

파일 경로: /var/www/html/wp-content/plugins/wellcoms-invoice/

권한: docker exec -u root wellcoms_wp chmod -R 777 ...

OPcache: docker exec wellcoms_wp php -r "opcache_reset();"

DB 작업: docker cp script.php wellcoms_wp:/tmp/ && docker exec wellcoms_wp php /tmp/script.php

DB 확인: docker exec wellcoms_wp bash -c "php -d error_reporting=0 -r '...$wpdb...'"

```


---


## 11. 버그 수정 이력


| 이슈 | 원인 | 해결 |

|------|------|------|

| 빈화면 (3회) | JS 문자열 `+` 연결 누락, `</style>` 누락, `<table class="main">` 누락 | 누락된 코드 추가 |

| "원원" 중복 | `fmt()`가 이미 '원' 반환 | 중복 '+원' 제거 |

| 구분 오류 | `r.is_expense`가 문자열 "0"일 때 truthy | `parseInt() === 1` 사용 |

| 목록 로드 실패 | `loadAcctList` 대신 `loadList` 호출 | 올바른 함수명으로 수정 |

| 기간 라벨 미갱신 | `updatePeriodLabel`이 return 객체에 누락 | return 객체에 추가 |

| 컬럼 추가 실패 | `dbDelta()`가 ALTER 미지원 | 수동 ALTER 쿼리 실행 |

| `$wpdb->get_var()` 버그 | 2번째 인자를 prepare 파라미터로 오해 | `$wpdb->prepare()` 래핑 |

| status 필드 불필요 | 사용자 요청으로 기능 제거 | DB/PHP/JS/HTML에서 status 관련 코드 전부 제거 |

| 견적서 저장 오류 | `wp_wellcoms_estimates` 테이블이 기존 견적서 플러그인과 충돌 | 테이블명을 `wp_wellcoms_inv_estimates`/`wp_wellcoms_inv_estimate_items`로 변경 |

| 이중 보더 | 개별 박스로 구성하면서 인접 보더가 2px로 보임 | `.stack + .stack { margin-top: -1px; }` + 단일 테이블 구조로 변경 |

| 타이틀 폭 미변경 | CSS class `td.top-title{padding:35px 0}`이 `table.m td{padding:0}`과 specificity 동등 | inline style로 직접 `padding` 적용 |

| 상하단 여백 불균형 | `.page{padding}`이 `*{padding:0}`에 의해 덮임 | `.page` div에 inline `style="padding:15mm 0"` 적용 |

| 공급받는자/공급자 하단 위치 차이 | 4행×33px=132px vs 5행×26px=130px 불일치 | `.info-wrap{height:132px}` 고정 + `table{height:100%}` |

| 등록번호 보더 색상 차이 | `.reg-wrap table td`가 `#888`, 나머지 `#999` | 전체 `#999`로 통일 |

| 품목 글자 위 정렬 | 비대칭 padding (`1px 2px 8px`) 사용 | 대칭 `padding:3px 4px` + `vertical-align:middle` 명시 |

| 우측 라인 얇음 | `.iw{overflow:hidden}`이 테이블 우측 보더 절반 잘라냄 | `overflow:visible`로 변경 |

| 금액 줄바꿈 | 셀 padding이 커서 금액 텍스트가 두 줄로 나뉨 | 금액 셀 `padding:2px`, `font-size:8pt`로 축소 |


---


## 12. 커밋 이력


| 커밋 | 내용 |

|------|------|

| 93bd653 | 초기 커밋: 플러그인 전체 구조, 거래명세서/매입매출/설정 |

| 382a9ad | status 필드 제거 |

| c26a470 | 견적서 기능 추가 및 테이블명 충돌 해결 |

| 0e754b5 | docs: README.md 추가 |

| (v1.6.0) | 인쇄 양식 전면 개편: 단일 표 구조, 보더 통일, 타이틀 20pt, 품목/규격 184:80, 금액 8pt, 여백 균형 |


댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)