normalizeDailyPayload($data, $existing ?: null); $data = $this->hydrateDailySnapshot($data, $existing ?: null); // estquantity 자동 계산 (입력때만 안그러면 d_estquantity 값이 0으로 덮어 씌워짐) if (!$existing && empty($data['d_estquantity'])) { $data['d_estquantity'] = $this->calculateEstQuantity($data, $existing); } // note 백업 (tbl_note 로 전달할 값) $noteForUpsert = $data['d_note'] ?? ''; $pickupNoteForUpsert = $data['d_pickupnote'] ?? ''; $level = $data['d_level'] ?? 9; // ========================= // 컬럼 필터링 // ========================= $allowed = $this->getDailyColumns(); $data = array_intersect_key($data, array_flip($allowed)); // ========================= // UPDATE // ========================= if ($isUpdate) { $setParts = []; foreach ($data as $k => $v) { // 업데이트 제외 컬럼 if (in_array($k, [ 'd_orderdate', 'd_accountno', 'd_customeruid', 'd_jobtype', 'd_inputdate', 'd_estquantity', 'd_lastpickupdate', 'd_lastpickupquantity', 'd_lastpaiddate' ], true)) continue; if (!array_key_exists($k, $existing)) continue; $setParts[] = "{$k}='" . mysqli_real_escape_string($connect, (string)$v) . "'"; } $setParts[] = "d_modifydate='{$now14}'"; if (!empty($setParts)) { // update는 무조건 d_uid 기준으로 $uid = (int)$existing['d_uid']; $sql = " UPDATE tbl_daily SET " . implode(",", $setParts) . " WHERE d_uid='{$uid}' LIMIT 1 "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } } } // ========================= // INSERT // ========================= else { $cols = []; $vals = []; foreach ($data as $k => $v) { $cols[] = $k; $vals[] = "'" . mysqli_real_escape_string($connect, (string)$v) . "'"; } if (!in_array('d_modifydate', $cols)) { $cols[] = 'd_modifydate'; $vals[] = "'{$now14}'"; } $sql = " INSERT INTO tbl_daily (" . implode(",", $cols) . ") VALUES (" . implode(",", $vals) . ") "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } } // ========================= // AFTER RELOAD (uid 기준) // ========================= if ($isUpdate) { $uid = (int)$existing['d_uid']; $res = mysqli_query($connect, " SELECT * FROM tbl_daily WHERE d_uid='{$uid}' LIMIT 1 "); } else { $res = mysqli_query($connect, " SELECT * FROM tbl_daily WHERE d_orderdate='{$d_orderdate}' AND d_accountno='{$d_accountno}' AND d_jobtype = '{$d_jobtype}' ORDER BY d_uid DESC LIMIT 1 "); } $afterData = mysqli_fetch_assoc($res); // note if ($noteForUpsert !== '') { $afterData['d_note'] = $noteForUpsert; $afterData['d_level'] = $level; } if ($pickupNoteForUpsert !== '') { $afterData['d_pickupnote'] = $pickupNoteForUpsert; $afterData['d_level'] = $level; } // ========================= // ERP OUTBOX // ========================= $actionType = $isUpdate ? 'UPDATED' : 'CREATED'; $payload = mysqli_real_escape_string( $connect, json_encode([ 'entity' => 'DAILY', 'action' => $actionType, 'key' => [ 'd_orderdate' => $afterData['d_orderdate'], 'd_accountno' => $afterData['d_accountno'], 'd_jobtype' => $afterData['d_jobtype'] ], 'before' => $beforeData, 'after' => $afterData ], JSON_UNESCAPED_UNICODE) ); $qry_outbox = " INSERT INTO tbl_erp_outbox ( eo_entity_type, eo_action_type, eo_key1, eo_key2, eo_key3, eo_payload, eo_status, eo_created_at ) VALUES ( 'DAILY', '{$actionType}', '{$afterData['d_orderdate']}', '{$afterData['d_accountno']}', '{$afterData['d_jobtype']}', '{$payload}', 'PENDING', NOW() ) "; if (!mysqli_query($connect, $qry_outbox)) { throw new Exception(mysqli_error($connect)); } // ========================= // AFTER WORK // ========================= $this->runAfterSave($beforeData, $afterData, $opt); // ========================= // COMMIT // ========================= if (!$inTx) { mysqli_query($connect, "COMMIT"); } return $afterData; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } private function isDuplicate($orderdate, $accountno, $jobtype): bool { $connect = $GLOBALS['conn']; $res = mysqli_query($connect, " SELECT 1 FROM tbl_daily WHERE d_orderdate='{$orderdate}' AND d_accountno='{$accountno}' AND d_jobtype = '{$jobtype}' LIMIT 1 "); return (bool)mysqli_fetch_assoc($res); } private function normalizeDailyPayload(array $data, ?array $existing = null): array { // 날짜 정리 if (isset($data['d_orderdate'])) { $data['d_orderdate'] = preg_replace('/[^0-9]/', '', $data['d_orderdate']); } if (isset($data['d_visitdate'])) { $data['d_visitdate'] = preg_replace('/[^0-9]/', '', $data['d_visitdate']); } // 숫자 필드 기본값 $intFields = [ 'd_quantity', //'d_sludge' 주석처리-값이 비었을 때 0이 되지 않게 ]; foreach ($intFields as $f) { if (isset($data[$f])) { $data[$f] = ($data[$f] === '' || $data[$f] === null) ? 0 : (int)$data[$f]; } } // 문자열 trim $strFields = [ 'd_pickupnote', 'd_note' ]; foreach ($strFields as $f) { if (isset($data[$f])) { $data[$f] = trim((string)$data[$f]); } } return $data; } private function hydrateDailySnapshot(array $data, ?array $existing = null): array { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } // ========================= // DRIVER SNAPSHOT // ========================= $druid = isset($data['d_druid']) ? (int)$data['d_druid'] : (int)($existing['d_druid'] ?? 0); // fallback: legacy member uid -> current driver uid if ($druid <= 0) { $driverUid = isset($data['d_driveruid']) ? (int)$data['d_driveruid'] : (int)($existing['d_driveruid'] ?? 0); if ($driverUid > 0) { $sqMap = qry(" SELECT m.m_currentdriver AS dr_uid, d.dr_initial FROM tbl_member m LEFT JOIN tbl_driver d ON d.dr_uid = m.m_currentdriver WHERE m.m_uid = '{$driverUid}' LIMIT 1 "); $map = fetch_array($sqMap); if ($map && (int)$map['dr_uid'] > 0) { $druid = (int)$map['dr_uid']; $data['d_druid'] = $druid; $data['d_drinitial'] = $map['dr_initial'] ?? ''; } } } // druid가 있으면 driver snapshot 확정 if ($druid > 0) { $sqDriver = qry(" SELECT dr_initial FROM tbl_driver WHERE dr_uid = '{$druid}' LIMIT 1 "); $driver = fetch_array($sqDriver); if ($driver && !empty($driver['dr_initial'])) { $data['d_druid'] = $druid; $data['d_drinitial'] = $driver['dr_initial']; } elseif ($existing) { $data['d_druid'] = $existing['d_druid'] ?? 0; $data['d_drinitial'] = $existing['d_drinitial'] ?? ''; } } // ========================= // CUSTOMER SNAPSHOT (확장) // ========================= $customerUid = isset($data['d_customeruid']) ? (int)$data['d_customeruid'] : (int)($existing['d_customeruid'] ?? 0); if ($customerUid > 0) { $sqCustomer = qry(" SELECT c_regionuid, c_region, c_fullcycle, c_fullcycleforced, c_fullcycleflag, c_lastpickupdate, c_lastpickupquantity, c_lastpaiddate, c_name, c_paymenttype, c_paymentcycle, c_rate, c_form_eu, c_form_corsia, c_maincontainer, c_container, c_location, c_phone, c_address, c_city, c_postal, c_area FROM tbl_customer WHERE c_uid = '{$customerUid}' LIMIT 1 "); $customer = fetch_array($sqCustomer); if ($customer) { $data['d_name'] = $customer['c_name'] ?? ''; $data['d_form_eu'] = $customer['c_form_eu'] ?? ''; $data['d_form_corsia'] = $customer['c_form_corsia'] ?? ''; $data['d_maincontainer'] = $customer['c_maincontainer'] ?? ''; //$data['d_container'] = $customer['c_container'] ?? ''; $data['d_container'] = $this->buildContainerString($customerUid, $connect); $data['d_location'] = $customer['c_location'] ?? ''; $data['d_phone'] = $customer['c_phone'] ?? ''; $data['d_address'] = $customer['c_address'] ?? ''; $data['d_city'] = $customer['c_city'] ?? ''; $data['d_postal'] = $customer['c_postal'] ?? ''; $data['d_area'] = $customer['c_area'] ?? ''; $data['d_regionuid'] = (int)($customer['c_regionuid'] ?? 0); $data['d_region'] = $customer['c_region'] ?? ''; $data['d_fullcycle'] = $customer['c_fullcycle'] ?? 0; $data['d_fullcycleforced'] = $customer['c_fullcycleforced'] ?? 0; $data['d_fullcycleflag'] = $customer['c_fullcycleflag'] ?? 0; $data['d_lastpickupdate'] = $customer['c_lastpickupdate'] ?? ''; $data['d_lastpickupquantity'] = $customer['c_lastpickupquantity'] ?? 0; $data['d_lastpaiddate'] = $customer['c_lastpaiddate'] ?? ''; $data['d_paymenttype'] = $customer['c_paymenttype'] ?? ''; $data['d_cycle'] = $customer['c_paymentcycle'] ?? ''; $data['d_rate'] = $customer['c_rate'] ?? ''; if (!isset($data['d_oil_2y'])) $data['d_oil_2y'] = 0; if (!isset($data['d_oil_1y'])) $data['d_oil_1y'] = 0; if (!isset($data['d_oil_0y'])) $data['d_oil_0y'] = 0; } } return $data; } function buildContainerString($c_uid, $connect) { $sql = " SELECT cc_type, COUNT(*) as cnt FROM tbl_customer_container WHERE cc_customer_uid = '{$c_uid}' AND cc_status = 'A' GROUP BY cc_type ORDER BY cc_type "; $res = mysqli_query($connect, $sql); $parts = []; while ($row = mysqli_fetch_assoc($res)) { $type = $row['cc_type']; $cnt = (int)$row['cnt']; if ($cnt > 1) { $parts[] = "{$type}x{$cnt}"; } else { $parts[] = $type; } } return implode(', ', $parts); } private function getDailyColumns(): array { return [ 'd_uid', // key 'd_orderdate', 'd_accountno', 'd_customeruid', 'd_jobtype', // driver / region 'd_driveruid', 'd_druid', 'd_drinitial', 'd_regionuid', 'd_region', // basic status 'd_ordertype', 'd_visitdate', 'd_visit', 'd_status', // qty / sludge / payment 'd_quantity', 'd_sludge', 'd_paymenttype', 'd_payamount', 'd_payeename', 'd_paystatus', 'd_paynote', // customer snapshot 'd_name', 'd_form_eu', 'd_form_corsia', 'd_maincontainer', 'd_container', 'd_location', 'd_phone', 'd_address', 'd_city', 'd_postal', 'd_area', // oil / cycle history snapshot 'd_oil_2y', 'd_oil_1y', 'd_oil_0y', 'd_fullcycle', 'd_fullcycleforced', 'd_fullcycleflag', 'd_lastpickupdate', 'd_lastpickupquantity', 'd_lastpaiddate', // pricing / estimate 'd_cycle', 'd_rate', 'd_estquantity', // audit 'd_createruid', 'd_updateruid', 'd_createddate', 'd_inputdate', 'd_modifydate', 'd_ruid', // misc 'd_payeesign' ]; } private function calculateEstQuantity(array $data, ?array $existing = null): int { $customerUid = $data['d_customeruid'] ?? ($existing['d_customeruid'] ?? 0); $orderdate = $data['d_orderdate'] ?? ($existing['d_orderdate'] ?? ''); if (!$customerUid || !$orderdate) return 0; $sqCus = qry(" SELECT c_fullquantity, c_fullquantitydaily FROM tbl_customer WHERE c_uid = '{$customerUid}' LIMIT 1 "); $customer = fetch_array($sqCus); if (!$customer) return 0; $today = new DateTime(date("Y-m-d")); $searchDate = new DateTime(substr($orderdate,0,4)."-".substr($orderdate,4,2)."-".substr($orderdate,6,2)); $interval = $today->diff($searchDate); $days = (int)$interval->days; return max( 0, ($customer['c_fullquantity'] - $customer['c_fullquantitydaily']) + ($days * $customer['c_fullquantitydaily']) ); } private function runAfterSave(?array $before, array $after, array $opt = []): void { // order장과 customer 상태 동기화 + 픽업 정보 갱신 $customerFields = $this->buildCustomerPatchFromDaily($before, $after); if (!empty($customerFields) && !empty($after['d_customeruid'])){ $customerFields['c_updatedby'] = $after['d_updateruid'] ?? $after['d_createruid'] ?? null; // patch $this->patchCustomerFromDaily( (int)$after['d_customeruid'], $customerFields ); } // request 처리 완료 $this->updateRequestFromDaily($after); // note 처리 $this->upsertDailyNote($after); // 픽업 발생 시 미래 오더 자동 정리 $this->handlePickupCleanup($before, $after); } private function runAfterDelete(array $before, array $opt = []): void { $connect = $GLOBALS['conn']; if (empty($before['d_customeruid'])) { return; } $customerUid = mysqli_real_escape_string($connect, $before['d_customeruid']); $orderdate = mysqli_real_escape_string($connect, $before['d_orderdate'] ?? ''); // 1) REQUEST 삭제 $this->deleteRequestByDaily($before); // 2) CUSTOMER 재계산 (lastpick, lastpaid 등 포함) $this->recalcCustomerFromDailyHistory($customerUid); // 3) GHOST 재판정 $this->reEvaluateGhost($customerUid); } private function deleteRequestByDaily(array $before): void { $connect = $GLOBALS['conn']; $customerUid = (int)($before['d_customeruid'] ?? 0); $orderdate = preg_replace('/[^0-9]/', '', $before['d_orderdate'] ?? ''); if ($customerUid <= 0 || $orderdate === '') { return; } mysqli_query($connect, " DELETE FROM tbl_request WHERE r_customeruid = '{$customerUid}' AND r_requestdate = '{$orderdate}' "); } private function buildCustomerPatchFromDaily( ?array $before, array $after ): array { $connect = $GLOBALS['conn']; $customerFields = []; if (empty($after['d_customeruid'])) { return $customerFields; } // Customer Current State $customerUid = (int)$after['d_customeruid']; $qr = mysqli_query($connect, " SELECT c_paymenttype, c_is_top, c_fpickup, c_schedule, c_scheduleday FROM tbl_customer WHERE c_uid = '{$customerUid}' LIMIT 1 "); $customer = mysqli_fetch_assoc($qr); if (!$customer) { return $customerFields; } // Common Values $ordertype = $after['d_ordertype'] ?? 'N'; $orderdate = $after['d_orderdate'] ?? ''; $visitdate = $after['d_visitdate'] ?? ''; $thisQuantity = (int)($after['d_quantity'] ?? 0); $lastQuantity = (int)($after['d_lastpickupquantity'] ?? 0); $status = $after['d_status'] ?? ''; $visit = $after['d_visit'] ?? ''; $isTop = trim($customer['c_is_top'] ?? ''); // Sludge if (isset($after['d_sludge']) && (string)$after['d_sludge'] !== '') { $customerFields['c_sludge'] = (int)$after['d_sludge']; } // Ghost Check if ( $thisQuantity > 0 && $lastQuantity > 0 && $thisQuantity < 10 && $lastQuantity < 10 && $isTop !== 'G' ) { $customerFields['c_is_top'] = 'G'; } if ($isTop === 'G' && $thisQuantity >= 10) { $customerFields['c_is_top'] = ''; } // First Pickup if ( empty($customer['c_fpickup']) && !empty($orderdate) && $thisQuantity >= 10 ) { $customerFields['c_fpickup'] = $orderdate; } // Order Flag if ($status === 'F') { $customerFields['c_orderdate'] = ''; $customerFields['c_orderflag'] = 0; } else { $customerFields['c_orderdate'] = $orderdate; $customerFields['c_orderflag'] = 1; } // ========================= // Last Pickup // // 변경된 정책 (20260601): // 수량 상관없이 완료 방문이면 기록 // ========================= if ($status === 'F') { $customerFields['c_lastpickupdate'] = $orderdate; $customerFields['c_lastpickupquantity'] = $thisQuantity; // Full Quantity Reset 10L 이상일 때만 if ($thisQuantity >= 10) { $customerFields['c_fullquantity'] = 0; } } return $customerFields; } private function updateRequestFromDaily(array $after): void { $connect = $GLOBALS['conn']; if (empty($after['d_ordertype']) || $after['d_ordertype'] !== 'R') { return; } if (empty($after['d_ruid'])) { return; } // pickup 완료 조건 (방문 완료 + 상태 F) if ( empty($after['d_visit']) || $after['d_visit'] !== 'Y' || empty($after['d_status']) || $after['d_status'] !== 'F' ) { return; } $r_uid = mysqli_real_escape_string($connect, $after['d_ruid']); mysqli_query($connect, " UPDATE tbl_request SET r_status = 'F' WHERE r_uid = '{$r_uid}' LIMIT 1 "); } private function upsertDailyNote(array $after): void { $connect = $GLOBALS['conn']; if (empty($after['d_uid'])) return; // note는 saveDaily payload에 포함되어 있어야 함 // if (empty($after['d_pickupnote']) && empty($after['d_note'])) { // return; // } $noteText = trim($after['d_pickupnote'] ?? $after['d_note'] ?? ''); // if (strlen($noteText) <= 1) return; $dailyuid = mysqli_real_escape_string($connect, $after['d_uid']); $customeruid = mysqli_real_escape_string($connect, $after['d_customeruid'] ?? ''); $memberuid = mysqli_real_escape_string($connect, $after['d_driveruid'] ?? ''); $noteEsc = mysqli_real_escape_string($connect, $noteText); $now14 = date('YmdHis'); $level = mysqli_real_escape_string($connect, $after['d_level'] ?? 9); if ($noteText === '') { mysqli_query($connect, " DELETE FROM tbl_note WHERE n_dailyuid = '{$dailyuid}' LIMIT 1 "); return; } // 기존 note 존재 여부 확인 $qr = mysqli_query($connect, " SELECT n_uid FROM tbl_note WHERE n_dailyuid = '{$dailyuid}' LIMIT 1 "); $existing = mysqli_fetch_assoc($qr); if ($existing) { // UPDATE mysqli_query($connect, " UPDATE tbl_note SET n_memberuid = '{$memberuid}', n_customeruid = '{$customeruid}', n_type = 'D', n_level = {$level}, n_view = 1, n_note = '{$noteEsc}', n_createddate = '{$now14}' WHERE n_dailyuid = '{$dailyuid}' LIMIT 1 "); } else { // INSERT mysqli_query($connect, " INSERT INTO tbl_note ( n_memberuid, n_customeruid, n_dailyuid, n_type, n_level, n_view, n_note, n_createddate ) VALUES ( '{$memberuid}', '{$customeruid}', '{$dailyuid}', 'D', {$level}, 1, '{$noteEsc}', '{$now14}' ) "); } } private function handlePickupCleanup(?array $before, array $after): void { $connect = $GLOBALS['conn']; $beforeQty = (int)($before['d_quantity'] ?? 0); $afterQty = (int)($after['d_quantity'] ?? 0); $status = $after['d_status'] ?? ''; $visit = $after['d_visit'] ?? ''; // 최초 pickup $isFirstPickup = ($beforeQty < 10 && $afterQty >= 10); if (!$isFirstPickup) return; if ($status !== 'F' || $visit !== 'Y') return; $customerUid = mysqli_real_escape_string($connect, $after['d_customeruid']); $accountno = mysqli_real_escape_string($connect, $after['d_accountno']); $today = date('Ymd'); $jobtype = mysqli_real_escape_string($connect, $after['d_jobtype'] ?? 'UCO'); // ========================= // 삭제 대상 조회 // ========================= $res = mysqli_query($connect, " SELECT d_orderdate, d_accountno, d_jobtype FROM tbl_daily WHERE d_customeruid = '{$customerUid}' AND d_orderdate > '{$today}' AND d_status = 'A' AND d_jobtype = '{$jobtype}' "); if (!$res) { throw new Exception(mysqli_error($connect)); } // ========================= // 하나씩 deleteDaily 호출 // ========================= while ($row = mysqli_fetch_assoc($res)) { try { $this->deleteDaily( $row['d_orderdate'], $row['d_accountno'], $row['d_jobtype'], ['in_tx' => true] ); } catch (Exception $e) { // 여기서 실패하면 전체 rollback 유도 throw $e; } } error_log("[PickupCleanup] ERP-safe delete completed for customer={$accountno}"); } public function deleteDaily(string $orderdate, string $accountno, string $jobtype, array $opt = []) { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $inTx = !empty($opt['in_tx']); // ========================= // KEY NORMALIZE // ========================= $orderdate = preg_replace('/[^0-9]/', '', $orderdate); if (strlen($orderdate) !== 8) { throw new Exception("invalid orderdate"); } $accountno = mysqli_real_escape_string($connect, trim($accountno)); if ($accountno === '') { throw new Exception("invalid accountno"); } $jobtype = mysqli_real_escape_string($connect, trim($jobtype)); if ($jobtype === '') { $jobtype = 'UCO'; } if (!$inTx) { mysqli_query($connect, "START TRANSACTION"); } try { // ========================= // 1. 기존 데이터 조회 // ========================= $res = mysqli_query($connect, " SELECT * FROM tbl_daily WHERE d_orderdate='{$orderdate}' AND d_accountno='{$accountno}' AND d_jobtype = '{$jobtype}' LIMIT 1 "); if (!$res) throw new Exception(mysqli_error($connect)); $existing = mysqli_fetch_assoc($res); if (!$existing) { throw new Exception("Daily record not found"); } // ========================= // 2. ERP OUTBOX 기록 // ========================= $payload = mysqli_real_escape_string( $connect, json_encode([ 'entity' => 'DAILY', 'action' => 'DELETED', 'key' => [ 'd_orderdate' => $orderdate, 'd_accountno' => $accountno, 'd_jobtype' => $jobtype ], 'before' => $existing, 'after' => null ], JSON_UNESCAPED_UNICODE) ); $qry_outbox = " INSERT INTO tbl_erp_outbox ( eo_entity_type, eo_action_type, eo_key1, eo_key2, eo_key3, eo_payload, eo_status, eo_created_at ) VALUES ( 'DAILY', 'DELETED', '{$orderdate}', '{$accountno}', '{$jobtype}', '{$payload}', 'PENDING', NOW() ) "; if (!mysqli_query($connect, $qry_outbox)) { throw new Exception(mysqli_error($connect)); } // ========================= // 3. 물리 삭제 // ========================= if (!mysqli_query($connect, " DELETE FROM tbl_daily WHERE d_orderdate='{$orderdate}' AND d_accountno='{$accountno}' AND d_jobtype = '{$jobtype}' LIMIT 1 ")) { throw new Exception(mysqli_error($connect)); } if (mysqli_affected_rows($connect) === 0) { throw new Exception("Delete failed"); } // ========================= // AFTER WORK 중앙화 // ========================= $this->runAfterDelete($existing, $opt); // ========================= // COMMIT // ========================= if (!$inTx) { mysqli_query($connect, "COMMIT"); } return true; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } private function reEvaluateGhost(int $customerUid): void { $connect = $GLOBALS['conn']; $res = mysqli_query($connect, " SELECT c.c_is_top, d.d_quantity, d.d_lastpickupquantity, d.d_updateruid FROM tbl_customer c LEFT JOIN tbl_daily d ON d.d_customeruid = c.c_uid AND d.d_status = 'F' WHERE c.c_uid = '{$customerUid}' ORDER BY d.d_orderdate DESC LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $row = mysqli_fetch_assoc($res); if (!$row) { return; } $currentTop = trim( (string)($row['c_is_top'] ?? '') ); $thisQuantity = (int)($row['d_quantity'] ?? 0); $lastQuantity = (int)($row['d_lastpickupquantity'] ?? 0); // ========================= // NO DAILY HISTORY // ========================= if (empty($row['d_quantity']) && empty($row['d_lastpickupquantity'])) { // 현재 ghost 인 경우만 해제 if ($currentTop === 'G') { $this->patchCustomerFromDaily( $customerUid, [ 'c_is_top' => '', 'c_updatedby' => $row['d_updateruid'] ?? null ] ); } return; } // ========================= // SHOULD BE GHOST // ========================= $shouldGhost = ($thisQuantity < 10) && ($lastQuantity < 10); // ghost 추가 if ($shouldGhost) { // 이미 G 면 skip if ($currentTop !== 'G') { $this->patchCustomerFromDaily( $customerUid, [ 'c_is_top' => 'G', 'c_updatedby' => $row['d_updateruid'] ?? null ] ); } return; } // ========================= // REMOVE GHOST // ========================= if ($currentTop === 'G') { // $shouldGhost 는 이미 false; $this->patchCustomerFromDaily( $customerUid, [ 'c_is_top' => '', 'c_updatedby' => $row['d_updateruid'] ?? null ] ); } } private function recalcCustomerFromDailyHistory(string $customerUid): void { $connect = $GLOBALS['conn']; $customerUidEsc = mysqli_real_escape_string($connect, $customerUid); // 0) "오늘 포함 이후" 오더장 존재 여부 체크 (남아있으면 리셋 금지) $today = date('Ymd'); $resFuture = mysqli_query($connect, " SELECT 1 FROM tbl_daily WHERE d_customeruid = '{$customerUidEsc}' AND d_orderdate >= '{$today}' AND d_status = 'A' LIMIT 1 "); if (!$resFuture) { throw new Exception(mysqli_error($connect)); } $hasFutureOrder = (bool)mysqli_fetch_assoc($resFuture); // 1) 마지막 완료 주문(F) 기준으로 last pickup 재계산 $res = mysqli_query($connect, " SELECT d_orderdate, d_quantity, d_sludge, d_paymenttype, d_paystatus, d_updateruid FROM tbl_daily WHERE d_customeruid = '{$customerUidEsc}' AND d_status = 'F' ORDER BY d_orderdate DESC LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $last = mysqli_fetch_assoc($res); $lastPickupDate = ''; $lastPickupQty = 0; $lastSludge = ''; $lastPaidDate = ''; if ($last) { $lastPickupDate = $last['d_orderdate']; $lastPickupQty = (int)$last['d_quantity']; $lastSludge = (string)$last['d_sludge']; if ($last['d_paymenttype'] === 'CA' && $last['d_paystatus'] === 'P') { $lastPaidDate = $last['d_orderdate']; } } // 2) 마지막 주문이 cash paid가 아니면, cash paid 마지막을 다시 찾음 if ($lastPaidDate === '') { $res2 = mysqli_query($connect, " SELECT d_orderdate FROM tbl_daily WHERE d_customeruid = '{$customerUidEsc}' AND d_status = 'F' AND d_paymenttype = 'CA' AND d_paystatus = 'P' ORDER BY d_orderdate DESC LIMIT 1 "); if (!$res2) { throw new Exception(mysqli_error($connect)); } $paid = mysqli_fetch_assoc($res2); if ($paid) { $lastPaidDate = $paid['d_orderdate']; } } // 3) CUSTOMER PATCH 구성 $customerFields = []; $customerFields['c_lastpickupdate'] = $lastPickupDate; $customerFields['c_lastpickupquantity'] = $lastPickupQty; $customerFields['c_lastpaiddate'] = $lastPaidDate; $customerFields['c_sludge'] = $lastSludge; // 4) orderflag/orderdate 리셋 정책 반영 // - 오늘 포함 이후 오더장이 남아있으면 유지 (아무것도 안함) // - 없으면 리셋 if (!$hasFutureOrder) { $customerFields['c_orderdate'] = ''; $customerFields['c_orderflag'] = 0; } $customerFields['c_updatedby'] = $last['d_updateruid'] ?? null; // 5) PATCH CUSTOMER $this->patchCustomerFromDaily( (int)$customerUid, $customerFields ); } /* ===================================================== * PATCH DAILY * ===================================================== */ public function patchDaily( int $dailyUid, array $fields = [], array $opt = [] ): array { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $dailyUid = (int)$dailyUid; if ($dailyUid <= 0) { throw new Exception("invalid daily uid"); } if (empty($fields)) { throw new Exception("empty patch fields"); } $inTx = !empty($opt['in_tx']); if (!$inTx) { mysqli_query($connect, "START TRANSACTION"); } try { // BEFORE $res = mysqli_query($connect, " SELECT * FROM tbl_daily WHERE d_uid = '{$dailyUid}' LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $beforeData = mysqli_fetch_assoc($res); if (!$beforeData) { throw new Exception("daily not found"); } // NORMALIZE $fields = $this->normalizeDailyPayload( $fields, $beforeData ); // MODIFY DATE $fields['d_modifydate'] = date("YmdHis"); // UPDATE $setParts = []; foreach ($fields as $k => $v) { if ($v === null) { $setParts[] = "{$k}=NULL"; continue; } $vEsc = mysqli_real_escape_string( $connect, (string)$v ); $setParts[] = "{$k}='{$vEsc}'"; } if (empty($setParts)) { throw new Exception("nothing to update"); } $sql = " UPDATE tbl_daily SET " . implode(',', $setParts) . " WHERE d_uid='{$dailyUid}' LIMIT 1 "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } // AFTER $res = mysqli_query($connect, " SELECT * FROM tbl_daily WHERE d_uid='{$dailyUid}' LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $afterData = mysqli_fetch_assoc($res); // OUTBOX $this->enqueueOutbox( 'UPDATED', $beforeData, $afterData ); // AFTER WORK if (empty($opt['skip_after'])) { $this->runAfterSave( $beforeData, $afterData, $opt ); } // COMMIT if (!$inTx) { mysqli_query($connect, "COMMIT"); } return $afterData; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } private function patchCustomerFromDaily( int $customerUid, array $fields = [] ): void { if ($customerUid <= 0) { return; } if (empty($fields)) { return; } $customerSvc = new CustomerService(); $customerSvc->patchCustomer( $customerUid, $fields, [ 'in_tx' => true, 'skip_after' => true ] ); } /* =============================== * 공통 JSON 응답 * =============================== */ private function jsonSuccess($data = []) { return json_encode([ 'result' => 'success', 'data' => $data ]); } private function jsonError($msg) { return json_encode([ 'result' => 'error', 'message'=> $msg ]); } }