최종 업데이트: 2026-08-11
HTML 프로토타입 시대(v0.1~v2.3) 기록은
patch_notes_backup/ourshop_patch_notes_html_era.md참조
Shot() 함수에 System.Drawing 기반 자동
크롭 추가bounds=[0,2940][1440,3120]update-guide.ps1 연동으로
HTML 반영Schedule 엔티티에 repeatGroupId: String?
컬럼 추가 (Room v5 migration)repeatGroupId 공유없음 / 매주 / 격주 / 매일 / 한달repeatGroupId 있는 일정 편집 시 주황 배너: "↺ 반복
일정입니다. 이 날만 수정됩니다."↺ 반복 파란 배지 표시date >= 선택일인 같은 그룹 N개 삭제repeatGroupId 전체
삭제ScheduleViewToggleDayPanelDayScheduleItemScheduleSheet (통합 추가/수정 시트)schedule: Schedule? 파라미터로
모드 구분detectShiftType(title): 기존 일정 제목에서 유형
추출shiftColorFor(type): 유형 → Color 매핑ShiftBoardTab
(읽기 전용)QuickShiftTarget / QuickShiftSheet / 셀
클릭 핸들러 제거onAddShift / onDeleteShift /
onUpdateShift 파라미터 제거ScheduleTabBar — 뷰 토글로 대체ScheduleDateHeader — DayPanel로 대체ScheduleCard — DayScheduleItem으로 대체AddScheduleSheet / EditScheduleSheet —
ScheduleSheet로 통합QuickShiftSheet / QuickShiftTarget — 읽기
전용 전환buildRepeatSchedules — 반복 기능 제거 (불필요한
복잡성)ui/screens/ScheduleScreen.kt — 전면 재작성 (1876 → 약
900줄)StaffViewModel: BuildConfig.IS_DEV 분기
추가
FirebaseAuth 인스턴스 생성 없이 mock
매장(개발 테스트 매장)으로 즉시 READY 상태 진입loadAll / loadSchedules / loadAttendances / loadPayroll
→ dev 빌드 시 Firestore 호출 스킵checkIn / checkOut → dev 빌드 시 애니메이션·토스트만
실행 (Firestore 기록 없음)submitInviteCode → dev 빌드 시 mock 매장 연결로
처리staff-app/.../ui/viewmodel/StaffViewModel.ktsettings.gradle.kts에 :staff-app 모듈
추가staff-app/build.gradle.kts: applicationId
com.hmhworks.ourshop.staff, flavor dev/prod, compileSdk
35scripts/build-staff.ps1,
scripts/build-install-staff.ps1 신규Staff 데이터 클래스에 staffUid,
staffEmail 필드 추가MIGRATION_3_4 (ALTER TABLE staff ADD
COLUMN 2개)createInviteCode(ownerUid, staffId, storeId): 6자리
코드 생성, /inviteCodes/{code} 24시간 유효watchStaffConnections(ownerUid, onNewLink): staff
컬렉션 스냅샷 리스너 — staffUid 신규 연결 감지deactivateStaffLink / activateStaffLink / deleteStaffLink:
직원 비활성·활성·삭제 시 staffLinks 연동generateInviteCode(staffId): 코드 생성 (dev 모드는
"123456" 반환)startWatchingStaffConnections / stopWatchingStaffConnections:
리스너 관리retireStaff / rehireStaff / deleteStaff: staffUid 있을
때 staffLinks 자동 업데이트newStaffLinkMsg: 직원 앱 연결 시 인앱 배너 메시지
StateFlowgenerateInviteCode(staffId, onResult): 초대코드 발급
래퍼login() 이후 startWatchingStaffConnections
자동 시작StaffAuthState: LOADING → NOT_LOGGED_IN / NO_WORKPLACE
/ READYvalidateAndLink(code, staffUid, staffEmail): 코드
유효성 검사 → 사장 앱 staff 문서에 staffUid/staffEmail 기록 →
/staffLinks/{staffUid}/workplaces/{storeId} 생성 → 코드
used 처리StaffTheme: primary #4F46E5(인디고), secondary
#7C3AED(바이올렛)staff-app/google-services.json: 동일 Firebase 프로젝트,
staff/staff.dev 패키지 항목 (Firebase Console 등록 필요)settings.gradle.kts — :staff-app 추가app/.../data/model/Staff.kt — staffUid/staffEmail
추가app/.../data/local/OurshopDatabase.kt — v4
마이그레이션app/.../data/repository/FirestoreRepository.kt —
초대코드·staffLinks 메서드app/.../data/repository/AppRepository.kt —
초대코드·staffLinks 연동app/.../ui/viewmodel/MainViewModel.kt —
newStaffLinkMsg, generateInviteCodeapp/.../ui/screens/StaffScreen.kt — 초대코드 카드staff-app/ — 신규 모듈 25개 파일 전체QuickShiftSheet
바텀시트 열림fixedWorkDays 설정된 직원의 이번 주 일정 자동 생성, 기존
일정 중복 스킵AppRepository.updateSchedule(id, title, startTime, endTime)
— 기존 일정 시간·제목 수정AppRepository.generateWeekSchedules(weekStartDate) —
고정 근무 패턴 → 이번 주 자동 생성AppRepository.copyWeekSchedules(from, to) — 주간 일정
복사 (중복 방지)MainViewModel.updateSchedule / generateWeekSchedules / copyPreviousWeek
래퍼 추가addSchedule 중복 체크에 staffIds.toSet()
포함
ui/screens/ScheduleScreen.kt — ShiftBoardTab 인터랙션
전면 개편, QuickShiftSheet 신규ui/viewmodel/MainViewModel.kt —
updateSchedule/generateWeekSchedules/copyPreviousWeek 추가data/repository/AppRepository.kt —
updateSchedule/generateWeekSchedules/copyWeekSchedules 추가"6/22 ~ 7/21 까지 → 매일 30개 생성 (주말 포함)"ui/screens/ScheduleScreen.kt —
repeatCount/buildRepeatSchedules/레이블 수정data/repository/AppRepository.kt — addSchedule 중복
체크 추가N명 표시 → 직원 없는 일정은
아무 표시 없음N개 로 표시 (직원
있으면 기존 컬러 점 + N명 유지)"✅ N개 일정이 저장되었어요"
ui/screens/ScheduleScreen.kt — CalendarGrid 인디케이터
조건 수정, SnackBar 추가AppRepository.deleteStore(): 매장 + 관련
직원·출퇴근·급여·스케줄 전부 삭제 (Option B)
deleteByStore(storeId) 쿼리로 일괄 삭제data/local/dao/StaffDao.kt —
deleteByStore() 추가data/local/dao/AttendanceDao.kt —
deleteByStore() 추가data/local/dao/PayrollDao.kt —
deleteByStore() 추가data/local/dao/ScheduleDao.kt —
deleteByStore() 추가data/repository/FirestoreRepository.kt —
deletePayroll() 추가data/repository/AppRepository.kt —
deleteStore() 추가ui/viewmodel/MainViewModel.kt —
deleteStore() 추가ui/screens/ProfileSheet.kt — 삭제 버튼 + AlertDialog
추가OnboardingScreen.kt: 매장 이름 입력 + 아이콘 선택 +
시작하기 버튼MainViewModel: isDbLoaded,
isNewUser, completeOnboarding() 추가AppRepository: completeOnboarding() — 기본
매장 이름·아이콘 업데이트 후 DB/Firestore 저장OurshopNavHost: ONBOARDING 라우트 추가, STORE_SELECT →
ONBOARDING 자동 전환
ui/screens/OnboardingScreen.kt (신규)ui/OurshopNavHost.ktui/viewmodel/MainViewModel.ktdata/repository/AppRepository.kt123-456-789012 힌트 추가ui/screens/AddStaffSheet.ktFirestoreRepository.kt 신규: Firestore CRUD 레이어
(Gson 직렬화)
/users/{uid}/stores·staff·attendance·payroll·schedulesuploadAll(): 첫 로그인 시 로컬 → Firestore 전체
업로드downloadAll(): Firestore → BackupData 전체
다운로드AppRepository.kt 수정:
isPro, currentUid, fsWrite
추가fsWrite 연결 (fire-and-forget
방식)syncFromCloud(): Firestore 데이터 존재 시 복원, 없으면
로컬 업로드MainViewModel.kt 수정:
CloudSyncState (Idle/Syncing/Done/Error) 추가syncFromCloud(): 동기화 상태 추적syncFromCloud() 호출isPro 변경 시 AppRepository.isPro
동기화libs.versions.toml, build.gradle.kts:
firebase-firestore,
kotlinx-coroutines-play-services 추가data/repository/FirestoreRepository.kt (신규)data/repository/AppRepository.ktui/viewmodel/MainViewModel.ktgradle/libs.versions.tomlapp/build.gradle.ktsui/screens/HomeScreen.ktapp/build.gradle.kts: prodPro flavor 추가
com.hmhworks.ourshop (prod와 동일, 설치
시 덮어씀)IS_DEV = false, MOCK_PRO = true → PRO 기능
전부 열림, 광고 없음versionNameSuffix = "-pro" → APK명
ourshop-prodPro-release-v1.0.0-pro.apkscripts/build.ps1, build-install.ps1,
install.ps1: prodPro 옵션 추가, APK 버전명 v0.1.0 → v1.0.0
수정.\scripts\build-install.ps1 -Target <ip:port> -Flavor prodPro -BuildType release
app/build.gradle.ktsscripts/build.ps1,
scripts/build-install.ps1,
scripts/install.ps1StoreSelectScreen: vm.stores가 비어 있을
때만 "새 매장 추가" 버튼 표시ProfileSheet: 매장이 이미 있으면 "새 매장 추가" 대신
"🔒 무료 버전: 1개 매장 지원" 비활성 텍스트 표시StaffScreen: 무료 버전에서 📋 버튼 미표시, PRO
버전에서만 노출StaffScreen: activeStaff.size >= 5 +
!isPro 시 "직원 추가" 버튼 → 한도 초과 다이얼로그 표시ProfileSheet: PRO/FREE 전환 토글을
BuildConfig.IS_DEV 빌드에서만 표시 (prod 빌드 노출
방지)!vm.isPro 조건 유지)!vm.isPro 조건 유지)ui/screens/StaffScreen.kt — PRO 게이트 코드 제거ui/screens/StoreSelectScreen.kt — 1개 매장 제한ui/screens/ProfileSheet.kt — 1개 매장 제한 + IS_DEV
토글📞 전화
💬 문자 🟡 카카오 칩 버튼 표시ui/screens/StaffScreen.kt — ContactActions
컴포저블 + 각 시트 적용IS_DEV 빌드에서 이메일·비밀번호 필드 초기값
"a" 세팅ui/screens/SplashScreen.kt — 로고 박스 alpha 제거ui/screens/LoginScreen.kt — dev 모드 초기값 세팅saved guard 추가 — 한 번 탭하면 버튼
비활성화, 시트 닫힐 때까지 재등록 불가onDismiss() 호출 (기존 2.7초
대기 → 탭 즉시 닫힘)PayrollDetailSheet에 월 네비게이션 추가 (◀ · 년월 ·
▶)ui/screens/ScheduleScreen.kt — AddScheduleSheet 저장
guardui/screens/AttendanceScreen.kt — SparklePopup overlay
클릭 즉시 닫기ui/screens/PayrollScreen.kt — PayrollDetailSheet 월
네비게이션ui/screens/ScheduleScreen.kt —
TimePickerDialog 컴포저블 추가 +
AddScheduleSheet UI 교체ui/screens/ScheduleScreen.kt —
buildRepeatSchedules() 헬퍼 + AddScheduleSheet
반복 UIAddStaffSheet에서 고정 근무 패턴 UI (요일 토글 + 시간
입력) 제거🗓 자동생성 버튼 제거 (고정 패턴 기반이라 더 이상
불필요)ui/screens/AddStaffSheet.kt — 고정 근무 패턴 섹션 및
관련 state 제거ui/screens/ScheduleScreen.kt — 자동생성 버튼 및
다이얼로그 제거, 헤더 정리Staff, AttendanceRecord,
Schedule, PayrollRecord 모델에
storeId: String 필드 추가ALTER TABLE 4개,
DEFAULT 'store1'으로 기존 데이터 자동 귀속)getByStore(storeId) 쿼리 추가AppRepository.loadFromDatabase(): 현재 매장 기준으로
필터링하여 로드AppRepository.setCurrentStore(): 매장 전환 시 해당 매장
데이터 자동 재로드 (prod only)addStaff(), addSchedule(),
updateAttendStatus(), recalcPayroll() 신규
생성 시 currentStoreId 자동 포함data/model/Staff.kt, AttendanceRecord.kt,
PayrollRecord.kt — storeId 필드 추가data/local/dao/StaffDao.kt,
AttendanceDao.kt, ScheduleDao.kt,
PayrollDao.kt — getByStore 추가data/local/OurshopDatabase.kt — version 3,
MIGRATION_2_3data/repository/AppRepository.kt — 매장 격리 로직
전체ui/screens/ScheduleScreen.kt — 고정 패턴 안내 배너ActivityResultContracts.TakePicture() + FileProvider
임시 파일(cache/photos/) 사용AndroidManifest.xml — CAMERA 권한
추가res/xml/file_paths.xml —
cache-path camera_photos 경로 추가ui/screens/AddStaffSheet.kt — 카메라 런처 + 버튼
UIStaff 모델에
fixedWorkDays: List<Int>,
fixedStartTime: String, fixedEndTime: String
필드 추가Converters.kt: List<Int>
TypeConverter 추가data/model/Staff.kt — 3 필드 추가data/local/Converters.kt — List<Int>
컨버터data/local/OurshopDatabase.kt — v2, MIGRATION_1_2data/repository/AppRepository.kt —
generateMonthlySchedules()ui/viewmodel/MainViewModel.kt — 위임 함수ui/screens/AddStaffSheet.kt — 고정 근무 UI 섹션ui/screens/ScheduleScreen.kt — 자동생성 버튼 + 2개
다이얼로그#splash-overlay 그대로
재현| 요소 | 내용 |
|---|---|
| 배경 | 크림 그라디언트 #FFFAF7 → #FFF0E8 |
| HMH 로고 박스 | 120dp, RoundedCornerShape(28dp), 다크 그라디언트
#3E4A59 → #2C3440 |
| "HMH" 텍스트 | 32sp Black 흰색, letterSpacing 4sp |
| "WORKS" 텍스트 | 9sp Bold 오렌지 #FF6B35, letterSpacing 5sp |
| 앱 이름 | "우리매장" 26sp Black #1F2937 |
| 서브타이틀 | "스마트한 매장 관리 솔루션" 13sp #9CA3AF |
| 브랜드 텍스트 | "HMH Works" 11sp Bold 오렌지, letterSpacing 2sp |
| 로딩 dot | 오렌지 원 7dp × 3개, 200ms 오프셋 scale+alpha 무한 애니메이션 |
icon-08-sage (세이지 그린 +
하우스 아이콘)icon-woo-cream-1024.png
(크림/베이지 배경 + "woo" 텍스트 아이콘)mockup/icons/icon-woo-cream-1024.png 원본 사용app/src/dev/res/mipmap-{mdpi~xxxhdpi}/ic_launcher.png —
48/72/96/144/192pxapp/src/dev/res/mipmap-{mdpi~xxxhdpi}/ic_launcher_round.png
— 동일app/src/dev/res/drawable-{mdpi~xxxhdpi}/ic_launcher_foreground.png
— adaptive icon 108/162/216/324/432px#5DAA76 (세이지) 유지NotificationHelper +
NotifyScheduler + NotifyReceiver 구현AppRepository.updateAttendStatus() 에서 출근/퇴근 시
즉시 알림 발송notify_check_in 설정이 ON인 경우에만 발송NotifyScheduler.schedulePaydayAlarms() —
AlarmManager.setAndAllowWhileIdle()AlarmManager.setRepeating() — 매일 22시에 출근중 + 퇴근
미체크 직원 목록 조회 후 알림AlarmManager.setRepeating() — 주간 근무시간/인건비 합산
후 알림ourshop_notify_prefs SharedPreferences에
저장BOOT_COMPLETED
receiver)POST_NOTIFICATIONS 권한 추가 (Android 13+)RECEIVE_BOOT_COMPLETED 권한 추가NotifyReceiver BroadcastReceiver 등록utils/NotificationHelper.kt — 채널 등록 + 알림 발송
헬퍼utils/NotifyScheduler.kt — AlarmManager 스케줄링receiver/NotifyReceiver.kt — BroadcastReceiver? 박스에
clickable 없음 (완전한 빈 껍데기)AlertDialog로 출퇴근 관리
사용법 안내
| 위치 | 기존 | 변경 후 |
|---|---|---|
| HeroKpi 값 (연간합계 등) | 13sp | 16sp |
| HeroKpi 레이블 | 9sp | 11sp |
| 막대차트 위 금액 레이블 | 6sp (전 막대) | 8sp (최고달·현재달만) |
| 막대차트 월 레이블 | 7.5sp | 9sp |
| 분기별 카드 "x/3개월" | 9sp | 11sp |
shareContractImage() 신규 함수:
createContractBitmap() → 캐시 저장 →
FileProvider URI →
Intent.ACTION_SEND(image/png)res/xml/file_paths.xml 신규:
cache-path contracts/ 경로 정의AndroidManifest.xml: FileProvider 등록
(${applicationId}.fileprovider)| 기존 | 변경 후 |
|---|---|
| 📤 공유하기 (텍스트) | 📸 이미지 저장 | 📤 이미지 공유 | 📸 갤러리 저장 |
| — | 📝 텍스트로 공유 (보조 버튼, 작게) |
updateAttendStatus가 퇴근 당일(6/6) 기준으로 기록을 찾음 →
6/5 출근 기록을 못 찾아 6/6 날짜에 checkIn="",
checkOut="06:00" 새 레코드 생성 → 두 기록 모두
checkIn/checkOut 중 하나가 빈 값이라 시간 계산 불가 →
급여 0원AppRepository.updateAttendStatus):
checkIn ≠ "", checkOut = "" 조건의 가장 최근 기록 탐색targetDate)를 유지한 채 퇴근 시간만
추가 → 출근 날짜 기준으로 기록 보존recalcPayroll이 LocalDate.now()(7월) 기준으로
호출 → 6월 급여가 아닌 7월 급여를 재계산 → 6월 야간 근무가 6월 급여에
미반영recalcPayroll 호출 시
targetDate(출근 날짜)의 year/month 사용calcHours: if (m < 0) m += 24 * 60 —
퇴근 시각 < 출근 시각이면 +24h 보정AppRepository.calcHours() /
PayrollScreen.calcHours() / AttendanceScreen
인라인| 단계 | 기존 | 수정 후 |
|---|---|---|
| 6/5 22:00 출근 | date=6/5, checkIn=22:00 저장 | 동일 ✅ |
| 6/6 06:00 퇴근 | date=6/6, checkIn="" 새 레코드 | date=6/5 기존 기록에 checkOut=06:00 추가 ✅ |
| 시간 계산 | 두 레코드 모두 불완전 → 0시간 | 6/5 레코드: 22:00→06:00 = 8.0시간 ✅ |
| 급여 재계산 | 7월 기준(월경계 버그) | 6월 기준 정확히 재계산 ✅ |
PayrollRecord가 자동 갱신되지 않아 급여 카드와 상세 시트
금액이 불일치AppRepository: calcHours(ci, co) 함수
오브젝트 레벨로 추출 (기존 로컬 중복 제거)AppRepository:
recalcPayroll(staffId, year, month) 신규 함수 — 출퇴근 기록
기반 당월 급여 즉시 재계산
PayrollRecord가 있으면
totalHours/workDays/baseAmount만 갱신
(isPaid·adjustmentLog 보존)updateAttendStatus(): 출퇴근 저장 후
recalcPayroll 자동 호출addStaff(): 신규 직원 등록 시 당월
PayrollRecord 즉시 초기 생성 (0시간)loadMockData(): 내부 중복 calcH 로컬 함수
제거 → 오브젝트 레벨 calcHours 사용mockup/icons/ 폴더의 디자인 아이콘으로 교체icon-07-cream (크림 배경
+ 주황 매장 아이콘)icon-08-sage (세이지 그린
+ 하우스 아이콘)app/src/prod/res/mipmap-{mdpi~xxxhdpi}/ic_launcher.png
— cream 런처 아이콘 (48~192px)app/src/prod/res/mipmap-{mdpi~xxxhdpi}/ic_launcher_round.png
— cream 라운드 아이콘app/src/prod/res/drawable-{mdpi~xxxhdpi}/ic_launcher_foreground.png
— adaptive icon 포그라운드 (108~432px)app/src/prod/res/values/colors.xml — adaptive 배경색
#FAF0E0 (크림)#5DAA76
(세이지)mipmap-anydpi-v26/ic_launcher.xml (main): 변경 없이
flavor 리소스 자동 오버라이드StaffScreen.kt: 파일 레벨 하드코딩
CONTRACT_CLAUSES 제거AppRepository.contractClauses 신규 상태 추가 (앱 전역
공유)ProfileSheet 조항 수정 저장 시
AppRepository.contractClauses.value 즉시 반영EmploymentContractSheet: 화면 표시, 공유 텍스트, 이미지
저장 모두 동적 조항 사용shortLabel() 축약 표시총시간 컬럼 추가,
테두리 스타일 버튼으로 변경YearlySummaryMini 제거 → HeroKpi로
교체HeroKpi, ForecastMini,
Int.shortLabel() 확장함수 신규 추가LoginScreen.kt 완전 재작성: 이메일/비밀번호 로그인 +
회원가입 탭signInWithEmailAndPassword() /
createUserWithEmailAndPassword() +
updateProfile() (displayName)CircularProgressIndicator 표시GetGoogleIdOption +
GoogleAuthProvider.getCredential()google-services.json type=3 항목
사용OurshopNavHost: 스플래시 후
FirebaseAuth.currentUser != null 이면 로그인 화면
건너뜀vm.syncFirebaseUser() 호출 → Firebase
displayName/email을 AppRepository에 반영MainViewModel.syncFirebaseUser() 신규 함수MainViewModel.logout():
FirebaseAuth.signOut() 호출 추가BuildConfig.IS_DEV = true 인 dev 빌드: 입력값만 있으면
즉시 onLogin() (Firebase 우회)com.hmhworks.ourshop.dev) 로 Firebase
등록 없이 개발 가능AdBanner.kt 신규 컴포저블 (AndroidView
기반)OurshopNavHost.kt MainScreen: HorizontalPager +
AdBanner + BottomNav 수직 배치InterstitialAdHelper.kt 신규 헬퍼 클래스
(로드/노출/자동 재로드)HomeScreen.kt: 리포트 닫기 시
interstitialHelper.show() 호출dev flavor: AdMob 공식 테스트 ID 사용 (정책 위반
방지)prod flavor: 실제 광고 ID
(ca-app-pub-9930972709470211/...)BuildConfig.ADMOB_BANNER_ID /
BuildConfig.ADMOB_INTERSTITIAL_ID로 분기AndroidManifest.xml: AdMob 앱 ID 등록OurshopApp.kt: MobileAds.initialize()
추가libs.versions.toml / build.gradle.kts:
play-services-ads:23.6.0 추가SplashScreen.kt)OurshopNavHost startDestination: login →
splashScheduleCard에 🗑 삭제 버튼 추가 (탭 → 삭제/취소 확인
버튼 인라인 표시)AppRepository.deleteSchedule() + Room DB write-through
추가MainViewModel.deleteSchedule() 추가HOLIDAYS_2026 → HOLIDAYS_KR로 이름 변경
(2026+2027 포함)HOLIDAYS_2026 alias 유지로 하위 호환 보장VoiceQuerySheet)VoiceQuerySheet 신규 컴포저블
(HomeScreen.kt):
RecognizerIntent.ACTION_RECOGNIZE_SPEECH (ko-KR) 시스템
다이얼로그processVoiceQuery() 자연어 파싱 → 말풍선 UI
표시 (질문/답변)processVoiceQuery() 자연어 질의 처리:
AttendanceScreen)isListening 상태 변수 제거Mic / MicOff 아이콘 import 제거AddStoreSheet)AddStoreSheet) 구성:
AppRepository.addStore() DB write-through 추가
(이전에는 in-memory만)BackupRestoreSheet SAF 연동)Storage Access Framework) 기반 JSON 파일 백업/복원
ActivityResultContracts.CreateDocument("application/json")
→ ourshop_backup_YYYY-MM-DD.jsonActivityResultContracts.OpenDocument → JSON 파싱 후 DB 전체
교체BackupData 신규 모델
(data/model/BackupData.kt):
stores/staff/attendance/payroll/schedules/profile/currentStoreId
포함AppRepository.restoreFromBackup(): DB 전체 clear →
insert → loadFromDatabase() 재로드last_backup_date)AppRepository.initialize()에서
ourshop_prefs SharedPreferences 초기화current_store_id) 앱 재시작 시
복원AppRepository.setCurrentStore() — 변경 +
SharedPreferences 동시 저장MainViewModel.selectStore() /
switchStore() 모두 setCurrentStore() 사용BuildConfig.VERSION_NAME 동적 표시data/local/)OurshopDatabase.kt:
@Database (entities 5종),
@TypeConverters(Converters::class), singleton
getDatabase()Converters.kt: Gson 기반 TypeConverter
모음
AttendStatus, EmploymentType,
AvatarType enum → String (name) 변환List<String> /
List<AdjustmentEntry> → JSON String 변환runCatching 안전 fallback 적용StaffDao,
AttendanceDao, PayrollDao,
ScheduleDao, StoreDao
getAll(), insert(),
insertAll(), update(),
deleteById(), deleteAll() (모두 suspend)data/model/)Staff, AttendanceRecord,
PayrollRecord, Schedule, Store
클래스에 @Entity/@PrimaryKey 어노테이션
추가PayrollRecord computed property
(bonusAmount, deductAmount,
totalAmount)에 @get:Ignore 추가Schedule.staffIds: List<String> — TypeConverter로
JSON 저장initialize(context: Context) —
OurshopDatabase.getDatabase() 초기화ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
— DB 전용 코루틴 스코프suspend fun loadFromDatabase() — Room →
mutableStateListOf 전체 로드private fun dbWrite(block) — fire-and-forget
write-through 헬퍼addStaff, updateStaff,
retireStaff, deleteStaff,
saveContract, saveContractSignatures,
resetContract, addSchedule,
updatePaydayDate, updateStoreIcon,
markPayrollPaid, addAdjustmentEntry,
removeAdjustmentEntry, updateAttendStatus)에
dbWrite { ... } 적용AppRepository.initialize(this) —
Application.onCreate()에서 Room DB 초기화init 블록: !BuildConfig.IS_DEV 일 때
viewModelScope.launch { AppRepository.loadFromDatabase() }
호출init { if (BuildConfig.IS_DEV) loadMockData() } — dev
flavor에서만 Mock 데이터 로드loadMockData() 함수로 분리 (직원 8명 + 스케줄 + 출퇴근
기록 + 급여 데이터 + Store)app/build.gradle.kts productFlavorsflavorDimensions: ["env"]
dev flavor: applicationIdSuffix=".dev", versionNameSuffix="-dev", IS_DEV=true, MOCK_PRO=true
prod flavor: (기본 applicationId), IS_DEV=false, MOCK_PRO=false
ourshop-{flavor}-{buildType}-v0.1.0.apkBuildConfig.IS_DEV: Mock 데이터 로드 여부 결정BuildConfig.MOCK_PRO: MainViewModel.isPro
초기값 (true=PRO 전부 열림 / false=실제
인앱결제)scripts/)build.ps1, install.ps1,
build-install.ps1 에 -Flavor (기본: dev),
-BuildType (기본: debug) 파라미터 추가assemble{FlavorCap}{BuildTypeCap} 형태로
동적 생성.\scripts\build-install.ps1 -Target 192.168.1.33:PORT -Flavor dev -BuildType debug🏪 → 🏡 (정원 있는
집)👥 → 🪪 (신분증
카드)📅 → 🗓️ (나선형
캘린더)⏱ → 🕐 (아날로그
시계)💰 → 🧾
(영수증)👥 직원 관리 →
🪪 직원 관리👥 근무보드 →
📊 근무보드tc.brand → tc.chipActiveBg (WARM 테마에서도 색
변경되도록)Color(0xFFEA580C) → tc.brandPrimary →
tc.brand, LinearProgressIndicator trackColor
동일 연동OutlinedButton 테두리/텍스트 Primary →
tc.brandColor(0xFFEA580C) → tc.brand; 합계 행 배경
Color(0xFFFFF7ED) → tc.brandSoft; 합계 숫자 →
tc.brandPrimary → tc.chipActiveBgPrimary → tc.brandProfileGradient
(고정 오렌지) → tc.actionBrush (테마 연동)ProfileGradient
→ tc.actionBrush, 텍스트 → tc.actionTextProfileGradient → tc.actionBrushtc.brand.copy(0.12f) → tc.backgroundSecondary
(중립 회색)tc.brand.copy(0.35f) → tc.dividertc.brand →
tc.inktc.brand →
tc.inkSecondarytc.chipActiveBg / tc.chipActiveText; 아바타
선택 테두리 → tc.brand; 이모지 선택 배경 →
tc.brand; 저장 버튼 → tc.actionBrushtc.chipActiveBg; 오늘 텍스트 → tc.brandtc.chipActiveBg / tc.chipActiveTexttc.brand.copy(alpha=0.08f); 오늘 날짜 원형 →
tc.chipActiveBgaccentColor fallback →
tc.brandAddStaffSheet):
저장 버튼 그라디언트 → tc.actionBrushAttendDetailSheet):
히어로 배경 + 저장 버튼 → tc.heroBrush /
tc.actionBrushStaffDetailSheet):
히어로 배경 + 정보수정 버튼 → tc.heroBrush /
tc.actionBrushScheduleTabBar): 활성 탭
텍스트·밑줄 → tc.brandScheduleDateHeader):
"오늘" 배지 → tc.brandShiftBoardTab): 배치 인원
칩 점 + 일별 총인원 숫자 → tc.brandPayrollDetailSheet):
히어로 배경 → tc.heroBrushAdjustSheet):
기본/최종 금액 요약 카드 → tc.actionBrushSettleSheet): 총 인건비
요약 카드 → tc.heroBrushPayHistorySheet): 연간
요약 카드 + 최고월 막대 → tc.heroBrush /
tc.brandProfileSheet):
수정·체크·PRO 배지·가게 아이콘 선택·급여일 스텝퍼·알림 스위치·계약서
번호뱃지·앱정보 전부 tc.brand /
tc.chipActiveBg 연동AppTheme.kt)AppColors 데이터 클래스: 26개 디자인 토큰 정의 (배경,
헤더, 히어로, 칩, 네비게이션 등)DefaultAppColors: 기존 오렌지 그라디언트 스타일 (변경
없음)WarmAppColors: 크림/웜 뉴트럴 스타일 — 흰 헤더, 다크
히어로 카드, 크림 배경LocalAppColors CompositionLocal로 전체 앱에 주입ProfileSheet.kt)MainViewModel.appTheme + vm.setTheme()
연동 — 즉시 전체 앱에 적용PayHistorySheet, AddStaffSheet(직원
추가/편집), AddScheduleSheet 세 시트에 드래그로 닫기 방지
적용
rememberModalBottomSheetState(confirmValueChange = { it != SheetValue.Hidden })
적용StaffScreen.addSheetState,
StaffScreen.editSheetState,
ScheduleScreen.addSheetStateAddStaffSheet):
제목 오른쪽에 ✕ 원형 버튼 추가AddScheduleSheet):
제목 오른쪽에 ✕ 원형 버튼 추가PayHistorySheet):
제목 오른쪽에 ✕ 원형 버튼 추가 (이전 패치)Color(0xFFF3F4F6)) + "✕"
텍스트ProGateSheet)ProGateSheet 표시 (PRO
플랜이면 기존대로 동작)ProGateSheet는
featureIcon / featureName / featureDesc 파라미터로 다른 PRO
기능에도 재사용 가능MainViewModel)isPro: Boolean 상태 추가 (기본 false =
무료 플랜)togglePro() 개발/테스트용 토글 메서드 추가ContactImportSheet)READ_CONTACTS 권한 요청(최초 1회) →
ContactImportSheet 열기MainViewModel.addMultipleStaff() 추가ContactsContract.CommonDataKinds.Phone 직접 쿼리,
IO 스레드 로딩, 전화번호 뒷 8자리 정규화 비교AddStaffSheet)READ_CONTACTS 권한 미허용 → 시스템 권한 요청 다이얼로그
표시AndroidManifest.xml: READ_CONTACTS 권한
추가ActivityResultContracts.PickContact +
ContactsContract.CommonDataKinds.Phone 쿼리StaffAvatar.kt 그라디언트 팔레트
도입
avatarGradients,
orange/teal/purple/pink/blue/green/amber/red/indigo)Brush.linearGradient() 그라디언트로
변경staffAvatarGradient(index: Int): List<Color> —
전체 앱에서 공용으로 재사용할 수 있도록 public exportstaffAvatarColor(index: Int): Color — 단일 색상 필요 시
호환 함수직원명 그라디언트 텍스트 (전체 탭 적용)
HomeStaffRow 직원 이름 그라디언트AttendanceCard 직원 이름 그라디언트StaffCard 활성 직원 이름 그라디언트 (퇴직자는
회색 유지)PayrollCard 직원 이름,
AdjustSheet 타이틀, SettleSheet 직원 이름,
PayHistorySheet 직원 이름 그라디언트TextStyle(brush = Brush.linearGradient(staffAvatarGradient(index))),
퇴직자는 TextStyle.Default + color = Color(0xFF9CA3AF)Staff.avatarColorIndex: Int = -1 필드
추가
avatarColorIndex >= 0 이면 고정
색상, 아니면 리스트 인덱스 기반 자동 적용AddStaffSheet)avatarColorIndex 필드에 반영WeeklyReportSheet)제거된 섹션
새로운 섹션
기술
today.minusDays(today.dayOfWeek.value - 1 + 7L) 로
지난주 월요일 계산calcH(ci, co) 헬퍼: HH:mm 파싱 후 근무시간(Double)
계산WeekSumSection 서브 컴포저블 분리직원 목록 6명 → 8명으로 확장 | ID | 이름 | 직책 | 고용형태 | 시급 | 은행 | |----|------|------|----------|------|------| | s1 | 강민규 | 매니저 | 정규직 | 12,000원 | 국민은행 | | s2 | 박지인 | 서빙 | 정규직 | 10,000원 | 신한은행 | | s3 | 강미혜 | 주방 | 아르바이트 | 9,860원 | 우리은행 | | s4 | 양희만 | 서빙 | 파트타임 | 10,000원 | 하나은행 | | s5 | 강율아 | 카운터 | 아르바이트 | 9,860원 | 카카오뱅크 | | s6 | 강로아 | 서빙 | 단기 | 10,000원 | 토스뱅크 | | s7 | 양지유 | 주방 | 아르바이트 | 9,860원 | 국민은행 | | s8 | 양지온 | 서빙 | 파트타임 | 10,000원 | 신한은행 |
지난주 출퇴근 데이터 추가 (주간 리포트 테스트용)
today 기준
lastMonday = today.minusDays(dayOfWeek.value - 1 + 7L)
계산list.none { it.staffId == staffId && it.date == date }
체크 후 추가📋 일정 / 👥 근무보드): 14dp 패딩,
19sp 폰트aspectRatio 0.82→1.05 (세로 공간 축소),
날짜 폰트 19sp, N명 배지 13spNestedScrollConnection.onPostScroll으로 LazyRow 남은
수평 스크롤 소비AttendDetailSheet):
👥 근무보드)📋 일정 |
👥 근무보드 전환CalendarGrid)📋 일정)ResignConfirmSheet): 퇴사일 + 퇴사 사유 칩 4종ResignedStaffDetailSheet): 퇴직 정보 + 재입사/영구삭제
버튼staffAvatarColor에서 index.coerceAtLeast(0)
처리HomeStaffDetailSheet)skipPartiallyExpanded = trueOurshopNavHost.kt
HorizontalPager 패딩을 padding(padding) →
padding(bottom = padding.calculateBottomPadding())으로
변경v2.3 이하 HTML 프로토타입 기록은
patch_notes_backup/ourshop_patch_notes_html_era.md참조