diff --git a/public_html/lib/mapper/erpDailyUcoTargetMapper.php b/public_html/lib/mapper/erpDailyUcoTargetMapper.php
new file mode 100644
index 0000000..dc4574d
--- /dev/null
+++ b/public_html/lib/mapper/erpDailyUcoTargetMapper.php
@@ -0,0 +1,42 @@
+ self::date($after['u_date'] ?? null),
+ "cduExternalDriverId" => $after['u_druid'] ?? null, // employee_external_map 참조
+ "cduTargetQty" => isset($after['u_target_qty']) ? (float)$after['u_target_qty'] : null,
+ "cduTargetVisitCnt" => isset($after['u_target_visit_cnt']) ? (int)$after['u_target_visit_cnt'] : null,
+ "cduTargetSyncedAt" => self::datetime($after['u_synced_at'] ?? null),
+ "cduExternalCreatedBy" => $after['u_createruid'] ?? null, // employee_external_map 참조
+ "cduExternalUpdatedBy" => $after['u_createruid'] ?? null,
+ "cduLoginUser" => $after['u_createruid'] ?? null,
+ ];
+ }
+
+ private static function date($yyyymmdd)
+ {
+ if (empty($yyyymmdd) || strlen($yyyymmdd) != 8) {
+ return null;
+ }
+ return substr($yyyymmdd,0,4)."-".
+ substr($yyyymmdd,4,2)."-".
+ substr($yyyymmdd,6,2);
+ }
+
+ private static function datetime($yyyymmddhhmiss)
+ {
+ if (empty($yyyymmddhhmiss) || strlen($yyyymmddhhmiss) != 14) {
+ return null;
+ }
+ return substr($yyyymmddhhmiss,0,4)."-".
+ substr($yyyymmddhhmiss,4,2)."-".
+ substr($yyyymmddhhmiss,6,2)."T".
+ substr($yyyymmddhhmiss,8,2).":".
+ substr($yyyymmddhhmiss,10,2).":".
+ substr($yyyymmddhhmiss,12,2);
+ }
+}
\ No newline at end of file
diff --git a/public_html/lib/runDailyUcoTargetOutbox.php b/public_html/lib/runDailyUcoTargetOutbox.php
new file mode 100644
index 0000000..a72343a
--- /dev/null
+++ b/public_html/lib/runDailyUcoTargetOutbox.php
@@ -0,0 +1,112 @@
+> /home/.../lib/cronlog/`date +\%Y\%m\%d\%H\%M\%S`-cron-uco.log 2>&1
+//
+// 인자: [1] 대상일자 YYYYMMDD (선택, 미지정 시 오늘)
+///////////////////////////////////////////////////
+
+date_default_timezone_set('America/Toronto');
+
+$time_start = microtime(true);
+
+$mode = (PHP_SAPI === 'cli') ? "SHELL" : "WEB";
+
+if ($mode == "SHELL") {
+ $GETDIR = dirname(__DIR__); // .../public_html (lib 의 상위)
+ $ENT = "\n";
+ putenv("DOCUMENT_ROOT=" . $GETDIR);
+} else {
+ $GETDIR = getenv("DOCUMENT_ROOT");
+ $ENT = "
";
+}
+
+echo "####[START UCO TARGET OUTBOX]####{$ENT}";
+
+include_once $GETDIR . "/include/function_class.php";
+include_once $GETDIR . "/include/arrayinfo.php";
+include_once $GETDIR . "/include/mysql_class_v7.php";
+
+$func = new Func();
+$jdb = new JDB();
+
+///////////////////////////////////////////////////
+// 대상 일자
+///////////////////////////////////////////////////
+$getDate = (isset($argv[1]) && preg_match('/^\d{8}$/', $argv[1]))
+ ? $argv[1]
+ : date('Ymd');
+
+$syncedAt = date('YmdHis');
+
+$logName = $GETDIR . "/lib/access.log";
+$func->PwriteLog($logName, "ACTION[Cron - Start - UCO Target Outbox for {$getDate}]");
+
+echo "[TARGET DATE][$getDate]{$ENT}";
+
+///////////////////////////////////////////////////
+// tbl_daily 집계 (드라이버별 UCO 목표)
+///////////////////////////////////////////////////
+$qry = "
+SELECT
+ d_druid AS driveruid,
+ COALESCE(SUM(d_estquantity), 0) AS target_qty,
+ COUNT(*) AS target_visit_cnt
+FROM tbl_daily
+WHERE d_orderdate = '{$getDate}'
+ AND d_jobtype = 'UCO'
+ AND d_status <> 'D'
+ AND d_druid IS NOT NULL
+ AND d_druid <> ''
+GROUP BY d_druid
+";
+
+echo "[DAILY AGG QUERY][$qry]{$ENT}";
+
+$result = $jdb->nQuery($qry, "uco daily aggregate query error");
+
+$cnt = 0;
+
+while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
+
+ $payload = array(
+ "action" => "TARGET",
+ "after" => array(
+ "u_date" => $getDate,
+ "u_druid" => $row['driveruid'],
+ "u_target_qty" => (float)$row['target_qty'],
+ "u_target_visit_cnt" => (int)$row['target_visit_cnt'],
+ "u_synced_at" => $syncedAt,
+ "u_createruid" => "SYSTEM",
+ ),
+ );
+
+ $payloadJson = addslashes(
+ json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ );
+
+ $qry_ins = "
+ INSERT INTO tbl_erp_outbox
+ (eo_entity_type, eo_action_type, eo_key1, eo_key2, eo_payload, eo_status, eo_retry_count)
+ VALUES
+ ('UCO', 'CREATED', '{$row['driveruid']}', '{$getDate}', '{$payloadJson}', 'PENDING', 0)
+ ";
+
+ $jdb->nQuery($qry_ins, "uco outbox insert error");
+
+ echo "[QUEUED][driver:{$row['driveruid']}][qty:{$row['target_qty']}][visit:{$row['target_visit_cnt']}]{$ENT}";
+
+ $cnt++;
+}
+
+$func->PwriteLog($logName, "SUCCESS[UCO Target Outbox] Date={$getDate}, Queued={$cnt}\n");
+
+$time_end = microtime(true);
+$timeStr = "Running Time: " . round(($time_end - $time_start), 2) . " sec";
+
+echo "####[END UCO TARGET OUTBOX][queued:$cnt][$timeStr]####{$ENT}";
\ No newline at end of file
diff --git a/public_html/lib/runErpOutbox.php b/public_html/lib/runErpOutbox.php
index 499716e..a9980cd 100644
--- a/public_html/lib/runErpOutbox.php
+++ b/public_html/lib/runErpOutbox.php
@@ -33,6 +33,7 @@ include_once $GETDIR . "/include/arrayinfo.php";
include_once $GETDIR . "/include/mysql_class_v7.php";
include_once $GETDIR . "/lib/mapper/erpDailyOrderMapper.php";
include_once $GETDIR . "/lib/mapper/erpCustomerMapper.php";
+include_once $GETDIR . "/lib/mapper/erpDailyUcoTargetMapper.php";
$func = new Func();
$jdb = new JDB();
@@ -160,7 +161,14 @@ while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
break;
- // UNKNOWN
+ // UCO (DAILY TARGET)
+ case 'UCO':
+ $body = erpDailyUcoTargetMapper::map($payload);
+ $method = 'POST';
+ $url = $serverUrl . "/crm-rest-api/customer-daily-uco/target";
+ break;
+
+ // UNKNOWN
default:
throw new Exception(
"Unsupported entity: ".$entity