Initial commit: Organized PTC project structure with .gitignore and README
This commit is contained in:
324
db/README.md
Normal file
324
db/README.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# Execution Budget DB
|
||||
|
||||
회사 실행예산 분석 시스템을 바로 시작할 수 있도록 만든 PostgreSQL 기본 스키마입니다.
|
||||
|
||||
현재 문서에는 2가지 데이터 흐름이 함께 있습니다.
|
||||
|
||||
- `seed.sql`: 구조 확인과 화면 테스트를 위한 샘플 데이터
|
||||
- [`PTC(2023-2026.02).xlsx`](/home/hyein/project/PTC(2023-2026.02).xlsx): 실제 적재 대상 원본 거래 데이터
|
||||
|
||||
즉, 지금 DB는 "데모용 샘플 데이터"로 바로 볼 수 있고, 실제 운영 데이터는 위 엑셀을 기준으로 다음 단계에서 적재해야 합니다.
|
||||
|
||||
## 기준 DB
|
||||
|
||||
- PostgreSQL 14 이상
|
||||
- 스키마명: `budget_app`
|
||||
- 파일: [`schema.sql`](/home/hyein/project/db/schema.sql)
|
||||
- 샘플 데이터: [`seed.sql`](/home/hyein/project/db/seed.sql)
|
||||
- 조회 예제: [`sample_queries.sql`](/home/hyein/project/db/sample_queries.sql)
|
||||
- 실제 엑셀 파싱: [`import_ptc_xlsx.py`](/home/hyein/project/db/import_ptc_xlsx.py)
|
||||
- 실제 엑셀 검증 쿼리: [`staging_queries.sql`](/home/hyein/project/db/staging_queries.sql)
|
||||
- 로컬 실행: [`docker-compose.yml`](/home/hyein/project/docker-compose.yml)
|
||||
|
||||
## 현재 상태
|
||||
|
||||
- [`schema.sql`](/home/hyein/project/db/schema.sql): 실행예산 분석용 기본 스키마
|
||||
- [`seed.sql`](/home/hyein/project/db/seed.sql): 브라우저에서 구조를 바로 확인하기 위한 예시 데이터
|
||||
- [`PTC(2023-2026.02).xlsx`](/home/hyein/project/PTC(2023-2026.02).xlsx): 실제 회사 거래내역 원본
|
||||
|
||||
중요:
|
||||
|
||||
- 현재 `docker compose up -d`로 보이는 데이터는 `seed.sql` 기준입니다
|
||||
- 아직 `PTC(2023-2026.02).xlsx` 내용이 자동으로 DB에 들어가도록 연결되지는 않았습니다
|
||||
- 이 엑셀은 성격상 `예산 파일`보다는 `실적/거래 원장`에 가깝습니다
|
||||
- 따라서 실제 적재 시에는 먼저 `staging` 테이블을 만든 뒤 정제해서 `actual_transactions`로 넣는 방식이 적합합니다
|
||||
- 현재는 `staging_ptc_transactions` 테이블과 CSV 생성 스크립트까지 준비된 상태입니다
|
||||
|
||||
## 바로 확인하는 방법
|
||||
|
||||
1. 터미널에서 `/home/hyein/project`로 이동
|
||||
2. `docker compose up -d`
|
||||
3. 브라우저에서 `http://localhost:8080` 접속
|
||||
4. 아래 정보로 로그인
|
||||
|
||||
- System: `PostgreSQL`
|
||||
- Server: `postgres`
|
||||
- Username: `budget`
|
||||
- Password: `budget123`
|
||||
- Database: `budgetdb`
|
||||
|
||||
접속 후 왼쪽에서 `budget_app` 스키마 안의 테이블과 뷰를 볼 수 있습니다.
|
||||
|
||||
처음 데이터가 안 보이면 아래를 확인하세요.
|
||||
|
||||
- `budget_app.companies`
|
||||
- `budget_app.projects`
|
||||
- `budget_app.vw_budget_vs_actual_monthly`
|
||||
- `budget_app.vw_project_profit_summary`
|
||||
- `budget_app.staging_ptc_transactions`
|
||||
|
||||
기존에 같은 컨테이너를 띄운 적이 있으면 초기 SQL이 다시 실행되지 않을 수 있습니다.
|
||||
|
||||
- 초기화가 필요하면 `docker compose down -v`
|
||||
- 다시 실행은 `docker compose up -d`
|
||||
|
||||
주의:
|
||||
|
||||
- 여기서 보이는 값은 현재 샘플 데이터입니다
|
||||
- 실제 엑셀 원본과 동일한 숫자가 보이는 단계는 아직 아닙니다
|
||||
|
||||
## 핵심 테이블
|
||||
|
||||
- `companies`: 회사 기본 정보
|
||||
- `fiscal_years`: 회계연도
|
||||
- `departments`: 부서
|
||||
- `employees`: 사용자/담당자
|
||||
- `business_units`: 사업부
|
||||
- `clients`: 발주처
|
||||
- `vendors`: 거래처
|
||||
- `projects`: 프로젝트/현장/사업
|
||||
- `account_categories`, `accounts`: 예산/실적 계정 과목
|
||||
- `budget_versions`: 예산 버전
|
||||
- `budget_items`: 월별 예산 금액
|
||||
- `purchase_requests`, `purchase_orders`: 집행 요청과 발주
|
||||
- `invoices`: 매출/매입 세금계산서
|
||||
- `actual_transactions`: 실제 실적 데이터
|
||||
- `cashflow_transactions`: 현금 유입/유출
|
||||
- `file_import_logs`: 엑셀 업로드 이력
|
||||
- `staging_ptc_transactions`: `PTC(2023-2026.02).xlsx` 원본 적재용 임시 테이블
|
||||
|
||||
## 실제 원본 엑셀 헤더
|
||||
|
||||
[`PTC(2023-2026.02).xlsx`](/home/hyein/project/PTC(2023-2026.02).xlsx) 확인 결과 헤더는 아래 14개입니다.
|
||||
|
||||
- `거래일`
|
||||
- `입/출금`
|
||||
- `계정코드`
|
||||
- `구분`
|
||||
- `부서`
|
||||
- `거래처`
|
||||
- `프로젝트코드`
|
||||
- `프로젝트 구분(안)`
|
||||
- `프로젝트명`
|
||||
- `적요`
|
||||
- `공급가액`
|
||||
- `부가세`
|
||||
- `합계금액`
|
||||
- `비고`
|
||||
|
||||
파일 특성 요약:
|
||||
|
||||
- 데이터 건수: `6,678건`
|
||||
- 기간: `2023-01-10 ~ 2026-02-28`
|
||||
- `출금` 중심 거래 데이터이며 일부 `입금` 포함
|
||||
- 운영상 `actual_transactions` 원본으로 보는 것이 가장 적절
|
||||
- `departments`, `accounts`, `projects`, `vendors` 마스터 추출에도 활용 가능
|
||||
|
||||
주의할 점:
|
||||
|
||||
- `프로젝트코드 -> 프로젝트명` 일부 불일치 존재
|
||||
- `프로젝트코드 -> 프로젝트 구분(안)` 일부 불일치 존재
|
||||
- 누락값 소수 존재
|
||||
- 음수 금액 존재
|
||||
|
||||
그래서 이 파일은 바로 본 테이블에 넣기보다 `staging -> 정제 -> 최종 적재` 흐름이 필요합니다.
|
||||
|
||||
## 실제 엑셀 적재 방법
|
||||
|
||||
### 1. 엑셀을 CSV로 변환
|
||||
|
||||
프로젝트 루트에서:
|
||||
|
||||
```bash
|
||||
python3 db/import_ptc_xlsx.py \
|
||||
--input "PTC(2023-2026.02).xlsx" \
|
||||
--output "db/ptc_staging.csv" \
|
||||
--batch "ptc_20260323"
|
||||
```
|
||||
|
||||
생성 결과:
|
||||
|
||||
- `db/ptc_staging.csv`
|
||||
|
||||
이 CSV는 `budget_app.staging_ptc_transactions`에 바로 넣을 수 있는 컬럼 구조입니다.
|
||||
|
||||
### 2. CSV를 Postgres 컨테이너 안으로 복사
|
||||
|
||||
```bash
|
||||
docker cp db/ptc_staging.csv budget-postgres:/tmp/ptc_staging.csv
|
||||
```
|
||||
|
||||
### 3. staging 테이블로 적재
|
||||
|
||||
```bash
|
||||
docker exec -i budget-postgres psql -U budget -d budgetdb -c "
|
||||
set search_path = budget_app, public;
|
||||
truncate table staging_ptc_transactions;
|
||||
copy staging_ptc_transactions (
|
||||
import_batch,
|
||||
source_file_name,
|
||||
source_sheet_name,
|
||||
source_row_no,
|
||||
transaction_date_raw,
|
||||
transaction_date,
|
||||
in_out,
|
||||
account_code_raw,
|
||||
account_name_raw,
|
||||
department_name_raw,
|
||||
vendor_name_raw,
|
||||
project_code_raw,
|
||||
project_type_raw,
|
||||
project_name_raw,
|
||||
description_raw,
|
||||
supply_amount_raw,
|
||||
vat_amount_raw,
|
||||
total_amount_raw,
|
||||
remarks_raw,
|
||||
supply_amount,
|
||||
vat_amount,
|
||||
total_amount,
|
||||
normalized_transaction_type,
|
||||
load_status,
|
||||
load_error
|
||||
) from '/tmp/ptc_staging.csv' with (format csv, header true, encoding 'UTF8');
|
||||
"
|
||||
```
|
||||
|
||||
### 4. 적재 결과 확인
|
||||
|
||||
```bash
|
||||
docker exec -it budget-postgres psql -U budget -d budgetdb
|
||||
```
|
||||
|
||||
접속 후:
|
||||
|
||||
```sql
|
||||
set search_path = budget_app, public;
|
||||
select import_batch, count(*) from staging_ptc_transactions group by import_batch;
|
||||
select * from staging_ptc_transactions order by source_row_no limit 20;
|
||||
```
|
||||
|
||||
또는 [`staging_queries.sql`](/home/hyein/project/db/staging_queries.sql)을 기준으로 검증하면 됩니다.
|
||||
|
||||
## 먼저 넣어야 하는 데이터 순서
|
||||
|
||||
샘플 데이터 기준:
|
||||
|
||||
1. `companies`
|
||||
2. `fiscal_years`
|
||||
3. `departments`
|
||||
4. `employees`
|
||||
5. `business_units`
|
||||
6. `clients`, `vendors`
|
||||
7. `account_categories`
|
||||
8. `accounts`
|
||||
9. `projects`
|
||||
10. `budget_versions`
|
||||
11. `budget_items`
|
||||
12. `purchase_requests`, `purchase_orders`, `invoices`
|
||||
13. `actual_transactions`
|
||||
|
||||
실제 엑셀 적재 기준 권장 순서:
|
||||
|
||||
1. 원본 엑셀을 `staging_ptc_transactions`에 그대로 적재
|
||||
2. `departments` 후보 추출
|
||||
3. `accounts` 후보 추출
|
||||
4. `projects` 후보 추출
|
||||
5. `vendors` 후보 추출
|
||||
6. 코드/명칭 불일치 정제
|
||||
7. `actual_transactions` 적재
|
||||
|
||||
즉, 실제 운영은 샘플 순서보다 `staging` 단계가 먼저입니다.
|
||||
|
||||
## 가장 많이 쓰게 될 분석
|
||||
|
||||
- 프로젝트별 예산 대비 실적
|
||||
- 부서별 월 집행률
|
||||
- 계정과목별 초과 집행
|
||||
- 프로젝트 손익
|
||||
- 발주/세금계산서 기준 실적 반영
|
||||
|
||||
기본 뷰도 포함돼 있습니다.
|
||||
|
||||
- `vw_budget_vs_actual_monthly`
|
||||
- `vw_project_profit_summary`
|
||||
|
||||
## 샘플 데이터 내용
|
||||
|
||||
- 회사 1개: `장헌건설`
|
||||
- 회계연도 1개: `2026`
|
||||
- 부서 4개
|
||||
- 직원 5명
|
||||
- 사업부 2개
|
||||
- 발주처 2개
|
||||
- 거래처 3개
|
||||
- 프로젝트 2개
|
||||
- 예산 버전 1개
|
||||
- 월별 예산 데이터 다수
|
||||
- 발주 요청/발주/세금계산서/실적 데이터 포함
|
||||
|
||||
즉, DB를 올리면 바로 "예산 대비 실적"과 "프로젝트 손익" 화면 구조를 테스트할 수 있습니다.
|
||||
|
||||
다만 이 샘플 데이터는 실제 엑셀 원본을 반영한 것이 아니라 데모용 예시입니다.
|
||||
|
||||
## 권장 운영 방식
|
||||
|
||||
- 예산은 `budget_versions` + `budget_items`로 버전 관리
|
||||
- 실제 집행은 최종적으로 `actual_transactions`에 적재
|
||||
- ERP, 엑셀, 수기 입력이 섞여도 `source_type`으로 출처 추적
|
||||
- 프로젝트 단위와 부서 단위를 모두 지원
|
||||
- 실제 엑셀 원본은 우선 `staging` 테이블에 저장 후 정제
|
||||
- `공급가액`, `부가세`, `합계금액` 중 어떤 금액을 실적으로 사용할지 회사 기준 확정 필요
|
||||
- `입/출금`과 `계정코드`를 함께 써서 `revenue/cost/expense` 분류 규칙 확정 필요
|
||||
- `staging_ptc_transactions.normalized_transaction_type`은 현재 1차 추정값입니다
|
||||
- 최종 적재 전 계정코드 기준 매핑표를 별도로 확정하는 것이 안전합니다
|
||||
|
||||
## 다음 추천 작업
|
||||
|
||||
1. `staging_ptc_transactions`로 실제 엑셀 적재 실행
|
||||
2. 계정코드별 `revenue/cost/expense` 분류표 확정
|
||||
3. 프로젝트코드/프로젝트명 불일치 정제 규칙 확정
|
||||
4. `staging -> actual_transactions` 변환 SQL 작성
|
||||
5. 필요하면 예산용 별도 엑셀 구조 추가
|
||||
6. 화면에 필요한 조회 API 설계
|
||||
|
||||
## 쿼리로 확인하는 방법
|
||||
|
||||
웹 화면 대신 SQL로 먼저 보고 싶으면 아래처럼 접속해서 확인할 수 있습니다.
|
||||
|
||||
```bash
|
||||
docker exec -it budget-postgres psql -U budget -d budgetdb
|
||||
```
|
||||
|
||||
접속 후:
|
||||
|
||||
```sql
|
||||
set search_path = budget_app, public;
|
||||
select * from companies;
|
||||
select * from projects;
|
||||
select * from vw_project_profit_summary;
|
||||
```
|
||||
|
||||
또는 [`sample_queries.sql`](/home/hyein/project/db/sample_queries.sql)에 준비된 조회문을 사용하면 됩니다.
|
||||
|
||||
## 예산 입력 예시
|
||||
|
||||
예를 들어 2026년 3월, 특정 프로젝트의 외주비 예산 5,000,000원을 넣으려면:
|
||||
|
||||
- `budget_versions`에 2026년 예산 버전 생성
|
||||
- `accounts`에 외주비 계정 등록
|
||||
- `budget_items`에 `month_no = 3`, `planned_amount = 5000000` 입력
|
||||
|
||||
## 실적 입력 예시
|
||||
|
||||
실제 외주비가 4,300,000원 집행되면:
|
||||
|
||||
- `actual_transactions`에 같은 프로젝트, 같은 계정, 같은 월로 입력
|
||||
- 이후 `vw_budget_vs_actual_monthly`에서 차이와 집행률 조회 가능
|
||||
|
||||
## 현재 문서에서 꼭 구분할 점
|
||||
|
||||
- `seed.sql`은 데모 확인용
|
||||
- 실제 운영 데이터는 [`PTC(2023-2026.02).xlsx`](/home/hyein/project/PTC(2023-2026.02).xlsx)
|
||||
- 현재 README의 실행 방법은 "데모 DB 확인 방법"으로 이해하면 맞습니다
|
||||
- 실제 엑셀 적재 절차는 다음 단계에서 추가 구현이 필요합니다
|
||||
208
db/import_ptc_xlsx.py
Normal file
208
db/import_ptc_xlsx.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Parse PTC(2023-2026.02).xlsx without external dependencies and export a CSV
|
||||
that can be loaded into budget_app.staging_ptc_transactions.
|
||||
|
||||
Usage:
|
||||
python3 db/import_ptc_xlsx.py \
|
||||
--input "PTC(2023-2026.02).xlsx" \
|
||||
--output db/ptc_staging.csv \
|
||||
--batch ptc_20260323
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree as ET
|
||||
from zipfile import ZipFile
|
||||
|
||||
|
||||
NS = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
|
||||
EXPECTED_HEADERS = [
|
||||
"거래일",
|
||||
"입/출금",
|
||||
"계정코드",
|
||||
"구분",
|
||||
"부서",
|
||||
"거래처",
|
||||
"프로젝트코드",
|
||||
"프로젝트 구분(안)",
|
||||
"프로젝트명",
|
||||
"적요",
|
||||
"공급가액",
|
||||
"부가세",
|
||||
"합계금액",
|
||||
"비고",
|
||||
]
|
||||
|
||||
|
||||
def col_to_num(col: str) -> int:
|
||||
value = 0
|
||||
for ch in col:
|
||||
if ch.isalpha():
|
||||
value = value * 26 + ord(ch.upper()) - 64
|
||||
return value
|
||||
|
||||
|
||||
def read_shared_strings(book: ZipFile) -> list[str]:
|
||||
strings = []
|
||||
root = ET.fromstring(book.read("xl/sharedStrings.xml"))
|
||||
for si in root.findall("a:si", NS):
|
||||
text = "".join(node.text or "" for node in si.iterfind(".//a:t", NS))
|
||||
strings.append(text)
|
||||
return strings
|
||||
|
||||
|
||||
def read_sheet_rows(book: ZipFile, shared_strings: list[str], sheet_path: str) -> list[list[str]]:
|
||||
root = ET.fromstring(book.read(sheet_path))
|
||||
rows = []
|
||||
for row in root.find("a:sheetData", NS).findall("a:row", NS):
|
||||
values = defaultdict(str)
|
||||
for cell in row.findall("a:c", NS):
|
||||
ref = cell.attrib.get("r", "")
|
||||
match = re.match(r"([A-Z]+)(\d+)", ref)
|
||||
col = col_to_num(match.group(1)) if match else None
|
||||
value_node = cell.find("a:v", NS)
|
||||
if value_node is None:
|
||||
value = ""
|
||||
else:
|
||||
value = value_node.text or ""
|
||||
if cell.attrib.get("t") == "s":
|
||||
value = shared_strings[int(value)]
|
||||
values[col] = value
|
||||
width = max(values) if values else 0
|
||||
rows.append([values[i] for i in range(1, width + 1)])
|
||||
return rows
|
||||
|
||||
|
||||
def excel_serial_to_date(value: str) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
try:
|
||||
serial = float(value)
|
||||
except ValueError:
|
||||
return value
|
||||
base = datetime(1899, 12, 30)
|
||||
return (base + timedelta(days=serial)).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def parse_amount(value: str) -> str:
|
||||
value = (value or "").strip()
|
||||
if not value or value == "-":
|
||||
return ""
|
||||
normalized = value.replace(",", "")
|
||||
return normalized
|
||||
|
||||
|
||||
def normalize_transaction_type(in_out: str, account_name: str) -> str:
|
||||
in_out = (in_out or "").strip()
|
||||
account_name = (account_name or "").strip()
|
||||
if in_out == "입금":
|
||||
return "revenue"
|
||||
if in_out == "출금":
|
||||
if "수입" in account_name or "매출" in account_name:
|
||||
return "revenue"
|
||||
return "cost_expense"
|
||||
return ""
|
||||
|
||||
|
||||
def export_csv(input_path: Path, output_path: Path, batch_name: str) -> None:
|
||||
with ZipFile(input_path) as book:
|
||||
shared_strings = read_shared_strings(book)
|
||||
rows = read_sheet_rows(book, shared_strings, "xl/worksheets/sheet1.xml")
|
||||
|
||||
if not rows:
|
||||
raise ValueError("No rows found in workbook")
|
||||
|
||||
headers = rows[0]
|
||||
if headers != EXPECTED_HEADERS:
|
||||
raise ValueError(f"Unexpected headers: {headers}")
|
||||
|
||||
data_rows = rows[1:]
|
||||
width = len(EXPECTED_HEADERS)
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with output_path.open("w", newline="", encoding="utf-8-sig") as fp:
|
||||
writer = csv.DictWriter(
|
||||
fp,
|
||||
fieldnames=[
|
||||
"import_batch",
|
||||
"source_file_name",
|
||||
"source_sheet_name",
|
||||
"source_row_no",
|
||||
"transaction_date_raw",
|
||||
"transaction_date",
|
||||
"in_out",
|
||||
"account_code_raw",
|
||||
"account_name_raw",
|
||||
"department_name_raw",
|
||||
"vendor_name_raw",
|
||||
"project_code_raw",
|
||||
"project_type_raw",
|
||||
"project_name_raw",
|
||||
"description_raw",
|
||||
"supply_amount_raw",
|
||||
"vat_amount_raw",
|
||||
"total_amount_raw",
|
||||
"remarks_raw",
|
||||
"supply_amount",
|
||||
"vat_amount",
|
||||
"total_amount",
|
||||
"normalized_transaction_type",
|
||||
"load_status",
|
||||
"load_error",
|
||||
],
|
||||
)
|
||||
writer.writeheader()
|
||||
|
||||
for index, row in enumerate(data_rows, start=2):
|
||||
current = row + [""] * (width - len(row)) if len(row) < width else row[:width]
|
||||
writer.writerow(
|
||||
{
|
||||
"import_batch": batch_name,
|
||||
"source_file_name": input_path.name,
|
||||
"source_sheet_name": "Sheet1",
|
||||
"source_row_no": index,
|
||||
"transaction_date_raw": current[0],
|
||||
"transaction_date": excel_serial_to_date(current[0]),
|
||||
"in_out": current[1],
|
||||
"account_code_raw": current[2],
|
||||
"account_name_raw": current[3],
|
||||
"department_name_raw": current[4],
|
||||
"vendor_name_raw": current[5],
|
||||
"project_code_raw": current[6],
|
||||
"project_type_raw": current[7],
|
||||
"project_name_raw": current[8],
|
||||
"description_raw": current[9],
|
||||
"supply_amount_raw": current[10],
|
||||
"vat_amount_raw": current[11],
|
||||
"total_amount_raw": current[12],
|
||||
"remarks_raw": current[13],
|
||||
"supply_amount": parse_amount(current[10]),
|
||||
"vat_amount": parse_amount(current[11]),
|
||||
"total_amount": parse_amount(current[12]),
|
||||
"normalized_transaction_type": normalize_transaction_type(current[1], current[3]),
|
||||
"load_status": "loaded",
|
||||
"load_error": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--input", required=True, help="Path to the xlsx file")
|
||||
parser.add_argument("--output", required=True, help="Path to the output CSV file")
|
||||
parser.add_argument("--batch", required=True, help="Import batch name")
|
||||
args = parser.parse_args()
|
||||
|
||||
export_csv(Path(args.input), Path(args.output), args.batch)
|
||||
print(f"CSV exported to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
39
db/migrations/20260323_add_staging_ptc.sql
Normal file
39
db/migrations/20260323_add_staging_ptc.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
set search_path = budget_app, public;
|
||||
|
||||
create table if not exists staging_ptc_transactions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
import_batch varchar(50) not null,
|
||||
source_file_name varchar(255) not null,
|
||||
source_sheet_name varchar(100) not null default 'Sheet1',
|
||||
source_row_no integer not null,
|
||||
transaction_date_raw varchar(50),
|
||||
transaction_date date,
|
||||
in_out varchar(20),
|
||||
account_code_raw varchar(30),
|
||||
account_name_raw varchar(100),
|
||||
department_name_raw varchar(100),
|
||||
vendor_name_raw varchar(200),
|
||||
project_code_raw varchar(50),
|
||||
project_type_raw varchar(50),
|
||||
project_name_raw varchar(200),
|
||||
description_raw text,
|
||||
supply_amount_raw varchar(50),
|
||||
vat_amount_raw varchar(50),
|
||||
total_amount_raw varchar(50),
|
||||
remarks_raw text,
|
||||
supply_amount numeric(18, 2),
|
||||
vat_amount numeric(18, 2),
|
||||
total_amount numeric(18, 2),
|
||||
normalized_transaction_type varchar(20),
|
||||
load_status varchar(20) not null default 'loaded',
|
||||
load_error text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_staging_ptc_row unique (import_batch, source_row_no),
|
||||
constraint chk_staging_load_status check (load_status in ('loaded', 'mapped', 'error'))
|
||||
);
|
||||
|
||||
create index if not exists idx_staging_ptc_batch on staging_ptc_transactions(import_batch, source_row_no);
|
||||
create index if not exists idx_staging_ptc_project on staging_ptc_transactions(project_code_raw);
|
||||
create index if not exists idx_staging_ptc_account on staging_ptc_transactions(account_code_raw);
|
||||
create index if not exists idx_staging_ptc_department on staging_ptc_transactions(department_name_raw);
|
||||
50
db/sample_queries.sql
Normal file
50
db/sample_queries.sql
Normal file
@@ -0,0 +1,50 @@
|
||||
set search_path = budget_app, public;
|
||||
|
||||
-- 1. 회사 목록
|
||||
select id, code, name from companies;
|
||||
|
||||
-- 2. 프로젝트 목록
|
||||
select project_code, project_name, status, contract_amount
|
||||
from projects
|
||||
order by project_code;
|
||||
|
||||
-- 3. 프로젝트별 월 예산 대비 실적
|
||||
select
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
v.month_no,
|
||||
a.code as account_code,
|
||||
a.name as account_name,
|
||||
v.budget_amount,
|
||||
v.actual_amount,
|
||||
v.variance_amount,
|
||||
v.execution_rate
|
||||
from vw_budget_vs_actual_monthly v
|
||||
join projects p on p.id = v.project_id
|
||||
join accounts a on a.id = v.account_id
|
||||
order by p.project_code, v.month_no, a.code;
|
||||
|
||||
-- 4. 프로젝트 손익 요약
|
||||
select
|
||||
project_code,
|
||||
project_name,
|
||||
revenue_amount,
|
||||
cost_amount,
|
||||
profit_amount,
|
||||
profit_rate
|
||||
from vw_project_profit_summary
|
||||
order by project_code;
|
||||
|
||||
-- 5. 발주 요청과 발주 현황
|
||||
select
|
||||
pr.request_no,
|
||||
po.order_no,
|
||||
p.project_code,
|
||||
v.name as vendor_name,
|
||||
pr.total_amount as request_amount,
|
||||
po.total_amount as order_amount
|
||||
from purchase_requests pr
|
||||
left join purchase_orders po on po.purchase_request_id = pr.id
|
||||
left join projects p on p.id = pr.project_id
|
||||
left join vendors v on v.id = pr.vendor_id
|
||||
order by pr.request_no;
|
||||
421
db/schema.sql
Normal file
421
db/schema.sql
Normal file
@@ -0,0 +1,421 @@
|
||||
-- Company execution budget analysis schema
|
||||
-- Target database: PostgreSQL 14+
|
||||
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
create schema if not exists budget_app;
|
||||
|
||||
set search_path = budget_app, public;
|
||||
|
||||
create table companies (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
code varchar(30) not null unique,
|
||||
name varchar(200) not null,
|
||||
business_number varchar(30),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table fiscal_years (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
fiscal_year integer not null,
|
||||
start_date date not null,
|
||||
end_date date not null,
|
||||
is_closed boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_fiscal_year unique (company_id, fiscal_year),
|
||||
constraint chk_fiscal_year_dates check (start_date <= end_date)
|
||||
);
|
||||
|
||||
create table departments (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
parent_department_id uuid references departments(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(100) not null,
|
||||
manager_name varchar(100),
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_department_code unique (company_id, code)
|
||||
);
|
||||
|
||||
create table employees (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
department_id uuid references departments(id),
|
||||
employee_no varchar(30) not null,
|
||||
name varchar(100) not null,
|
||||
title varchar(100),
|
||||
email varchar(200),
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_employee_no unique (company_id, employee_no)
|
||||
);
|
||||
|
||||
create table business_units (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(100) not null,
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_business_unit_code unique (company_id, code)
|
||||
);
|
||||
|
||||
create table vendors (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(200) not null,
|
||||
business_number varchar(30),
|
||||
contact_name varchar(100),
|
||||
phone varchar(50),
|
||||
email varchar(200),
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_vendor_code unique (company_id, code)
|
||||
);
|
||||
|
||||
create table clients (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(200) not null,
|
||||
business_number varchar(30),
|
||||
contact_name varchar(100),
|
||||
phone varchar(50),
|
||||
email varchar(200),
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_client_code unique (company_id, code)
|
||||
);
|
||||
|
||||
create table projects (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
business_unit_id uuid references business_units(id),
|
||||
department_id uuid references departments(id),
|
||||
client_id uuid references clients(id),
|
||||
project_code varchar(40) not null,
|
||||
project_name varchar(200) not null,
|
||||
project_type varchar(50),
|
||||
status varchar(30) not null default 'planning',
|
||||
contract_amount numeric(18, 2) not null default 0,
|
||||
start_date date,
|
||||
end_date date,
|
||||
manager_employee_id uuid references employees(id),
|
||||
description text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_project_code unique (company_id, project_code),
|
||||
constraint chk_project_status check (status in ('planning', 'active', 'on_hold', 'closed', 'cancelled'))
|
||||
);
|
||||
|
||||
create table account_categories (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(100) not null,
|
||||
category_type varchar(20) not null,
|
||||
sort_order integer not null default 0,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_account_category_code unique (company_id, code),
|
||||
constraint chk_account_category_type check (category_type in ('revenue', 'cost', 'expense'))
|
||||
);
|
||||
|
||||
create table accounts (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
category_id uuid not null references account_categories(id),
|
||||
code varchar(30) not null,
|
||||
name varchar(100) not null,
|
||||
account_type varchar(20) not null,
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_account_code unique (company_id, code),
|
||||
constraint chk_account_type check (account_type in ('revenue', 'cost', 'expense'))
|
||||
);
|
||||
|
||||
create table budget_versions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
fiscal_year_id uuid not null references fiscal_years(id),
|
||||
version_name varchar(100) not null,
|
||||
version_no integer not null,
|
||||
status varchar(20) not null default 'draft',
|
||||
created_by uuid references employees(id),
|
||||
approved_by uuid references employees(id),
|
||||
approved_at timestamptz,
|
||||
notes text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_budget_version unique (company_id, fiscal_year_id, version_no),
|
||||
constraint chk_budget_version_status check (status in ('draft', 'submitted', 'approved', 'archived'))
|
||||
);
|
||||
|
||||
create table budget_items (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
budget_version_id uuid not null references budget_versions(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
account_id uuid not null references accounts(id),
|
||||
month_no integer not null,
|
||||
planned_amount numeric(18, 2) not null default 0,
|
||||
memo text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_budget_item unique (budget_version_id, project_id, department_id, account_id, month_no),
|
||||
constraint chk_budget_month check (month_no between 1 and 12)
|
||||
);
|
||||
|
||||
create table purchase_requests (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
vendor_id uuid references vendors(id),
|
||||
requester_employee_id uuid references employees(id),
|
||||
request_no varchar(40) not null,
|
||||
request_date date not null,
|
||||
status varchar(20) not null default 'requested',
|
||||
description text,
|
||||
total_amount numeric(18, 2) not null default 0,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_purchase_request_no unique (company_id, request_no),
|
||||
constraint chk_purchase_request_status check (status in ('requested', 'approved', 'rejected', 'ordered', 'cancelled'))
|
||||
);
|
||||
|
||||
create table purchase_request_items (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
purchase_request_id uuid not null references purchase_requests(id) on delete cascade,
|
||||
account_id uuid not null references accounts(id),
|
||||
item_name varchar(200) not null,
|
||||
quantity numeric(18, 3) not null default 1,
|
||||
unit_price numeric(18, 2) not null default 0,
|
||||
amount numeric(18, 2) not null default 0,
|
||||
needed_date date,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table purchase_orders (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
purchase_request_id uuid references purchase_requests(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
vendor_id uuid references vendors(id),
|
||||
order_no varchar(40) not null,
|
||||
order_date date not null,
|
||||
status varchar(20) not null default 'issued',
|
||||
total_amount numeric(18, 2) not null default 0,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_purchase_order_no unique (company_id, order_no),
|
||||
constraint chk_purchase_order_status check (status in ('issued', 'partial_received', 'received', 'cancelled'))
|
||||
);
|
||||
|
||||
create table purchase_order_items (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
purchase_order_id uuid not null references purchase_orders(id) on delete cascade,
|
||||
account_id uuid not null references accounts(id),
|
||||
item_name varchar(200) not null,
|
||||
quantity numeric(18, 3) not null default 1,
|
||||
unit_price numeric(18, 2) not null default 0,
|
||||
amount numeric(18, 2) not null default 0,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table invoices (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
vendor_id uuid references vendors(id),
|
||||
client_id uuid references clients(id),
|
||||
invoice_no varchar(40) not null,
|
||||
invoice_type varchar(20) not null,
|
||||
issue_date date not null,
|
||||
due_date date,
|
||||
supply_amount numeric(18, 2) not null default 0,
|
||||
tax_amount numeric(18, 2) not null default 0,
|
||||
total_amount numeric(18, 2) not null default 0,
|
||||
status varchar(20) not null default 'issued',
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_invoice_no unique (company_id, invoice_no),
|
||||
constraint chk_invoice_type check (invoice_type in ('sales', 'purchase')),
|
||||
constraint chk_invoice_status check (status in ('issued', 'paid', 'cancelled'))
|
||||
);
|
||||
|
||||
create table actual_transactions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
fiscal_year_id uuid references fiscal_years(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
account_id uuid not null references accounts(id),
|
||||
vendor_id uuid references vendors(id),
|
||||
client_id uuid references clients(id),
|
||||
employee_id uuid references employees(id),
|
||||
source_type varchar(30) not null,
|
||||
source_id uuid,
|
||||
transaction_date date not null,
|
||||
month_no integer not null,
|
||||
transaction_type varchar(20) not null,
|
||||
amount numeric(18, 2) not null,
|
||||
description text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint chk_actual_month check (month_no between 1 and 12),
|
||||
constraint chk_transaction_type check (transaction_type in ('revenue', 'cost', 'expense')),
|
||||
constraint chk_source_type check (source_type in ('manual', 'invoice', 'purchase_order', 'erp', 'excel_upload'))
|
||||
);
|
||||
|
||||
create table cashflow_transactions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
project_id uuid references projects(id),
|
||||
department_id uuid references departments(id),
|
||||
transaction_date date not null,
|
||||
cashflow_type varchar(20) not null,
|
||||
amount numeric(18, 2) not null,
|
||||
description text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint chk_cashflow_type check (cashflow_type in ('inflow', 'outflow'))
|
||||
);
|
||||
|
||||
create table file_import_logs (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
company_id uuid not null references companies(id),
|
||||
import_type varchar(30) not null,
|
||||
file_name varchar(255) not null,
|
||||
row_count integer not null default 0,
|
||||
success_count integer not null default 0,
|
||||
failure_count integer not null default 0,
|
||||
imported_by uuid references employees(id),
|
||||
imported_at timestamptz not null default now(),
|
||||
notes text,
|
||||
constraint chk_import_type check (import_type in ('budget', 'actual', 'project', 'vendor', 'client', 'account'))
|
||||
);
|
||||
|
||||
create table staging_ptc_transactions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
import_batch varchar(50) not null,
|
||||
source_file_name varchar(255) not null,
|
||||
source_sheet_name varchar(100) not null default 'Sheet1',
|
||||
source_row_no integer not null,
|
||||
transaction_date_raw varchar(50),
|
||||
transaction_date date,
|
||||
in_out varchar(20),
|
||||
account_code_raw varchar(30),
|
||||
account_name_raw varchar(100),
|
||||
department_name_raw varchar(100),
|
||||
vendor_name_raw varchar(200),
|
||||
project_code_raw varchar(50),
|
||||
project_type_raw varchar(50),
|
||||
project_name_raw varchar(200),
|
||||
description_raw text,
|
||||
supply_amount_raw varchar(50),
|
||||
vat_amount_raw varchar(50),
|
||||
total_amount_raw varchar(50),
|
||||
remarks_raw text,
|
||||
supply_amount numeric(18, 2),
|
||||
vat_amount numeric(18, 2),
|
||||
total_amount numeric(18, 2),
|
||||
normalized_transaction_type varchar(20),
|
||||
load_status varchar(20) not null default 'loaded',
|
||||
load_error text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint uq_staging_ptc_row unique (import_batch, source_row_no),
|
||||
constraint chk_staging_load_status check (load_status in ('loaded', 'mapped', 'error'))
|
||||
);
|
||||
|
||||
create index idx_projects_company_status on projects(company_id, status);
|
||||
create index idx_projects_department on projects(department_id);
|
||||
create index idx_budget_items_project on budget_items(project_id, month_no);
|
||||
create index idx_budget_items_department on budget_items(department_id, month_no);
|
||||
create index idx_actual_transactions_project on actual_transactions(project_id, transaction_date);
|
||||
create index idx_actual_transactions_department on actual_transactions(department_id, transaction_date);
|
||||
create index idx_actual_transactions_account on actual_transactions(account_id, transaction_date);
|
||||
create index idx_purchase_orders_project on purchase_orders(project_id, order_date);
|
||||
create index idx_invoices_project on invoices(project_id, issue_date);
|
||||
create index idx_staging_ptc_batch on staging_ptc_transactions(import_batch, source_row_no);
|
||||
create index idx_staging_ptc_project on staging_ptc_transactions(project_code_raw);
|
||||
create index idx_staging_ptc_account on staging_ptc_transactions(account_code_raw);
|
||||
create index idx_staging_ptc_department on staging_ptc_transactions(department_name_raw);
|
||||
|
||||
create or replace view vw_budget_vs_actual_monthly as
|
||||
select
|
||||
bv.company_id,
|
||||
fy.fiscal_year,
|
||||
bi.project_id,
|
||||
bi.department_id,
|
||||
bi.account_id,
|
||||
bi.month_no,
|
||||
sum(bi.planned_amount) as budget_amount,
|
||||
coalesce(sum(at.amount), 0) as actual_amount,
|
||||
sum(bi.planned_amount) - coalesce(sum(at.amount), 0) as variance_amount,
|
||||
case
|
||||
when sum(bi.planned_amount) = 0 then 0
|
||||
else round((coalesce(sum(at.amount), 0) / sum(bi.planned_amount)) * 100, 2)
|
||||
end as execution_rate
|
||||
from budget_items bi
|
||||
join budget_versions bv on bv.id = bi.budget_version_id
|
||||
join fiscal_years fy on fy.id = bv.fiscal_year_id
|
||||
left join actual_transactions at
|
||||
on at.company_id = bv.company_id
|
||||
and coalesce(at.project_id, '00000000-0000-0000-0000-000000000000'::uuid) = coalesce(bi.project_id, '00000000-0000-0000-0000-000000000000'::uuid)
|
||||
and coalesce(at.department_id, '00000000-0000-0000-0000-000000000000'::uuid) = coalesce(bi.department_id, '00000000-0000-0000-0000-000000000000'::uuid)
|
||||
and at.account_id = bi.account_id
|
||||
and at.month_no = bi.month_no
|
||||
group by
|
||||
bv.company_id,
|
||||
fy.fiscal_year,
|
||||
bi.project_id,
|
||||
bi.department_id,
|
||||
bi.account_id,
|
||||
bi.month_no;
|
||||
|
||||
create or replace view vw_project_profit_summary as
|
||||
select
|
||||
p.id as project_id,
|
||||
p.project_code,
|
||||
p.project_name,
|
||||
p.status,
|
||||
p.contract_amount,
|
||||
coalesce(sum(case when a.account_type = 'revenue' then at.amount else 0 end), 0) as revenue_amount,
|
||||
coalesce(sum(case when a.account_type in ('cost', 'expense') then at.amount else 0 end), 0) as cost_amount,
|
||||
coalesce(sum(case when a.account_type = 'revenue' then at.amount else 0 end), 0)
|
||||
- coalesce(sum(case when a.account_type in ('cost', 'expense') then at.amount else 0 end), 0) as profit_amount,
|
||||
case
|
||||
when coalesce(sum(case when a.account_type = 'revenue' then at.amount else 0 end), 0) = 0 then 0
|
||||
else round(
|
||||
(
|
||||
(
|
||||
coalesce(sum(case when a.account_type = 'revenue' then at.amount else 0 end), 0)
|
||||
- coalesce(sum(case when a.account_type in ('cost', 'expense') then at.amount else 0 end), 0)
|
||||
)
|
||||
/ coalesce(sum(case when a.account_type = 'revenue' then at.amount else 0 end), 0)
|
||||
) * 100,
|
||||
2
|
||||
)
|
||||
end as profit_rate
|
||||
from projects p
|
||||
left join actual_transactions at on at.project_id = p.id
|
||||
left join accounts a on a.id = at.account_id
|
||||
group by p.id, p.project_code, p.project_name, p.status, p.contract_amount;
|
||||
584
db/seed.sql
Normal file
584
db/seed.sql
Normal file
@@ -0,0 +1,584 @@
|
||||
set search_path = budget_app, public;
|
||||
|
||||
insert into companies (code, name, business_number)
|
||||
values ('JH001', '장헌건설', '123-45-67890')
|
||||
on conflict (code) do nothing;
|
||||
|
||||
insert into fiscal_years (company_id, fiscal_year, start_date, end_date, is_closed)
|
||||
select c.id, 2026, date '2026-01-01', date '2026-12-31', false
|
||||
from companies c
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, fiscal_year) do nothing;
|
||||
|
||||
insert into departments (company_id, parent_department_id, code, name, manager_name)
|
||||
select c.id, null, x.code, x.name, x.manager_name
|
||||
from companies c
|
||||
cross join (
|
||||
values
|
||||
('HQ', '본사', '김대표'),
|
||||
('SALES', '영업본부', '박영업'),
|
||||
('EXEC', '실행본부', '이실행'),
|
||||
('FIN', '재무팀', '최재무')
|
||||
) as x(code, name, manager_name)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into employees (company_id, department_id, employee_no, name, title, email)
|
||||
select
|
||||
c.id,
|
||||
d.id,
|
||||
x.employee_no,
|
||||
x.name,
|
||||
x.title,
|
||||
x.email
|
||||
from companies c
|
||||
join departments d on d.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('E001', '김대표', '대표', 'ceo@jh.local', 'HQ'),
|
||||
('E002', '박영업', '영업이사', 'sales@jh.local', 'SALES'),
|
||||
('E003', '이실행', '실행이사', 'exec@jh.local', 'EXEC'),
|
||||
('E004', '최재무', '재무팀장', 'finance@jh.local', 'FIN'),
|
||||
('E005', '정현장', '현장소장', 'site1@jh.local', 'EXEC')
|
||||
) as x(employee_no, name, title, email, dept_code)
|
||||
on d.code = x.dept_code
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, employee_no) do nothing;
|
||||
|
||||
insert into business_units (company_id, code, name)
|
||||
select c.id, x.code, x.name
|
||||
from companies c
|
||||
cross join (
|
||||
values
|
||||
('BU001', '건축사업부'),
|
||||
('BU002', '토목사업부')
|
||||
) as x(code, name)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into clients (company_id, code, name, business_number, contact_name, phone, email)
|
||||
select c.id, x.code, x.name, x.business_number, x.contact_name, x.phone, x.email
|
||||
from companies c
|
||||
cross join (
|
||||
values
|
||||
('CL001', '한맥개발', '210-81-11111', '김발주', '02-1111-1111', 'client1@hmac.local'),
|
||||
('CL002', '동해산업', '210-81-22222', '이발주', '02-2222-2222', 'client2@donghae.local')
|
||||
) as x(code, name, business_number, contact_name, phone, email)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into vendors (company_id, code, name, business_number, contact_name, phone, email)
|
||||
select c.id, x.code, x.name, x.business_number, x.contact_name, x.phone, x.email
|
||||
from companies c
|
||||
cross join (
|
||||
values
|
||||
('VD001', '성우외주', '301-86-11111', '오외주', '031-111-1111', 'vendor1@sw.local'),
|
||||
('VD002', '대한자재', '301-86-22222', '문자재', '031-222-2222', 'vendor2@dh.local'),
|
||||
('VD003', '정우장비', '301-86-33333', '최장비', '031-333-3333', 'vendor3@jw.local')
|
||||
) as x(code, name, business_number, contact_name, phone, email)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into account_categories (company_id, code, name, category_type, sort_order)
|
||||
select c.id, x.code, x.name, x.category_type, x.sort_order
|
||||
from companies c
|
||||
cross join (
|
||||
values
|
||||
('REV', '매출', 'revenue', 1),
|
||||
('COST', '공사원가', 'cost', 2),
|
||||
('EXP', '판관비', 'expense', 3)
|
||||
) as x(code, name, category_type, sort_order)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into accounts (company_id, category_id, code, name, account_type)
|
||||
select
|
||||
c.id,
|
||||
ac.id,
|
||||
x.code,
|
||||
x.name,
|
||||
x.account_type
|
||||
from companies c
|
||||
join account_categories ac
|
||||
on ac.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('REV001', '기성매출', 'revenue', 'REV'),
|
||||
('REV002', '추가공사매출', 'revenue', 'REV'),
|
||||
('COST001', '외주비', 'cost', 'COST'),
|
||||
('COST002', '자재비', 'cost', 'COST'),
|
||||
('COST003', '장비비', 'cost', 'COST'),
|
||||
('EXP001', '현장관리비', 'expense', 'EXP'),
|
||||
('EXP002', '본사관리비', 'expense', 'EXP')
|
||||
) as x(code, name, account_type, category_code)
|
||||
on ac.code = x.category_code
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, code) do nothing;
|
||||
|
||||
insert into projects (
|
||||
company_id,
|
||||
business_unit_id,
|
||||
department_id,
|
||||
client_id,
|
||||
project_code,
|
||||
project_name,
|
||||
project_type,
|
||||
status,
|
||||
contract_amount,
|
||||
start_date,
|
||||
end_date,
|
||||
manager_employee_id,
|
||||
description
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
bu.id,
|
||||
d.id,
|
||||
cl.id,
|
||||
x.project_code,
|
||||
x.project_name,
|
||||
x.project_type,
|
||||
x.status,
|
||||
x.contract_amount,
|
||||
x.start_date,
|
||||
x.end_date,
|
||||
e.id,
|
||||
x.description
|
||||
from companies c
|
||||
join business_units bu on bu.company_id = c.id
|
||||
join departments d on d.company_id = c.id
|
||||
join clients cl on cl.company_id = c.id
|
||||
join employees e on e.company_id = c.id
|
||||
join (
|
||||
values
|
||||
(
|
||||
'PJT-2026-001',
|
||||
'장헌 오피스 신축공사',
|
||||
'건축',
|
||||
'active',
|
||||
1500000000.00,
|
||||
date '2026-01-10',
|
||||
date '2026-12-20',
|
||||
'BU001',
|
||||
'EXEC',
|
||||
'CL001',
|
||||
'E005',
|
||||
'오피스 신축 메인 프로젝트'
|
||||
),
|
||||
(
|
||||
'PJT-2026-002',
|
||||
'동해 물류창고 증축공사',
|
||||
'건축',
|
||||
'active',
|
||||
950000000.00,
|
||||
date '2026-02-01',
|
||||
date '2026-10-30',
|
||||
'BU001',
|
||||
'EXEC',
|
||||
'CL002',
|
||||
'E003',
|
||||
'물류창고 증축 프로젝트'
|
||||
)
|
||||
) as x(
|
||||
project_code,
|
||||
project_name,
|
||||
project_type,
|
||||
status,
|
||||
contract_amount,
|
||||
start_date,
|
||||
end_date,
|
||||
bu_code,
|
||||
dept_code,
|
||||
client_code,
|
||||
employee_no,
|
||||
description
|
||||
)
|
||||
on bu.code = x.bu_code
|
||||
and d.code = x.dept_code
|
||||
and cl.code = x.client_code
|
||||
and e.employee_no = x.employee_no
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, project_code) do nothing;
|
||||
|
||||
insert into budget_versions (
|
||||
company_id,
|
||||
fiscal_year_id,
|
||||
version_name,
|
||||
version_no,
|
||||
status,
|
||||
created_by,
|
||||
approved_by,
|
||||
approved_at,
|
||||
notes
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
fy.id,
|
||||
'2026 본예산',
|
||||
1,
|
||||
'approved',
|
||||
e1.id,
|
||||
e2.id,
|
||||
now(),
|
||||
'초기 승인 예산'
|
||||
from companies c
|
||||
join fiscal_years fy on fy.company_id = c.id and fy.fiscal_year = 2026
|
||||
join employees e1 on e1.company_id = c.id and e1.employee_no = 'E004'
|
||||
join employees e2 on e2.company_id = c.id and e2.employee_no = 'E001'
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, fiscal_year_id, version_no) do nothing;
|
||||
|
||||
insert into budget_items (
|
||||
company_id,
|
||||
budget_version_id,
|
||||
project_id,
|
||||
department_id,
|
||||
account_id,
|
||||
month_no,
|
||||
planned_amount,
|
||||
memo
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
bv.id,
|
||||
p.id,
|
||||
d.id,
|
||||
a.id,
|
||||
x.month_no,
|
||||
x.planned_amount,
|
||||
x.memo
|
||||
from companies c
|
||||
join budget_versions bv on bv.company_id = c.id and bv.version_no = 1
|
||||
join projects p on p.company_id = c.id
|
||||
join departments d on d.id = p.department_id
|
||||
join accounts a on a.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PJT-2026-001', 'REV001', 1, 120000000.00, '1월 기성매출'),
|
||||
('PJT-2026-001', 'REV001', 2, 130000000.00, '2월 기성매출'),
|
||||
('PJT-2026-001', 'REV001', 3, 150000000.00, '3월 기성매출'),
|
||||
('PJT-2026-001', 'COST001', 1, 40000000.00, '1월 외주비'),
|
||||
('PJT-2026-001', 'COST001', 2, 45000000.00, '2월 외주비'),
|
||||
('PJT-2026-001', 'COST001', 3, 50000000.00, '3월 외주비'),
|
||||
('PJT-2026-001', 'COST002', 1, 25000000.00, '1월 자재비'),
|
||||
('PJT-2026-001', 'COST002', 2, 28000000.00, '2월 자재비'),
|
||||
('PJT-2026-001', 'EXP001', 1, 8000000.00, '1월 현장관리비'),
|
||||
('PJT-2026-001', 'EXP001', 2, 8000000.00, '2월 현장관리비'),
|
||||
('PJT-2026-002', 'REV001', 2, 90000000.00, '2월 기성매출'),
|
||||
('PJT-2026-002', 'REV001', 3, 110000000.00, '3월 기성매출'),
|
||||
('PJT-2026-002', 'COST001', 2, 30000000.00, '2월 외주비'),
|
||||
('PJT-2026-002', 'COST002', 2, 22000000.00, '2월 자재비'),
|
||||
('PJT-2026-002', 'EXP001', 2, 6000000.00, '2월 현장관리비')
|
||||
) as x(project_code, account_code, month_no, planned_amount, memo)
|
||||
on p.project_code = x.project_code
|
||||
and a.code = x.account_code
|
||||
where c.code = 'JH001'
|
||||
on conflict (budget_version_id, project_id, department_id, account_id, month_no) do nothing;
|
||||
|
||||
insert into purchase_requests (
|
||||
company_id,
|
||||
project_id,
|
||||
department_id,
|
||||
vendor_id,
|
||||
requester_employee_id,
|
||||
request_no,
|
||||
request_date,
|
||||
status,
|
||||
description,
|
||||
total_amount
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
p.id,
|
||||
p.department_id,
|
||||
v.id,
|
||||
e.id,
|
||||
x.request_no,
|
||||
x.request_date,
|
||||
x.status,
|
||||
x.description,
|
||||
x.total_amount
|
||||
from companies c
|
||||
join projects p on p.company_id = c.id
|
||||
join vendors v on v.company_id = c.id
|
||||
join employees e on e.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PR-2026-0001', date '2026-01-12', 'approved', '1차 외주 발주 요청', 38000000.00, 'PJT-2026-001', 'VD001', 'E005'),
|
||||
('PR-2026-0002', date '2026-02-08', 'ordered', '자재 구매 요청', 21000000.00, 'PJT-2026-002', 'VD002', 'E003')
|
||||
) as x(request_no, request_date, status, description, total_amount, project_code, vendor_code, employee_no)
|
||||
on p.project_code = x.project_code
|
||||
and v.code = x.vendor_code
|
||||
and e.employee_no = x.employee_no
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, request_no) do nothing;
|
||||
|
||||
insert into purchase_request_items (
|
||||
purchase_request_id,
|
||||
account_id,
|
||||
item_name,
|
||||
quantity,
|
||||
unit_price,
|
||||
amount,
|
||||
needed_date
|
||||
)
|
||||
select
|
||||
pr.id,
|
||||
a.id,
|
||||
x.item_name,
|
||||
x.quantity,
|
||||
x.unit_price,
|
||||
x.amount,
|
||||
x.needed_date
|
||||
from purchase_requests pr
|
||||
join companies c on c.id = pr.company_id
|
||||
join accounts a on a.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PR-2026-0001', 'COST001', '철근 가공 외주', 1.0, 38000000.00, 38000000.00, date '2026-01-20'),
|
||||
('PR-2026-0002', 'COST002', '철골 자재 구매', 1.0, 21000000.00, 21000000.00, date '2026-02-15')
|
||||
) as x(request_no, account_code, item_name, quantity, unit_price, amount, needed_date)
|
||||
on pr.request_no = x.request_no
|
||||
and a.code = x.account_code
|
||||
where c.code = 'JH001';
|
||||
|
||||
insert into purchase_orders (
|
||||
company_id,
|
||||
purchase_request_id,
|
||||
project_id,
|
||||
department_id,
|
||||
vendor_id,
|
||||
order_no,
|
||||
order_date,
|
||||
status,
|
||||
total_amount
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
pr.id,
|
||||
p.id,
|
||||
p.department_id,
|
||||
v.id,
|
||||
x.order_no,
|
||||
x.order_date,
|
||||
x.status,
|
||||
x.total_amount
|
||||
from companies c
|
||||
join purchase_requests pr on pr.company_id = c.id
|
||||
join projects p on p.id = pr.project_id
|
||||
join vendors v on v.id = pr.vendor_id
|
||||
join (
|
||||
values
|
||||
('PO-2026-0001', date '2026-01-15', 'issued', 38000000.00, 'PR-2026-0001'),
|
||||
('PO-2026-0002', date '2026-02-10', 'received', 21000000.00, 'PR-2026-0002')
|
||||
) as x(order_no, order_date, status, total_amount, request_no)
|
||||
on pr.request_no = x.request_no
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, order_no) do nothing;
|
||||
|
||||
insert into purchase_order_items (
|
||||
purchase_order_id,
|
||||
account_id,
|
||||
item_name,
|
||||
quantity,
|
||||
unit_price,
|
||||
amount
|
||||
)
|
||||
select
|
||||
po.id,
|
||||
a.id,
|
||||
x.item_name,
|
||||
x.quantity,
|
||||
x.unit_price,
|
||||
x.amount
|
||||
from purchase_orders po
|
||||
join companies c on c.id = po.company_id
|
||||
join accounts a on a.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PO-2026-0001', 'COST001', '철근 가공 외주', 1.0, 38000000.00, 38000000.00),
|
||||
('PO-2026-0002', 'COST002', '철골 자재 구매', 1.0, 21000000.00, 21000000.00)
|
||||
) as x(order_no, account_code, item_name, quantity, unit_price, amount)
|
||||
on po.order_no = x.order_no
|
||||
and a.code = x.account_code
|
||||
where c.code = 'JH001';
|
||||
|
||||
insert into invoices (
|
||||
company_id,
|
||||
project_id,
|
||||
department_id,
|
||||
vendor_id,
|
||||
client_id,
|
||||
invoice_no,
|
||||
invoice_type,
|
||||
issue_date,
|
||||
due_date,
|
||||
supply_amount,
|
||||
tax_amount,
|
||||
total_amount,
|
||||
status
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
p.id,
|
||||
p.department_id,
|
||||
v.id,
|
||||
cl.id,
|
||||
x.invoice_no,
|
||||
x.invoice_type,
|
||||
x.issue_date,
|
||||
x.due_date,
|
||||
x.supply_amount,
|
||||
x.tax_amount,
|
||||
x.total_amount,
|
||||
x.status
|
||||
from companies c
|
||||
join projects p on p.company_id = c.id
|
||||
left join vendors v on v.company_id = c.id
|
||||
left join clients cl on cl.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('INV-S-2026-0001', 'sales', date '2026-01-31', date '2026-02-28', 120000000.00, 12000000.00, 132000000.00, 'issued', 'PJT-2026-001', null, 'CL001'),
|
||||
('INV-P-2026-0001', 'purchase', date '2026-01-25', date '2026-02-15', 38000000.00, 3800000.00, 41800000.00, 'paid', 'PJT-2026-001', 'VD001', null),
|
||||
('INV-P-2026-0002', 'purchase', date '2026-02-18', date '2026-03-10', 21000000.00, 2100000.00, 23100000.00, 'paid', 'PJT-2026-002', 'VD002', null)
|
||||
) as x(
|
||||
invoice_no,
|
||||
invoice_type,
|
||||
issue_date,
|
||||
due_date,
|
||||
supply_amount,
|
||||
tax_amount,
|
||||
total_amount,
|
||||
status,
|
||||
project_code,
|
||||
vendor_code,
|
||||
client_code
|
||||
)
|
||||
on p.project_code = x.project_code
|
||||
and (v.code = x.vendor_code or x.vendor_code is null)
|
||||
and (cl.code = x.client_code or x.client_code is null)
|
||||
where c.code = 'JH001'
|
||||
on conflict (company_id, invoice_no) do nothing;
|
||||
|
||||
insert into actual_transactions (
|
||||
company_id,
|
||||
fiscal_year_id,
|
||||
project_id,
|
||||
department_id,
|
||||
account_id,
|
||||
vendor_id,
|
||||
client_id,
|
||||
employee_id,
|
||||
source_type,
|
||||
source_id,
|
||||
transaction_date,
|
||||
month_no,
|
||||
transaction_type,
|
||||
amount,
|
||||
description
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
fy.id,
|
||||
p.id,
|
||||
p.department_id,
|
||||
a.id,
|
||||
v.id,
|
||||
cl.id,
|
||||
e.id,
|
||||
x.source_type,
|
||||
null,
|
||||
x.transaction_date,
|
||||
extract(month from x.transaction_date)::integer,
|
||||
x.transaction_type,
|
||||
x.amount,
|
||||
x.description
|
||||
from companies c
|
||||
join fiscal_years fy on fy.company_id = c.id and fy.fiscal_year = 2026
|
||||
join projects p on p.company_id = c.id
|
||||
join accounts a on a.company_id = c.id
|
||||
left join vendors v on v.company_id = c.id
|
||||
left join clients cl on cl.company_id = c.id
|
||||
left join employees e on e.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PJT-2026-001', 'REV001', date '2026-01-31', 'revenue', 118000000.00, 'invoice', '1월 기성매출 실적', null, 'CL001', 'E002'),
|
||||
('PJT-2026-001', 'COST001', date '2026-01-25', 'cost', 38000000.00, 'purchase_order', '1월 외주비 실적', 'VD001', null, 'E005'),
|
||||
('PJT-2026-001', 'COST002', date '2026-01-27', 'cost', 24000000.00, 'manual', '1월 자재비 실적', 'VD002', null, 'E005'),
|
||||
('PJT-2026-001', 'EXP001', date '2026-01-31', 'expense', 7500000.00, 'manual', '1월 현장관리비', null, null, 'E005'),
|
||||
('PJT-2026-001', 'REV001', date '2026-02-28', 'revenue', 126000000.00, 'invoice', '2월 기성매출 실적', null, 'CL001', 'E002'),
|
||||
('PJT-2026-001', 'COST001', date '2026-02-26', 'cost', 47000000.00, 'manual', '2월 외주비 실적', 'VD001', null, 'E005'),
|
||||
('PJT-2026-001', 'EXP001', date '2026-02-28', 'expense', 8100000.00, 'manual', '2월 현장관리비', null, null, 'E005'),
|
||||
('PJT-2026-002', 'REV001', date '2026-02-28', 'revenue', 92000000.00, 'invoice', '2월 기성매출 실적', null, 'CL002', 'E002'),
|
||||
('PJT-2026-002', 'COST001', date '2026-02-18', 'cost', 29500000.00, 'purchase_order', '2월 외주비 실적', 'VD001', null, 'E003'),
|
||||
('PJT-2026-002', 'COST002', date '2026-02-20', 'cost', 21000000.00, 'invoice', '2월 자재비 실적', 'VD002', null, 'E003'),
|
||||
('PJT-2026-002', 'EXP001', date '2026-02-28', 'expense', 5900000.00, 'manual', '2월 현장관리비', null, null, 'E003')
|
||||
) as x(
|
||||
project_code,
|
||||
account_code,
|
||||
transaction_date,
|
||||
transaction_type,
|
||||
amount,
|
||||
source_type,
|
||||
description,
|
||||
vendor_code,
|
||||
client_code,
|
||||
employee_no
|
||||
)
|
||||
on p.project_code = x.project_code
|
||||
and a.code = x.account_code
|
||||
and (v.code = x.vendor_code or x.vendor_code is null)
|
||||
and (cl.code = x.client_code or x.client_code is null)
|
||||
and (e.employee_no = x.employee_no or x.employee_no is null)
|
||||
where c.code = 'JH001';
|
||||
|
||||
insert into cashflow_transactions (
|
||||
company_id,
|
||||
project_id,
|
||||
department_id,
|
||||
transaction_date,
|
||||
cashflow_type,
|
||||
amount,
|
||||
description
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
p.id,
|
||||
p.department_id,
|
||||
x.transaction_date,
|
||||
x.cashflow_type,
|
||||
x.amount,
|
||||
x.description
|
||||
from companies c
|
||||
join projects p on p.company_id = c.id
|
||||
join (
|
||||
values
|
||||
('PJT-2026-001', date '2026-02-05', 'inflow', 132000000.00, '1월 매출 입금'),
|
||||
('PJT-2026-001', date '2026-02-15', 'outflow', 41800000.00, '외주비 지급'),
|
||||
('PJT-2026-002', date '2026-03-12', 'outflow', 23100000.00, '자재비 지급')
|
||||
) as x(project_code, transaction_date, cashflow_type, amount, description)
|
||||
on p.project_code = x.project_code
|
||||
where c.code = 'JH001';
|
||||
|
||||
insert into file_import_logs (
|
||||
company_id,
|
||||
import_type,
|
||||
file_name,
|
||||
row_count,
|
||||
success_count,
|
||||
failure_count,
|
||||
imported_by,
|
||||
notes
|
||||
)
|
||||
select
|
||||
c.id,
|
||||
'budget',
|
||||
'budget_2026_sample.xlsx',
|
||||
15,
|
||||
15,
|
||||
0,
|
||||
e.id,
|
||||
'샘플 예산 데이터 적재'
|
||||
from companies c
|
||||
join employees e on e.company_id = c.id and e.employee_no = 'E004'
|
||||
where c.code = 'JH001';
|
||||
54
db/staging_queries.sql
Normal file
54
db/staging_queries.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
set search_path = budget_app, public;
|
||||
|
||||
-- 1. Batch row count
|
||||
select import_batch, count(*) as row_count
|
||||
from staging_ptc_transactions
|
||||
group by import_batch
|
||||
order by import_batch desc;
|
||||
|
||||
-- 2. Account code master candidates
|
||||
select
|
||||
account_code_raw,
|
||||
account_name_raw,
|
||||
count(*) as txn_count
|
||||
from staging_ptc_transactions
|
||||
where coalesce(account_code_raw, '') <> ''
|
||||
group by account_code_raw, account_name_raw
|
||||
order by account_code_raw, account_name_raw;
|
||||
|
||||
-- 3. Department master candidates
|
||||
select
|
||||
department_name_raw,
|
||||
count(*) as txn_count
|
||||
from staging_ptc_transactions
|
||||
where coalesce(department_name_raw, '') <> ''
|
||||
group by department_name_raw
|
||||
order by txn_count desc, department_name_raw;
|
||||
|
||||
-- 4. Project code consistency check
|
||||
select
|
||||
project_code_raw,
|
||||
count(distinct project_name_raw) as distinct_project_names,
|
||||
count(distinct project_type_raw) as distinct_project_types
|
||||
from staging_ptc_transactions
|
||||
where coalesce(project_code_raw, '') <> ''
|
||||
group by project_code_raw
|
||||
having count(distinct project_name_raw) > 1
|
||||
or count(distinct project_type_raw) > 1
|
||||
order by project_code_raw;
|
||||
|
||||
-- 5. Missing key values
|
||||
select
|
||||
source_row_no,
|
||||
transaction_date,
|
||||
in_out,
|
||||
account_code_raw,
|
||||
project_code_raw,
|
||||
project_name_raw,
|
||||
description_raw
|
||||
from staging_ptc_transactions
|
||||
where coalesce(account_code_raw, '') = ''
|
||||
or coalesce(project_code_raw, '') = ''
|
||||
or coalesce(project_name_raw, '') = ''
|
||||
or coalesce(description_raw, '') = ''
|
||||
order by source_row_no;
|
||||
Reference in New Issue
Block a user