normalizeCustomerPayload($data, $beforeData); // ===================================== // HYDRATE // ===================================== $data = $this->hydrateCustomerSnapshot($data, $beforeData); // ===================================== // CREATE // ===================================== if (!$isUpdate) { $columns = []; $values = []; foreach ($data as $k => $v) { $columns[] = $k; $values[] = "'" . mysqli_real_escape_string($connect, (string)$v) . "'"; } $sql = " INSERT INTO tbl_customer ( " . implode(',', $columns) . " ) VALUES ( " . implode(',', $values) . " ) "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } $c_uid = mysqli_insert_id($connect); } // ===================================== // UPDATE // ===================================== else { $setParts = []; foreach ($data as $k => $v) { $vEsc = mysqli_real_escape_string( $connect, (string)$v ); $setParts[] = "{$k}='{$vEsc}'"; } $sql = " UPDATE tbl_customer SET " . implode(',', $setParts) . " WHERE c_uid='{$beforeData['c_uid']}' LIMIT 1 "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } $c_uid = (int)$beforeData['c_uid']; } // ===================================== // AFTER RELOAD // ===================================== $res = mysqli_query($connect, " SELECT * FROM tbl_customer WHERE c_uid='{$c_uid}' LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $afterData = mysqli_fetch_assoc($res); // ===================================== // OUTBOX // ===================================== $actionType = $isUpdate ? 'UPDATED' : 'CREATED'; if (!$isUpdate|| $this->hasCustomerChanges($beforeData, $afterData)) { $this->enqueueOutbox( $actionType, $beforeData, $afterData ); } // ===================================== // AFTER WORK // ===================================== if (empty($opt['skip_after'])) { $this->runAfterChange( $beforeData, $afterData, $data, $opt ); } // ===================================== // COMMIT // ===================================== if (!$inTx) { mysqli_query($connect, "COMMIT"); } return $afterData; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } /* ===================================================== * PATCH CUSTOMER * ===================================================== */ public function patchCustomer( int $customerUid, array $fields, array $opt = [] ): array { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $inTx = !empty($opt['in_tx']); if (!$inTx) { mysqli_query($connect, "START TRANSACTION"); } try { $customerUid = (int)$customerUid; if ($customerUid <= 0) { throw new Exception("invalid customer uid"); } // ===================================== // BEFORE // ===================================== $res = mysqli_query($connect, "SELECT * FROM tbl_customer WHERE c_uid='{$customerUid}' LIMIT 1"); if (!$res) { throw new Exception(mysqli_error($connect)); } $beforeData = mysqli_fetch_assoc($res); if (!$beforeData) { throw new Exception("customer not found"); } // ===================================== // normalize // ===================================== $fields = $this->normalizeCustomerPayload( $fields, $beforeData ); if (empty($fields)) { return $beforeData; } $fields['c_updateddate'] = date("YmdHis"); // ===================================== // GEO RESET // ===================================== $this->handleGeoReset( $beforeData, $fields ); // ===================================== // 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}'"; } $sql = " UPDATE tbl_customer SET " . implode(',', $setParts) . " WHERE c_uid='{$customerUid}' LIMIT 1 "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } // ===================================== // AFTER // ===================================== $res = mysqli_query($connect, " SELECT * FROM tbl_customer WHERE c_uid='{$customerUid}' LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $afterData = mysqli_fetch_assoc($res); // ===================================== // OUTBOX // ===================================== if ($this->hasCustomerChanges($beforeData, $afterData)) { $this->enqueueOutbox( 'UPDATED', $beforeData, $afterData ); } // ===================================== // AFTER WORK // ===================================== if (empty($opt['skip_after'])) { $this->runAfterChange( $beforeData, $afterData, $fields, $opt ); } if (!$inTx) { mysqli_query($connect, "COMMIT"); } return $afterData; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } /* ===================================================== * DELETE CUSTOMER (사용 안함) * ===================================================== */ public function deleteCustomer( int $customerUid, array $opt = [] ): bool { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $customerUid = (int)$customerUid; if ($customerUid <= 0) { throw new Exception("invalid customer uid"); } $inTx = !empty($opt['in_tx']); if (!$inTx) { mysqli_query($connect, "START TRANSACTION"); } try { $res = mysqli_query($connect, " SELECT * FROM tbl_customer WHERE c_uid='{$customerUid}' LIMIT 1 "); if (!$res) { throw new Exception(mysqli_error($connect)); } $beforeData = mysqli_fetch_assoc($res); if (!$beforeData) { throw new Exception("customer not found"); } $sql = " UPDATE tbl_customer SET c_status='D' WHERE c_uid='{$customerUid}' LIMIT 1 "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } $res = mysqli_query($connect, " SELECT * FROM tbl_customer WHERE c_uid='{$customerUid}' LIMIT 1 "); $afterData = mysqli_fetch_assoc($res); $this->enqueueOutbox( 'DELETED', $beforeData, $afterData ); if (!$inTx) { mysqli_query($connect, "COMMIT"); } return true; } catch (Exception $e) { if (!$inTx) { mysqli_query($connect, "ROLLBACK"); } throw $e; } } /* ===================================================== * OUTBOX * ===================================================== */ private function enqueueOutbox( string $actionType, ?array $beforeData, ?array $afterData ): void { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $key1 = $afterData['c_accountno'] ?? $beforeData['c_accountno'] ?? ''; $payload = mysqli_real_escape_string( $connect, json_encode([ 'entity' => 'CUSTOMER', 'action' => $actionType, 'key' => [ 'c_accountno' => $key1 ], 'before' => $beforeData, 'after' => $afterData ], JSON_UNESCAPED_UNICODE) ); $sql = " INSERT INTO tbl_erp_outbox ( eo_entity_type, eo_action_type, eo_key1, eo_payload, eo_status, eo_created_at ) VALUES ( 'CUSTOMER', '{$actionType}', '{$key1}', '{$payload}', 'PENDING', NOW() ) "; if (!mysqli_query($connect, $sql)) { throw new Exception(mysqli_error($connect)); } } /* ===================================================== * AFTER SAVE * ===================================================== */ private function runAfterChange( ?array $before, array $after, array $changedFields = [], array $opt = [] ): void { // ========================= // DAILY pricing sync // ========================= $pricingFields = [ 'c_rate', 'c_paymentcycle', 'c_paymenttype' ]; $hasPricingChange = false; foreach ($pricingFields as $f) { if ( array_key_exists($f, $changedFields) || (($before[$f] ?? null) != ($after[$f] ?? null)) ) { $hasPricingChange = true; break; } } if ($hasPricingChange) { $this->syncFutureDailyPricing( $before, $after ); } // ========================= // DRIVER sync // ========================= if ( array_key_exists('c_driveruid', $changedFields) || (($before['c_driveruid'] ?? null) != ($after['c_driveruid'] ?? null)) ) { $this->syncFutureDriver( $before, $after ); } } /* ===================================================== * DRIVER SYNC * ===================================================== */ private function syncFutureDriver( ?array $before, array $after ): void { if (!$before) { return; } $oldDriver = $before['c_driveruid'] ?? ''; $newDriver = $after['c_driveruid'] ?? ''; if ($oldDriver == $newDriver) { return; } $customerUid = (int)$after['c_uid']; $today = date("Ymd"); $dailyService = new DailyService(); $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } // FUTURE DAILY $qry = " SELECT d_uid, d_orderdate, d_accountno FROM tbl_daily WHERE d_customeruid = '{$customerUid}' AND d_orderdate >= '{$today}' "; $result = mysqli_query($connect, $qry); while ($row = mysqli_fetch_assoc($result)) { $dailyService->saveDaily( [ 'd_orderdate' => $row['d_orderdate'], 'd_accountno' => $row['d_accountno'], 'd_driveruid' => $newDriver, 'd_updateruid' => $after['c_updatedby'] ?? $after['c_createdby'] ?? null ], [ 'match_uid' => $row['d_uid'], 'allow_update' => true ] ); } // FUTURE REQUEST mysqli_query($connect, " UPDATE tbl_request SET r_driveruid='{$newDriver}' WHERE r_customeruid='{$customerUid}' AND r_requestdate >= '{$today}' "); } /* ===================================================== * FUTURE DAILY PRICING (위에거 포함한 bulk update 로직 만드는 거 추후에 작업) * ===================================================== */ private function syncFutureDailyPricing( ?array $before, array $after ): void { if (!$before) { return; } $rateChanged = (string)($before['c_rate'] ?? '') !== (string)($after['c_rate'] ?? ''); $cycleChanged = (string)($before['c_paymentcycle'] ?? '') !== (string)($after['c_paymentcycle'] ?? ''); $paymentChanged = (string)($before['c_paymenttype'] ?? '') !== (string)($after['c_paymenttype'] ?? ''); if ( !$rateChanged && !$cycleChanged && !$paymentChanged ) { return; } $customerUid = (int)$after['c_uid']; $today = date("Ymd"); $dailyService = new DailyService(); $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } $qry = " SELECT d_uid, d_orderdate, d_accountno FROM tbl_daily WHERE d_customeruid = '{$customerUid}' AND d_orderdate > '{$today}' "; $result = mysqli_query($connect, $qry); while ($row = mysqli_fetch_assoc($result)) { $payload = [ 'd_orderdate' => $row['d_orderdate'], 'd_accountno' => $row['d_accountno'], 'd_updateruid' => $after['c_updatedby'] ?? $after['c_createdby'] ?? null ]; if ($rateChanged) { $payload['d_rate'] = $after['c_rate']; } if ($cycleChanged) { $payload['d_cycle'] = $after['c_paymentcycle']; } if ($paymentChanged) { $payload['d_paymenttype'] = $after['c_paymenttype']; } $dailyService->saveDaily( $payload, [ 'match_uid' => $row['d_uid'], 'allow_update' => true ] ); } } /* ===================================================== * GEO RESET * ===================================================== */ private function handleGeoReset( array $before, array &$fields ): void { $beforeAddress = trim( (string)($before['c_address'] ?? '') ); $beforePostal = trim( (string)($before['c_postal'] ?? '') ); $newAddress = trim( (string)( $fields['c_address'] ?? $beforeAddress ) ); $newPostal = trim( (string)( $fields['c_postal'] ?? $beforePostal ) ); if ( $beforeAddress !== $newAddress || $beforePostal !== $newPostal ) { $fields['c_geolat'] = null; $fields['c_geolon'] = null; } } /* ===================================================== * NORMALIZE * ===================================================== */ private function normalizeCustomerPayload( array $data, ?array $existing = null ): array { $dateFields = [ 'c_contractdate', 'c_installdate', 'c_salescommissiondate', 'c_removaldate', 'c_inactivedate', 'c_exchangedate', 'c_switchformdate', 'c_forceddate' ]; foreach ($dateFields as $f) { if (!isset($data[$f])) { continue; } $data[$f] = preg_replace( '/[^0-9]/', '', (string)$data[$f] ); } $strFields = [ 'c_name', 'c_address', 'c_city', 'c_postal', 'c_location', 'c_comment_ri', 'c_comment_ci', 'c_email', 'c_phone', 'c_phoneext', 'c_cell', 'c_createdby', 'c_updatedby' ]; foreach ($strFields as $f) { if (!isset($data[$f])) { continue; } $data[$f] = trim( str_replace( "\\", '', (string)$data[$f] ) ); } if (isset($data['c_phone'])) { $data['c_phone'] = str_replace( '-', '', $data['c_phone'] ); } return $data; } /* ===================================================== * HYDRATE * ===================================================== */ private function hydrateCustomerSnapshot( array $data, ?array $existing = null ): array { $connect = $GLOBALS['conn'] ?? null; if (!$connect) { throw new Exception("DB connection not found"); } // driver -> region snapshot if (!empty($data['c_driveruid'])) { $driveruid = mysqli_real_escape_string( $connect, (string)$data['c_driveruid'] ); $res = mysqli_query($connect, " SELECT m_regionuid, m_region FROM tbl_member WHERE m_uid='{$driveruid}' LIMIT 1 "); if ($res) { $driver = mysqli_fetch_assoc($res); if ($driver) { $data['c_regionuid'] = $driver['m_regionuid']; $data['c_region'] = $driver['m_region']; } } } // created date if (empty($existing)) { if (empty($data['c_createddate'])) { $data['c_createddate'] = date("YmdHis"); } } $data['c_updateddate'] = date("YmdHis"); return $data; } private function hasCustomerChanges( ?array $before, ?array $after ): bool { if (!$before || !$after) { return true; } $businessFields = [ // 기본 'c_accountno', 'c_name', 'c_status', 'c_form_eu', 'c_form_corsia', 'c_rate', 'c_paymenttype', 'c_hstno', 'c_driveruid', // 지역 'c_regionuid', 'c_area', 'c_address', 'c_mailingaddr', 'c_postal', 'c_city', 'c_province', 'c_geolat', 'c_geolon', // 연락처 'c_email', 'c_phone', 'c_phoneext', // 계약 'c_contractdate', 'c_contractby', 'c_installdate', 'c_salesperson', 'c_salescommissiondate', 'c_salesmethod', // Full Cycle (erp 에서 의미없는 변화) // 'c_fullcycle', // 'c_fullquantity', // 'c_fullcycleflag', // 'c_fullcycleforced', // 'c_forceddate', // 'c_crondate', // Schedule 'c_schedule', 'c_scheduleday', // Location 'c_location', // Pickup 'c_lastpickupdate', 'c_lastpickupquantity', 'c_sludge', // Comment 'c_comment_ri', 'c_comment_ci' ]; foreach ($businessFields as $field) { $beforeVal = trim((string)($before[$field] ?? '')); $afterVal = trim((string)($after[$field] ?? '')); if ($beforeVal !== $afterVal) { return true; } } return false; } }