
2026.01.04 - [IT 개발,관리,연동,자동화] - '무인점포는 무인이 아니다'...월 300 수익의 꿈이 '리모컨 감옥'으로 변하는 이유
1. 서론: 성장하는 무인점포 시장의 이면에 가려진 기회
무인점포 시장의 폭발적 성장은 '노동 없는 수익'이라는 기대를 기반으로 하지만, 현실은 '리모컨 감옥'이라는 역설에 갇혀있습니다. 24시간 감시와 예측 불가능한 문제 해결에 묶인 점주들은 노동으로부터 자유롭지 못합니다. 이 구조적 비효율이야말로 저희가 포착한, 시장의 패러다임을 바꿀 거대한 기회입니다. 본 제안서는 점주들을 해방시키고 시장 전체의 질적 성장을 견인할 혁신적인 솔루션을 제시합니다.
현재 대다수의 무인점포 점주들은 다음과 같은 세 가지 핵심적인 고충에 직면해 있습니다.
- 리모컨 감옥: 점주들은 매장 CCTV를 24시간 들여다보며 직접 문제를 해결해야 하는 상황에 놓여있습니다. 이는 '무인(無人)'이라는 단어가 무색하게, 시간과 장소에 얽매이는 또 다른 형태의 노동으로 변질되어 진정한 의미의 자동 수익을 실현하지 못하게 만듭니다.
- 비용 부담: 개별적으로 관리 인력을 고용하는 것은 높은 인건비 부담으로 이어집니다. 특히 최저임금 상승과 단기 근무자 채용의 어려움은 점주들에게 상당한 재정적, 운영적 압박으로 작용합니다.
- 품질의 비표준화: 전문적인 매뉴얼이나 시스템 없이 개인의 역량에 의존해 매장을 관리할 경우, 서비스 품질이 일정하게 유지되기 어렵습니다. 이는 고객 경험의 저하로 이어져 장기적인 매장 경쟁력을 약화시키는 주된 요인이 됩니다.
저희는 이러한 문제들이 개별 점주의 노력만으로는 해결될 수 없는 시장의 구조적 한계임을 인지했습니다. 이어지는 섹션에서는 이 모든 고충을 근본적으로 해결하고, 무인점포 운영의 패러다임을 전환할 혁신적인 솔루션을 구체적으로 제안하고자 합니다.
2. 혁신적 해결책: '클러스터링 기반 공유 관리 모델'의 제안
본 제안서의 핵심은 기존의 단순 인력 매칭 서비스와는 차원을 달리하는, 운영 패러다임 자체를 바꾸는 혁신적인 모델에 있습니다. 저희는 **'클러스터링(Clustering) 기반의 공유 관리 모델'**을 통해 개별 점포가 겪는 운영의 한계를 극복하고, 규모의 경제를 통한 효율성 극대화를 제안합니다.
'클러스터링 기반의 공유 관리 모델'은 특정 지역 내에 밀집한 여러 무인점포를 하나의 관리 단위로 묶어 전문 매니저가 순회하며 통합 관리하는 방식입니다. 이는 각 점주가 개별적으로 인력을 고용하는 기존 방식에서 벗어나, 검증된 전문 인력과 시스템을 '공유'하고 '구독'하는 새로운 개념입니다. 이 모델은 지역 기반의 클러스터링을 통해 관리 동선을 최적화하고, 표준화된 서비스를 합리적인 비용으로 제공함으로써 시너지를 창출합니다.
이 공유 관리 모델은 앞서 제기된 무인점포 점주들의 고질적인 문제들을 다음과 같이 직접적으로 해결합니다.
| 문제점 (Pain Point) | 해결 방안 (Solution) |
| 리모컨 감옥 (24시간 직접 관리) | 전문 매니저의 정기·수시 관리를 통해 운영 자동화 실현 및 점주의 완전한 해방 |
| 높은 비용 부담 (개별 인력 고용) | 월 구독료 전환을 통한 고정비 절감 및 현금흐름 예측성 확보 |
| 품질의 비표준화 (개인 역량 의존) | 데이터 기반의 표준화된 수행으로 프랜차이즈 직영점 수준의 품질 구현 |
이 모델은 단순한 아이디어를 넘어, 체계적인 서비스와 기술적 기반 위에서 구현됩니다. 다음 섹션에서는 본 플랫폼이 제공하는 구체적인 핵심 서비스와 이를 뒷받침하는 IT 시스템에 대해 상세히 설명하겠습니다.
3. 핵심 서비스 및 기술적 기반: 표준화와 투명성을 통한 신뢰 구축
저희가 제안하는 모델은 체계적으로 설계된 핵심 서비스와 투명한 IT 시스템을 통해 구체화됩니다. 서비스의 표준화는 모든 매장에서 일관된 고품질의 관리를 보장하며, 데이터 기반의 투명성은 점주에게 언제 어디서든 매장 상태를 확인할 수 있다는 강력한 신뢰를 제공합니다. 이것이 저희 플랫폼의 핵심 경쟁력입니다.
플랫폼은 전문 교육을 받은 '지역 매장 매니저'를 통해 다음과 같은 4가지 핵심 관리 업무를 표준화된 방식으로 수행합니다.
- 환경 미화: 매장 내부 바닥 및 외부 청소, 쓰레기 분리수거 및 처리, 고객 이용이 잦은 키오스크 및 진열대 소독 등 청결 유지를 위한 모든 활동을 포함합니다.
- 재고 및 비품 관리: 비어있는 진열대에 상품을 채우고, 배송된 택배를 수령하여 정리합니다. 또한, 영수증 용지, 쓰레기봉투와 같은 필수 소모품을 적시에 보충하여 매장 운영에 차질이 없도록 합니다.
- 시설 점검: 키오스크, 냉난방기, 냉동고 등 주요 장비의 정상 작동 여부를 확인하고, 기물 파손이나 노숙 등 매장 내 특이사항 발생 시 사진과 함께 보고하며 필요한 초동 조치를 수행합니다.
- 데이터 기반 리포팅: 모든 관리 업무는 표준화된 체크리스트에 따라 수행되며, 관리 전후 비교 사진을 포함한 상세 리포트를 플랫폼 앱을 통해 점주에게 실시간으로 제공합니다.
점주들은 더 이상 CCTV만 바라보며 불안해할 필요가 없습니다. 저희의 IT 시스템은 '사진 인증'과 '체크리스트 확인' 기능을 통해 매니저의 모든 관리 활동을 투명하게 공유합니다. 이는 점주에게 물리적 해방을 넘어 심리적 안정감을 제공하며, 플랫폼에 대한 깊은 신뢰를 구축하는 기반이 됩니다.
이처럼 강력하고 신뢰도 높은 서비스는 지속 가능한 수익 모델로 자연스럽게 연결됩니다. 다음 섹션에서는 점주, 관리 매니저, 그리고 플랫폼 모두가 상생하는 비즈니스 모델에 대해 설명하겠습니다.
4. 지속 가능한 비즈니스 모델: 모두가 상생하는 수익 구조
본 플랫폼의 비즈니스 모델은 특정 주체의 이익만을 극대화하는 제로섬 게임이 아닙니다. 점주에게는 비용 절감을, 관리 매니저에게는 안정적 수익을, 플랫폼에는 합리적 수수료를 제공함으로써 모든 참여자가 함께 성장하는 지속 가능한 상생 구조를 지향합니다.
수익 모델은 다음과 같은 세 가지 핵심 축으로 구성됩니다.
점주 대상: 비용 효율적인 구독 모델
점주는 매장당 월 15만 원에서 20만 원 수준의 합리적인 구독료를 지불하고 통합 관리 서비스를 이용합니다. 이는 개인이 직접 아르바이트를 고용하며 발생하는 높은 인건비, 채용 및 관리에 드는 시간과 노력, 그리고 서비스 품질의 불확실성까지 모두 고려했을 때 비교할 수 없는 경제적 가치를 제공합니다.
관리 매니저 대상: 안정적 수익과 효율적 업무 동선
플랫폼은 인근 5개에서 10개의 점포를 하나의 클러스터로 묶어 관리 매니저에게 배정합니다. 이를 통해 불필요한 이동 시간을 최소화하고 업무 효율을 극대화하여, 매니저가 안정적이고 예측 가능한 수익을 확보할 수 있도록 지원합니다.
플랫폼 수익: 합리적인 중개 수수료
플랫폼의 핵심 수익원은 점주가 지불하는 구독료와 관리 매니저에게 지급되는 인건비 사이에서 발생하는 중개 수수료입니다. 클러스터링을 통한 규모의 경제가 실현될수록 플랫폼의 수익성은 더욱 안정적으로 성장하게 됩니다.
특히 저희는 관리 인력을 단순한 단기 아르바이트가 아닌, 담당 지역에 대한 전문성과 책임감을 갖춘 **'지역 매장 매니저'**로 포지셔닝하는 전략을 추구합니다. 이러한 전문 직무 정체성 부여는 우수 인력의 이직률을 낮춰 플랫폼의 반복적인 채용 및 교육 비용을 직접적으로 절감시킵니다. 이는 곧 안정적인 서비스 품질로 이어져 점주들의 만족도와 재계약률을 높이고, 플랫폼의 장기적인 반복 매출(Recurring Revenue)을 견고하게 만드는 선순환 구조의 핵심입니다.
5. 시장 확장 전략 및 성장 잠재력
본 사업의 성공은 견고한 비즈니스 모델을 얼마나 효과적으로 시장에 적용하고 확장하는지에 달려있습니다. 저희는 초기 시장을 선점하기 위한 명확한 진입 전략과 장기적인 성장을 위한 로드맵을 통해, 본 플랫폼이 가진 높은 확장성과 잠재력을 실현하고자 합니다.
핵심 성장 전략은 다음과 같은 세 가지 방향으로 추진될 것입니다.
- 전략적 지역 클러스터링: 사업 초기에는 신도시 상업지구나 대학가와 같이 무인점포가 고도로 밀집된 지역을 우선 타겟으로 설정합니다. 이 지역의 점주들을 대상으로 '협의체' 또는 '관리 그룹' 형태의 공동 가입을 유도하여, 최소한의 자원으로 최대의 운영 효율을 내는 성공 사례를 빠르게 만들어낼 것입니다. 이는 초기 시장을 효과적으로 확보하고, 입소문을 통한 자연스러운 확장을 유도하는 핵심 전략입니다.
- 업종 통합을 통한 시장 확대: 저희의 관리 서비스는 특정 업종에 국한되지 않습니다. 아이스크림 가게, 무인 카페, 코인 세탁소 등 다양한 업종에 모두 적용 가능한 **'범용 관리 매뉴얼'**을 개발하고 고도화할 것입니다. 이는 서비스 가능한 전체 시장의 규모(Total Addressable Market)를 기하급수적으로 확대할 뿐만 아니라, 특정 무인 업종의 유행이나 경기 변동에 흔들리지 않는 안정적인 사업 포트폴리오를 구축하는 핵심적인 리스크 관리 전략입니다.
- 전문 인력 양성 및 브랜드화: '지역 매장 매니저'라는 전문 직무를 체계적으로 교육하고 양성하는 시스템을 구축할 것입니다. 이는 서비스 품질을 상향 평준화하는 가장 확실한 방법이며, "우리 플랫폼에 맡기면 대기업 직영점 수준으로 관리된다"는 강력한 신뢰를 시장에 각인시키는 역할을 합니다. 결국, 잘 훈련된 매니저 자체가 플랫폼의 가장 강력한 브랜드 자산이 되는 선순환 구조를 만들 것입니다.
이러한 전략들이 성공적으로 실행될 때, 저희 플랫폼은 단순한 관리 대행 서비스를 넘어 시장 전체의 운영 표준을 제시하는 리더로 자리매김할 것입니다. 다음 결론 부분에서는 이 모든 것이 가져올 궁극적인 기대효과와 저희의 비전을 종합적으로 제시하겠습니다.

6. 기대효과 및 비전: 무인점포 생태계의 혁신 리더
본 제안서에서 설명한 '클러스터링 기반 공유 관리 모델'은 단순한 편의성 증진을 넘어, 무인점포 생태계에 참여하는 모든 이해관계자에게 긍정적인 파급 효과를 가져오는 혁신입니다. 저희는 이 모델을 통해 시장의 고질적인 문제를 해결하고, 지속 가능한 성장의 기틀을 마련함으로써 높은 투자 가치를 창출할 것을 확신합니다.
플랫폼 도입을 통해 각 이해관계자는 다음과 같은 핵심적인 가치를 얻게 될 것입니다.
- 점주: 24시간 감시와 관리가 필요했던 '리모컨 감옥'에서 완전히 해방됩니다. 이를 통해 비로소 노동 없는 **'진정한 패시브 인컴'**을 실현하며, 무인점포 창업의 본질적인 목표를 달성하게 됩니다.
- 관리 매니저: 최적화된 동선 내에서 업무를 수행함으로써 이동에 낭비되는 시간을 줄이고 업무 몰입도를 높일 수 있습니다. 이는 안정적인 고정 수입 확보로 이어져, 전문 직업인으로서의 만족도와 장기근속률을 높입니다.
- 매장(시장 전체): 개인의 역량에 따라 천차만별이었던 관리 품질이 대기업 프랜차이즈 수준으로 상향 표준화됩니다. 이는 개별 매장의 경쟁력을 높이는 것은 물론, 무인점포 시장 전체에 대한 소비자의 신뢰도를 향상시키는 효과를 가져옵니다.
저희의 궁극적인 비전은 개별 서비스를 제공하는 것을 넘어, **"무인점포의 운영 시스템 자체를 구독형으로 제공"**하는 것입니다. 이는 점주가 매장 개설에만 집중하면, 이후의 모든 운영은 저희 플랫폼이 책임지는 새로운 산업 표준을 만드는 것을 의미합니다. 지금의 투자는 단순히 성장하는 기업에 합류하는 것을 넘어, 무인점포 산업의 차세대 운영 표준을 정의하는 기회가 될 것입니다
**'클러스터링 기반 무인점포 공유 관리 모델'**은 다수의 점포, 인력(매니저), 그리고 표준화된 워크플로우를 효율적으로 연결하는 것이 핵심입니다.
이 비즈니스 로직을 지원하기 위한 MySQL 데이터베이스 스키마를 설계해 드립니다. 데이터 간의 정규화를 유지하면서도, '클러스터링'과 '구독 모델'이라는 특징을 반영하였습니다.
1. 데이터베이스 ERD 개념 구조
핵심 엔티티를 4개의 그룹으로 분류했습니다.
- 회원 및 조직: 점주(Owner), 지역 매니저(Manager)
- 인프라: 지역 클러스터(Cluster), 매장(Store)
- 운영: 업무 매뉴얼(Manual), 관리 태스크(Task/Log)
- 비즈니스: 구독 서비스(Subscription), 결제(Payment)
2. MySQL 테이블 설계 (DDL)
-- 1. 지역 클러스터 (지역별 묶음 관리)
CREATE TABLE clusters (
cluster_id INT AUTO_INCREMENT PRIMARY KEY,
cluster_name VARCHAR(100) NOT NULL, -- 예: '송도 신도시 A구역'
region_code VARCHAR(20),
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 사용자 (점주 및 지역 매니저)
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(50) NOT NULL,
phone VARCHAR(20),
role ENUM('OWNER', 'MANAGER', 'ADMIN') NOT NULL,
cluster_id INT, -- 매니저의 경우 담당 클러스터 배정
FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id)
);
-- 3. 매장 정보
CREATE TABLE stores (
store_id INT AUTO_INCREMENT PRIMARY KEY,
owner_id INT NOT NULL,
cluster_id INT NOT NULL,
store_name VARCHAR(100) NOT NULL,
store_type ENUM('ICE_CREAM', 'CAFE', 'LAUNDRY', 'OTHER') NOT NULL,
address TEXT NOT NULL,
business_hours VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (owner_id) REFERENCES users(user_id),
FOREIGN KEY (cluster_id) REFERENCES clusters(cluster_id)
);
-- 4. 구독 정보
CREATE TABLE subscriptions (
sub_id INT AUTO_INCREMENT PRIMARY KEY,
store_id INT UNIQUE NOT NULL,
plan_name VARCHAR(50) DEFAULT 'Standard',
monthly_fee DECIMAL(10, 2) NOT NULL, -- 예: 200000.00
status ENUM('ACTIVE', 'EXPIRED', 'PENDING') DEFAULT 'ACTIVE',
start_date DATE,
next_billing_date DATE,
FOREIGN KEY (store_id) REFERENCES stores(store_id)
);
-- 5. 업무 체크리스트 매뉴얼 (업종별 범용 매뉴얼)
CREATE TABLE task_manuals (
manual_id INT AUTO_INCREMENT PRIMARY KEY,
store_type ENUM('ICE_CREAM', 'CAFE', 'LAUNDRY', 'COMMON') NOT NULL,
task_name VARCHAR(255) NOT NULL, -- 예: '키오스크 소독'
description TEXT,
importance_level INT DEFAULT 1
);
-- 6. 관리 수행 로그 (데이터 리포트의 핵심)
CREATE TABLE visit_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
store_id INT NOT NULL,
manager_id INT NOT NULL,
visit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
summary_report TEXT,
photo_url_before VARCHAR(255), -- 관리 전 사진
photo_url_after VARCHAR(255), -- 관리 후 사진
FOREIGN KEY (store_id) REFERENCES stores(store_id),
FOREIGN KEY (manager_id) REFERENCES users(user_id)
);
-- 7. 개별 체크리스트 결과
CREATE TABLE check_results (
result_id INT AUTO_INCREMENT PRIMARY KEY,
log_id INT NOT NULL,
manual_id INT NOT NULL,
is_completed BOOLEAN DEFAULT FALSE,
issue_note TEXT, -- 특이사항 발생 시 기록
FOREIGN KEY (log_id) REFERENCES visit_logs(log_id),
FOREIGN KEY (manual_id) REFERENCES task_manuals(manual_id)
);
3. 주요 설계 포인트 설명
- Cluster 기반 동선 최적화: users 테이블에서 매니저(MANAGER)에게 cluster_id를 부여하여 특정 구역의 매장들(stores)만 효율적으로 필터링하여 관리할 수 있도록 설계했습니다.
- 업종 통합 매뉴얼 (task_manuals): store_type 컬럼을 통해 공통 업무(COMMON)와 업종별 특화 업무를 구분했습니다. 매니저가 매장에 방문하면 시스템이 해당 매장 타입에 맞는 체크리스트를 자동으로 로드합니다.
- 리모컨 감옥 해방 (IT 연동): visit_logs와 check_results 테이블이 핵심입니다. 매니저가 현장에서 앱으로 사진과 체크항목을 입력하면, 점주는 실시간으로 log_id를 조회해 리포트를 확인할 수 있습니다.
- 구독 결제 관리: subscriptions 테이블을 통해 매장당 월정액 과금 체계를 관리하며, 향후 결제 API 연동 시 next_billing_date를 통해 자동 결제 로직을 구현할 수 있습니다.
4. 확장 제안
시스템 고도화 시 다음 테이블을 추가하는 것이 좋습니다.
- Inventory Table: 재고 부족 보고 시 어떤 품목이 부족한지 구체적으로 기록.
- Incident Table: 시설 파손이나 노숙자 발생 등 '응급 조치'가 필요한 긴급 상황만 별도로 관리하고 알림(Push) 발송.
실제 시스템 운영 시 데이터가 어떻게 흐르는지, 그리고 점주와 매니저가 사용할 핵심 기능 쿼리 예시를 정리해 드리겠습니다.
1. 서비스 운영 프로세스 데이터 흐름
이 ERP 시스템의 핵심은 **'클러스터 단위의 일괄 처리'**입니다.
- 매니저 로그인: 본인에게 할당된 cluster_id에 속한 모든 stores 목록을 조회합니다.
- 방문 시작: 특정 매장에 도착하면 visit_logs에 레코드가 생성됩니다.
- 체크리스트 수행: task_manuals에서 해당 업종에 맞는 항목을 불러와 check_results에 저장합니다.
- 리포트 생성: 사진 업로드와 함께 로그가 완료(Update)되면 점주에게 푸시 알림이 발송됩니다.
2. 핵심 운영 SQL 쿼리 예시
ERP 화면 구성 시 가장 자주 사용될 로직들입니다.
A. 매니저용: 오늘 내가 관리해야 할 매장 리스트 조회
SELECT s.store_name, s.address, s.store_type
FROM stores s
JOIN users u ON s.cluster_id = u.cluster_id
WHERE u.user_id = {현재_매니저_ID};
B. 점주용: 우리 매장 최근 관리 리포트 (사진 포함)
SELECT v.visit_time, v.photo_url_before, v.photo_url_after, v.summary_report,
c.task_name, r.is_completed, r.issue_note
FROM visit_logs v
JOIN check_results r ON v.log_id = r.log_id
JOIN task_manuals c ON r.manual_id = c.manual_id
WHERE v.store_id = {내_매장_ID}
ORDER BY v.visit_time DESC;
C. 관리자용: 클러스터별 수익성 분석 (구독료 vs 인건비)
SELECT c.cluster_name,
SUM(sub.monthly_fee) AS total_revenue,
(SELECT COUNT(*) FROM users WHERE cluster_id = c.cluster_id AND role = 'MANAGER') * {매니저_평균_급여} AS est_cost
FROM clusters c
JOIN stores s ON c.cluster_id = s.cluster_id
JOIN subscriptions sub ON s.store_id = sub.store_id
GROUP BY c.cluster_id;
3. 향후 확장성을 위한 팁
- 정산 시스템 (Settlement): 현재는 구독료만 있지만, 매니저의 방문 횟수나 작업 난이도에 따라 인센티브를 차등 지급하는 기능을 위해 settlements 테이블을 추가할 수 있습니다.
- IoT 연동: 키오스크 API와 연동된다면, 용지가 부족할 때 자동으로 task_manuals에 '용지 교체' 항목을 **긴급 작업(Urgent Task)**으로 생성하는 트리거를 만들 수 있습니다.
- 매니저 등급제: '지역 매니저'라는 직무 정체성을 강화하기 위해 고객(점주) 만족도 점수를 저장하는 컬럼을 users 테이블에 추가하여 우수 매니저 포상 근거로 활용하세요.










NestJS(Prisma)와 Nuxt.js 조합은 현재 웹 개발 생태계에서 매우 강력한 Full-stack TypeScript 조합입니다. 제안하신 데이터베이스 설계를 바탕으로 핵심 백엔드 로직과 프론트엔드 구조를 구현해 보겠습니다.
1. Back-end: NestJS + Prisma
Prisma를 사용하여 DB 스키마를 정의하고, 매니저가 방문 로그를 작성하는 핵심 API를 구현합니다.
Prisma Schema (schema.prisma)
데이터베이스 구조를 Prisma 문법으로 정의합니다.
// schema.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
enum Role {
OWNER
MANAGER
ADMIN
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
role Role
clusterId Int?
cluster Cluster? @relation(fields: [clusterId], references: [id])
stores Store[] @relation("OwnerStores")
visitLogs VisitLog[]
}
model Cluster {
id Int @id @default(autoincrement())
name String
users User[]
stores Store[]
}
model Store {
id Int @id @default(autoincrement())
name String
ownerId Int
owner User @relation("OwnerStores", fields: [ownerId], references: [id])
clusterId Int
cluster Cluster @relation(fields: [clusterId], references: [id])
subscription Subscription?
visitLogs VisitLog[]
}
model VisitLog {
id Int @id @default(autoincrement())
storeId Int
store Store @relation(fields: [storeId], references: [id])
managerId Int
manager User @relation(fields: [managerId], references: [id])
visitTime DateTime @default(now())
photoUrlBefore String?
photoUrlAfter String?
summary String? @db.Text
}
Visit Service (visit.service.ts)
매니저가 관리 리포트를 제출하는 로직입니다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Injectable()
export class VisitService {
constructor(private prisma: PrismaService) {}
// 방문 로그 생성 (매니저용)
async createVisitLog(data: {
storeId: number;
managerId: number;
summary: string;
photoBefore: string;
photoAfter: string;
}) {
return this.prisma.visitLog.create({
data: {
storeId: data.storeId,
managerId: data.managerId,
summary: data.summary,
photoUrlBefore: data.photoBefore,
photoUrlAfter: data.photoAfter,
},
});
}
// 특정 매장의 관리 이력 조회 (점주용)
async getStoreHistory(storeId: number) {
return this.prisma.visitLog.findMany({
where: { storeId },
include: { manager: { select: { name: true } } },
orderBy: { visitTime: 'desc' },
});
}
}
2. Front-end: Nuxt.js (Vue 3)
Nuxt.js를 사용하여 점주가 자신의 매장 상태를 확인하는 대시보드 화면을 구성합니다.
Dashboard Page (pages/dashboard/index.vue)
<template>
<div class="p-6">
<h1 class="text-2xl font-bold mb-6">우리 매장 관리 현황</h1>
<div v-if="pending" class="text-center">로딩 중...</div>
<div v-else class="grid gap-4">
<div v-for="log in logs" :key="log.id" class="border rounded-lg p-4 shadow-sm bg-white">
<div class="flex justify-between items-center mb-4">
<span class="font-semibold text-gray-700">{{ formatDate(log.visitTime) }}</span>
<span class="px-2 py-1 bg-green-100 text-green-700 rounded text-sm">관리 완료</span>
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<p class="text-xs text-gray-500 mb-1">관리 전</p>
<img :src="log.photoUrlBefore" class="w-full h-32 object-cover rounded" />
</div>
<div>
<p class="text-xs text-gray-500 mb-1">관리 후</p>
<img :src="log.photoUrlAfter" class="w-full h-32 object-cover rounded" />
</div>
</div>
<p class="text-gray-600 italic">"{{ log.summary }}"</p>
<p class="mt-2 text-sm text-gray-400 text-right">담당 매니저: {{ log.manager.name }}</p>
</div>
</div>
</div>
</template>
<script setup>
// 실제 환경에서는 useFetch를 통해 NestJS API를 호출합니다.
const { data: logs, pending } = await useFetch('/api/stores/1/history');
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString('ko-KR');
};
</script>
3. 운영 전략 실현을 위한 팁
- Subscription Guard: NestJS에서 Custom Decorator를 만들어, 구독료(subscriptions 테이블)가 미납된 점주는 대시보드 접근을 제한하는 Middleware를 쉽게 구현할 수 있습니다.
- Shared UI Library: Nuxt에서 매니저용 앱(모바일 최적화)과 점주용 웹을 하나의 프로젝트에서 layers 기능을 사용하여 코드 베이스를 공유할 수 있습니다.
- Real-time Notification: NestJS의 EventEmitter나 Websocket을 활용해 매니저가 체크리스트를 완료하는 즉시 점주 앱으로 Push 알림을 보낼 수 있습니다.
실무 개발을 위해 Prisma Seed(초기 데이터 설정) 코드와 매니저가 현장에서 사용할 모바일 체크리스트 UI의 핵심 로직을 바로 이어서 구현해 드릴게요.
1. Prisma Seed 설정 (prisma/seed.ts)
개발 환경에서 클러스터, 매뉴얼, 테스트용 매장을 즉시 생성하여 로직을 테스트할 수 있게 합니다.
import { PrismaClient, Role } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// 1. 클러스터 생성
const clusterA = await prisma.cluster.create({
data: { name: '송도 신도시 A구역' },
});
// 2. 점주 및 매니저 생성
const manager = await prisma.user.create({
data: {
email: 'manager1@platform.com',
name: '김매니저',
role: Role.MANAGER,
clusterId: clusterA.id,
},
});
const owner = await prisma.user.create({
data: {
email: 'owner1@shop.com',
name: '이점주',
role: Role.OWNER,
},
});
// 3. 매장 및 구독 생성
await prisma.store.create({
data: {
name: '무인 아이스크림 송도점',
ownerId: owner.id,
clusterId: clusterA.id,
subscription: {
create: { monthlyFee: 150000, planName: 'Standard' },
},
},
});
console.log('Seed data created successfully');
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
2. Nuxt.js: 매니저용 현장 체크리스트 UI
매니저가 클러스터 내 매장을 방문하여 업무를 수행하는 인터페이스입니다.
pages/manager/check/[storeId].vue
<template>
<div class="max-w-md mx-auto p-4 bg-gray-50 min-h-screen">
<header class="mb-6">
<h2 class="text-xl font-bold">현장 점검: {{ storeName }}</h2>
<p class="text-sm text-gray-500">클러스터 기반 표준 매뉴얼 적용됨</p>
</header>
<section class="space-y-4 mb-8">
<div v-for="task in tasks" :key="task.id"
class="flex items-center p-4 bg-white rounded-xl shadow-sm border border-gray-100">
<input type="checkbox" v-model="task.completed"
class="w-6 h-6 text-blue-600 rounded-full border-gray-300 focus:ring-blue-500" />
<label class="ml-4 font-medium text-gray-700">{{ task.name }}</label>
</div>
</section>
<section class="grid grid-cols-2 gap-4 mb-8">
<div class="text-center">
<button class="w-full aspect-square bg-blue-50 border-2 border-dashed border-blue-200 rounded-xl flex flex-col items-center justify-center text-blue-500">
<span class="text-xs font-bold">관리 전 사진</span>
</button>
</div>
<div class="text-center">
<button class="w-full aspect-square bg-green-50 border-2 border-dashed border-green-200 rounded-xl flex flex-col items-center justify-center text-green-500">
<span class="text-xs font-bold">관리 후 사진</span>
</button>
</div>
</section>
<button @click="submitReport"
class="w-full py-4 bg-gray-900 text-white font-bold rounded-xl shadow-lg active:scale-95 transition">
리포트 전송 및 완료
</button>
</div>
</template>
<script setup>
const storeName = ref('무인 아이스크림 송도점');
const tasks = ref([
{ id: 1, name: '바닥 청소 및 쓰레기 비우기', completed: false },
{ id: 2, name: '키오스크 용지 확인 및 소독', completed: false },
{ id: 3, name: '부족 재고 리포트 작성', completed: false }
]);
const submitReport = async () => {
// NestJS API 호출 로직 (useFetch 또는 $fetch)
alert('리포트가 점주님께 전송되었습니다!');
navigateTo('/manager/dashboard');
};
</script>
3. 이 아키텍처의 핵심 가치
- 표준화된 운영: task_manuals 테이블의 데이터를 Nuxt 페이지에서 불러옴으로써 모든 매니저가 동일한 퀄리티로 관리하게 합니다.
- 클러스터링 최적화: 매니저용 대시보드에서는 GPS 기반으로 현재 가장 가까운 클러스터 내 매장을 우선 노출할 수 있습니다.
- 데이터 투명성: 매니저가 등록한 사진과 체크리스트 결과는 Prisma를 통해 즉시 DB에 쌓이며, 점주 앱에서 실시간 조회 가능합니다.
매니저가 올린 현장 사진을 안전하게 보관할 S3 업로드 로직과, 관리가 완료되었을 때 점주에게 즉시 알리는 카카오 알림톡(또는 알림) 연동 기능을 구현해 보겠습니다.
1. AWS S3 이미지 업로드 (NestJS + Prisma)
매니저가 찍은 사진을 클라우드에 저장하고 해당 URL을 DB에 기록하는 과정입니다.
NestJS: S3 Service 구현
aws-sdk 또는 @aws-sdk/client-s3를 사용하여 구현합니다.
// s3.service.ts
import { Injectable } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { v4 as uuid } from 'uuid';
@Injectable()
export class S3Service {
private s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
},
});
async uploadImage(file: Express.Multer.File): Promise<string> {
const fileName = `${uuid()}-${file.originalname}`;
const command = new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: fileName,
Body: file.buffer,
ContentType: file.mimetype,
});
await this.s3.send(command);
return `https://${process.env.AWS_S3_BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${fileName}`;
}
}
2. 알림 시스템 연동 (카카오 알림톡/Push)
관리 완료 시 점주에게 알림을 보냅니다. 실무에서는 비즈톡(Biztalk) 같은 대행사 API를 주로 사용합니다.
NestJS: Notification Service
// notification.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class NotificationService {
async sendKakaoAlimtalk(ownerPhone: string, storeName: string) {
// 실제로는 알림톡 대행사 API(예: Solapi, Aligo)에 HTTP 요청을 보냅니다.
const message = `[관리완료] ${storeName}의 오늘 점검이 완료되었습니다. 앱에서 리포트를 확인하세요!`;
console.log(`Sending Kakao Message to ${ownerPhone}: ${message}`);
// axios.post('대행사_URL', { to: ownerPhone, msg: message, ... });
}
}
3. 통합 비즈니스 로직 (Visit Controller)
S3 업로드와 알림 발송을 하나로 묶어 처리합니다.
// visit.controller.ts
@Post('complete')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'before', maxCount: 1 },
{ name: 'after', maxCount: 1 },
]))
async completeVisit(
@Body() body: any,
@UploadedFiles() files: { before?: Express.Multer.File[], after?: Express.Multer.File[] }
) {
// 1. S3 사진 업로드
const beforeUrl = await this.s3Service.uploadImage(files.before[0]);
const afterUrl = await this.s3Service.uploadImage(files.after[0]);
// 2. DB 저장 (Prisma)
const log = await this.prisma.visitLog.create({
data: {
storeId: Number(body.storeId),
managerId: Number(body.managerId),
photoUrlBefore: beforeUrl,
photoUrlAfter: afterUrl,
summary: body.summary,
},
include: { store: { include: { owner: true } } }
});
// 3. 점주에게 알림 발송
await this.notificationService.sendKakaoAlimtalk(
log.store.owner.phone,
log.store.name
);
return { success: true, logId: log.id };
}
4. Nuxt.js: 파일 업로드 및 전송 UI 연동
프론트엔드에서는 FormData를 사용하여 사진과 데이터를 전송합니다.
<script setup>
const onFileChange = (event, type) => {
const file = event.target.files[0];
if (type === 'before') beforeFile.value = file;
else afterFile.value = file;
};
const submitReport = async () => {
const formData = new FormData();
formData.append('before', beforeFile.value);
formData.append('after', afterFile.value);
formData.append('storeId', route.params.storeId);
formData.append('summary', '오늘 청소 및 재고 보충 완료했습니다.');
try {
const response = await $fetch('/api/visit/complete', {
method: 'POST',
body: formData
});
if(response.success) alert('성공적으로 전송되었습니다.');
} catch (err) {
alert('전송 중 오류가 발생했습니다.');
}
};
</script>
5. 인프라 구성 요약
| 기능 | 기술 스택 | 설명 |
| 이미지 저장 | AWS S3 | 관리 전/후 고화질 사진을 저렴하고 안정적으로 저장 |
| 데이터 관리 | Prisma + MySQL | 사진 URL과 점검 로그를 매칭하여 영구 기록 |
| 실시간 알림 | Kakao Alimtalk API | 점주가 앱을 켜지 않아도 관리 완료 여부를 즉시 인지 |
| 프론트엔드 | Nuxt.js | 모바일 브라우저에서도 네이티브 앱처럼 동작하는 PWA 환경 제공 |
이제 기본적인 **무인점포 관리 ERP의 핵심 사이클(방문 -> 점검 -> 업로드 -> 알림)**이 완성되었습니다.
사업의 완성을 위해 **'돈이 흐르는 구조'**인 **결제 연동(토스페이먼츠)**과 운영 효율을 위한 정산 데이터 관리(Excel) 기능을 구현해 보겠습니다.
1. 결제 연동 (NestJS + Toss Payments)
점주가 매월 구독료를 결제할 수 있도록 결제 승인 로직을 구현합니다.
NestJS: Payment Service
// payment.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class PaymentService {
private readonly secretKey = process.env.TOSS_SECRET_KEY;
async confirmPayment(paymentKey: string, orderId: string, amount: number) {
const encodedKey = Buffer.from(`${this.secretKey}:`).toString('base64');
const response = await axios.post(
'https://api.tosspayments.com/v1/payments/confirm',
{ paymentKey, orderId, amount },
{ headers: { Authorization: `Basic ${encodedKey}` } }
);
return response.data; // 결제 승인 결과 반환
}
}
2. 매니저 정산 데이터 엑셀 추출 (NestJS + ExcelJS)
플랫폼 관리자가 클러스터별/매니저별 업무 실적을 엑셀로 내려받아 인건비를 정산할 수 있게 합니다.
NestJS: Settlement Service
// settlement.service.ts
import { Injectable } from '@nestjs/common';
import * as ExcelJS from 'exceljs';
import { PrismaService } from './prisma.service';
@Injectable()
export class SettlementService {
constructor(private prisma: PrismaService) {}
async generateMonthlyReport(managerId: number, month: number) {
const logs = await this.prisma.visitLog.findMany({
where: {
managerId,
visitTime: {
gte: new Date(2026, month - 1, 1),
lt: new Date(2026, month, 1),
},
},
include: { store: true },
});
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('정산내역');
worksheet.columns = [
{ header: '방문일시', key: 'date', width: 20 },
{ header: '매장명', key: 'store', width: 25 },
{ header: '관리항목 요약', key: 'summary', width: 40 },
];
logs.forEach(log => {
worksheet.addRow({
date: log.visitTime.toLocaleString(),
store: log.store.name,
summary: log.summary,
});
});
return workbook;
}
}
3. Nuxt.js: 결제 페이지 UI (Toss SDK)
프론트엔드에서 점주가 결제창을 띄우는 코드입니다.
<script setup>
import { loadTossPayments } from '@tosspayments/payment-sdk';
const clientKey = 'test_ck_...'; // 토스에서 발급받은 키
const handlePayment = async () => {
const tossPayments = await loadTossPayments(clientKey);
await tossPayments.requestPayment('카드', {
amount: 150000,
orderId: `sub_${Date.now()}`,
orderName: '무인점포 공유관리 구독 (1개월)',
successUrl: `${window.location.origin}/payment/success`,
failUrl: `${window.location.origin}/payment/fail`,
});
};
</script>
<template>
<div class="p-6 bg-white rounded-2xl shadow-lg border">
<h3 class="text-xl font-bold mb-2">구독 플랜: Standard</h3>
<p class="text-3xl font-black text-blue-600 mb-6">150,000원 <span class="text-sm text-gray-400 font-normal">/월</span></p>
<ul class="space-y-3 mb-8 text-gray-600">
<li>✅ 주 3회 전문 매니저 방문</li>
<li>✅ 실시간 사진 리포트 제공</li>
<li>✅ 긴급 출동 서비스 포함</li>
</ul>
<button @click="handlePayment" class="w-full py-4 bg-blue-600 text-white rounded-xl font-bold">
지금 결제하고 관리 시작하기
</button>
</div>
</template>
4. 플랫폼 최종 아키텍처 완성도
이제 기술적인 핵심 모듈이 모두 갖춰졌습니다:
- 인프라: AWS S3(이미지), MySQL(데이터), Prisma(ORM)
- 비즈니스 로직: NestJS 기반의 방문 기록, 알림톡 발송, 결제 승인
- 사용자 인터페이스: Nuxt.js 기반의 매니저용 모바일 체크리스트 및 점주용 대시보드/결제 페이지
매니저가 클러스터 내의 매장 위치를 한눈에 파악하고, 최적의 동선으로 이동할 수 있도록 **구글 지도(Google Maps API)**를 연동한 기능을 구현해 보겠습니다.
1. 구글 지도 연동 설정 (Nuxt.js)
Nuxt.js에서 구글 지도를 사용하기 위해 vue3-google-map 라이브러리를 사용하거나 직접 스크립트를 로드할 수 있습니다. 여기서는 직접 제어하기 위해 스크립트 방식을 제안합니다.
nuxt.config.ts 설정
export default defineNuxtConfig({
app: {
head: {
script: [
{
src: `https://maps.googleapis.com/maps/api/js?key=${process.env.GOOGLE_MAPS_API_KEY}&libraries=places,geometry`,
async: true,
defer: true
}
]
}
}
})
2. 매니저용: 클러스터 매장 지도 표시 (pages/manager/map.vue)
매니저가 관리하는 클러스터 내 모든 매장을 마커로 표시하고, 각 마커 클릭 시 매장 정보와 점검 상태를 보여줍니다.
<template>
<div class="h-screen flex flex-col">
<header class="p-4 bg-white shadow-sm font-bold text-lg">
📍 내 관리 구역: 송도 신도시 A
</header>
<div id="map" class="flex-1"></div>
<div v-if="selectedStore" class="fixed bottom-4 left-4 right-4 bg-white p-4 rounded-2xl shadow-2xl border">
<h4 class="font-bold">{{ selectedStore.name }}</h4>
<p class="text-sm text-gray-500 mb-3">{{ selectedStore.address }}</p>
<button @click="navigateTo(`/manager/check/${selectedStore.id}`)"
class="w-full py-3 bg-blue-600 text-white rounded-lg font-bold">
점검 시작하기
</button>
</div>
</div>
</template>
<script setup>
const selectedStore = ref(null);
let map = null;
onMounted(() => {
// 1. 지도 초기화 (클러스터 중심 좌표 기준)
map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.3947, lng: 126.6393 }, // 송도 기준
zoom: 14,
disableDefaultUI: true,
});
// 2. 매장 마커 표시 (실제로는 백엔드 API에서 데이터 로드)
const stores = [
{ id: 1, name: '무인 아이스크림 송도점', lat: 37.3947, lng: 126.6450, address: '송도동 123-4' },
{ id: 2, name: '24시 무인 편의점 B', lat: 37.3980, lng: 126.6350, address: '송도동 56-7' }
];
stores.forEach(store => {
const marker = new google.maps.Marker({
position: { lat: store.lat, lng: store.lng },
map: map,
title: store.name,
icon: {
url: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png'
}
});
marker.addListener('click', () => {
selectedStore.value = store;
map.panTo(marker.getPosition());
});
});
});
</script>
3. 경로 최적화 (NestJS Backend)
여러 매장을 방문해야 할 때, Google Routes API의 optimizeWaypointOrder 기능을 사용하여 가장 효율적인 방문 순서를 계산할 수 있습니다.
NestJS: Route Optimization Service
// route.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class RouteService {
async getOptimizedRoute(origin: string, storeLocations: string[]) {
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
// waypoint 앞에 'optimize:true|'를 붙여 경로 최적화 수행
const waypoints = `optimize:true|${storeLocations.join('|')}`;
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin}&destination=${origin}&waypoints=${waypoints}&key=${apiKey}`;
const response = await axios.get(url);
return response.data; // 최적화된 경로 순서와 정보 반환
}
}
4. 사용자 가치 (UX)
- 매니저 동선 최적화: 지도상에서 현재 위치와 매장 위치를 한눈에 비교하여 동선을 최소화할 수 있습니다.
- 직관적인 상태 확인: 점검이 완료된 매장은 마커 색상을 변경(예: 파란색 -> 회색)하여 관리 현황을 시각화합니다.
- 빠른 현장 대응: 지도 내 '길 찾기' 버튼을 추가하여 구글 맵이나 카카오맵 앱으로 즉시 연동되도록 구현할 수 있습니다.
이제 지도 기능까지 포함하여 진정한 현장 관리 ERP의 모습을 갖추게 되었습니다.
이 영상은 구글 지도 API를 웹 프로젝트에 연동하고 마커를 생성하는 기본적인 방법을 설명하고 있어 실제 구현에 참고하기 좋습니다.
'IT 개발,관리,연동,자동화' 카테고리의 다른 글
| 🎨 SQL 쿼리, 예술처럼 정렬하기: '하노이 탑 스타일' 완벽 가이드 (0) | 2026.01.08 |
|---|---|
| 구글의 AI 코딩 비서 '안티그래비티', 직접 써보니 충격적인 5가지 사실 (0) | 2026.01.07 |
| 정유사 계열 주유소의 POS(Point of Sale) 시스템설계 (0) | 2026.01.05 |
| 코딩 한 줄 없이 나만의 AI 비서를? 카카오가 공개한 미래, 5가지 핵심 요약 (0) | 2026.01.04 |
| '무인점포는 무인이 아니다'...월 300 수익의 꿈이 '리모컨 감옥'으로 변하는 이유 (1) | 2026.01.04 |