# 거래명세표 플러그인 개발 문서
> **플러그인명**: 웰컴스 거래명세표 (`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, 여백 균형 |
| 이전 | 홈페이지 개발 기록 | 김재석 | 2026-04-28 |
|---|---|---|---|
| 다음 | MangBoard Markdown Support — 개발 문서 기록 | 김재석 | 2026-04-28 |