로컬푸드 직매장 정산 자동화 API 문서
1. 공통 기준
- 대상: 웹 서비스(Next.js 화면에서 사용)
- 데이터 형식:
application/json - 파일 업로드:
multipart/form-data - 금액: 원 단위 정수(
number)로 전달합니다. - 날짜:
YYYY-MM-DD - 날짜·시간: 한국 시간 기준 ISO 형식으로 기록합니다. (NFR-002)
- 인증: 로그인 후 받은 세션 또는 토큰을 요청에 포함합니다.
- 권한: 서버가 로그인 사용자 역할을 확인합니다. 화면에서 버튼을 숨겨도 권한 없는 요청은 서버에서 차단합니다. (NFR-004, NFR-005)
공통 오류 응답 예시
{
"error": {
"code": "SETTLEMENT_NOT_CLOSABLE",
"message": "필수 오류 또는 매출 대조 차이가 남아 있어 정산을 마감할 수 없습니다.",
"details": {
"errorCount": 2,
"reconciliationDifference": 1500
}
}
}
| 상태 코드 | 사용자가 이해할 수 있는 상황 |
|---|---|
400 | 입력값, 파일 형식 또는 날짜가 올바르지 않습니다. |
401 | 로그인 정보가 없거나 로그인 시간이 만료되었습니다. |
403 | 이 작업을 할 권한이 없습니다. |
404 | 요청한 농가, 품목, 정산 주차 또는 정산서를 찾을 수 없습니다. |
409 | 이미 등록된 값, 중복 거래, 기간 충돌 또는 마감된 자료의 수정 시도입니다. |
422 | 정산 규칙 누락, 상품 미연결 등 계산에 필요한 정보가 부족합니다. |
2. 로그인과 계정 권한
운영자 로그인 (FR-001)
| 항목 | 내용 |
|---|---|
| 목적 | 정산 담당자, 점장, 매장 운영자가 로그인하고 역할별 권한을 적용합니다. |
| 인증 | 필요 없음 |
| 방식·경로 | POST /api/auth/operator/login |
요청값
{
"loginId": "manager01",
"password": "비밀번호"
}
성공 응답
{
"user": {
"id": "usr_001",
"name": "김정산",
"role": "SETTLEMENT_MANAGER",
"closingPermission": false
},
"sessionExpiresAt": "2026-08-27T18:00:00+09:00"
}
오류 상황
- 아이디 또는 비밀번호가 맞지 않으면 “로그인 정보가 맞지 않습니다.”라고 안내합니다.
- 중지된 계정은 로그인할 수 없습니다.
- 계정 존재 여부는 별도로 알려 주지 않습니다.
운영자 로그아웃 (FR-001)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자 로그인 세션을 종료합니다. |
| 인증 | 운영자 |
| 방식·경로 | POST /api/auth/operator/logout |
성공 응답
{
"message": "로그아웃되었습니다."
}
담당자 계정 목록 조회 (FR-002)
| 항목 | 내용 |
|---|---|
| 목적 | 매장 운영자가 정산 담당자와 마감 권한자의 계정 상태를 확인합니다. |
| 인증 | 매장 운영자 |
| 방식·경로 | GET /api/operator-accounts |
성공 응답
{
"accounts": [
{
"id": "usr_001",
"name": "김정산",
"loginId": "manager01",
"role": "SETTLEMENT_MANAGER",
"closingPermission": false,
"status": "ACTIVE",
"updatedAt": "2026-08-27T09:00:00+09:00"
}
]
}
담당자 계정 등록 (FR-002)
| 항목 | 내용 |
|---|---|
| 목적 | 정산 담당자 또는 마감 권한을 가진 담당자 계정을 등록합니다. |
| 인증 | 매장 운영자 |
| 방식·경로 | POST /api/operator-accounts |
요청값
{
"name": "이점장",
"loginId": "store-chief",
"initialPassword": "초기비밀번호",
"role": "STORE_MANAGER",
"closingPermission": true
}
성공 응답
{
"account": {
"id": "usr_002",
"name": "이점장",
"role": "STORE_MANAGER",
"closingPermission": true,
"status": "ACTIVE"
}
}
오류 상황
- 일반 정산 담당자는 다른 사람의 계정이나 마감 권한을 변경할 수 없습니다.
- 이미 사용하는 로그인 아이디는 등록할 수 없습니다.
담당자 계정 권한·상태 변경 (FR-002)
| 항목 | 내용 |
|---|---|
| 목적 | 담당자 계정의 역할, 마감 권한, 사용 상태를 변경하거나 중지합니다. 변경 이력도 남깁니다. |
| 인증 | 매장 운영자 |
| 방식·경로 | PATCH /api/operator-accounts/{accountId} |
요청값
{
"role": "STORE_MANAGER",
"closingPermission": true,
"status": "ACTIVE",
"changeReason": "점장 변경"
}
성공 응답
{
"account": {
"id": "usr_002",
"role": "STORE_MANAGER",
"closingPermission": true,
"status": "ACTIVE"
},
"updatedBy": "usr_010",
"updatedAt": "2026-08-27T10:00:00+09:00"
}
오류 상황
- 마지막 마감 권한자를 중지하려 하면 대체 마감 권한자를 먼저 지정하도록 안내합니다.
- 사용 이력이 있는 계정은 삭제하지 않고 중지 상태로만 바꿉니다.
농가 로그인 (FR-003, FR-020)
| 항목 | 내용 |
|---|---|
| 목적 | 농가가 본인 계정으로 로그인해 본인의 정산서만 조회하도록 합니다. |
| 인증 | 필요 없음 |
| 방식·경로 | POST /api/auth/farmer/login |
요청값
{
"loginId": "farmer01",
"password": "비밀번호"
}
성공 응답
{
"farmer": {
"id": "farmer_001",
"name": "햇살농장"
},
"sessionExpiresAt": "2026-08-27T18:00:00+09:00"
}
오류 상황
- 중지된 농가 계정은 로그인할 수 없습니다.
- 농가는 다른 농가의 정산서 주소를 직접 입력해도 조회할 수 없습니다.
농가 로그인 계정 연결·상태 변경 (FR-003)
| 항목 | 내용 |
|---|---|
| 목적 | 등록된 농가에 로그인 계정을 연결하고 사용 여부를 관리합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | PUT /api/farmers/{farmerId}/login-account |
요청값
{
"loginId": "farmer01",
"initialPassword": "초기비밀번호",
"status": "ACTIVE"
}
성공 응답
{
"farmerId": "farmer_001",
"loginAccount": {
"id": "farmer-account_001",
"loginId": "farmer01",
"status": "ACTIVE"
}
}
오류 상황
- 이미 다른 농가에 연결된 계정은 연결할 수 없습니다.
- 첫 로그인 비밀번호 변경과 비밀번호 복구 방식은 확인 필요입니다.
3. 정산 주차와 기본 정보 관리
정산 주차 목록·상세 조회 (FR-004, FR-015)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자가 현재 정산 주차, 입금 예정일, 정산 진행 상태를 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks |
조회값
| 이름 | 설명 |
|---|---|
status | 선택값. 예: PROCESSING, CLOSING_COMPLETED |
from, to | 정산 대상 기간 조회용 날짜 |
성공 응답
{
"settlementWeeks": [
{
"id": "sw_2026084",
"salesPeriodStart": "2026-08-17",
"salesPeriodEnd": "2026-08-23",
"closingDate": "2026-08-27",
"expectedDepositDate": "2026-08-31",
"status": "ERROR_EXISTS",
"errorCount": 3,
"reconciliationDifference": 0,
"statementGenerated": false,
"closable": false
}
]
}
정산 주차 생성 (FR-004)
| 항목 | 내용 |
|---|---|
| 목적 | 판매 기간, 목요일 마감일, 입금 예정일이 포함된 정산 주차를 만듭니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks |
요청값
{
"salesPeriodStart": "2026-08-17",
"salesPeriodEnd": "2026-08-23",
"closingDate": "2026-08-27",
"expectedDepositDate": "2026-08-31"
}
성공 응답
{
"settlementWeek": {
"id": "sw_2026084",
"status": "PREPARING",
"salesPeriodStart": "2026-08-17",
"salesPeriodEnd": "2026-08-23",
"closingDate": "2026-08-27",
"expectedDepositDate": "2026-08-31"
}
}
오류 상황
- 다른 정산 주차와 판매 기간이 겹치면 생성할 수 없습니다.
- 종료일이 시작일보다 빠르면 저장할 수 없습니다.
농가·품목·품목군 목록 조회 (FR-005)
| 항목 | 내용 |
|---|---|
| 목적 | 농가, 품목군, 품목, 상품코드 정보를 조회해 판매 항목 연결과 규칙 등록에 사용합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/master-data |
성공 응답
{
"farmers": [
{
"id": "farmer_001",
"name": "햇살농장",
"status": "ACTIVE"
}
],
"productGroups": [
{
"id": "pg_vegetable",
"name": "채소"
}
],
"products": [
{
"id": "product_001",
"name": "무농약 상추 200g",
"farmerId": "farmer_001",
"productGroupId": "pg_vegetable",
"productCode": "880001",
"status": "ACTIVE"
}
]
}
농가 등록·변경·중지 (FR-005)
| 항목 | 내용 |
|---|---|
| 목적 | 농가 기본 정보와 사용 상태를 관리합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/farmers, PATCH /api/farmers/{farmerId} |
등록 요청값
{
"name": "햇살농장",
"representativeName": "김농부",
"phoneNumber": "010-0000-0000",
"status": "ACTIVE"
}
성공 응답
{
"farmer": {
"id": "farmer_001",
"name": "햇살농장",
"status": "ACTIVE"
}
}
오류 상황
- 정산 이력이 있는 농가는 삭제할 수 없고 사용 중지로 처리합니다.
품목군 등록·변경 (FR-005)
| 항목 | 내용 |
|---|---|
| 목적 | 여러 품목에 공통 정산 규칙을 적용할 수 있도록 품목군을 관리합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/product-groups, PATCH /api/product-groups/{productGroupId} |
요청값
{
"name": "채소",
"status": "ACTIVE"
}
성공 응답
{
"productGroup": {
"id": "pg_vegetable",
"name": "채소",
"status": "ACTIVE"
}
}
품목과 상품코드 등록·변경·중지 (FR-005)
| 항목 | 내용 |
|---|---|
| 목적 | 농가, 품목군, 품목명, 규격, 상품코드를 연결합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/products, PATCH /api/products/{productId} |
요청값
{
"name": "무농약 상추",
"specification": "200g",
"farmerId": "farmer_001",
"productGroupId": "pg_vegetable",
"productCode": "880001",
"status": "ACTIVE"
}
성공 응답
{
"product": {
"id": "product_001",
"name": "무농약 상추",
"farmerId": "farmer_001",
"productGroupId": "pg_vegetable",
"productCode": "880001",
"status": "ACTIVE"
}
}
오류 상황
- 하나의 상품코드를 둘 이상의 사용 중인 품목에 연결할 수 없습니다.
- 정산 이력이 있는 품목은 삭제할 수 없고 사용 중지로 처리합니다.
4. 정산 규칙 관리
정산 규칙 목록·상세 조회 (FR-006)
| 항목 | 내용 |
|---|---|
| 목적 | 수수료율, 할인 수수료, 반품·폐기 부담 기준과 적용 기간을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-rules |
조회값
| 이름 | 설명 |
|---|---|
farmerId | 특정 농가 규칙만 조회 |
productId | 특정 품목 규칙만 조회 |
productGroupId | 특정 품목군 규칙만 조회 |
effectiveDate | 특정 판매일에 적용되는 규칙 조회 |
성공 응답
{
"rules": [
{
"id": "rule_001",
"name": "햇살농장 채소 기본 약정",
"farmerId": "farmer_001",
"productGroupId": "pg_vegetable",
"commissionRate": 10,
"discountCommissionBasis": "DISCOUNTED_PRICE",
"returnBurdenParty": "FARMER",
"disposalBurdenParty": "FARMER",
"effectiveStartDate": "2026-01-01",
"effectiveEndDate": null,
"status": "ACTIVE"
}
]
}
정산 규칙 등록 (FR-006)
| 항목 | 내용 |
|---|---|
| 목적 | 농가별·품목별 약정에 따라 수수료율, 할인 수수료, 반품·폐기 부담 기준을 등록합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-rules |
요청값
{
"name": "햇살농장 채소 기본 약정",
"farmerId": "farmer_001",
"productId": null,
"productGroupId": "pg_vegetable",
"commissionRate": 10,
"discountCommissionBasis": "DISCOUNTED_PRICE",
"returnBurdenParty": "FARMER",
"returnCalculationBasis": "RETURN_AMOUNT",
"disposalBurdenParty": "STORE",
"disposalCalculationBasis": "RECORDED_AMOUNT",
"effectiveStartDate": "2026-09-01",
"effectiveEndDate": null,
"agreementConfirmed": true
}
성공 응답
{
"rule": {
"id": "rule_001",
"status": "ACTIVE",
"effectiveStartDate": "2026-09-01"
}
}
오류 상황
- 수수료율, 적용 시작일 또는 확정 여부가 없으면 저장할 수 없습니다.
- 같은 적용 대상과 기간에 규칙이 겹치면 저장할 수 없습니다.
- 확정되지 않은 약정은 적용 중 규칙으로 저장할 수 없습니다.
정산 규칙 변경 이력 등록 (FR-006)
| 항목 | 내용 |
|---|---|
| 목적 | 기존 규칙을 덮어쓰지 않고 종료일을 설정한 뒤 새 규칙을 등록합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-rules/{ruleId}/revisions |
요청값
{
"previousRuleEndDate": "2026-08-31",
"newRule": {
"name": "햇살농장 채소 변경 약정",
"farmerId": "farmer_001",
"productGroupId": "pg_vegetable",
"commissionRate": 12,
"discountCommissionBasis": "ORIGINAL_PRICE",
"returnBurdenParty": "FARMER",
"disposalBurdenParty": "STORE",
"effectiveStartDate": "2026-09-01",
"agreementConfirmed": true
},
"changeReason": "농가와 재약정"
}
성공 응답
{
"previousRule": {
"id": "rule_001",
"effectiveEndDate": "2026-08-31"
},
"newRule": {
"id": "rule_002",
"effectiveStartDate": "2026-09-01"
}
}
오류 상황
- 마감 완료된 정산에 적용된 규칙 자체는 수정하거나 삭제할 수 없습니다.
- 새 규칙은 시작일 이후의 미마감 거래에만 적용합니다.
5. 계산대 판매 파일과 판매 원장
계산대 판매 파일 업로드 (FR-007)
| 항목 | 내용 |
|---|---|
| 목적 | 정산 주차에 해당하는 계산대 판매 파일을 올리고 파일 형식과 행별 데이터를 검사합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/cashier-sales-files |
| 요청 형식 | multipart/form-data |
요청값
| 이름 | 형식 | 설명 |
|---|---|---|
file | 파일 | 계산대 판매 파일 |
fileName | 문자열 | 화면 표시용 파일명 |
성공 응답
{
"file": {
"id": "file_001",
"fileName": "sales-2026-08-23.csv",
"uploadedAt": "2026-08-24T09:30:00+09:00",
"uploadedBy": "usr_001"
},
"result": {
"totalRows": 1250,
"validRows": 1245,
"errorRows": 5,
"status": "PARTIALLY_VALID"
}
}
오류 상황
- 지원하지 않는 파일 형식 또는 필수 열이 없으면 파일을 반영하지 않습니다.
- 판매일이 정산 주차 밖이면 해당 행을 오류로 표시합니다.
- 동일 파일이 이미 반영되어 있으면 중복 업로드를 막습니다.
- 일부 오류 행이 있을 때 정상 행만 반영할지 전체를 다시 올릴지는 실제 계산대 판매 파일 형식 확인이 필요합니다.
파일 오류 행과 업로드 이력 조회 (FR-007, FR-024)
| 항목 | 내용 |
|---|---|
| 목적 | 업로드한 계산대 판매 파일의 오류 행, 업로드 담당자, 처리 시점을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/cashier-sales-files |
성공 응답
{
"files": [
{
"id": "file_001",
"fileName": "sales-2026-08-23.csv",
"uploadedBy": {
"id": "usr_001",
"name": "김정산"
},
"uploadedAt": "2026-08-24T09:30:00+09:00",
"validRows": 1245,
"errorRows": 5
}
]
}
상품코드 미연결 항목 조회 (FR-008)
| 항목 | 내용 |
|---|---|
| 목적 | 자동 연결되지 않은 상품코드 또는 상품코드가 비어 있는 판매 항목을 확인합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/unlinked-sales-items |
성공 응답
{
"items": [
{
"ledgerEntryId": "ledger_001",
"productCode": "999001",
"sourceProductName": "친환경 깻잎",
"saleDate": "2026-08-22",
"actualSaleAmount": 3000,
"connectionStatus": "UNLINKED",
"sourceFileId": "file_001",
"sourceRowNumber": 48
}
]
}
판매 항목 직접 연결과 상품코드 재사용 등록 (FR-008)
| 항목 | 내용 |
|---|---|
| 목적 | 처음 보는 상품코드 또는 빈 상품코드 판매 항목을 농가와 품목에 연결합니다. 상품코드가 있으면 이후 동일 코드에 재사용합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/sales-ledger/{ledgerEntryId}/product-connection |
요청값
{
"farmerId": "farmer_001",
"productId": "product_001",
"reuseForSameProductCode": true
}
성공 응답
{
"ledgerEntry": {
"id": "ledger_001",
"connectionMethod": "MANUAL",
"farmerId": "farmer_001",
"productId": "product_001",
"connectionStatus": "CONNECTED"
},
"productCodeMappingSaved": true
}
오류 상황
- 같은 상품코드가 여러 품목과 충돌하면 자동 연결하지 않고 직접 선택하도록 합니다.
- 기존 직접 연결을 바꾸더라도 이전 연결 이력은 삭제하지 않습니다.
판매 원장 조회 (FR-009, FR-010)
| 항목 | 내용 |
|---|---|
| 목적 | 판매·할인·반품 거래, 원본 파일 행, 상품 연결 결과, 적용 정산 규칙을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/sales-ledger |
조회값
| 이름 | 설명 |
|---|---|
farmerId | 농가별 조회 |
productId | 품목별 조회 |
transactionType | SALE, DISCOUNT_SALE, RETURN |
status | SETTLABLE, ERROR, DUPLICATE_SUSPECTED |
성공 응답
{
"entries": [
{
"id": "ledger_001",
"transactionId": "pos-20260822-0001",
"transactionType": "DISCOUNT_SALE",
"saleDate": "2026-08-22",
"farmer": {
"id": "farmer_001",
"name": "햇살농장"
},
"product": {
"id": "product_001",
"name": "무농약 상추 200g"
},
"originalSaleAmount": 4000,
"actualSaleAmount": 3000,
"discountAmount": 1000,
"appliedRuleId": "rule_001",
"status": "SETTLABLE",
"source": {
"fileId": "file_001",
"rowNumber": 48
}
}
]
}
중복 가능 거래 확인 처리 (FR-009)
| 항목 | 내용 |
|---|---|
| 목적 | 시스템이 자동 판별하지 못한 중복 가능 거래를 원본 자료와 비교해 정산 포함 또는 제외로 처리합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/sales-ledger/{ledgerEntryId}/duplicate-review |
요청값
{
"decision": "INCLUDE",
"reason": "서로 다른 영수증 번호의 정상 판매입니다."
}
성공 응답
{
"ledgerEntryId": "ledger_001",
"duplicateReviewStatus": "CONFIRMED_NOT_DUPLICATE",
"settlementEligibility": "SETTLABLE"
}
오류 상황
- 거래 고유값이 없는 계산대 판매 파일의 자동 중복 판단 기준은 확인 필요입니다.
- 확인되지 않은 중복 가능 거래는 정산 계산에서 제외되며 마감을 막습니다.
판매·할인·반품 내역 상세 조회 (FR-010)
| 항목 | 내용 |
|---|---|
| 목적 | 한 거래의 정상 판매금액, 실제 판매금액, 할인금액, 반품 연결 여부와 적용 정산 규칙을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/sales-ledger/{ledgerEntryId} |
성공 응답
{
"ledgerEntry": {
"id": "ledger_001",
"transactionType": "DISCOUNT_SALE",
"originalSaleAmount": 4000,
"actualSaleAmount": 3000,
"discountAmount": 1000,
"appliedRule": {
"id": "rule_001",
"commissionRate": 10,
"discountCommissionBasis": "DISCOUNTED_PRICE"
},
"sourceFile": {
"id": "file_001",
"rowNumber": 48
}
}
}
오류 상황
- 할인 전 또는 할인 후 금액이 필요한데 원본 파일에 없으면 확인 필요 오류로 표시합니다.
- 반품이 원래 판매와 연결되지 않으면 미확인 오류로 표시합니다.
- 마감된 거래는 조회만 할 수 있습니다.
6. 폐기와 미회수 재고
폐기·미회수 재고 등록 (FR-011)
| 항목 | 내용 |
|---|---|
| 목적 | 유통기한 경과, 미회수 재고 등의 폐기 내역과 정산 부담 금액을 등록합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/disposals |
요청값
{
"farmerId": "farmer_001",
"productId": "product_001",
"quantity": 3,
"disposalReason": "EXPIRED",
"burdenParty": "FARMER",
"settlementAmount": 6000,
"evidenceIds": ["evidence_001"],
"notifiedFarmerDate": "2026-08-23",
"notifiedFarmerMethod": "전화"
}
성공 응답
{
"disposal": {
"id": "disposal_001",
"status": "SETTLABLE",
"burdenParty": "FARMER",
"settlementAmount": 6000,
"recordedBy": "usr_001",
"recordedAt": "2026-08-23T19:00:00+09:00"
}
}
오류 상황
- 농가, 품목, 수량, 폐기 사유, 부담 주체가 없으면 확정할 수 없습니다.
- 적용할 폐기 규칙이 없으면 임의로 계산하지 않고 오류로 남깁니다.
- 농가 부담인데 농가 안내 기록이 없으면 경고합니다. 마감 필수 조건 여부는 확인 필요입니다.
폐기·미회수 재고 목록·상세 조회 (FR-011, FR-010)
| 항목 | 내용 |
|---|---|
| 목적 | 정산 주차의 폐기와 미회수 재고 내역, 증빙, 부담 주체, 농가 안내 기록을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/disposals |
증빙 사진 업로드 (FR-011, FR-022)
| 항목 | 내용 |
|---|---|
| 목적 | 폐기 내역 또는 농가 이의 제기에 필요한 사진 증빙을 업로드합니다. |
| 인증 | 정산 담당자 또는 농가 |
| 방식·경로 | POST /api/evidence |
| 요청 형식 | multipart/form-data |
요청값
| 이름 | 설명 |
|---|---|
file | 이미지 파일 |
purpose | DISPOSAL 또는 OBJECTION |
성공 응답
{
"evidence": {
"id": "evidence_001",
"fileName": "disposal-photo.jpg",
"contentType": "image/jpeg",
"uploadedAt": "2026-08-23T19:00:00+09:00"
}
}
오류 상황
- 이미지가 아닌 파일 또는 허용 크기를 초과한 파일은 업로드할 수 없습니다.
- 농가는 본인 이의 제기에만 증빙을 연결할 수 있습니다.
7. 계산, 오류 검사, 매출 대조
정산 금액 계산과 재계산 (FR-012)
| 항목 | 내용 |
|---|---|
| 목적 | 판매일에 유효한 정산 규칙을 적용해 농가별 순 판매액, 수수료, 공제액, 지급액을 계산합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/calculate |
성공 응답
{
"settlementWeekId": "sw_2026084",
"calculationStatus": "COMPLETED",
"summary": {
"farmerCount": 30,
"netSalesAmount": 2500000,
"commissionAmount": 285000,
"farmerBurdenAmount": 18000,
"storeCompensationAmount": 5000,
"correctionAmount": 0,
"totalPayoutAmount": 2202000
},
"errorCount": 0
}
오류 상황
- 적용 가능한 정산 규칙이 없거나 규칙이 충돌하면 계산 완료로 처리하지 않습니다.
- 할인 수수료 계산에 필요한 금액이 없으면 오류로 처리합니다.
- 지급액이 음수가 되는 경우의 마감 허용 기준은 확인 필요입니다.
정산 오류 목록과 처리 상태 조회 (FR-013)
| 항목 | 내용 |
|---|---|
| 목적 | 미연결 상품, 규칙 누락·충돌, 중복 가능 거래, 확인되지 않은 할인·반품·폐기 내역을 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/errors |
성공 응답
{
"errors": [
{
"id": "error_001",
"type": "SETTLEMENT_RULE_MISSING",
"severity": "BLOCKING",
"message": "판매일에 적용할 정산 규칙이 없습니다.",
"targetType": "SALES_LEDGER",
"targetId": "ledger_001",
"resolutionAction": "정산 규칙을 등록하거나 판매 항목을 확인하세요."
}
],
"blockingErrorCount": 1
}
매출 대조 결과 조회 (FR-014)
| 항목 | 내용 |
|---|---|
| 목적 | 계산대 판매 파일 순 판매액과 농가 지급액, 수수료, 부담 금액, 수기 조정을 비교합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/reconciliation |
성공 응답
{
"cashierNetSalesAmount": 2500000,
"manualAdjustmentAmount": 0,
"adjustedNetSalesAmount": 2500000,
"farmerPayoutTotalExcludingCorrections": 2202000,
"commissionTotal": 285000,
"farmerBurdenTotal": 18000,
"storeCompensationTotal": 5000,
"correctionAmountExcluded": 0,
"currentSettlementAmount": 2500000,
"difference": 0,
"reconciled": true
}
오류 상황
- 차이가 0원이 아니면 정산서를 생성하거나 마감할 수 없습니다.
- 근거 없는 수동 차액 입력으로 대조 차이를 0원으로 만들 수 없습니다.
- 수기 조정은 대상 거래와 사유가 연결된 경우에만 반영합니다.
수기 조정 등록 (FR-014, FR-024)
| 항목 | 내용 |
|---|---|
| 목적 | 계산대 판매 파일에 반영되지 않은 조정이 실제로 필요한 경우 대상 거래와 사유를 연결해 기록합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/manual-adjustments |
요청값
{
"amount": -3000,
"reason": "계산대 취소 내역 누락 확인",
"relatedLedgerEntryIds": ["ledger_009"]
}
성공 응답
{
"manualAdjustment": {
"id": "adjustment_001",
"amount": -3000,
"reason": "계산대 취소 내역 누락 확인",
"recordedBy": "usr_001"
}
}
8. 정산서 생성과 마감
농가별 정산서 생성 (FR-016)
| 항목 | 내용 |
|---|---|
| 목적 | 필수 오류가 없고 매출 대조 차이가 0원인 정산 주차에서 농가별 정산서를 생성합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/settlement-statements |
성공 응답
{
"settlementWeekId": "sw_2026084",
"generatedStatementCount": 30,
"status": "STATEMENTS_GENERATED",
"statements": [
{
"id": "statement_001",
"farmerId": "farmer_001",
"payoutAmount": 72500,
"expectedDepositDate": "2026-08-31"
}
]
}
오류 상황
- 필수 오류가 남아 있거나 매출 대조 차이가 0원이 아니면 정산서를 생성할 수 없습니다.
- 생성 후 마감 전에는 오류 처리와 재계산 뒤 정산서를 다시 생성할 수 있습니다.
운영자용 정산서 목록·상세 조회 (FR-016, FR-019)
| 항목 | 내용 |
|---|---|
| 목적 | 농가별 정산서의 판매액, 할인, 반품, 폐기, 수수료, 지급액 및 정정 연결 정보를 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/settlement-statements<br>GET /api/settlement-statements/{statementId} |
성공 응답
{
"statement": {
"id": "statement_001",
"farmer": {
"id": "farmer_001",
"name": "햇살농장"
},
"netSalesAmount": 85000,
"discountAmount": 5000,
"returnAmount": 0,
"disposalAmount": 6000,
"commissionAmount": 6500,
"correctionAmount": 0,
"payoutAmount": 72500,
"expectedDepositDate": "2026-08-31",
"status": "GENERATED"
}
}
운영자용 정산서 내려받기 (FR-016)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자가 농가별 정산서를 인쇄 또는 전달할 수 있는 파일로 내려받습니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-statements/{statementId}/download |
성공 응답
- 정산서 파일을 내려받습니다.
- 파일에는 농가명, 정산 주차, 판매·할인·반품·폐기 내역, 수수료, 지급액, 입금 예정일을 포함합니다.
정산 마감 (FR-017)
| 항목 | 내용 |
|---|---|
| 목적 | 목요일에 정산 주차를 확정하고 판매·수수료·정산 금액을 서버에서 수정 불가 상태로 잠급니다. |
| 인증 | 마감 권한자 |
| 방식·경로 | POST /api/settlement-weeks/{settlementWeekId}/close |
요청값
{
"closingConfirmation": true
}
성공 응답
{
"settlementWeek": {
"id": "sw_2026084",
"status": "CLOSING_COMPLETED",
"closedAt": "2026-08-27T16:30:00+09:00",
"closedBy": {
"id": "usr_002",
"name": "이점장"
}
}
}
오류 상황
- 마감 권한이 없는 정산 담당자는 마감할 수 없습니다.
- 필수 오류가 남아 있거나 매출 대조 차이가 0원이 아니면 마감할 수 없습니다.
- 정산서가 생성되지 않은 정산 주차는 마감할 수 없습니다.
- 마감 후 판매 원장, 정산 규칙 적용 결과, 지급액을 직접 수정하려 하면 차단합니다. (NFR-003)
마감·정정 연결 이력 조회 (FR-019, FR-024)
| 항목 | 내용 |
|---|---|
| 목적 | 누가 언제 정산을 마감했는지와 마감 후 정정 항목이 어느 원 정산서 및 다음 정산 주차에 연결되었는지 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/history |
성공 응답
{
"closing": {
"closedAt": "2026-08-27T16:30:00+09:00",
"closedBy": "이점장"
},
"correctionItems": [
{
"id": "correction_001",
"originalStatementId": "statement_001",
"targetSettlementWeekId": "sw_2026091",
"amount": 3000,
"status": "SCHEDULED"
}
]
}
9. 마감 후 정정
정정 항목 작성 (FR-018)
| 항목 | 내용 |
|---|---|
| 목적 | 마감 후 발견된 오류를 원 정산서를 바꾸지 않고 다음 정산 주차에 증감액으로 반영합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | POST /api/correction-items |
요청값
{
"originalSettlementStatementId": "statement_001",
"targetSettlementWeekId": "sw_2026091",
"farmerId": "farmer_001",
"amount": 3000,
"reason": "마감 후 확인된 할인 수수료 차액",
"relatedLedgerEntryIds": ["ledger_001"]
}
성공 응답
{
"correctionItem": {
"id": "correction_001",
"originalSettlementStatementId": "statement_001",
"targetSettlementWeekId": "sw_2026091",
"amount": 3000,
"status": "SCHEDULED"
}
}
오류 상황
- 마감되지 않은 정산서에는 정정 항목을 작성할 수 없습니다.
- 원 정산서의 농가와 다른 농가에 정정 항목을 반영할 수 없습니다.
- 다음 정산 주차가 이미 마감되었으면 다른 미마감 정산 주차를 선택해야 합니다.
다음 정산 반영 예정 정정 항목 조회 (FR-018, FR-019)
| 항목 | 내용 |
|---|---|
| 목적 | 다음 정산에 포함될 정정 항목과 원 정산서 연결 관계를 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/correction-items |
성공 응답
{
"correctionItems": [
{
"id": "correction_001",
"farmerId": "farmer_001",
"amount": 3000,
"reason": "마감 후 확인된 할인 수수료 차액",
"originalSettlementWeekId": "sw_2026084",
"originalStatementId": "statement_001",
"status": "SCHEDULED"
}
]
}
10. 농가 정산 조회와 이의 제기
농가의 이번 주 정산 요약 조회 (FR-020)
| 항목 | 내용 |
|---|---|
| 목적 | 농가가 스마트폰 첫 화면에서 이번 주 판매금액, 수수료, 받을 금액, 입금 예정일을 큰 글씨로 확인합니다. |
| 인증 | 농가 |
| 방식·경로 | GET /api/farmer/me/current-settlement |
성공 응답
{
"settlementWeek": {
"id": "sw_2026084",
"salesPeriod": "2026-08-17 ~ 2026-08-23"
},
"summary": {
"netSalesAmount": 85000,
"commissionAmount": 6500,
"payoutAmount": 72500,
"expectedDepositDate": "2026-08-31"
},
"statementId": "statement_001",
"statementStatus": "CLOSING_COMPLETED"
}
오류 상황
- 해당 농가의 정산서가 아직 생성되지 않았으면 “이번 주 정산서를 준비 중입니다.”라고 표시합니다.
- 농가는 본인 정산서만 조회할 수 있습니다.
농가 정산서 목록·상세 조회 (FR-021)
| 항목 | 내용 |
|---|---|
| 목적 | 농가가 자신의 정산서와 판매·할인·반품·폐기·수수료·정정 항목 상세를 조회합니다. |
| 인증 | 농가 |
| 방식·경로 | GET /api/farmer/me/settlement-statements<br>GET /api/farmer/me/settlement-statements/{statementId} |
성공 응답
{
"statement": {
"id": "statement_001",
"salesPeriod": "2026-08-17 ~ 2026-08-23",
"netSalesAmount": 85000,
"commissionAmount": 6500,
"returnAmount": 0,
"disposalAmount": 6000,
"correctionAmount": 0,
"payoutAmount": 72500,
"expectedDepositDate": "2026-08-31",
"salesDetails": [
{
"productName": "무농약 상추 200g",
"transactionType": "DISCOUNT_SALE",
"originalSaleAmount": 4000,
"actualSaleAmount": 3000,
"commissionAmount": 300
}
]
}
}
농가 정산서 내려받기 (FR-021)
| 항목 | 내용 |
|---|---|
| 목적 | 농가가 자신의 정산서를 스마트폰에서 파일로 내려받습니다. |
| 인증 | 농가 |
| 방식·경로 | GET /api/farmer/me/settlement-statements/{statementId}/download |
오류 상황
- 다른 농가의 정산서 식별값으로 요청하면 조회할 수 없습니다.
농가 이의 제기 등록 (FR-022)
| 항목 | 내용 |
|---|---|
| 목적 | 농가가 자신의 정산서 또는 상세 거래에 대해 이의 내용과 증빙 사진을 제출합니다. |
| 인증 | 농가 |
| 방식·경로 | POST /api/farmer/me/objections |
요청값
{
"settlementStatementId": "statement_001",
"ledgerEntryId": "ledger_001",
"content": "8월 22일 상추 할인 판매 금액을 확인하고 싶습니다.",
"evidenceIds": ["evidence_002"]
}
성공 응답
{
"objection": {
"id": "objection_001",
"status": "SUBMITTED",
"submittedAt": "2026-08-28T09:20:00+09:00"
}
}
오류 상황
- 본인 정산서가 아닌 경우 이의 제기를 할 수 없습니다.
- 이의 내용이 없으면 제출할 수 없습니다.
- 마감 전 정산서에 대한 이의 제기 허용 기준은 화면 정책으로 확인 필요입니다.
운영자용 이의 제기 목록·상세 조회 (FR-023)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자가 제출된 이의 제기, 증빙 사진, 연결된 정산서와 거래 내역을 확인합니다. |
| 인증 | 정산 담당자, 점장, 매장 운영자 |
| 방식·경로 | GET /api/objections<br>GET /api/objections/{objectionId} |
성공 응답
{
"objections": [
{
"id": "objection_001",
"farmerName": "햇살농장",
"statementId": "statement_001",
"status": "SUBMITTED",
"submittedAt": "2026-08-28T09:20:00+09:00"
}
]
}
농가 이의 제기 처리 결과 등록 (FR-023)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자가 이의 처리 결과, 담당자, 처리일, 농가에 알린 날짜와 방법을 기록합니다. 필요하면 정정 항목 작성을 연결합니다. |
| 인증 | 정산 담당자 |
| 방식·경로 | PATCH /api/objections/{objectionId} |
요청값
{
"status": "RESOLVED",
"result": "할인 수수료 계산 기준을 확인했고 정정 금액 3,000원을 다음 정산에 반영합니다.",
"processedAt": "2026-08-28",
"notifiedFarmerDate": "2026-08-28",
"notifiedFarmerMethod": "전화",
"correctionItemId": "correction_001"
}
성공 응답
{
"objection": {
"id": "objection_001",
"status": "RESOLVED",
"processedBy": "usr_001",
"processedAt": "2026-08-28",
"notifiedFarmerDate": "2026-08-28",
"notifiedFarmerMethod": "전화",
"correctionItemId": "correction_001"
}
}
오류 상황
- 처리 결과, 담당자, 처리일이 없으면 처리 완료로 변경할 수 없습니다.
- 마감 완료된 정산서의 금액을 직접 바꾸지 않습니다. 금액 변경이 필요하면 정정 항목을 연결합니다.
11. 주간 현황과 작업 이력
주간 정산 현황 조회 (FR-015)
| 항목 | 내용 |
|---|---|
| 목적 | 운영자가 한 화면에서 파일 업로드, 상품 연결, 오류, 매출 대조, 정산서 생성, 마감 가능 여부를 확인합니다. |
| 인증 | 운영자 |
| 방식·경로 | GET /api/settlement-weeks/{settlementWeekId}/dashboard |
성공 응답
{
"settlementWeek": {
"id": "sw_2026084",
"status": "ERROR_EXISTS",
"expectedDepositDate": "2026-08-31"
},
"progress": {
"uploadedFileCount": 1,
"totalSalesRows": 1250,
"connectedSalesRows": 1240,
"unlinkedSalesRows": 5,
"errorCount": 3,
"reconciliationDifference": 0,
"statementGenerated": false,
"closable": false
},
"nextActions": [
{
"type": "RESOLVE_ERRORS",
"message": "정산 규칙이 없는 판매 항목 3건을 처리하세요."
}
]
}
주요 작업 이력 조회 (FR-024)
| 항목 | 내용 |
|---|---|
| 목적 | 계정·권한 변경, 파일 업로드, 직접 연결, 규칙 등록·변경, 폐기 등록, 정산서 생성, 마감, 정정, 이의 처리 이력을 확인합니다. |
| 인증 | 매장 운영자 |
| 방식·경로 | GET /api/audit-logs |
조회값
| 이름 | 설명 |
|---|---|
targetType | 예: SETTLEMENT_WEEK, SETTLEMENT_RULE, OBJECTION |
targetId | 특정 대상 식별값 |
actionType | 예: CREATE, UPDATE, CLOSE, CORRECTION_CREATE |
from, to | 이력 발생 기간 |
성공 응답
{
"logs": [
{
"id": "log_001",
"actionType": "SETTLEMENT_CLOSED",
"targetType": "SETTLEMENT_WEEK",
"targetId": "sw_2026084",
"summary": "2026년 8월 4주차 정산을 마감했습니다.",
"performedBy": {
"id": "usr_002",
"name": "이점장"
},
"performedAt": "2026-08-27T16:30:00+09:00"
}
]
}
12. 요구사항-API 연결표
| 요구사항 | 구현 API |
|---|---|
| FR-001 | 운영자 로그인, 로그아웃 |
| FR-002 | 담당자 계정 목록 조회, 등록, 권한·상태 변경 |
| FR-003 | 농가 로그인, 농가 로그인 계정 연결·상태 변경 |
| FR-004 | 정산 주차 목록·상세 조회, 정산 주차 생성 |
| FR-005 | 농가·품목·품목군 목록 조회, 농가 등록·변경·중지, 품목군 등록·변경, 품목과 상품코드 등록·변경·중지 |
| FR-006 | 정산 규칙 목록·상세 조회, 정산 규칙 등록, 정산 규칙 변경 이력 등록 |
| FR-007 | 계산대 판매 파일 업로드, 파일 오류 행과 업로드 이력 조회 |
| FR-008 | 상품코드 미연결 항목 조회, 판매 항목 직접 연결과 상품코드 재사용 등록 |
| FR-009 | 판매 원장 조회, 중복 가능 거래 확인 처리 |
| FR-010 | 판매 원장 조회, 판매·할인·반품 내역 상세 조회, 폐기·미회수 재고 목록·상세 조회 |
| FR-011 | 폐기·미회수 재고 등록, 목록·상세 조회, 증빙 사진 업로드 |
| FR-012 | 정산 금액 계산과 재계산 |
| FR-013 | 정산 오류 목록과 처리 상태 조회 |
| FR-014 | 매출 대조 결과 조회, 수기 조정 등록 |
| FR-015 | 정산 주차 목록·상세 조회, 주간 정산 현황 조회 |
| FR-016 | 농가별 정산서 생성, 운영자용 정산서 목록·상세 조회, 운영자용 정산서 내려받기 |
| FR-017 | 정산 마감 |
| FR-018 | 정정 항목 작성, 다음 정산 반영 예정 정정 항목 조회 |
| FR-019 | 운영자용 정산서 목록·상세 조회, 마감·정정 연결 이력 조회 |
| FR-020 | 농가의 이번 주 정산 요약 조회 |
| FR-021 | 농가 정산서 목록·상세 조회, 농가 정산서 내려받기 |
| FR-022 | 증빙 사진 업로드, 농가 이의 제기 등록 |
| FR-023 | 운영자용 이의 제기 목록·상세 조회, 농가 이의 제기 처리 결과 등록 |
| FR-024 | 파일 오류 행과 업로드 이력 조회, 수기 조정 등록, 마감·정정 연결 이력 조회, 주요 작업 이력 조회 |
13. 첫 버전 제외 항목
다음 항목은 SRS의 첫 개발 범위에서 제외합니다.
- 실제 은행 이체
- 은행 이체용 지급 파일 생성
- 문자·카카오톡 알림
- 여러 로컬푸드 직매장 통합 관리
- 외부 정보 기반의 자동 상품코드 판별
- 마감 완료 정산의 재개방 및 직접 수정
- 별도 모바일 앱과 앱 스토어 배포
다만 개발 인터뷰에는 “농가별 정산 결과를 은행 이체용 파일로 내려받아 운영자가 은행 시스템에 올릴 수 있어야 한다”는 요구가 포함되어 있습니다. SRS의 제외 범위와 충돌하므로, 은행 이체용 지급 파일은 첫 버전 확정 전에 포함 여부를 결정해야 합니다.