CASE STUDY 01 모바일 업무관리 · Flutter / Kotlin

복잡한 현장 업무를 기록부터 정산까지 구조화하고, 데이터가 사라지지 않는 모바일 업무관리 제품을 만듭니다.

고객이 요청한 장부 관리 앱을 직원·근무지·근무일지·정산이 연결되는 업무 흐름으로 구체화하고, 개인 작업과 공용 협업의 저장 규칙을 분리했습니다.

고객의 요구 제시와 작업자의 제품·데이터 문제 구체화만 공개합니다. 고객명, 개인 원문, 금액, 성과 수치는 사용하지 않습니다.

ONGOING WORK01 / Ongyeol
온

기록에서 정산까지

끊기지 않는 업무 맥락
  1. 기록직원 · 근무지 · 시간
  2. 상태진행 → 완료
  3. 정산청구 · 지급 분리
implemented현재 문서·source 근거

고객 문제

기록을 더 만드는 일이 아니라, 업무의 관계와 상태를 보이게 하는 일

고객은 현장 장부를 관리할 앱을 요청했습니다. 작업자는 이를 단순 입력 화면이 아니라 ‘누가, 어디에서, 언제 일했고, 무엇이 완료되어 어떤 정산으로 이어지는가’를 연결하는 제품·데이터 문제로 바꿨습니다.

01

고객 입력

직원 · 근무지 · 근무시간 · 청구액 · 지급액

02

제품 구조

기준정보 · 근무일지 · 완료 상태 · 두 방향 정산

03

사용자 결과

기록 → 진행 확인 → 완료 → 정산 확인의 연속 흐름

해결한 사용자 흐름

사용자는 기록의 맥락을 잃지 않고 다음 상태로 이동합니다.

입력 화면을 늘리는 대신, 업무가 어느 단계에 있고 다음에 무엇을 확인해야 하는지 하나의 흐름으로 묶었습니다.

  1. 01

    업무 기록

    장부 안에서 직원과 근무지를 연결하고 시간·청구·지급 정보를 한 흐름으로 기록합니다.

  2. 02

    진행 상태 확인

    아직 끝나지 않은 근무를 진행 상태로 분리해 다음 행동이 필요한 대상을 놓치지 않게 합니다.

  3. 03

    완료 전환

    업무 완료를 명시적인 상태로 바꾸고, 원래 맥락을 보존한 채 정산 대상으로 연결합니다.

  4. 04

    정산 확인

    업체 청구와 직원 지급을 서로 다른 상태로 관리해 미수·미지급을 구분합니다.

데이터가 사라지지 않게

같은 장부처럼 보여도, 저장과 확정의 규칙은 다릅니다.

저장 위치를 사용자가 선택하고, 개인 작업과 여러 사람의 협업이 요구하는 일관성 수준을 섞지 않았습니다.

01

로컬 장부

로그인·네트워크 없이 기기에 보존

사용자가 동기화를 선택하기 전에는 서버로 보내지 않습니다.

02

개인 장부

먼저 로컬에 저장하고 연결 시 서버와 수렴

outbox·push·cursor pull로 미전송 변경과 서버 변경을 조율합니다.

03

공용 장부

온라인에서 서버가 승인한 상태만 확정

membership·role·revision을 서버가 확인하고 참여자가 같은 확정 상태를 봅니다.

원칙로그인이 로컬 데이터 전송의 동의가 되지 않고, WebSocket 신호가 업무 데이터의 정본이 되지 않게 했습니다.

2026.08 · CURRENT IMPLEMENTATION

화면·데이터·릴리스의 끊김을 같은 제품 문제로 다뤘습니다.

초기 기능을 나열하는 데서 멈추지 않고, 화면 폭이 바뀌는 순간과 동기화 변경량이 늘어나는 순간, 새 API가 기존 앱과 만나는 순간까지 제품이 이어지도록 고도화했습니다.

온결 iPad 로컬 시뮬레이터에서 왼쪽 근무일지 목록의 선택 상태와 오른쪽 상세 정보가 함께 보이는 화면LOCAL SIMULATOR · IPAD LANDSCAPE
RENDERED PRODUCT EVIDENCE목록을 잃지 않고 상세를 비교하는 태블릿 작업 공간

선택한 근무일지는 목록에서 상태와 함께 강조되고, 오른쪽에는 직원·근무지·정산일·근무 시간·지급·청구 상태가 동시에 이어집니다.

2026-08-24 로컬 iPad Simulator 렌더입니다. 실기기·App Store·production 동작을 뜻하지 않습니다.
01USER CONTINUITY

화면 폭이 바뀌어도 하던 일을 잃지 않습니다.

휴대폰은 한 화면에 집중하고, 충분히 넓은 화면은 목록과 상세를 함께 보여 줍니다. 선택·필터·목록 맥락은 회전과 창 크기 변화 뒤에도 가능한 한 이어집니다.

APP adaptive workspace source · implemented
02DATA RECOVERY

변경량이 늘어도 순서와 복구 의미를 지킵니다.

sync pull payload를 entity type별 batch로 읽으면서 cursor·revision·tombstone 순서를 유지합니다. 실시간 relay는 DB commit 신호로 깨어나고, 안전 polling과 HTTP cursor pull이 누락을 복구합니다.

API batch materialization · realtime outbox · implemented
03RELEASE SAFETY

새 API가 지원 앱 빌드의 데이터 계약을 깨뜨리지 않게 막습니다.

release profile, canonical fixture, OpenAPI baseline과 consumer/provider 의미 테스트를 CI에 연결했습니다. 해석할 수 없는 critical type은 cursor를 전진시키지 않고 로컬 Outbox를 보존합니다.

Root · APP · API compatibility matrix · implemented

어떻게 만들었는가

화면의 의도에서 서버 확정 상태까지, 책임을 층으로 나눴습니다.

비개발자는 데이터가 어디에 남는지 볼 수 있고, 기술 검토자는 각 계층이 무엇을 결정하고 무엇을 믿지 않는지 내려가며 확인할 수 있습니다.

APPFlutter · Riverpod · Drift
  1. 01Widget
  2. 02State
  3. 03Service
  4. 04Repository
  5. 05DAO
  6. 06Drift
APIKotlin · Quarkus · PostgreSQL
  1. 01Controller
  2. 02Application Service
  3. 03Domain Service
  4. 04Repository
  5. 05PostgreSQL

01Flutter 화면과 Riverpod 상태는 사용자 intent와 화면 상태를 맡습니다.

02Service와 Repository가 use case, 오류 분류, local/remote source 경계를 숨깁니다.

03Quarkus API는 인증·권한·idempotency·revision과 transaction을 서버에서 확정합니다.

04Drift는 local replica, PostgreSQL은 서버 연결 장부의 canonical state입니다.

TECHNICAL DETAIL계층별 구현 경계 더 보기

APP

Widget은 사용자 intent, State는 로딩·오류·선택 상태, Service는 use case와 오류 분류, Repository는 local/remote source, DAO와 Drift는 query·transaction·schema를 맡습니다.

API

Controller는 HTTP·DTO, Application Service는 authorization·transaction·idempotency, Domain Service는 conflict·merge 의미, Repository와 PostgreSQL은 canonical persistence를 맡습니다.

개인 / 공용 장부 통신

빠른 신호보다, 다시 확인할 수 있는 복구 경로를 우선했습니다.

개인 장부는 로컬 변경을 안전하게 서버로 옮기고, 공용 장부는 서버 승인과 권한을 먼저 확인합니다. 두 경로 모두 HTTP cursor pull로 누락을 복구합니다.

PERSONAL

개인 장부 · local-first sync

  1. 1

    사용자 변경과 outbox operation을 같은 로컬 transaction에 기록

  2. 2

    같은 논리 operation은 같은 Idempotency-Key와 expectedRevision으로 push

  3. 3

    서버는 account·ledger·entity scope와 revision을 검증해 transaction 확정

  4. 4

    cursor 이후 변경을 pull하고 local row·revision·cursor를 함께 반영

SHARED

공용 장부 · server-authoritative

  1. 1

    APP이 온라인 상태와 접근 가능성을 확인하고 HTTP mutation 전송

  2. 2

    API가 membership·role·capability·revision·idempotency를 검증

  3. 3

    WebSocket은 cursor.changed 같은 최소 변경 신호만 전달

  4. 4

    신호 누락·재연결 뒤에도 HTTP cursor pull로 서버 상태에 수렴

IDEMPOTENCY같은 논리 작업은 같은 key

응답 유실 뒤 새 작업으로 중복 반영하지 않습니다.

REVISION오래된 상태의 저장을 감지

충돌을 성공으로 바꾸거나 조용히 덮어쓰지 않습니다.

AUTHORIZATION권한은 매 요청 서버에서 확인

클라이언트가 보낸 account·role 주장을 신뢰하지 않습니다.

실패를 숨기지 않는 상태

오류마다 데이터 영향, 다음 행동, 복구 방법이 다릅니다.

‘저장 실패’ 하나로 합치지 않고 개인 오프라인, 공용 오프라인, 결과 불명확, 충돌, 권한 부족, 접근 회수, 앱 계약 불일치를 구분했습니다.

01

개인 작업 · 네트워크 끊김

로컬 저장 유지
다음 행동

업무를 계속하고 연결 후 같은 outbox operation으로 전송

보호하는 것

입력과 원본을 지우지 않음

02

공용 장부 · HTTP 오프라인

온라인 연결 필요
다음 행동

진입·저장을 멈추고 최신 서버 상태 확인 뒤 재개

보호하는 것

오프라인 변경을 확정하거나 Outbox로 만들지 않음

03

응답 유실 · 결과 불명확

확인 중
다음 행동

새 operation을 만들지 않고 같은 key 또는 status/pull로 결과 확인

보호하는 것

중복 반영을 성공처럼 숨기지 않음

04

revision 충돌

사용자 조치 필요
다음 행동

최신 상태를 확인하고 다시 저장하거나 취소

보호하는 것

last-write-wins로 조용히 덮어쓰지 않음

05

역할·capability 부족

해당 작업만 거절
다음 행동

입력과 접근 가능한 장부를 유지하고 허용 범위를 안내

보호하는 것

단일 403을 장부 접근 회수로 오인하지 않음

06

접근 권한 회수 확인

즉시 가림·이탈
다음 행동

membership 전체 상태를 재확인하고 허용되지 않는 local replica 정리

보호하는 것

다른 장부에서 접근 가능한 기준정보는 보존

07

지원하지 않는 앱 계약

업데이트 필요
다음 행동

서버 쓰기를 멈추고 호환 가능한 업데이트 경로를 안내

보호하는 것

local CRUD와 기존 Outbox를 보존하고 cursor를 전진시키지 않음

역할과 AI 협업

속도와 책임을 같은 말로 포장하지 않습니다.

AI가 기여한 작업과 사람이 책임진 판단을 분리하고, 공개 근거가 없는 ownership은 사례의 성과로 쓰지 않습니다.

MAKER RESPONSIBILITY

작업자가 책임진 범위

  • 고객이 제시한 요구를 제품 흐름과 데이터 관계 문제로 구체화
  • 로컬·개인·공용 장부의 서로 다른 저장·권한·실패 의미를 결정
  • 공개할 수 있는 근거와 주장 경계를 선택하고 최종 결과를 검토

AI ASSISTANCE

AI가 보조한 범위

  • 대안 탐색, 구현 초안, 문서 정리와 반복 검증을 보조
  • 근거 경로를 찾고 정적·렌더 검사를 반복할 수 있게 지원
  • 결정권·고객 맥락·최종 승인·외부 사실을 대신하지 않음

공개 근거가 확정되지 않은 코드 영역을 단독 구현으로 표현하지 않습니다. AI를 사용했다는 사실과 작업자가 책임진 결정·검토·검증을 분리합니다.

어떻게 검증했는가

무엇을 확인했고, 무엇은 확인하지 않았는지 함께 적습니다.

문서·source, 자동화 테스트, 로컬 렌더, 외부 runtime, production은 서로 대체할 수 없는 별도 근거입니다.

근거 상태 6단계 읽는 법
implemented

현재 문서·source에서 구현 구조를 확인

tested

이번 근거 실행에서 자동화 테스트 통과

local-verified

이번 산출물을 로컬에서 직접 빌드·렌더 검토

external-verified

현재 외부 서비스·기기에서 직접 확인

production

현재 production 동작을 직접 확인

open

이번 범위에서 확인하지 않아 주장하지 않음

제품과 저장 경계

implemented

현재 PRODUCT·OFFLINE-FIRST-SYNC·SHARED-LEDGER 정본과 APP 구조에서 로컬/개인/공용 장부 의미를 확인했습니다.

현재 기기 동작이나 production 상태를 뜻하지 않습니다.

APP / API 계층

implemented

현재 ARCHITECTURE와 source에서 Flutter–Riverpod–Drift, Quarkus–PostgreSQL 계층과 경계를 확인했습니다.

이 페이지는 전체 endpoint·schema 목록을 복제하지 않습니다.

동기화·충돌·복구 구조

implemented

현재 CLIENT_SERVER·DATABASE와 source에서 outbox, Idempotency-Key, expectedRevision, batch pull materialization, cursor pull, realtime ticket·commit signal 경로를 확인했습니다.

exactly-once 또는 완전 동시 CAS 보장을 주장하지 않습니다.

화면 연속성과 릴리스 호환성

implemented

현재 APP adaptive workspace와 Root·APP·API compatibility contract에서 목록–상세 연속성, 지원 build profile, fixture·OpenAPI·semantic gate 구조를 확인했습니다.

현재 실기기·Store 채택률·production minimum-build 동작을 뜻하지 않습니다.

Ongyeol 자동화 테스트 결과

open

관련 test source는 확인했지만 이 포트폴리오 작업에서는 Ongyeol 저장소 테스트를 다시 실행하지 않았습니다.

오래된 개수·통과 기록을 현재 성과로 사용하지 않습니다.

온결 외부 운영·성과

open

이번 최신화에서는 온결 제품의 Store·외부 runtime·고객 성과를 다시 검증하지 않았습니다.

온결의 production, 고객 성과, 트래픽·부하 수치를 주장하지 않습니다.

포트폴리오 웹

local-verified

production static export와 390px/1440px 대표 흐름, 실제 제품 화면, navigation, keyboard focus, hard states와 기본 contrast를 직접 렌더 검토했습니다.

이 상태는 포트폴리오 산출물의 로컬 검증이며, 공개 runtime 검증과 구분합니다.

제공 가능한 외주 범위

업무를 설명하는 문장부터, 실제로 쓰이는 제품 흐름까지 함께 만듭니다.

비개발자의 요구를 제품·데이터 문제로 번역하고, 실패와 복구까지 포함한 구현 가능한 범위로 정리합니다.

01

Flutter 기반 업무관리 MVP

현장 입력, 상태 흐름, 기준정보와 정산을 하나의 모바일 제품으로 구조화합니다.

02

오프라인·로컬 우선 설계

네트워크가 불안정한 환경에서 저장 위치, 재시도, 충돌, 복구가 보이도록 설계합니다.

03

사내 운영 도구

반복 업무와 권한 경계를 화면·데이터·API 책임으로 나눠 구현 가능한 범위로 만듭니다.

04

기존 제품 흐름 진단

데이터 손실 위험, 중복 요청, 실패를 성공처럼 보이는 상태와 과도한 수동 절차를 점검합니다.

PROJECT INQUIRY

현재 업무에서 가장 자주 멈추는 지점부터 알려주세요.

목표, 사용 환경, 지금 가진 자료, 원하는 일정을 함께 보내주시면 MVP·운영 도구·기존 흐름 개선 중 어디서 시작할지 정리해 답합니다.

문의 방법 확인

이 포트폴리오를 전달받은 이메일·메신저·외주 플랫폼 채널에서 그대로 답장해 주세요. 공개용 개인정보나 가짜 연락처는 넣지 않았습니다.