Smart Life US

← 목록 · 2026-08-22 · 엑셀·업무 자동화

구글시트 야드 슬롯 자동 배정과 이동 기록 — 체크인 5편

구글시트 야드 슬롯 자동 배정과 이동 기록 — 체크인 5편

도입: 구글시트 야드 슬롯 자동 배정 방법이 필요한 이유

구글시트 야드 슬롯 자동 배정 방법을 찾는 분들은 대부분 “트럭이 몰리는 시간대에 어느 야드 슬롯이 비어 있는지, 어디까지 찼는지”를 실시간에 가깝게 관리하고 싶어 합니다. 이 글은 체크인 5편으로, 앞 편인 구글시트 체크인 예약 상태 자동 변경: Apps Script로 구현하기 — 체크인 4편에서 만든 체크인 저장·상태 전환 코드에 이어집니다. 이번에는 LockService를 활용해 구글시트 트레일러 야드 슬롯 배정을 자동화하고, 같은 흐름 안에서 이동 기록까지 남기는 방법을 정리합니다.

야드 슬롯 자동 배정 흐름

실제 현장에서는 야드 슬롯이 금방 엉킵니다. 교대가 바뀌면 화이트보드에 적어 둔 내용과 실제 주차 상황이 달라지기도 하고, 누구 지시에 따라 어느 슬롯으로 옮겼는지 기록이 남지 않아 나중에 체류 시간을 계산할 수 없는 경우가 자주 발생합니다. 이 글에서는 구글시트와 Apps Script만으로 야드 슬롯을 자동으로 하나 배정하고, LockService로 동시에 저장하는 동안 슬롯 중복을 줄이며, TRAILER_MOVES 시트에 이동 로그를 쌓는 구조를 실제 운영 경험을 바탕으로 설명합니다. 다만 코드 구조상 슬롯 중복은 완화 수준이며, 컨테이너 중복 배정·빈 번호 점유는 별도의 규칙으로 관리해야 한다는 점도 함께 짚습니다.

체크인 이후 흐름: 왜 야드 슬롯 자동 배정이 필요한가

이 시리즈의 전체 흐름은 “예약 생성 → 도어 배정 → 체크인 저장 → 상태 자동 전환”입니다. 도어가 계획대로 비어 있으면 바로 도어로 붙이면 되지만, 실제 운영에서는 도어 대기열이 길어지는 경우가 잦습니다. 이런 상황에서는 트레일러를 일단 야드에 세웠다가, 나중에 도어로 당기는 방식으로 운영합니다. 구글시트 기반 체크인 시스템을 쓰면서도 이 야드 구간만은 화이트보드와 무전기에 의존하는 경우가 많은데, 그 지점에서 데이터 끊김이 생깁니다.

제가 경험한 물류센터에서도 처음에는 도어 배정까지만 시스템으로 관리하고, 야드 슬롯은 “Y01~Y40” 정도를 칸으로 나눠 벽에 붙여 두고 자석으로 표시했습니다. 피크 시즌에는 어느 슬롯이 실제로 비어 있는지, 어떤 트레일러가 얼마나 오래 서 있었는지 파악하기 어려웠고, 회의 때마다 “야드 체류가 길다”는 말만 반복될 뿐 근거 데이터를 제시하기 어려웠습니다. 이 문제를 줄이기 위해 체크인 단계에서 바로 “가능하면 도어, 아니면 야드”로 자동 배정하는 구조를 붙였고, 그 결과로 SLOT별 회전율과 평균 체류 시간, 도어 앞 대기 패턴까지 분석할 수 있었습니다.

이 글에서 구현하는 구조는 다음과 같습니다.

첫째, YARD 시트에 슬롯 목록과 현재 컨테이너 번호, 활성 여부를 유지합니다.

둘째, CHK_pickFreeYardSlot_() 함수로 “앞에서부터 비어 있는 첫 슬롯”을 LockService 안에서 골라 컨테이너 번호를 적어 넣습니다.

셋째, TRAILER_MOVES 시트에 “GATE → Yxx” 형태의 이동 로그를 남겨, 나중에 슬롯별·트레일러별 동선을 되짚을 수 있게 합니다.

이 흐름을 앞 편의 체크인 저장 로직과 이어 붙이면, 기존 WMS와 별개로 현장 운영에 필요한 기본적인 야드 가시성을 추가로 확보할 수 있습니다.

YARD 시트 구조와 야드 슬롯 선택 규칙 설계

구글시트로 야드 슬롯 자동 배정을 구현하려면, 먼저 YARD 시트의 구조와 “빈 슬롯”의 정의를 명확하게 잡아야 합니다. 이 글에서는 다음과 같은 단순한 규칙을 전제로 합니다.

  • A열: 슬롯 ID (Y01, Y02 등)
  • B열: 현재 컨테이너/트레일러 번호
  • C열: 활성 여부 (Y/N)
  • 2행부터 한 행당 하나의 슬롯

컨테이너 열이 완전히 비어 있고, 활성 플래그가 비어 있거나 Y인 경우에만 “배정 가능한 슬롯”으로 간주합니다.

실무에서는 슬롯을 구역별로 나누거나, 냉장/상온처럼 용도별로 나누는 경우도 많습니다. 그럴 때는 열을 더 만들어 구역코드·온도대 등을 넣고, 선택 함수에서 조건을 조금씩 추가하면 됩니다. 다만 처음부터 모든 경우를 코드에 넣으려고 하면 테스트가 복잡해지므로, 우선 “단일 구역, 선입 선출” 같은 가장 단순한 규칙으로 시작해 보는 것을 권장합니다. 제가 운영했던 현장에서도 처음에는 이런 단순한 규칙으로 도입한 뒤, 문제가 눈에 보이는 부분부터 단계적으로 조건을 늘려 갔습니다.

또 하나 중요한 점은 YARD 시트의 “실제 사용 습관”입니다. 예를 들어 C열의 활성 여부는 운영자가 수기로 관리하는 경우가 많습니다. 이 글의 코드는 N이면 확실한 비활성, 빈 값이나 Y인 경우는 활성으로 보고 배정 대상에 포함합니다. 만약 다른 값을 혼용하기 시작하면 코드와 현실이 어긋나는 문제가 생길 수 있으므로, 활성 플래그는 Y/N 둘 중 하나로만 쓰는 규칙을 팀과 미리 합의해 두는 것이 좋습니다. 이런 기본 합의가 있어야 LockService로 슬롯 중복을 줄이는 효과를 제대로 얻을 수 있습니다.

LockService를 이용한 야드 슬롯 자동 배정 함수

이제 실질적인 구글시트 야드 슬롯 자동 배정 함수, 즉 CHK_pickFreeYardSlot_() 구현을 살펴보겠습니다. 이 함수의 역할은 네 가지입니다.

  1. YARD 시트에서 활성 슬롯 목록을 읽어 컨테이너가 비어 있는 후보 인덱스를 모은다.
  2. Apps Script의 LockService.getScriptLock() 으로 잠금을 잡아, 동시에 두 사용자가 같은 슬롯을 고르는 상황을 줄인다.
  3. 잠금 안에서 한 번 더 시트를 읽어 실제로 비어 있는지 다시 확인한 뒤 컨테이너 번호를 적어 넣는다.
  4. 성공하면 선택된 슬롯 ID를, 적절한 슬롯이 없으면 null 을 반환한다.

실무적으로는 “동시에 두 대가 체크인할 수 있다”는 가정을 두고 코드를 짜는 것이 안전합니다. LockService가 있다고 해서 모든 중복 배정이 100% 사라지는 것은 아니지만, 최소한 같은 시각에 두 명이 같은 슬롯을 잡는 가능성은 크게 줄어듭니다.

또 하나 유의할 점은, 이 함수가 컨테이너 번호의 유효성이나 중복 점유 여부까지 완벽히 검증하지는 않는다는 것입니다. 예를 들어 같은 컨테이너 번호를 두 번 호출하면 서로 다른 슬롯이 연달아 점유될 수 있습니다. 이런 부분은 운영 규칙이나 상위 로직에서 “한 컨테이너는 동시에 하나의 슬롯/도어에만 있어야 한다”는 제약을 따로 관리해야 합니다.

1단계 — 야드 관련 상수 정의

이 코드는 야드 관련 설정과 시트 이름을 모아 두는 부분입니다.

구글시트 → 확장 프로그램 → Apps Script → 기존 체크인 프로젝트의 Code.gs 맨 위쪽(다른 CHK 상수들과 나란히)에 붙여넣습니다.

붙여넣은 뒤에는 저장만 하면 됩니다.

Apps Script (JavaScript)
const CHK_YARD_CONFIG = {                     // → 체크인 야드 설정
  YARD_SHEET_NAME: 'YARD',                    // → YARD 시트 이름
  YARD_COL_SLOT: 1,                           // → 슬롯 ID 열 번호(A)
  YARD_COL_CONTAINER: 2,                      // → 현재 컨테이너/트레일러 열(B)
  YARD_COL_ACTIVE: 3,                         // → 활성 여부 열(C)
  MOVE_SHEET_NAME: 'TRAILER_MOVES',           // → 이동 로그 시트 이름
  MOVE_COL_TS: 1,                             // → 기록일시 열(A)
  MOVE_COL_DATE: 2,                           // → 기준일자 열(B)
  MOVE_COL_CNTR: 3,                           // → 컨테이너/트레일러 번호 열(C)
  MOVE_COL_FROM: 4,                           // → 출발 위치 열(D)
  MOVE_COL_TO: 5,                             // → 도착 위치 열(E)
  MOVE_COL_NOTE: 6                            // → 비고 열(F)
};                                            // → 여기까지 상수

제대로 됐는지 확인하는 법: Apps Script 편집기에 빨간 오류 표시가 없고, 기존 상수 이름과 중복 선언 오류가 뜨지 않으면 정상입니다.

2단계 — 빈 야드 슬롯 하나 고르는 함수

이 코드는 YARD 시트에서 비어 있는 슬롯을 찾아 LockService 잠금 안에서 컨테이너 번호를 기록합니다.

구글시트 → 확장 프로그램 → Apps Script → Code.gs 맨 아래쪽(다른 CHK 함수들 아래)에 붙여넣습니다.

붙여넣은 뒤 저장 후, CHK_testPickFreeYardSlot_() 함수를 한번 실행해 결과를 확인합니다.

Apps Script (JavaScript)
function CHK_pickFreeYardSlot_(containerNo) {                // → 비어 있는 야드 슬롯 선택
  const ss = SpreadsheetApp.getActiveSpreadsheet();          // → 현재 스프레드시트
  const sheet = ss.getSheetByName(CHK_YARD_CONFIG.YARD_SHEET_NAME);  // → YARD 시트 가져오기
  if (!sheet) {                                              // → 시트가 없으면
    throw new Error('YARD 시트를 찾을 수 없습니다');          // → 예외 발생
  }

  const lastRow = sheet.getLastRow();                        // → 마지막 행 번호
  if (lastRow < 2) {                                         // → 헤더만 있는 경우
    return null;                                             // → 빈 슬롯 없음
  }

  const range = sheet.getRange(2, 1, lastRow - 1, 3);        // → A2:C 범위
  const values = range.getValues();                          // → 데이터 읽기

  const freeIndexes = [];                                    // → 빈 슬롯 인덱스 목록
  for (let i = 0; i < values.length; i++) {                  // → 각 행 반복
    const row = values[i];                                   // → 현재 행
    const slotId = String(row[CHK_YARD_CONFIG.YARD_COL_SLOT - 1]).trim();          // → 슬롯 ID
    const currentCntr = String(row[CHK_YARD_CONFIG.YARD_COL_CONTAINER - 1]).trim(); // → 현재 컨테이너
    const activeFlag = String(row[CHK_YARD_CONFIG.YARD_COL_ACTIVE - 1]).trim();     // → 활성 여부
    if (!slotId) {                                           // → 슬롯 ID 없으면
      continue;                                              // → 건너뛰기
    }
    if (activeFlag && activeFlag.toUpperCase() === 'N') {    // → 비활성(N)이면
      continue;                                              // → 건너뛰기
    }
    if (!currentCntr) {                                      // → 현재 컨테이너가 비어 있으면
      freeIndexes.push(i);                                   // → 빈 슬롯 후보로 추가
    }
  }

  if (freeIndexes.length === 0) {                            // → 후보가 없으면
    return null;                                             // → 빈 슬롯 없음
  }

  const lock = LockService.getScriptLock();                  // → 스크립트 잠금 가져오기
  let slotId = null;                                         // → 선택된 슬롯 ID
  try {
    lock.waitLock(5000);                                     // → 최대 5초 대기
    const freshRange = sheet.getRange(2, 1, lastRow - 1, 3); // → 다시 범위 읽기
    const freshValues = freshRange.getValues();              // → 최신 데이터
    for (let idx of freeIndexes) {                           // → 빈 후보 인덱스 반복
      const row = freshValues[idx];                          // → 현재 행
      const slot = String(row[CHK_YARD_CONFIG.YARD_COL_SLOT - 1]).trim();          // → 슬롯 ID
      const currentCntr = String(row[CHK_YARD_CONFIG.YARD_COL_CONTAINER - 1]).trim(); // → 현재 컨테이너
      const activeFlag = String(row[CHK_YARD_CONFIG.YARD_COL_ACTIVE - 1]).trim();     // → 활성 여부
      if (!slot) {                                           // → 슬롯 ID 없으면
        continue;                                            // → 건너뛰기
      }
      if (activeFlag && activeFlag.toUpperCase() === 'N') {  // → 비활성 슬롯이면
        continue;                                            // → 건너뛰기
      }
      if (currentCntr) {                                     // → 이미 누가 쓰고 있으면
        continue;                                            // → 건너뛰기
      }
      slotId = slot;                                         // → 이 슬롯 선택
      freshValues[idx][CHK_YARD_CONFIG.YARD_COL_CONTAINER - 1] = containerNo || ''; // → 컨테이너 기록
      break;                                                 // → 루프 종료
    }

    if (!slotId) {                                           // → 결국 못 찾았으면
      return null;                                           // → 빈 슬롯 없음
    }

    freshRange.setValues(freshValues);                       // → 변경 내용 쓰기
    return slotId;                                           // → 선택된 슬롯 ID 반환
  } finally {
    lock.releaseLock();                                      // → 잠금 해제
  }
}                                                            // → 함수 끝

function CHK_testPickFreeYardSlot_() {                       // → 테스트 함수
  const testContainer = 'TEST-CNTR-001';                     // → 예시 컨테이너 번호
  const slotId = CHK_pickFreeYardSlot_(testContainer);       // → 슬롯 선택 실행
  Logger.log('선택된 슬롯: ' + slotId);                     // → 결과 로그
  if (!slotId) {                                             // → 선택 실패 시
    Logger.log('빈 슬롯이 없습니다');                        // → 안내 로그
  }
}                                                            // → 테스트 함수 끝

제대로 됐는지 확인하는 법: YARD 시트의 B열(컨테이너)을 모두 비워 두고 CHK_testPickFreeYardSlot_() 를 실행했을 때, 실행 로그에 선택된 슬롯: Y01 같은 메시지가 나오고, 해당 행의 B열에 TEST-CNTR-001 이 입력되면 정상입니다.

이동 이력 시트와 CHK_appendTrailerMove_ 구현

야드 슬롯만 자동 배정하면 “지금 어디에 서 있는지”는 알 수 있지만, “언제 어디로 옮겼는지”는 알 수 없습니다. 구글시트 이동 기록 자동화를 위해 별도의 TRAILER_MOVES 시트를 두고, 이동 한 번마다 한 줄씩 로그를 남기는 구조가 가장 관리하기 쉽습니다. 저는 실무에서 이 구조를 도입한 뒤, 특정 기간 동안 야드에서 도어까지 평균 이동 시간이 얼마인지, 어느 시간대에 야드 회전이 막히는지 등을 눈으로 확인할 수 있었습니다.

TRAILER_MOVES 시트에는 기본적으로 여섯 가지 정보를 기록합니다.

  • 기록 시각(타임스탬프)
  • 기준일자(예약일 또는 운영일자)
  • 컨테이너·트레일러 번호
  • 출발 위치
  • 도착 위치
  • 비고

출발·도착 위치에는 GATE, Y01, DOCK-03 같은 코드를 그대로 쓰고, 비고에는 CHECKIN, TO_DOOR처럼 간단한 태그나 체크인 ID를 남기면 나중에 필터링과 피벗 분석이 수월해집니다. 날짜 형식은 앞 편에서 만든 APPT_ymd_() 를 그대로 사용해 YYYY-MM-DD로 통일합니다.

3단계 — 이동 로그 시트 생성과 한 줄 추가 함수

이 코드는 이동 로그 시트를 자동 생성하고, 이동 한 번당 한 줄씩 추가합니다.

구글시트 → 확장 프로그램 → Apps Script → Code.gs 맨 아래에 붙여넣습니다.

붙여넣은 뒤 저장 후, CHK_testAppendTrailerMove_() 를 실행하여 실제 로그가 쌓이는지 확인합니다.

Apps Script (JavaScript)
function CHK_getOrCreateMoveSheet_() {                           // → 이동 로그 시트 가져오기/생성
  const ss = SpreadsheetApp.getActiveSpreadsheet();              // → 현재 스프레드시트
  let sheet = ss.getSheetByName(CHK_YARD_CONFIG.MOVE_SHEET_NAME); // → 시트 찾기
  if (!sheet) {                                                  // → 없으면
    sheet = ss.insertSheet(CHK_YARD_CONFIG.MOVE_SHEET_NAME);     // → 새로 만들기
    const headers = [                                            // → 헤더 배열
      'TIMESTAMP',                                               // → 기록일시
      'DATE',                                                    // → 기준일자
      'CONTAINER',                                               // → 컨테이너/트레일러
      'FROM',                                                    // → 출발 위치
      'TO',                                                      // → 도착 위치
      'NOTE'                                                     // → 비고
    ];
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]); // → 헤더 쓰기
    sheet.setFrozenRows(1);                                      // → 헤더 고정
  }
  return sheet;                                                  // → 시트 반환
}                                                                 // → 함수 끝

function CHK_appendTrailerMove_(dateYmd, containerNo, fromLoc, toLoc, note) { // → 이동 로그 추가
  if (!dateYmd || !containerNo || !fromLoc || !toLoc) {          // → 필수값 검증
    throw new Error('이동 로그 필수값이 누락되었습니다');         // → 예외 발생
  }

  const tz = 'America/New_York';                                 // → 고정 시간대
  const now = new Date();                                        // → 현재 시각

  // APPT_ymd_() 는 이전 체크인 글에서 사용한 날짜 포맷터라고 가정
  const dateStr = APPT_ymd_(dateYmd);                            // → YYYY-MM-DD 문자열화

  const sheet = CHK_getOrCreateMoveSheet_();                     // → 로그 시트 확보
  const lastRow = sheet.getLastRow();                            // → 마지막 행
  const targetRow = lastRow + 1;                                 // → 기록할 행 번호

  const row = [];                                                // → 행 데이터(0-based 인덱스)
  row[CHK_YARD_CONFIG.MOVE_COL_TS - 1]   = Utilities.formatDate(
    now,
    tz,
    'yyyy-MM-dd HH:mm:ss'
  );                                                              // → 기록일시
  row[CHK_YARD_CONFIG.MOVE_COL_DATE - 1] = String(dateStr).trim();              // → 기준일자
  row[CHK_YARD_CONFIG.MOVE_COL_CNTR - 1] = String(containerNo).trim();          // → 컨테이너
  row[CHK_YARD_CONFIG.MOVE_COL_FROM - 1] = String(fromLoc).trim();              // → 출발 위치
  row[CHK_YARD_CONFIG.MOVE_COL_TO - 1]   = String(toLoc).trim();                // → 도착 위치
  row[CHK_YARD_CONFIG.MOVE_COL_NOTE - 1] = note ? String(note).trim() : '';     // → 비고

  // row 배열의 길이는 MOVE_COL_NOTE(=6) 까지 자동으로 채워지므로 6컬럼을 한 번에 쓴다
  sheet.getRange(targetRow, 1, 1, 6).setValues([row]);           // → 시트에 쓰기

  return {                                                       // → 결과 반환
    ok: true,                                                    // → 성공 여부
    row: targetRow                                               // → 기록된 행 번호
  };
}                                                                 // → 함수 끝

function CHK_testAppendTrailerMove_() {                          // → 테스트 함수
  const today = new Date();                                      // → 오늘 날짜
  const dateStr = APPT_ymd_(today);                              // → YYYY-MM-DD
  const res = CHK_appendTrailerMove_(                            // → 이동 로그 추가
    dateStr,                                                     // → 기준일자
    'TEST-CNTR-002',                                             // → 컨테이너 번호
    'GATE',                                                      // → 출발 위치
    'Y01',                                                       // → 도착 위치
    '테스트 이동'                                                // → 비고
  );
  Logger.log(JSON.stringify(res));                               // → 결과 로그
}                                                                 // → 테스트 함수 끝

제대로 됐는지 확인하는 법: Apps Script에서 CHK_testAppendTrailerMove_() 를 실행했을 때, 스프레드시트에 TRAILER_MOVES 시트가 생성되고 2행에 테스트 데이터가 한 줄 생기면 정상입니다. 로그 시트가 이미 있다면 기존 데이터 아래에 새 행이 추가됩니다.

체크인 저장 로직에 야드 자동 배정과 이동 기록 연결

이제 앞 편에서 만든 체크인 저장 함수와 이번 글의 야드 배정·이동 로그를 묶어야 합니다. 기본 아이디어는 단순합니다. 체크인 시점에 해당 예약에 도어가 비어 있으면 도어로 보내고, 그렇지 않거나 “야드 우선” 정책이라면 야드 슬롯을 자동 배정합니다. 이때 CHK_assignYardOnCheckin_() 같은 헬퍼 함수를 만들어 두면, saveCheckInData() 안에서 한 줄로 호출해서 결과만 확인하면 됩니다.

실무에서 적용했을 때는 다음과 같은 규칙으로 운영했습니다.

  1. 예약 자체에 도어 지정이 되어 있으면 야드 배정은 하지 않는다.
  2. 예약에 도어가 없거나, 체크인 처리자가 “야드 우선” 옵션을 선택한 경우에만 이 헬퍼를 호출한다.
  3. 야드 배정이 성공하면 TRAILER_MOVES 시트에 FROM='GATE', TO='Yxx' 로 이동 로그를 남긴다.
  4. 나중에 야드에서 도어로 이동할 때도 동일한 CHK_appendTrailerMove_() 를 재사용해 Yxx → DOCK-03 이동을 적는다.

4단계 — 체크인 시 야드 배정과 이동 로그 호출 헬퍼

이 코드는 체크인 검증을 마친 예약 객체를 받아, 필요할 때 야드 슬롯을 자동으로 배정하고 이동 로그까지 남깁니다.

구글시트 → 확장 프로그램 → Apps Script → Code.gs 맨 아래에 붙여넣습니다.

붙여넣은 뒤 저장 후, 테스트용으로 간단한 appt 객체를 만들어 CHK_assignYardOnCheckin_(appt, {}) 를 실행해 봅니다.

Apps Script (JavaScript)
function CHK_assignYardOnCheckin_(appt, checkinData) {           // → 체크인 시 야드 배정 헬퍼
  const containerNo = String(appt.containerNo || '').trim();     // → 예약 컨테이너 번호
  if (!containerNo) {                                            // → 없으면
    return { ok: false, reason: 'NO_CONTAINER' };                // → 처리 안 함
  }

  const currentDoor = String(appt.door || '').trim();            // → 현재 도어
  if (currentDoor) {                                             // → 이미 도어 배정이 있으면
    return { ok: false, reason: 'HAS_DOOR' };                    // → 야드 배정 안 함
  }

  const dateYmd = appt.date;                                     // → 예약일자(YYYY-MM-DD)

  const yardSlot = CHK_pickFreeYardSlot_(containerNo);           // → 야드 슬롯 선택
  if (!yardSlot) {                                               // → 빈 슬롯 없으면
    return { ok: false, reason: 'NO_YARD_SLOT' };                // → 실패 반환
  }

  const note = 'CHECKIN';                                        // → 비고(간단 태그)
  const moveRes = CHK_appendTrailerMove_(                        // → 이동 로그 기록
    dateYmd,                                                     // → 기준일자
    containerNo,                                                 // → 컨테이너 번호
    'GATE',                                                      // → 출발 위치
    yardSlot,                                                    // → 도착 위치(야드 슬롯)
    note                                                         // → 비고
  );

  return {                                                       // → 종합 결과
    ok: true,                                                    // → 성공 여부
    yardSlot: yardSlot,                                          // → 배정된 슬롯
    moveRow: moveRes.row                                         // → 이동 로그 행
  };
}                                                                 // → 함수 끝

제대로 됐는지 확인하는 법: 스크립트 편집기에서 실행 함수로 CHK_assignYardOnCheckin_ 을 선택한 뒤, 디버그 탭에서 appt 파라미터에 {"date":"2026-08-21","containerNo":"TEST-CNTR-003","door":""} 같은 값을 넣고 실행합니다. 이후 YARD 시트에서 한 슬롯의 컨테이너가 TEST-CNTR-003 로 채워지고, TRAILER_MOVES 시트에 GATE → 해당 슬롯 이동이 추가되면 의도대로 작동한 것입니다.

saveCheckInData() 와 실제로 연결할 때는, 체크인 저장이 성공한 뒤 이 헬퍼를 호출하고 ok 가 true 인 경우에만 프런트 화면에 “야드 슬롯 Yxx로 안내” 메시지를 띄우는 식으로 조합하면 됩니다. 저장이 실패한 경우에는 야드 배정을 건너뛰도록 하는 것이 안전합니다.

실무 팁: 슬롯 비우기와 실패 케이스 점검

마지막으로, 구글시트 야드 슬롯 자동 배정과 이동 로그를 실제 창고에 붙여 보며 느낀 실무 팁을 몇 가지 정리합니다.

  1. 슬롯 비우기 규칙을 반드시 같이 정의하기

슬롯 배정만 만들고 “언제 비울지”를 정의하지 않으면 금방 꽉 찹니다. 현장에서는 보통 야드에서 도어로 옮기는 시점, 또는 트레일러가 출차하는 시점에 그 슬롯의 B열(컨테이너)을 공백으로 돌려놓는 정리 함수를 함께 둡니다. 이때에도 동일한 LockService를 사용해 “읽고 → 검증하고 → 비우기” 과정을 한 번에 처리해야, 두 사람이 동시에 같은 슬롯을 비우려고 할 때 꼬이는 일을 줄일 수 있습니다.

  1. 컨테이너 번호 검증은 체크인 단계에서

컨테이너 번호가 공백이거나 잘못 들어온 경우를 체크인 상위 로직에서 먼저 걸러 두는 것이 좋습니다. 이 글의 CHK_pickFreeYardSlot_() 은 전달받은 값을 그대로 쓰도록 단순하게 설계되어 있어, 빈 문자열을 주면 실제로는 슬롯에 빈 값이 기록됩니다. 이후 호출 때는 컨테이너 열이 비어 있다고 판단해 다시 같은 슬롯을 배정할 수 있습니다. 실무에서는 컨테이너 번호를 필수값으로 두고, 체크인 저장 단계에서 비어 있으면 아예 저장을 막는 쪽이 관리하기 수월합니다.

  1. 로그와 실제 상태의 미세한 불일치는 감안하기

Apps Script 코드가 에러 없이 실행된다고 해서 항상 원하는 결과가 나오는 것은 아닙니다. 예를 들어 CHK_appendTrailerMove_() 호출이 중간에 실패하면 이동 로그는 남지 않는데, 이미 야드 슬롯에는 컨테이너가 기록된 상태일 수 있습니다. 이 정도 수준의 불일치는 구글시트 기반 시스템에서는 어느 정도 감수하고, 주기적으로 로그 시트와 YARD 시트를 비교하는 검증용 리포트를 만들어 보완하는 편이 현실적입니다. 중요한 것은 “어디까지 자동으로 보장하고, 어디부터는 수기 점검에 맡길지” 기준을 명확히 정의하는 것입니다.

  1. 현장별 제약을 반영해 단계적으로 확장하기

이 구조는 하나의 예시일 뿐 현장마다 상황이 다릅니다. 구획이 나뉜 대형 야드나, 보안 규정이 까다로운 환경에서는 다른 제약조건이 붙을 수 있습니다. 그럴수록 먼저 소규모 테스트용 스프레드시트를 만들어 오늘 소개한 함수들을 돌려 보고, 팀에서 합의된 규칙을 코드로 조금씩 옮겨 가는 방식이 안전합니다.

정리

이 글에서는 구글시트 야드 슬롯 자동 배정과 이동 기록을 위해, YARD 시트 구조를 정리하고 CHK_YARD_CONFIG 상수를 정의한 뒤, CHK_pickFreeYardSlot_() 으로 LockService 기반 슬롯 배정을 구현하고 CHK_appendTrailerMove_() / CHK_assignYardOnCheckin_() 으로 이동 로그까지 남가는 흐름을 살펴보았습니다. 이 정도만 갖추어도 “체크인 → 야드 슬롯 자동 배정 → 이동 기록”까지가 하나의 데이터 흐름으로 이어져, 이후 체류 시간 분석과 회전율 관리의 토대가 됩니다.

바로 적용해 보고 싶다면, 우선 실제 야드 슬롯 목록을 YARD 시트 A열에 정리한 뒤 이 글의 코드를 붙여넣고 CHK_testPickFreeYardSlot_() 를 실행해 보시기 바랍니다. 슬롯 하나가 자동으로 선택되고 컨테이너 번호가 기록되는 것까지 확인되면, 그다음에는 체크인 저장 로직과 TRAILER_MOVES 시트를 연결해 자신만의 구글시트 기반 야드 관리 시스템으로 확장해 나갈 수 있습니다.