diff --git a/public_html/assets/api/install.php b/public_html/assets/api/install.php index a1f7697..9fa3861 100644 --- a/public_html/assets/api/install.php +++ b/public_html/assets/api/install.php @@ -2,6 +2,11 @@ trait InstallAPI { + protected $dailyService; + public function __construct() { + $this->dailyService = new DailyService(); + } + public function search_customer() { $keyword = trim($_POST['keyword'] ?? ''); if ($keyword === '') { @@ -29,7 +34,7 @@ trait InstallAPI { SELECT 1 FROM tbl_install_waitlist iw WHERE iw.iw_customer_uid = c.c_uid - AND iw.iw_status <> 'DELETED' + AND iw.iw_status not in ('DELETED', 'COMPLETED') ) ORDER BY c.c_name LIMIT 20 @@ -47,6 +52,7 @@ trait InstallAPI { $c_uid = $_POST['c_uid'] ?? 0; $job_type = $_POST['job_type'] ?? ''; $driver_uid = $_POST['driver_uid'] ?? ''; + $member_uid = $_POST['member_uid'] ?? ''; $request_note = $_POST['request_note'] ?? ''; if (!$c_uid || !$job_type) { @@ -90,7 +96,7 @@ trait InstallAPI { '{$driver_uid}' AS iw_request_by, '".addslashes($request_note)."' AS iw_request_note, NOW() AS iw_request_at, - '{$driver_uid}' AS iw_created_by, + '{$member_uid}' AS iw_created_by, NOW() AS iw_created_at FROM tbl_customer c WHERE c.c_uid = '{$c_uid}' @@ -216,11 +222,11 @@ trait InstallAPI { FROM tbl_install_waitlist iw LEFT JOIN tbl_daily_install di - ON di.di_wait_uid = iw.iw_uid + ON di.di_wait_uid = iw.iw_uid LEFT JOIN tbl_member m - ON iw.iw_request_by = m.m_uid + ON iw.iw_created_by = m.m_uid WHERE iw.iw_customer_uid = '{$c_uid}' - AND iw.iw_status IN ('DRAFT','WAITING','ASSIGNED') + AND iw.iw_status IN ('DRAFT','WAITING','ASSIGNED') ORDER BY iw.iw_uid DESC, di.di_uid DESC LIMIT 1 "; @@ -270,49 +276,74 @@ trait InstallAPI { $iw_uid = intval($_POST['iw_uid'] ?? 0); $items = $_POST['items'] ?? []; - $action = $_POST['action']; + $requestAction = $_POST['action'] ?? null; if (!$iw_uid) { echo $this->json(['ok' => 0, 'msg' => 'no iw_uid']); return; } - // 기존 컨테이너 전부 삭제 - qry(" - DELETE FROM tbl_install_waitlist_container - WHERE iwc_wait_uid = '{$iw_uid}' AND iwc_action = '{$action}' - "); + qry("START TRANSACTION"); - // 재 insert - if (is_array($items)) { - foreach ($items as $it) { + try { - $action = addslashes($it['action'] ?? ''); - $type = addslashes($it['type'] ?? ''); - $capacity = isset($it['capacity']) && $it['capacity'] !== '' - ? intval($it['capacity']) - : 'NULL'; - $is_used = addslashes($it['is_used'] ?? 'N'); - $owner_type = addslashes($it['owner_type'] ?? 'C'); - $has_lock = addslashes($it['has_lock'] ?? 'N'); - $has_wheel = addslashes($it['has_wheel'] ?? 'N'); - $has_frame = addslashes($it['has_frame'] ?? 'N'); + // 기존 컨테이너 전부 삭제 + qry(" + DELETE FROM tbl_install_waitlist_container + WHERE iwc_wait_uid = '{$iw_uid}' AND iwc_action = '{$requestAction}' + "); - $sql = " - INSERT INTO tbl_install_waitlist_container - SET - iwc_wait_uid = '{$iw_uid}', - iwc_action = '{$action}', - iwc_type = '{$type}', - iwc_capacity = {$capacity}, - iwc_is_used = '{$is_used}', - iwc_owner_type = '{$owner_type}', - iwc_has_lock = '{$has_lock}', - iwc_has_wheel = '{$has_wheel}', - iwc_has_woodframe = '{$has_frame}' - "; - qry($sql); + // 재 insert + if (is_array($items)) { + foreach ($items as $it) { + + // item action 따로 사용 + $itemAction = addslashes($it['action'] ?? ''); + if (!$itemAction) { + throw new Exception('missing item action'); + } + + $type = addslashes($it['type'] ?? ''); + $capacity = isset($it['capacity']) && $it['capacity'] !== '' + ? intval($it['capacity']) + : 'NULL'; + $is_used = addslashes($it['is_used'] ?? 'N'); + $owner_type = addslashes($it['owner_type'] ?? 'C'); + $has_lock = addslashes($it['has_lock'] ?? 'N'); + $has_wheel = addslashes($it['has_wheel'] ?? 'N'); + $has_frame = addslashes($it['has_frame'] ?? 'N'); + + $sql = " + INSERT INTO tbl_install_waitlist_container + SET + iwc_wait_uid = '{$iw_uid}', + iwc_action = '{$itemAction}', + iwc_type = '{$type}', + iwc_capacity = {$capacity}, + iwc_is_used = '{$is_used}', + iwc_owner_type = '{$owner_type}', + iwc_has_lock = '{$has_lock}', + iwc_has_wheel = '{$has_wheel}', + iwc_has_woodframe = '{$has_frame}' + "; + + if (!qry($sql)) { + throw new Exception('insert failed'); + } + } } + + qry("COMMIT"); + + } catch (Exception $e) { + + qry("ROLLBACK"); + + echo $this->json([ + 'ok' => 0, + 'msg' => $e->getMessage() + ]); + return; } echo $this->json(['ok' => 1]); @@ -512,18 +543,18 @@ trait InstallAPI { c.c_name AS di_customer_name, SUM( CASE - WHEN dic.dic_action = 'REMOVE' + WHEN dic.dic_action = 'PICKUP' AND dic.dic_type = 'D' THEN 1 ELSE 0 END - ) AS remove_d_cnt, + ) AS pickup_d_cnt, SUM( CASE - WHEN dic.dic_action = 'REMOVE' + WHEN dic.dic_action = 'PICKUP' AND dic.dic_type <> 'D' THEN 1 ELSE 0 END - ) AS remove_b_cnt, + ) AS pickup_b_cnt, SUM( CASE WHEN dic.dic_action = 'INSTALL' @@ -688,78 +719,124 @@ trait InstallAPI { $fail('validate', 'invalid params'); } - // 둘 다 비면 아무것도 할게 없음 if ((!is_array($trial_rows) || !count($trial_rows)) && (!is_array($existing_rows) || !count($existing_rows))) { $fail('validate', 'nothing to confirm'); } + /* ======================================================= + 0. 기존 DB max seq 가져오기 + ======================================================= */ + $qr_max = qry(" + SELECT MAX(di_order_seq) AS max_seq + FROM tbl_daily_install + WHERE di_driver_uid = '{$di_driver_uid}' + AND di_install_date = '{$di_install_date}' + "); + + $row_max = fetch_array($qr_max); + $base_seq = intval($row_max['max_seq'] ?? 0); + + /* ======================================================= + 1. 전체 rows 합치고 seq 재정렬 + ======================================================= */ + $all = array_merge( + is_array($existing_rows) ? $existing_rows : [], + is_array($trial_rows) ? $trial_rows : [] + ); + + usort($all, function($a, $b){ + return intval($a['di_order_seq'] ?? 0) <=> intval($b['di_order_seq'] ?? 0); + }); + + $seq = 1; + foreach($all as &$r){ + // base_seq 더해줌 + $r['di_order_seq'] = $base_seq + $seq++; + } + unset($r); + + /* ======================================================= + 2. 다시 existing / trial 분리 + ======================================================= */ + $existing_map = []; + foreach($existing_rows as $row){ + $existing_map[intval($row['di_uid'])] = true; + } + + $new_existing_rows = []; + $new_trial_rows = []; + + foreach($all as $r){ + if(isset($existing_map[intval($r['di_uid'] ?? 0)])){ + $new_existing_rows[] = $r; + }else{ + $new_trial_rows[] = $r; + } + } + qry("START TRANSACTION"); /* ======================================================= A) 기존 daily_install 순서 업데이트 ======================================================= */ - if (is_array($existing_rows) && count($existing_rows)) { - foreach ($existing_rows as $idx => $row) { - $di_uid = intval($row['di_uid'] ?? 0); - $di_order_seq = intval($row['di_order_seq'] ?? 0); + foreach ($new_existing_rows as $row) { - if (!$di_uid || !$di_order_seq) { - qry("ROLLBACK"); - $fail('validate_existing_row', 'invalid existing row', ['row'=>$row]); - } + $di_uid = intval($row['di_uid'] ?? 0); + $di_order_seq = intval($row['di_order_seq'] ?? 0); - // driver/date까지 같이 조건으로 걸면 더 안전함 (다른 날짜/driver 수정 방지) - $sql = " - UPDATE tbl_daily_install - SET di_order_seq = '{$di_order_seq}' - WHERE di_uid = '{$di_uid}' + if (!$di_uid || !$di_order_seq) { + qry("ROLLBACK"); + $fail('validate_existing_row', 'invalid existing row', ['row'=>$row]); + } + + $sql = " + UPDATE tbl_daily_install + SET di_order_seq = '{$di_order_seq}' + WHERE di_uid = '{$di_uid}' AND di_driver_uid = '{$di_driver_uid}' AND di_install_date = '{$di_install_date}' - LIMIT 1 - "; + LIMIT 1 + "; - if (!qry($sql)) { - qry("ROLLBACK"); - $fail('update_existing_order', 'failed', ['di_uid'=>$di_uid]); - } + if (!qry($sql)) { + qry("ROLLBACK"); + $fail('update_existing_order', 'failed', ['di_uid'=>$di_uid]); } } /* ======================================================= - B) trial(waitlist) -> daily_install INSERT + status update + B) trial(waitlist) → INSERT ======================================================= */ - if (is_array($trial_rows) && count($trial_rows)) { - foreach ($trial_rows as $idx => $row) { + foreach ($new_trial_rows as $row) { - $iw_uid = intval($row['iw_uid'] ?? 0); - $di_order_seq = intval($row['di_order_seq'] ?? 0); + $iw_uid = intval($row['iw_uid'] ?? 0); + $di_order_seq = intval($row['di_order_seq'] ?? 0); - if (!$iw_uid || !$di_order_seq) { - qry("ROLLBACK"); - $fail('validate_trial_row', 'invalid trial row', ['row'=>$row]); - } + if (!$iw_uid || !$di_order_seq) { + qry("ROLLBACK"); + $fail('validate_trial_row', 'invalid trial row', ['row'=>$row]); + } - $di_uid = $this->insert_daily_install_from_waitlist( - $iw_uid, - $di_driver_uid, - $di_install_date, - $di_order_seq - ); + $di_uid = $this->insert_daily_install_from_waitlist( + $iw_uid, + $di_driver_uid, + $di_install_date, + $di_order_seq + ); - if (!$di_uid) { - qry("ROLLBACK"); - $fail('insert_daily_install', 'failed', ['iw_uid'=>$iw_uid]); - } + if (!$di_uid) { + qry("ROLLBACK"); + $fail('insert_daily_install', 'failed', ['iw_uid'=>$iw_uid]); + } - if (!$this->insert_daily_install_container_from_waitlist($di_uid, $iw_uid)) { - qry("ROLLBACK"); - $fail('insert_container', 'failed', ['iw_uid'=>$iw_uid]); - } + if (!$this->insert_daily_install_container_from_waitlist($di_uid, $iw_uid)) { + qry("ROLLBACK"); + $fail('insert_container', 'failed', ['iw_uid'=>$iw_uid]); + } - if (!$this->update_waitlist_status($iw_uid, 'ASSIGNED')) { - qry("ROLLBACK"); - $fail('update_waitlist', 'failed', ['iw_uid'=>$iw_uid]); - } + if (!$this->update_waitlist_status($iw_uid, 'ASSIGNED')) { + qry("ROLLBACK"); + $fail('update_waitlist', 'failed', ['iw_uid'=>$iw_uid]); } } @@ -771,12 +848,23 @@ trait InstallAPI { public function unassign_daily_install() { - $di_uid = intval($_POST['di_uid'] ?? 0); - if (!$di_uid) { - echo $this->json(['ok'=>0,'msg'=>'invalid di_uid']); + $di_uids = $_POST['di_uids'] ?? []; + if (!is_array($di_uids) || !count($di_uids)) { + echo $this->json(['ok'=>0,'msg'=>'invalid di_uids']); return; } + // 숫자만 필터 + $di_uids = array_map('intval', $di_uids); + $di_uids = array_filter($di_uids); + + if (!count($di_uids)) { + echo $this->json(['ok'=>0,'msg'=>'empty di_uids']); + return; + } + + $in = implode(',', $di_uids); + $fail = function($stage, $msg, $extra = []) { echo $this->json(array_merge([ 'ok' => 0, @@ -787,45 +875,70 @@ trait InstallAPI { exit; }; - $qr = qry("SELECT di_wait_uid, di_driver_uid, di_install_date FROM tbl_daily_install WHERE di_uid = '{$di_uid}'"); - $row = fetch_array($qr); + // 1. 대상 조회 + $qr = qry(" + SELECT di_uid, di_wait_uid, di_driver_uid, di_install_date + FROM tbl_daily_install + WHERE di_uid IN ($in) + "); - if (empty($row['di_wait_uid'])) { - $fail('select', 'daily_install not found', ['di_uid'=>$di_uid]); + $rows = []; + while ($r = fetch_array($qr)) { + $rows[] = $r; } - $iw_uid = intval($row['di_wait_uid']); - $di_driver_uid = intval($row['di_driver_uid']); - $di_install_date = $row['di_install_date']; // ✅ 문자열 유지 + if (!count($rows)) { + $fail('select', 'no rows found'); + } qry("START TRANSACTION"); - // container 삭제는 하지말자. - // if (qry("DELETE FROM tbl_daily_install_container WHERE dic_di_uid = '{$di_uid}'") === false) { - // qry("ROLLBACK"); - // $fail('delete_container', 'failed', ['di_uid'=>$di_uid]); - // } - - // daily_install 삭제 - if (!qry("DELETE FROM tbl_daily_install WHERE di_uid = '{$di_uid}' LIMIT 1")) { + // 2. 삭제 + if (!qry("DELETE FROM tbl_daily_install WHERE di_uid IN ($in)")) { qry("ROLLBACK"); - $fail('delete_daily_install', 'failed', ['di_uid'=>$di_uid]); + $fail('delete_daily_install', 'failed'); } - // waitlist WAITING 복귀 - if (!$this->update_waitlist_status($iw_uid, 'WAITING')) { - qry("ROLLBACK"); - $fail('update_waitlist', 'failed', ['iw_uid'=>$iw_uid]); + // 3. waitlist 복귀 (중복 제거) + $iw_uids = []; + foreach ($rows as $r) { + if (!empty($r['di_wait_uid'])) { + $iw_uids[] = intval($r['di_wait_uid']); + } + } + $iw_uids = array_unique($iw_uids); + + foreach ($iw_uids as $iw_uid) { + if (!$this->update_waitlist_status($iw_uid, 'WAITING')) { + qry("ROLLBACK"); + $fail('update_waitlist', 'failed', ['iw_uid'=>$iw_uid]); + } } - // seq 재정렬 (같은 driver/date) - if (!$this->resequence_daily_install($di_driver_uid, $di_install_date)) { - qry("ROLLBACK"); - $fail('resequence', 'failed', ['di_driver_uid'=>$di_driver_uid,'di_install_date'=>$di_install_date]); + // 4. resequence (driver + date별 그룹) + $group = []; + + foreach ($rows as $r) { + $key = $r['di_driver_uid'].'|'.$r['di_install_date']; + $group[$key] = [ + 'driver_uid' => $r['di_driver_uid'], + 'date' => $r['di_install_date'] + ]; + } + + foreach ($group as $g) { + if (!$this->resequence_daily_install($g['driver_uid'], $g['date'])) { + qry("ROLLBACK"); + $fail('resequence', 'failed', $g); + } } qry("COMMIT"); - echo $this->json(['ok'=>1]); + + echo $this->json([ + 'ok' => 1, + 'cnt' => count($rows) + ]); } public function resequence_daily_install($di_driver_uid, $di_install_date) { @@ -916,28 +1029,28 @@ trait InstallAPI { $c_uid = intval($_POST['c_uid'] ?? 0); $type = addslashes($_POST['type'] ?? ''); - $capacity = $_POST['capacity'] !== '' ? intval($_POST['capacity']) : "NULL"; + if( $mode != 'PICKUP' ) $capacity = $_POST['capacity'] !== '' ? intval($_POST['capacity']) : "NULL"; $is_used = $_POST['is_used'] ?? 'N'; $has_lock = $_POST['has_lock'] ?? 'N'; $has_wheel = $_POST['has_wheel'] ?? 'N'; $has_frame = $_POST['has_frame'] ?? 'N'; $owner_type = $_POST['owner_type'] ?? 'C'; $install_date = $_POST['install_date'] ?? null; - $remove_date = $_POST['remove_date'] ?? null; + $pickup_date = $_POST['pickup_date'] ?? null; $install_note = addslashes($_POST['install_note'] ?? ''); - $remove_note = addslashes($_POST['remove_note'] ?? ''); + $pickup_note = addslashes($_POST['pickup_note'] ?? ''); if($mode==='ADD'){ $sql = " INSERT INTO tbl_customer_container (cc_customer_uid,cc_type,cc_capacity,cc_is_used, cc_has_lock,cc_has_wheel,cc_has_woodframe,cc_owner_type, - cc_status,cc_install_at,cc_install_note, + cc_status, cc_install_date, cc_install_at,cc_install_note, cc_createddate,cc_updateddate) VALUES( '{$c_uid}','{$type}',{$capacity}, '{$is_used}','{$has_lock}','{$has_wheel}','{$has_frame}','{$owner_type}', - 'A','{$install_date}','{$install_note}', + 'A','{$install_date}','{$install_date}','{$install_note}', DATE_FORMAT(NOW(),'%Y%m%d%H%i%s'), DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') ) @@ -958,22 +1071,24 @@ trait InstallAPI { $status = $row['cc_status']; // 날짜 처리 (빈 값이면 NULL) $install_at_sql = $install_date ? "'{$install_date} 00:00:00'" : "NULL"; - $remove_at_sql = $remove_date ? "'{$remove_date} 00:00:00'" : "NULL"; + $pickup_at_sql = $pickup_date ? "'{$pickup_date} 00:00:00'" : "NULL"; // ===== ACTIVE 수정 ===== if ($status === 'A') { $sql = " - UPDATE tbl_customer_container SET - cc_capacity={$capacity}, - cc_is_used='{$is_used}', - cc_has_lock='{$has_lock}', - cc_has_wheel='{$has_wheel}', - cc_has_woodframe='{$has_frame}', - cc_owner_type='{$owner_type}', - cc_install_at={$install_at_sql}, - cc_install_note='{$install_note}', - cc_modified_at=NOW(), - cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') + UPDATE tbl_customer_container + SET + cc_capacity={$capacity}, + cc_is_used='{$is_used}', + cc_has_lock='{$has_lock}', + cc_has_wheel='{$has_wheel}', + cc_has_woodframe='{$has_frame}', + cc_owner_type='{$owner_type}', + cc_install_date={$install_date}, + cc_install_at={$install_at_sql}, + cc_install_note='{$install_note}', + cc_modified_at=NOW(), + cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') WHERE cc_uid='{$cc_uid}' "; qry($sql); @@ -981,25 +1096,27 @@ trait InstallAPI { // ===== INACTIVE 수정 ===== else { $sql = " - UPDATE tbl_customer_container SET - cc_remove_at={$remove_at_sql}, - cc_remove_note='{$remove_note}', - cc_modified_at=NOW(), - cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') + UPDATE tbl_customer_container + SET + cc_pickup_at={$pickup_at_sql}, + cc_pickup_note='{$pickup_note}', + cc_modified_at=NOW(), + cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') WHERE cc_uid='{$cc_uid}' "; qry($sql); } } - if($mode==='REMOVE'){ + if($mode==='PICKUP'){ $sql=" - UPDATE tbl_customer_container SET - cc_status='I', - cc_remove_date='{$remove_date}', - cc_remove_note='{$remove_note}', - cc_remove_at=NOW(), - cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') + UPDATE tbl_customer_container + SET + cc_status='I', + cc_pickup_date='{$pickup_date}', + cc_pickup_note='{$pickup_note}', + cc_pickup_at=NOW(), + cc_updateddate=DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') WHERE cc_uid='{$cc_uid}' "; qry($sql); @@ -1025,19 +1142,19 @@ trait InstallAPI { } } - if ($remove_date) { + if ($pickup_date) { - $remove_yyyymmdd = str_replace('-', '', $remove_date); + $pickup_yyyymmdd = str_replace('-', '', $pickup_date); $r = qry("SELECT c_removaldate FROM tbl_customer WHERE c_uid='{$c_uid}'"); $row = mysqli_fetch_assoc($r); $current = $row['c_removaldate']; - if (!$current || $current === 'N/A' || $remove_yyyymmdd > $current) { + if (!$current || $current === 'N/A' || $pickup_yyyymmdd > $current) { qry(" UPDATE tbl_customer - SET c_removaldate='{$remove_yyyymmdd}' + SET c_removaldate='{$pickup_yyyymmdd}' WHERE c_uid='{$c_uid}' "); } @@ -1050,11 +1167,13 @@ trait InstallAPI { // DAILY INSTALL public function daily_install_update_status() { - $di_uid = intval($_POST['di_uid'] ?? 0); - $member_uid = intval($_POST['member_uid'] ?? 0); - $newStatus = strtoupper(trim($_POST['status'] ?? '')); - $note = trim($_POST['install_note'] ?? ''); - $oil_qty = intval($_POST['oil_qty'] ?? 0); + $di_uid = intval($_POST['di_uid'] ?? 0); + $member_uid = intval($_POST['member_uid'] ?? 0); + $newStatus = strtoupper(trim($_POST['status'] ?? '')); + $install_note = trim($_POST['install_note'] ?? ''); + $additional_note = trim($_POST['additional_note'] ?? ''); + $oil_qty = intval($_POST['oil_qty'] ?? 0); + $sludge = intval($_POST['sludge'] ?? 0); $allowed = ['P','M','X']; @@ -1089,18 +1208,24 @@ trait InstallAPI { try { // 2) 상태 업데이트 - $this->updateDailyInstallStatus($di_uid, $newStatus, $note, $oil_qty); + $this->updateDailyInstallStatus($di_uid, $newStatus, $install_note, $oil_qty, $sludge); + + // 2-1) additional note 저장 + $this->upsertAdditionalNote($di, $additional_note); // 3) 컨테이너 반영 if ($di['di_status'] === 'A' && $newStatus === 'P') { - $this->apply_container_changes_for_di($di_uid, intval($di['di_customer_uid'])); + $this->apply_container_changes_for_di($di_uid, $install_note, intval($di['di_customer_uid'])); + + // 3-1) 컨테이너 반영 후 customer 의 main 볼륨이랑 main 컨테이너 변경 + $this->updateCustomerMainContainer(intval($di['di_customer_uid'])); } // 4) oil pickup → daily - $this->saveDailyFromInstallIfNeeded($di_uid, $oil_qty); + $this->saveDailyFromInstallIfNeeded($di_uid, $oil_qty, $sludge); // 5) waitlist 처리 - $this->handleWaitlistAfterInstall($di, $newStatus, $member_uid, $note); + $this->handleWaitlistAfterInstall($di, $newStatus, $member_uid, $install_note); // 6) 순서 재정렬 $this->reorder_daily_install_sequence( @@ -1121,15 +1246,17 @@ trait InstallAPI { private function updateDailyInstallStatus( int $di_uid, string $newStatus, - string $note, - int $oil_qty + string $install_note, + int $oil_qty, + int $sludge ) { $conn = $GLOBALS['conn']; $newStatus = mysqli_real_escape_string($conn, $newStatus); - $noteEsc = mysqli_real_escape_string($conn, $note); + $noteEsc = mysqli_real_escape_string($conn, $install_note); $oil_qty = intval($oil_qty); + $sludge = intval($sludge); $sql = " UPDATE tbl_daily_install @@ -1137,6 +1264,7 @@ trait InstallAPI { di_status = '{$newStatus}', di_install_note = '{$noteEsc}', di_oil_pickup = '{$oil_qty}', + di_sludge = '{$sludge}', di_install_time = IF('{$newStatus}' <> 'A', CURTIME(), di_install_time) WHERE di_uid = '{$di_uid}' LIMIT 1 @@ -1147,18 +1275,166 @@ trait InstallAPI { } } - private function saveDailyFromInstallIfNeeded(int $di_uid, int $oil_qty): void + /** + * install 추가 메모를 tbl_note에 upsert + * + * install 하면서 컨테이너 위치관련 note를 n_dailyuid 를 'i_{di_uid}' 형태로 저장 + * 예: di_uid = 123 이면 n_dailyuid = 'i_123' + */ + private function upsertAdditionalNote(array $di, string $additional_note): void + { + $connect = $GLOBALS['conn']; + // 디버그 + // file_put_contents(__DIR__.'/debug.log', "NOTE: ".$additional_note."\n", FILE_APPEND); + $noteText = trim($additional_note); + if (strlen($noteText) <= 1) { + return; + } + + $di_uid = intval($di['di_uid'] ?? 0); + if ($di_uid <= 0) { + return; + } + + // install 전용 note key + $noteDailyUid = 'i_' . $di_uid; + + $n_dailyuid = mysqli_real_escape_string($connect, $noteDailyUid); + $customeruid = mysqli_real_escape_string($connect, $di['di_customer_uid'] ?? ''); + $memberuid = mysqli_real_escape_string($connect, $_SESSION['ss_UID'] ?? ''); + $noteEsc = mysqli_real_escape_string($connect, $noteText); + $now14 = date('YmdHis'); + $level = intval($_SESSION['ss_LEVEL'] ?? 9); + + // 기존 note 존재 여부 확인 + $qr = mysqli_query($connect, " + SELECT n_uid + FROM tbl_note + WHERE n_dailyuid = '{$n_dailyuid}' + LIMIT 1 + "); + + if (!$qr) { + throw new Exception('install note select failed'); + } + + $existing = mysqli_fetch_assoc($qr); + + if ($existing) { + // UPDATE + $ok = 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 = '{$n_dailyuid}' + LIMIT 1 + "); + + if (!$ok) { + throw new Exception('install note update failed'); + } + + } else { + // INSERT + $ok = 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}', + '{$n_dailyuid}', + 'D', + {$level}, + 1, + '{$noteEsc}', + '{$now14}' + ) + "); + + if (!$ok) { + throw new Exception('install note insert failed'); + } + } + } + + private function saveDailyFromInstallIfNeeded(int $di_uid, int $oil_qty, int $sludge): void { - // 0이면 daily에 반영할 필요 없음 if ($oil_qty <= 0) return; - // payload 만들기 - $payload = $this->buildDailyPayloadFromInstall($di_uid, $oil_qty); + $qr = qry("SELECT * FROM tbl_daily_install WHERE di_uid='{$di_uid}' LIMIT 1"); + $di = mysqli_fetch_assoc($qr); + if (!$di) throw new Exception('install not found'); - // ************************************************* payload 에 아래 포함시켜라. - // $payload['d_jobtype'] = $payload['d_jobtype'] ?? 'INSTALL'; + $visitdate = date('Ymd', strtotime($di['di_install_date'])); + $now14 = date('YmdHis'); - $this->saveDaily($payload, ['in_tx' => 1]); + // customer account + $qrCus = qry(" + SELECT + c_accountno, + c_regionuid, + c_region + FROM tbl_customer + WHERE c_uid='{$di['di_customer_uid']}' + LIMIT 1 + "); + $customer = mysqli_fetch_assoc($qrCus); + + if (!$customer) { + throw new Exception('customer not found'); + } + + // ========================= + // payload (saveInput 스타일) + // ========================= + $payload = [ + 'd_customeruid' => $di['di_customer_uid'], + 'd_accountno' => $customer['c_accountno'], + 'd_orderdate' => $visitdate, + 'd_driveruid' => $_SESSION['ss_UID'] ?? 0, // member uid + 'd_druid' => 11, // driver uid 고정 hard coding + 'd_drinitial' => 'I.N', // driver initial 고정 hard coding + 'd_regionuid' => $customer['c_regionuid'] ?? 0, + 'd_region' => $customer['c_region'] ?? '', + 'd_visit' => 'Y', + 'd_visitdate' => $visitdate . '000000', + 'd_status' => 'F', + 'd_jobtype' => 'INSTALL', + 'd_quantity' => $oil_qty, + 'd_sludge' => $sludge, + 'd_inputdate' => $now14, + 'd_is_transfer' => 'N', + 'd_level' => $_SESSION['ss_LEVEL'] ?? '', + 'd_createruid' => $_SESSION['ss_UID'] ?? '', + 'd_createddate' => $now14, + ]; + + // ========================= + // 기존 daily 찾기 (중복 방지) + // ========================= + $opt = [ + 'allow_update' => true, + 'old_key' => [ + 'd_orderdate' => $visitdate, + 'd_accountno' => $customer['c_accountno'], + 'd_jobtype' => 'INSTALL' + ] + ]; + + $this->dailyService->saveDaily($payload, $opt); } private function handleWaitlistAfterInstall(array $di, string $newStatus, int $member_uid, string $note) @@ -1257,6 +1533,8 @@ trait InstallAPI { iw_address, iw_city, iw_postal, + iw_lat, + iw_lng, iw_requested_due_date, iw_work_type, iw_status, @@ -1265,8 +1543,12 @@ trait InstallAPI { iw_scheduled, iw_scheduled_cycle, iw_request_by, + iw_request_at, iw_request_note, iw_cs_note, + iw_work_note, + iw_required_person_cnt, + iw_multi_person_reason, iw_request_source ) VALUES ( '{$wait['iw_customer_uid']}', @@ -1276,6 +1558,8 @@ trait InstallAPI { '".mysqli_real_escape_string($GLOBALS['conn'], $wait['iw_address'])."', '".mysqli_real_escape_string($GLOBALS['conn'], $wait['iw_city'])."', '".mysqli_real_escape_string($GLOBALS['conn'], $wait['iw_postal'])."', + '{$wait['iw_lat']}', + '{$wait['iw_lng']}', '{$nextDate}', '".mysqli_real_escape_string($GLOBALS['conn'], $wait['iw_work_type'])."', 'WAITING', @@ -1284,8 +1568,12 @@ trait InstallAPI { 1, '{$cycleDays}', '{$wait['iw_request_by']}', + '{$installDate}', '".mysqli_real_escape_string($GLOBALS['conn'], (string)$wait['iw_request_note'])."', '".mysqli_real_escape_string($GLOBALS['conn'], (string)$wait['iw_cs_note'])."', + '".mysqli_real_escape_string($GLOBALS['conn'], (string)$wait['iw_work_note'])."', + '".mysqli_real_escape_string($GLOBALS['conn'], (string)$wait['iw_required_person_cnt'])."', + '".mysqli_real_escape_string($GLOBALS['conn'], (string)$wait['iw_multi_person_reason'])."', 'AUTO' ) "; @@ -1339,63 +1627,12 @@ trait InstallAPI { } } - private function buildDailyPayloadFromInstall(int $di_uid, int $oil_qty): array - { - $qr = qry("SELECT * FROM tbl_daily_install WHERE di_uid='{$di_uid}' LIMIT 1"); - $di = mysqli_fetch_assoc($qr); - if (!$di) throw new Exception('install not found'); - - $orderdate = date('Ymd', strtotime($di['di_install_date'])); - $now14 = date('YmdHis'); - - $qrCus = qry("SELECT * FROM tbl_customer WHERE c_uid='{$di['di_customer_uid']}' LIMIT 1"); - $customer = mysqli_fetch_assoc($qrCus); - if (!$customer) throw new Exception('customer not found'); - - $days = 0; - if (!empty($customer['c_lastpickupdate'])) { - $last = strtotime(substr($customer['c_lastpickupdate'],0,8)); - $today = strtotime($orderdate); - $days = max(0, floor(($today-$last)/86400)); - } - - $estquantity = max( - 0, - ($customer['c_fullquantity'] - $customer['c_fullquantitydaily']) - + ($days * $customer['c_fullquantitydaily']) - ); - - return [ - 'd_orderdate' => $orderdate, - 'd_accountno' => $di['di_accountno'], - 'd_customeruid' => $di['di_customer_uid'], - // 'd_driveruid' => $di['di_driver_uid'], - 'd_driveruid' => 43, // 현재 E.X 로 고정, hard coding - 'd_name' => $di['di_customer_name'], - 'd_paymenttype' => $customer['c_paymenttype'], - 'd_cycle' => $customer['c_paymentcycle'], - 'd_rate' => $customer['c_rate'], - 'd_status' => 'F', - 'd_visit' => 'Y', - 'd_visitdate' => $now14, - 'd_quantity' => $oil_qty, - 'd_estquantity' => $estquantity, - 'd_inputdate' => $now14, - 'd_createddate' => $now14, - 'd_createruid' => $_SESSION['ss_UID'] ?? '' - ]; - } - - - - - - public function upload_after_photo() { $di_uid = intval($_POST['di_uid'] ?? 0); $member_uid = intval($_POST['member_uid'] ?? 0); $note = trim($_POST['note'] ?? ''); + $i_type = $_POST['i_type'] ?? 'install'; $fail = function($stage, $msg, $extra = []) { echo $this->json(array_merge([ @@ -1423,17 +1660,17 @@ trait InstallAPI { $fail('select_di', 'daily_install not found'); } - if(!isset($_FILES['after_photo']) || $_FILES['after_photo']['error'] !== 0){ + if(!isset($_FILES['photo']) || $_FILES['photo']['error'] !== 0){ $fail('file', 'no file'); } + $file = $_FILES['photo']; $customer_uid = intval($di['di_customer_uid']); - $file = $_FILES['after_photo']; $ok = $this->save_customer_image_single( $customer_uid, $member_uid, - 'install', + $i_type, $di_uid, // i_sourceuid = di_uid $note, $file @@ -1466,14 +1703,13 @@ trait InstallAPI { return false; } - // 작업 단위 1장 유지: (customeruid + type + sourceuid) + // 작업 단위 1장 유지: (customeruid + type, sourceuid 는 제외) $qry_update = " UPDATE tbl_customer_image SET i_status = 'I' WHERE i_customeruid = '{$i_customeruid}' - AND i_type = '{$i_type}' - AND i_sourceuid = '{$i_sourceuid}' - AND i_status = 'A' + AND i_type = '{$i_type}' + AND i_status = 'A' "; if(qry($qry_update) === false) return false; @@ -1483,10 +1719,10 @@ trait InstallAPI { mkdir($upload_folder, 0777, true); } - $newFileName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_0.".$ext; + $newFileName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_0.jpg"; $savePath = $upload_folder . "/" . $newFileName; - if(!move_uploaded_file($file['tmp_name'], $savePath)){ + if (!$this->compressAndResizeImage($file['tmp_name'], $savePath, $ext, 1200, 75)) { return false; } @@ -1521,7 +1757,7 @@ trait InstallAPI { return qry($sqlIns) !== false; } - private function apply_container_changes_for_di($di_uid, $customer_uid){ + private function apply_container_changes_for_di($di_uid, $note, $customer_uid){ $containers = []; $rc = qry("SELECT * FROM tbl_daily_install_container WHERE dic_di_uid = '{$di_uid}'"); @@ -1550,29 +1786,23 @@ trait InstallAPI { $has_wheel = mysqli_real_escape_string($GLOBALS['conn'], $c['dic_has_wheel'] ?? 'N'); $has_wood = mysqli_real_escape_string($GLOBALS['conn'], $c['dic_has_woodframe'] ?? 'N'); - // REMOVE - if($action === 'REMOVE'){ - - $removeNote = mysqli_real_escape_string( - $GLOBALS['conn'], - "Removed via DI#{$di_uid}" - ); - + // PICKUP + if($action === 'PICKUP'){ qry(" UPDATE tbl_customer_container SET cc_status='I', - cc_remove_note='{$removeNote}', - cc_remove_at=NOW(), - cc_remove_date=NOW(), + cc_pickup_note='{$note}', + cc_pickup_at=NOW(), + cc_pickup_date=NOW(), cc_modified_at=NOW() WHERE cc_uid = ( SELECT cc_uid FROM ( SELECT cc_uid FROM tbl_customer_container WHERE cc_customer_uid='{$customer_uid}' - AND cc_status='A' - AND cc_type='{$type}' - AND (cc_capacity {$capacityCond}) + AND cc_status='A' + AND cc_type='{$type}' + AND (cc_capacity {$capacityCond}) ORDER BY IFNULL(cc_install_at,'1970-01-01'), cc_uid LIMIT 1 ) x @@ -1594,24 +1824,230 @@ trait InstallAPI { cc_status, cc_install_note, cc_install_at, cc_install_date, - cc_createddate, cc_updateddate, - cc_modified_at + cc_createddate, cc_updateddate ) VALUES ( '{$customer_uid}', '{$type}', {$capacityVal}, '{$owner_type}', '{$is_used}', '{$has_lock}', '{$has_wheel}', '{$has_wood}', 'A', - 'Installed via DI#{$di_uid}', + '{$note}', NOW(), NOW(), - '{$nowYmdHis}', '{$nowYmdHis}', - NOW() + NOW(), NOW() ) "); } } } + private function updateCustomerMainContainer($customer_uid) + { + $customer_uid = intval($customer_uid); + + // ========================================= + // 0) 수동 관리 + // ========================================= + $qr = qry(" + SELECT + c_manualvolume, + c_fullcycleflag, + c_fullcycleforced, + c_mainvolume + FROM tbl_customer + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + $cus = mysqli_fetch_assoc($qr); + + if (!$cus) { + throw new Exception('customer not found'); + } + + if ($cus['c_manualvolume'] === 'Y') { + // 아무것도 하지 않음 + return; + } + // 기존 mainvolume 저장 + $oldVolume = intval($cus['c_mainvolume'] ?? 0); + + // ========================================= + // 1) active container 조회 + // ========================================= + $rc = qry(" + SELECT + cc_type, + cc_capacity, + cc_owner_type + FROM tbl_customer_container + WHERE cc_customer_uid = '{$customer_uid}' + AND cc_status = 'A' + "); + + $containers = []; + while ($row = mysqli_fetch_assoc($rc)) { + $containers[] = $row; + } + + // ========================================= + // 2) 컨테이너 없으면 초기화 + // ========================================= + if (!count($containers)) { + + qry(" + UPDATE tbl_customer + SET + c_mainvolume = 0, + c_maincontainer = '' + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + + return; + } + + // ========================================= + // 3) 타입 분류 (D / B) + // ========================================= + $d_list = []; // D 계열 + $b_list = []; // B 계열 + + foreach ($containers as $c) { + + $type = strtoupper(trim($c['cc_type'] ?? '')); + $capacity = intval($c['cc_capacity'] ?? 0); // null / '' → 0 처리 + + // B 타입 판별 400B, 600B, CB 등 + if (preg_match('/^\d*B$/', $type) || $type === 'CB') { + + $b_list[] = [ + 'type' => $type, + 'capacity' => $capacity + ]; + } + + // D 타입 판별 + else if ($type === 'D') { + + $d_list[] = [ + 'type' => $type, + 'capacity' => 200 // D는 고정 200 + ]; + } + + // BUCKET / TANK 등은 무시 + } + + // ========================================= + // 4) B가 하나라도 있으면 → B 기준 처리 + // ========================================= + if (count($b_list) > 0) { + + $maxCapacity = 0; + $maxType = ''; + + foreach ($b_list as $b) { + + if ($b['capacity'] > $maxCapacity) { + $maxCapacity = $b['capacity']; + $maxType = $b['type']; + } + } + + // capacity가 전부 0일 수도 있음 (예외 처리) + if ($maxCapacity <= 0) { + $maxType = $b_list[0]['type']; // fallback + } + + // forced cycle + $newVolume = $maxCapacity; + $this->adjustFullCycle($customer_uid, $cus, $oldVolume, $newVolume); + + // main volume + qry(" + UPDATE tbl_customer + SET + c_mainvolume = '{$maxCapacity}', + c_maincontainer = '{$maxType}' + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + + return; + } + + // ========================================= + // 5) B 없고 D만 있는 경우 + // ========================================= + if (count($d_list) > 0) { + + $count = count($d_list); + + if ($count === 1) { + $volume = 200; + } else { + $volume = $count * 200; + } + + // forced cycle + $newVolume = $volume; + $this->adjustFullCycle($customer_uid, $cus, $oldVolume, $newVolume); + + // main volume + qry(" + UPDATE tbl_customer + SET + c_mainvolume = '{$volume}', + c_maincontainer = 'D' + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + + return; + } + + // ========================================= + // 6) fallback (이상 케이스) + // ========================================= + qry(" + UPDATE tbl_customer + SET + c_mainvolume = 0, + c_maincontainer = '' + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + } + + private function adjustFullCycle($customer_uid, $cus, $oldVolume, $newVolume) + { + // 조건 체크 + if ($cus['c_manualvolume'] !== 'N') return; + if (intval($cus['c_fullcycleflag']) !== 1) return; + + $forced = floatval($cus['c_fullcycleforced'] ?? 0); + + // 기존 값이 없으면 의미 없음 + if ($forced <= 0) return; + + // oldVolume 0이면 비율 계산 불가 → skip + if ($oldVolume <= 0) return; + + // newVolume 0이면 forced도 0으로 + if ($newVolume <= 0) { + $newForced = 0; + } else { + $ratio = $newVolume / $oldVolume; + $newForced = $forced * $ratio; + } + + qry(" + UPDATE tbl_customer + SET c_fullcycleforced = '{$newForced}' + WHERE c_uid = '{$customer_uid}' + LIMIT 1 + "); + } + private function reorder_daily_install_sequence($driver_uid, $install_date) { $driver_uid = intval($driver_uid); @@ -1738,4 +2174,186 @@ trait InstallAPI { echo $this->json(['ok'=>1]); exit; } + + public function bulk_update_daily_install() { + + $di_uids = $_POST['di_uids'] ?? []; + $new_driver_uid = intval($_POST['di_driver_uid'] ?? 0); + $new_install_date = trim($_POST['di_install_date'] ?? ''); + + if (!is_array($di_uids) || !count($di_uids)) { + echo $this->json(['ok'=>0, 'msg'=>'invalid di_uids']); + return; + } + + $di_uids = array_map('intval', $di_uids); + $di_uids = array_filter($di_uids); + + if (!count($di_uids)) { + echo $this->json(['ok'=>0, 'msg'=>'empty di_uids']); + return; + } + + if (!$new_driver_uid || !$new_install_date) { + echo $this->json(['ok'=>0, 'msg'=>'invalid target params']); + return; + } + + $in = implode(',', $di_uids); + $conn = $GLOBALS['conn']; + + $fail = function($stage, $msg, $extra = []) use ($conn) { + echo $this->json(array_merge([ + 'ok' => 0, + 'stage' => $stage, + 'msg' => $msg, + 'db' => mysqli_error($conn) + ], $extra)); + exit; + }; + + // 1) 대상 조회 + $qr = qry(" + SELECT di_uid, di_driver_uid, di_install_date + FROM tbl_daily_install + WHERE di_uid IN ($in) + "); + + $rows = []; + while ($r = fetch_array($qr)) { + $rows[] = $r; + } + + if (!count($rows)) { + $fail('select', 'no rows found'); + } + + // 2) 기존 그룹 수집 + $oldGroups = []; + foreach ($rows as $r) { + $key = intval($r['di_driver_uid']) . '|' . $r['di_install_date']; + $oldGroups[$key] = [ + 'driver_uid' => intval($r['di_driver_uid']), + 'date' => $r['di_install_date'] + ]; + } + + qry("START TRANSACTION"); + + // 3) 대상 업데이트 + $new_driver_uid_esc = intval($new_driver_uid); + $new_install_date_esc = mysqli_real_escape_string($conn, $new_install_date); + + $sql = " + UPDATE tbl_daily_install + SET + di_driver_uid = '{$new_driver_uid_esc}', + di_install_date = '{$new_install_date_esc}' + WHERE di_uid IN ($in) + "; + + if (!qry($sql)) { + qry("ROLLBACK"); + $fail('update', 'bulk update failed'); + } + + // 4) 새 그룹 resequence + if (!$this->resequence_daily_install($new_driver_uid_esc, $new_install_date_esc)) { + qry("ROLLBACK"); + $fail('resequence_new', 'failed', [ + 'di_driver_uid' => $new_driver_uid_esc, + 'di_install_date' => $new_install_date_esc + ]); + } + + // 5) 이전 그룹 resequence + foreach ($oldGroups as $g) { + $sameGroup = + intval($g['driver_uid']) === $new_driver_uid_esc && + $g['date'] === $new_install_date_esc; + + if ($sameGroup) continue; + + if (!$this->resequence_daily_install($g['driver_uid'], $g['date'])) { + qry("ROLLBACK"); + $fail('resequence_old', 'failed', $g); + } + } + + qry("COMMIT"); + + echo $this->json([ + 'ok' => 1, + 'cnt' => count($rows) + ]); + } + + function compressAndResizeImage($srcPath, $destPath, $ext, $maxWidth = 1200, $quality = 75) { + + list($width, $height, $imageType) = getimagesize($srcPath); + + if (!$width || !$height) return false; + + switch ($imageType) { + case IMAGETYPE_JPEG: + $srcImg = imagecreatefromjpeg($srcPath); + + if (function_exists('exif_read_data')) { + $exif = @exif_read_data($srcPath); + + if (!empty($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 3: + $srcImg = imagerotate($srcImg, 180, 0); + break; + case 6: + $srcImg = imagerotate($srcImg, -90, 0); + break; + case 8: + $srcImg = imagerotate($srcImg, 90, 0); + break; + } + } + + $width = imagesx($srcImg); + $height = imagesy($srcImg); + } + + break; + case IMAGETYPE_PNG: + $srcImg = imagecreatefrompng($srcPath); + break; + case IMAGETYPE_WEBP: + $srcImg = imagecreatefromwebp($srcPath); + break; + default: + return false; + } + + if (!$srcImg) return false; + + if ($width > $maxWidth) { + $newWidth = $maxWidth; + $newHeight = intval(($height / $width) * $newWidth); + } else { + $newWidth = $width; + $newHeight = $height; + } + + $dstImg = imagecreatetruecolor($newWidth, $newHeight); + + imagecopyresampled( + $dstImg, $srcImg, + 0, 0, 0, 0, + $newWidth, $newHeight, + $width, $height + ); + + $result = imagejpeg($dstImg, $destPath, $quality); + + imagedestroy($srcImg); + imagedestroy($dstImg); + + return $result; + } } diff --git a/public_html/assets/css/main.css b/public_html/assets/css/main.css index 704f774..bdbbfda 100644 --- a/public_html/assets/css/main.css +++ b/public_html/assets/css/main.css @@ -1815,6 +1815,491 @@ table.table-search-report .tb-list th { z-index: 999; } +/* Install wait list */ +.waitlist-scroll { + height: calc(100vh - 260px); /* header + search 고려 */ + overflow-y: auto; +} +.wait-card { + border-bottom: 1px solid #eee; + padding: 12px 16px; + cursor: pointer; +} +.wait-card:hover { + background: #f8f9fa; +} +.wait-card.trial-selected { + background: #fff8e1; /* 연한 노랑 */ + border-color: #ffc107; + box-shadow: 0 0 0 2px rgba(255,193,7,.25); +} +.container-chips { + cursor: pointer; + display: flex; + gap: 6px; + flex-wrap: wrap; +} +.container-chips .chip { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + line-height: 1.2; + padding: 3px 8px; + border-radius: 6px; + white-space: nowrap; + background: #e9ecef; + color: #333; +} +.container-chips .chip-company { + background: #e9ecef; + color: #333; +} +.container-chips .chip-restaurant { + background: #fff3cd; /* warning subtle */ + color: #664d03; + border: 1px solid #ffecb5; +} +.container-chips .chip-flag { + margin-left: 4px; + padding: 0 6px; + font-size: 11px; + font-weight: 700; + border-radius: 999px; + background: #ffe69c; + color: #664d03; +} +.container-chips .chip-task { + background: #dee2e6; + color: #212529; +} +.container-chips .chip i { + font-size: 13px; + line-height: 1; +} +.container-chips .chip-count { + margin-left: 4px; + font-size: 11px; + font-weight: 600; + color: #495057; +} +.note-line { + display: flex; + gap: 6px; + align-items: flex-start; +} +.note-label { + font-size: 12px; + font-weight: 600; + color: #666; + min-width: 58px; +} +.note-text { + font-size: 12px; + line-height: 1.3; +} +/*modal*/ +.modal-body { + overflow: hidden; + padding: 0; +} +.install-wait-viewport { + overflow: hidden; + width: 100%; +} +.install-wait-steps-wrapper { + display: grid; + grid-auto-flow: column; + grid-auto-columns: 100%; + transition: transform 0.35s ease; +} +.install-wait-step { + padding: 1.25rem; + box-sizing: border-box; + display: flex; + flex-direction: column; +} +.install-wait-step > .step-content { + flex: 1; +} +.customer-row.selected { + background-color: #e7f1ff; + border-left: 4px solid #0d6efd; + padding-left: 0.75rem; +} +#btnStep1Next, +#btnStep2Next { + min-width: 48px; + transition: all 0.15s ease; +} +.iw-inline-ctrl { + height: 34px; +} +button.iw-inline-ctrl { + padding: 0 12px; + line-height: 1.2; +} +input.iw-inline-ctrl, +.iw-inline-ctrl input { + padding-top: 4px; + padding-bottom: 4px; + line-height: 1.2; +} +.input-group.iw-inline-ctrl { + display: flex; + align-items: stretch; +} +.input-group.iw-inline-ctrl .form-control { + height: 100%; +} +.iw-toggle-on { + background-color: #e7f1ff; + border-color: #0d6efd; + color: #0d6efd; +} +.iw-toggle-on:hover { + background-color: #dbe9ff; +} +.active-step { + background-color: #e7f1ff; + border-color: #0d6efd; + color: #0d6efd; +} +.active-step:hover { + background-color: #dbe9ff; + transform: translateX(2px); +} +.iw-job-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); /* 항상 한 줄 */ + gap: 0.5rem; +} +.iw-job-card { + display: flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + padding: 0.6rem 0.5rem; + font-size: 0.9rem; + background: #fff; + border: 1px solid #dee2e6; + border-radius: 0.375rem; + cursor: pointer; + transition: all 0.15s ease; +} +.iw-job-card i { + font-size: 1rem; +} +.iw-job-card:hover { + background: #f1f5ff; + border-color: #b6c8ff; +} +.iw-job-card.active { + background: #e7f1ff; + border-color: #0d6efd; + color: #0d6efd; +} +.iw-container-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.5rem; +} +.iw-container-card { + border: 1px solid #dee2e6; + padding: 0.5rem; + border-radius: 0.375rem; + cursor: pointer; + text-align: center; + font-size: 0.85rem; +} +.iw-container-card.active { + background: #e7f1ff; + border-color: #0d6efd; + color: #0d6efd; +} +/**/ +#iwScheduleDate, +#iwPhotos, +#iwRequestDate, +#iwRequestBy, +#iwSelectCapacity, +#iwRequestNote, +#iwCsNote, +#kwCustomer, +#iwMapDate +{ + margin-bottom: 0px !important; +} +#iwSelectCapacity { + width: 75px; +} +#iwRequestBy{ + padding-top: 0.875em; + padding-bottom: 0.875em; +} +.iw-action-row { + display: flex; + gap: 12px; + margin-bottom: 12px; +} +.iw-action-row .task-action-box { + flex: 1; +} +.iw-add-btn { + min-width: 70px; + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; +} +.iw-name, .customer-name { + display: inline-block; + max-width: 140px; /* 이름 최대 폭 */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: bottom; +} +.iw-header { + background-color: var(--color-primary); + color: var(--color-white); +} +.iw-ui-control { + height: 36px; + display: flex; + align-items: center; +} +.iw-checkbox-wrap { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + cursor: pointer; +} +.iw-checkbox-wrap input { + transform: scale(1.3); + cursor: pointer; +} +.task-action-box { + flex: 1; + padding: 8px; + border: 1px solid #dee2e6; + border-radius: 6px; + background: #f8f9fa; +} +.task-action-title { + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 4px; +} +.task-action-title.pickup { + color: #dc3545; /* bootstrap danger */ +} +.task-action-title.install { + color: #198754; /* bootstrap success */ +} +@media (max-width: 768px) { + .task-actions { + flex-direction: column; + } +} +#filterTwoPerson.active { + background-color: #212529; + color: #fff; +} +.container-card { + border:1px solid #ddd; + border-radius:6px; + padding:10px; + margin-bottom:8px; + background:#fff; +} +.container-card.inactive { + background:#f7f7f7; + opacity:0.85; +} +#containerCardWrap { + display: grid; + gap: 10px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} +@media (min-width: 1200px) { + #containerCardWrap { grid-template-columns: repeat(3, minmax(0, 1fr)); } +} +@media (max-width: 768px) { + #containerCardWrap { grid-template-columns: repeat(1, minmax(0, 1fr)); } +} +.flag-marker { + position: relative; + padding: 4px 8px; + border-radius: 6px; + font-size: 13px; + font-weight: 700; + white-space: nowrap; + box-shadow: 0 2px 6px rgba(0,0,0,.25); +} +.flag-body { + display: flex; + align-items: center; + gap: 6px; +} +.flag-tail { + position: absolute; + left: 50%; + bottom: -8px; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 8px solid; + border-top-color: var(--flag-bg); +} +.flag-order { + font-weight: 800; +} +.flag-icon { + font-size: 12px; +} +.install-map-legend{ + position:absolute; + top:12px; + right:12px; + z-index:10; + min-width:140px; + max-width:220px; + background:rgba(255,255,255,0.95); + border:1px solid #dcdcdc; + border-radius:10px; + box-shadow:0 2px 10px rgba(0,0,0,0.08); + padding:10px 12px; + font-size:13px; +} + +.install-map-legend-title{ + font-weight:700; + margin-bottom:8px; +} + +.install-map-legend-item{ + display:flex; + align-items:center; + gap:8px; + margin-bottom:6px; +} +.install-map-legend-item:last-child{ + margin-bottom:0; +} + +.install-map-legend-swatch{ + width:14px; + height:14px; + border-radius:50%; + flex:0 0 14px; + border:1px solid rgba(0,0,0,0.15); +} +.reorder-selected-row { + outline: 2px solid red; +} +.summary-bar { + position: relative; +} +.view-toggle { + display: flex; + border: 1px solid #ccc; + border-radius: 8px; + overflow: hidden; + background: #fff; + margin-left: 12px; +} +.view-toggle label { + margin: 0; + cursor: pointer; +} +.view-toggle input[type="radio"] { + display: none; +} +.view-toggle span { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 36px; + color: #666; + font-size: 18px; + background: #f1f1f1; +} +.view-toggle input[type="radio"]:checked + span { + background: #ffca1a; + color: #333; +} +tr.status-P { + background-color: #b9c2c9; + color: #343a42; + text-decoration: line-through; + opacity: 0.75; +} +tr.status-M { + background-color: #fff3cd; /* bootstrap warning light */ + border-left: 4px solid #ff9800; + text-decoration: line-through; + opacity: 0.75; +} +tr.status-X { + background-color: #f8d7da; /* bootstrap danger light */ + border-left: 4px solid #dc3545; + text-decoration: line-through; + opacity: 0.75; +} + +/* ============================ + photo + ============================ */ +.photo-card { + border: 1px solid #ddd; + border-radius: 10px; + padding: 10px; + background: #fafafa; + height: 100%; +} + +.photo-header { + font-weight: 600; + font-size: 13px; + margin-bottom: 6px; +} + +.photo-card.before .photo-header { + color: #6c757d; +} + +.photo-card.after .photo-header { + color: #0d6efd; +} + +.photo-body { + min-height: 80px; + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +/* 이미지 썸네일 */ +.photo-body img { + width: 60px; + height: 60px; + object-fit: cover; + border-radius: 6px; + border: 1px solid #ccc; +} + /* ============================ Segmented Radio Button ============================ */ @@ -1838,6 +2323,7 @@ table.table-search-report .tb-list th { .radio-segment span { display: block; + width: 100%; text-align: center; padding: 8px 0; background: #e7e7e7; @@ -1877,4 +2363,4 @@ table.table-search-report .tb-list th { .radio-segment label:last-child span { border-radius: 0 0 5px 5px; } -} \ No newline at end of file +} diff --git a/public_html/assets/css/mapMAPCSS.css b/public_html/assets/css/mapMAPCSS.css index 5f8f028..db68f2a 100644 --- a/public_html/assets/css/mapMAPCSS.css +++ b/public_html/assets/css/mapMAPCSS.css @@ -239,7 +239,9 @@ #map-modal-input .td-title-info, #map-modal-history .td-title-info, #map-modal-info .td-title-info, - #map-modal-note .td-title-info { + #map-modal-container-request .td-title-info, + #customerImageModal .td-title-info + { background-color: var(--color-sub); color: #fff; } @@ -430,7 +432,9 @@ #map-modal-history .modal-header, #map-modal-info .modal-header, #map-modal-note .modal-header, -#map-modal-input .modal-header{ +#map-modal-input .modal-header, +#map-modal-container-request .modal-header, +#customerImageModal .modal-header{ padding: 0px 15px !important; } @@ -438,7 +442,9 @@ #map-modal-history .modal-header h4, #map-modal-info .modal-header h4, #map-modal-note .modal-header h4, -#map-modal-input .modal-header h4{ +#map-modal-input .modal-header h4, +#map-modal-container-request .modal-header h4, +#customerImageModal .modal-header h4{ font-size: 18px !important; } @@ -446,7 +452,9 @@ #map-modal-input .modal-content, #map-modal-history .modal-content, #map-modal-info .modal-content, -#map-modal-note .modal-content{ +#map-modal-note .modal-content, +#map-modal-container-request .modal-content, +#customerImageModal .modal-content{ border-radius: 0.5em !important; } @@ -454,7 +462,9 @@ #map-modal-input, #map-modal-history, #map-modal-info, -#map-modal-note{ +#map-modal-note, +#map-modal-container-request, +#customerImageModal{ top: 55px; @@ -496,6 +506,12 @@ .btn-nearby-modal{ background-color: #e24400; } +.btn-container-request { + background-color: #af415c; +} +.btn-image-modal { + background-color: #4195af; +} .btn-info-modal{ background-color: var(--color-sub); } @@ -694,17 +710,17 @@ input#Liter{ } } -.map-grid-layout-col-3 { +.map-grid-layout-col-4 { max-width: 1000px; margin: 0 auto; display: grid; gap: .3rem; } @media (min-width: 300px) { - .map-grid-layout-col-3 { grid-template-columns: repeat(3, 1fr); } + .map-grid-layout-col-4 { grid-template-columns: repeat(4, 1fr); } } @media (max-width: 819px) { - .map-grid-layout-col-3 { + .map-grid-layout-col-4 { max-width: 1000px; margin: 0 auto; display: grid; diff --git a/public_html/assets/internal_api.php b/public_html/assets/internal_api.php index 7604ac5..b9a3ddc 100644 --- a/public_html/assets/internal_api.php +++ b/public_html/assets/internal_api.php @@ -1449,8 +1449,7 @@ class API extends CONF { WHERE td.d_customeruid = tc.c_uid AND td.d_customeruid='".$_POST['c_uid']."' - AND td.d_orderdate='".$_POST['orderdate']."' - AND td.d_jobtype ='".($_POST['jobtype'] ?? 'UCO')."'" + AND td.d_orderdate='".$_POST['orderdate']."'" ); $result = array(); diff --git a/public_html/doc/customer_detail.php b/public_html/doc/customer_detail.php index e549196..9870f7c 100644 --- a/public_html/doc/customer_detail.php +++ b/public_html/doc/customer_detail.php @@ -83,20 +83,37 @@ if ($mode == "update") { $c_form_euSTR = $func -> convertFormat ($c_form_eu, 3); $c_form_corsiaSTR = $func -> convertFormat ($c_form_corsia, 3); $c_contractdateSTR = $func -> convertFormat ($c_contractdate, 3); - if ($c_installdate != "N/A") $c_installdateSTR = $func -> convertFormat ($c_installdate, 3); - else $c_installdateSTR = "N/A"; $c_schedulebasicSTR = $func -> convertFormat ($c_schedulebasic, 3); $c_fpickupSTR = $func -> convertFormat ($c_fpickup, 3); - - if ($c_removaldate != "N/A") $c_removaldateSTR = $func -> convertFormat ($c_removaldate, 3); - else $c_removaldateSTR = "N/A"; + $c_lastpickupdateSTR = $func -> convertFormat ($c_lastpickupdate, 3); if ($c_inactivedate != "N/A") $c_inactivedateSTR = $func -> convertFormat ($c_inactivedate, 3); else $c_inactivedateSTR = "N/A"; if ($c_inactivedateSTR == "-") $c_inactivedateSTR = str_replace("-", "", $c_inactivedateSTR); - if ($c_exchangedate != "N/A") $c_exchangedateSTR = $func -> convertFormat ($c_exchangedate, 3); - else $c_exchangedateSTR = "N/A"; + if ($c_installdate != "N/A") $c_installdateSTR = $func -> convertFormat ($c_installdate, 3); + else $c_installdateSTR = "N/A"; + + // + function safeDate($val) { + if (!$val || $val === 'N/A') return null; + if (!preg_match('/^\d{8}$/', $val)) return null; + return $val; + } + + $dates = []; + $d1 = safeDate($c_installdate); + $d2 = safeDate($c_exchangedate); + $d3 = safeDate($c_removaldate); + + if ($d1) $dates[] = $d1; + if ($d2) $dates[] = $d2; + if ($d3) $dates[] = $d3; + + $lastWork = !empty($dates) ? max($dates) : null; + + $lastWorkSTR = $lastWork ? $func->convertFormat($lastWork, 3): 'N/A'; + // if ($c_switchformdate != "N/A") $c_switchformdateSTR = $func -> convertFormat ($c_switchformdate, 3); else $c_switchformdateSTR = "N/A"; @@ -105,6 +122,27 @@ if ($mode == "update") { if (is_numeric($c_salescommissiondate)) $c_salescommissiondateSTR = $func -> convertFormat ($c_salescommissiondate, 7); else $c_salescommissiondateSTR = $c_salescommissiondate; + + // =============================== + // Customer Containers + // =============================== + $isManualChecked = ($c_manualvolume == 'Y') ? 'checked' : ''; + + $containerList = []; + + $q2 = " + SELECT * + FROM tbl_customer_container + WHERE cc_customer_uid = '{$c_uid}' + ORDER BY cc_status, cc_uid DESC + "; + + $rlt = mysqli_query($jdb->DBConn, $q2); + + while ($row = mysqli_fetch_array($rlt, MYSQLI_ASSOC)) { + $containerList[] = $row; + } + // } else { $c_salescommissiondateSTR = "NOT YET"; @@ -277,7 +315,7 @@ if ($mode == "update") { } else { // 기존 daily 정보 조회 - $query = "SELECT * FROM tbl_daily WHERE d_customeruid = '$c_uid' AND d_orderdate = '".date("Ymd")."' AND d_jobtype = 'UCO'"; + $query = "SELECT * FROM tbl_daily WHERE d_customeruid = '$c_uid' AND d_orderdate = '".date("Ymd")."'"; $result = $jdb->fQuery($query, "fetch query error"); if (is_array($result)) { @@ -363,79 +401,73 @@ addLog ("add", "CUSTOMER DETAIL", "VIEW", $lguserid, $query, $c_uid); $(document).ready(function(){ $(function () { - $('#c_contractdate, #c_form_eu, #c_form_corsia, #c_installdate, #c_fpickup, #c_removaldate, #d_visitdate, #c_exchangedate, #c_inactivedate, #c_switchformdate').datepicker({ - - dateFormat: 'yy-mm-dd', - //minDate: "-10D", - //maxDate: "+1M", - //maxDate: "+1M +10D", - //showOn: "button", - //yearRange: '-50:+5', - buttonImage: "/images/cal_red.png", - //beforeShowDay: $.datepicker.noWeekends, - buttonImageOnly: true, - //showOn: "both", - changeMonth: true, - changeYear: true, - firstDay: 7 + $('#c_contractdate, #c_form_eu, #c_form_corsia, #c_fpickup, #c_removaldate, #d_visitdate, #c_inactivedate, #c_switchformdate').datepicker({ + dateFormat: 'yy-mm-dd', + buttonImage: "/images/cal_red.png", + buttonImageOnly: true, + changeMonth: true, + changeYear: true, + firstDay: 7 }); $('#c_salescommissiondate').datepicker({ - - dateFormat: 'yy-mm', - //minDate: "-10D", - //maxDate: "+1M", - //maxDate: "+1M +10D", - //showOn: "button", - //yearRange: '-50:+5', - buttonImage: "/images/cal_red.png", - //beforeShowDay: $.datepicker.noWeekends, - buttonImageOnly: true, - //showOn: "both", - changeMonth: true, - changeYear: true, - firstDay: 7 + dateFormat: 'yy-mm', + buttonImage: "/images/cal_red.png", + buttonImageOnly: true, + changeMonth: true, + changeYear: true, + firstDay: 7 }); - //var array = ["2023-05-27","2023-05-29"]; var array = []; - $('#r_requestdate').datepicker({ - - dateFormat: 'yy-mm-dd', - minDate: "0D", - //maxDate: "+5D", - //maxDate: "+1M +10D", - //showOn: "button", - //yearRange: '-50:+5', - buttonImage: "/images/cal_red.png", - //beforeShowDay: $.datepicker.noWeekends, - buttonImageOnly: true, - //showOn: "both", - changeMonth: true, - changeYear: true, - firstDay: 7, - beforeShowDay: function(date) { - // Sat, Sunday - // if(date.getDay()==6||date.getDay()==0) return [false,"","Not available"]; - - //if(date.getDay()==0) return [false,"","Not available"]; - - // Specific Date - if($.inArray($.datepicker.formatDate('yy-mm-dd', date ), array) > -1) - { - return [false,"","Not available"]; - } - else - { - return [true,'',"Available"]; - } - } + $('#r_requestdate').datepicker({ + dateFormat: 'yy-mm-dd', + minDate: "0D", + buttonImage: "/images/cal_red.png", + buttonImageOnly: true, + changeMonth: true, + changeYear: true, + firstDay: 7, + beforeShowDay: function(date) { + // Specific Date + if($.inArray($.datepicker.formatDate('yy-mm-dd', date ), array) > -1) return [false,"","Not available"]; + else return [true,'',"Available"]; + } }); }); +$('#containerModal').on('shown.bs.modal', function () { + + const $inputs = $('#cm_install_date, #cm_pickup_date'); + + $inputs.each(function(){ + if ($(this).hasClass('hasDatepicker')) { + $(this).datepicker('destroy'); + } + }); + + $inputs.datepicker({ + dateFormat: 'yy-mm-dd', + changeMonth: true, + changeYear: true, + firstDay: 7, + beforeShow: function(input, inst) { + setTimeout(function(){ + const rect = input.getBoundingClientRect(); + + inst.dpDiv.css({ + position: 'fixed', // + top: rect.bottom + 'px', // viewport 기준 + left: rect.left + 'px', + zIndex: 2147483647 + }); + }, 0); + } + }); + +}); }); - - - - + + @@ -1046,9 +1331,9 @@ $(document).ready(function() - Area + Postal Code - + Payment @@ -1060,43 +1345,18 @@ $(document).ready(function() - - Postal Code - - - - - Main Volume - - - - - Phone - Main Container + Area - - + + - - Ext - - - - - Container Detail - - - @@ -1105,9 +1365,9 @@ $(document).ready(function() - Container Exc. Date
Init
+ Ext - + @@ -1174,6 +1434,18 @@ $(document).ready(function() + + First Pickup
Init
+ + + + + Last Pickup + + + + + Comment @@ -1183,17 +1455,256 @@ $(document).ready(function() - - +
+ +
+ +
+
+

Container Information

+
+ + + + + + + + + + + + + + + + + + + +
Main Container + + Main Volume +
Manual >
+
+ +
First Install Date + + Last Container Work Date + +
Container
Show Inactive
+
+ + + + +
+
+ +
+ +
+ +
+ + +
No containers found.
+ + + + + + +
+ +
+ +
+ + + + + + + + + Used + + + + + + + + + + + + + + + + + + O + + + + +
+ +
+ + + + + + + + + + Inactive + + + + +
+
+ + +
+ Capacity: +
+ +
+ Installed: () + + + +
+ + +
+ Picked Up: () + + + +
+ + +
+ + + + +
+ + +
+
+
+ + +
+
@@ -1257,9 +1768,9 @@ $(document).ready(function() - Install Date
N/A
+ Payable To - + @@ -1269,30 +1780,6 @@ $(document).ready(function() - First Pickup
Init
- - - - - - - Switch Form Date
Init
- - - - - Payable To - - - - - - - Contact By - - - - Payment Cycle - + - Email + Switch Form Date
Init
- + Mailing Address - + - Removal Date
Init
+ Contact By - + HST No @@ -1327,9 +1814,9 @@ $(document).ready(function() - Sludge + Email - + Identification Code @@ -1812,6 +2299,7 @@ document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () { const modalEl = document.getElementById("image-upload-modal"); + if (!modalEl) return; modalEl.addEventListener("hidden.bs.modal", function () { document.getElementById("uploadImageForm").reset(); @@ -2258,22 +2746,31 @@ $(document).ready(function(){ /** * Request type on click */ - $('#request-modal').on('show.bs.modal', function(event) { - const type = $(event.relatedTarget).data('request-type'); - $('#request-modal #request-type').val(type); - }); + document.addEventListener('DOMContentLoaded', function () { + const requestModal = document.getElementById('request-modal'); + if (requestModal) { + requestModal.addEventListener('show.bs.modal', function (event) { + const trigger = event.relatedTarget; + if (!trigger) return; + }); + } + const addNoteModal = document.getElementById('add-note-modal'); + if (addNoteModal) { + addNoteModal.addEventListener('show.bs.modal', function (event) { + const trigger = event.relatedTarget; + if (!trigger) return; + }); + } - $('#add-note-modal').on('show.bs.modal', function (event) { - const type = $(event.relatedTarget).data('add-note-type'); - $('#add-note-modal #add-note-type').val(type); - }) - - - $('#input-pickup-modal').on('show.bs.modal', function (event) { - const type = $(event.relatedTarget).data('input-pickup-type'); - $('#input-pickup-modal #input-pickup-type').val(type); - }) + const pickupModal = document.getElementById('input-pickup-modal'); + if (pickupModal) { + pickupModal.addEventListener('show.bs.modal', function (event) { + const trigger = event.relatedTarget; + if (!trigger) return; + }); + } + }); diff --git a/public_html/doc/customer_list.php b/public_html/doc/customer_list.php index 9d91e7c..dff35cb 100644 --- a/public_html/doc/customer_list.php +++ b/public_html/doc/customer_list.php @@ -107,8 +107,36 @@ $query = "SELECT * $result=$jdb->nQuery($query, "list error"); -while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { - for($i=0; $inQuery($sql_cc, "Load customer containers error"); + + while ($c = mysqli_fetch_assoc($rc)) { + $cu = (int)$c['cc_customer_uid']; + if (!isset($customerContainerMap[$cu])) $customerContainerMap[$cu] = []; + $customerContainerMap[$cu][] = $c; + } +} + +foreach ($rows as $list) { + for($i=0; $i $value ) $$key = $value; @@ -121,13 +149,18 @@ while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { //$rtd=$jdb->fQuery($qry, "fetch query error"); //if ($MCOMPANY == "0" || $MCOMPANY == "") $companyStr = "-"; //else $companyStr = $rtd[CNAME]; + $c_uid = (int)$list['c_uid']; $c_phone = preg_replace('/[^A-Za-z0-9\-]/', '', $c_phone); $c_phoneSTR = substr ($c_phone, 0, 3)."-".substr ($c_phone, 3, 3)."-".substr ($c_phone, 6, 4); $c_nameSTR = str_replace("\\", "", $c_name); $c_addressSTR = str_replace("\\", "", $c_address); - $c_containerSTR = str_replace("\\", "", $c_container); + $customerContainers = $customerContainerMap[$c_uid] ?? []; + ob_start(); + include $_SERVER['DOCUMENT_ROOT'].'/lib/customer_container_chips.php'; + $containerChipsHtml = ob_get_clean(); + // $c_containerSTR = str_replace("\\", "", $c_container); $c_statusSTR = $arrStatus[$c_status]; $c_istopSTR = ($c_is_top === 'A') ? 'A Rank' : (($c_is_top === 'G') ? 'Ghost' : ''); @@ -161,9 +194,7 @@ while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { $c_postal $c_paymenttype $c_rate - $c_maincontainer - $c_container - ".trim($m_region)." + $containerChipsHtml $m_initial $c_phoneSTR $c_statusSTR @@ -178,7 +209,7 @@ while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { if( $total_count < 1 ) { $strList = " - No Data + No Data "; } @@ -409,8 +440,6 @@ $(document).ready(function(){ Payment Rate Container - Detail - Region Driver Phone Status diff --git a/public_html/doc/customer_process.php b/public_html/doc/customer_process.php index ada786d..1b2e499 100644 --- a/public_html/doc/customer_process.php +++ b/public_html/doc/customer_process.php @@ -147,9 +147,8 @@ if ($actionStr == "CUSTOMERINFO") { $columns[] = "c_regionuid"; $columns[] = "c_region"; - - // integration flag - $columns[] = "c_is_transfer"; + + $columns[] = "c_manualvolume"; //////////// // data @@ -240,8 +239,7 @@ if ($actionStr == "CUSTOMERINFO") { $values[] = $rt_dvr['m_regionuid']; $values[] = str_replace("\\", "", trim($rt_dvr['m_region'])); - // c_is_transfer - $values[] = "N"; + $values[] = isset($_POST['isManual']) ? 'Y' : 'N'; //for ($i=0; $i < count($columns); $i++) //echo "[$columns[$i]][$values[$i]]
"; diff --git a/public_html/doc/forecast_list.php b/public_html/doc/forecast_list.php index e97ce07..8479f1a 100644 --- a/public_html/doc/forecast_list.php +++ b/public_html/doc/forecast_list.php @@ -28,52 +28,6 @@ if ($_SESSION['ss_LEVEL'] == 9) $listCnt = $rt_cfg['cfg_drivercnt']; else $listCnt = $rt_cfg['cfg_forecastcnt']; if ($listCnt == 0) $listCnt = 10000; -/* -echo"




"; - -for($i=0; $i"; - } - } - else echo "[$key][$value]
"; - print_r($_POST); -} - -// exit; -*/ - - - -/* -// sorting -if($switch) { - $switched = $func -> switchOrder($switch, $switched); - if ($switch == "c_address") { - $add_switch_query .= " ORDER BY TRIM(SUBSTRING(c_address,LOCATE(' ',c_address)+1)) $switched, - CAST(TRIM(LEFT(c_address,LOCATE(' ',c_address) - 1)) AS SIGNED) $switched,"; - } else { - $add_switch_query .= " ORDER BY $switch $switched, "; - } - $switched = $switch . "^" . $switched; - -}else if($switched) { - $switched1 = explode("^", $switched); - $add_switch_query .= " ORDER BY $switched1[0] $switched1[1], "; -} else { - $add_switch_query .= " ORDER BY "; -} - -$getSWHStr = $switched; -*/ - - // searching if($key_word) { @@ -296,7 +250,7 @@ else $typeQRY = " AND (c_schedule = 'None') "; if ($c_type_o == "O") $orderflagQRY = " AND c_orderflag = 0 "; else $orderflagQRY = " ";; -$qry_driver = "SELECT * FROM tbl_member WHERE m_uid = '$c_driveruid' "; +$qry_driver = "SELECT m_initial, m_firstname, m_lastname FROM tbl_member WHERE m_uid = '$c_driveruid' "; $rt_driver = $jdb->fQuery($qry_driver, "fetch query error"); // $query = "SELECT * FROM tbl_sampletypes @@ -304,9 +258,44 @@ $rt_driver = $jdb->fQuery($qry_driver, "fetch query error"); // where tbl_members.m_uid != '' // ORDER BY tbl_members.m_uid DESC "; +// 중복 제거 +$qryR_dupQRY = ""; +if ($c_type_r == 'R') { + $orderdate = str_replace("-", "", $orderdate); + $qryR_dupQRY = " and c_uid not in ("; + $qryR_dup = "SELECT * FROM tbl_request WHERE r_driveruid ='$c_driveruid' AND r_status = 'A' AND r_requestdate = '$orderdate'"; + $result=$jdb->nQuery($qryR_dup, "list error"); + while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { + for($i=0; $i $value ) + $$key = $value; + } + $qryR_dupQRY = $qryR_dupQRY . $r_customeruid . ','; + } + $qryR_dupQRY = $qryR_dupQRY . '0) '; // 의미없는 uid 추가 +} +$qryS_dupQRY = ""; +if ($c_type_p == 'P') { + $getWeekDay = strtoupper(date('D', strtotime($orderdate))); + $orderdate = str_replace("-", "", $orderdate); + $qryS_dupQRY = " and c_uid not in ("; + $qryS_dup = "SELECT * FROM tbl_customer + WHERE c_driveruid ='$org_driveruid' AND c_status = 'A' + AND (c_schedulebasic = '$orderdate' OR (c_schedule = '1W' AND c_scheduleday LIKE '%".$getWeekDay."%'))"; + $result=$jdb->nQuery($qryS_dup, "list error"); + while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { + for($i=0; $i $value ) + $$key = $value; + } + $qryS_dupQRY = $qryS_dupQRY . $c_uid . ','; + } + $qryS_dupQRY = $qryS_dupQRY . '0) '; // 의미없는 uid 추가 +} + //total record $query = "SELECT COUNT(c_uid) FROM tbl_customer - WHERE c_uid <> '' AND c_status = 'A' " . $orderflagQRY. $driveruidQRY . $typeQRY . $add_srchquery . $newaccountQRY . $dormantaccountQRY . $fullcycleQRY . $fullquantityQRY . $containerQRY . $areaQRY; + WHERE c_uid <> '' AND c_status = 'A' " . $orderflagQRY. $driveruidQRY . $typeQRY . $add_srchquery . $newaccountQRY . $dormantaccountQRY . $fullcycleQRY . $fullquantityQRY . $containerQRY . $areaQRY . $qryR_dupQRY . $qryS_dupQRY; //if ($qrySTR) { // $qrySTRSTR = base64_decode($qrySTR); @@ -319,7 +308,7 @@ $total_count=$jdb->rQuery($query, "record query error"); $add_query .= " LIMIT ".$listCnt; $query = "SELECT * FROM tbl_customer - WHERE c_uid <> '' AND c_status = 'A' " . $orderflagQRY. $driveruidQRY . $typeQRY . $add_srchquery . $newaccountQRY . $dormantaccountQRY . $fullcycleQRY . $fullquantityQRY . $containerQRY . $areaQRY . + WHERE c_uid <> '' AND c_status = 'A' " . $orderflagQRY. $driveruidQRY . $typeQRY . $add_srchquery . $newaccountQRY . $dormantaccountQRY . $fullcycleQRY . $fullquantityQRY . $containerQRY . $areaQRY . $qryR_dupQRY . $qryS_dupQRY . $add_query_order . $add_query; //if ($qrySTR) { @@ -335,10 +324,38 @@ $query = "SELECT * FROM tbl_customer $result=$jdb->nQuery($query, "list error"); +// container +$rows = []; +$customer_uids = []; +while ($r = mysqli_fetch_assoc($result)) { + $rows[] = $r; + $customer_uids[] = (int)$r['c_uid']; +} +$customerContainerMap = []; +$customer_uids = array_values(array_unique($customer_uids)); +if ($customer_uids) { + $in = implode(',', $customer_uids); + $sql_cc = " + SELECT cc_customer_uid, cc_type, cc_owner_type, cc_is_used, cc_has_lock, cc_has_wheel, cc_has_woodframe + FROM tbl_customer_container + WHERE cc_status = 'A' + AND cc_customer_uid IN ($in) + ORDER BY cc_customer_uid, cc_type, cc_uid + "; + + $rc = $jdb->nQuery($sql_cc, "Load customer containers error"); + + while ($c = mysqli_fetch_assoc($rc)) { + $cu = (int)$c['cc_customer_uid']; + if (!isset($customerContainerMap[$cu])) $customerContainerMap[$cu] = []; + $customerContainerMap[$cu][] = $c; + } +} + $totalrowcnt = 1; $list_number = 1; -while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { +foreach ($rows as $list) { for($i=0; $i $value ) @@ -358,7 +375,10 @@ while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { $c_nameSTR = str_replace("\\", "", $c_name); $c_addressSTR = str_replace("\\", "", $c_address); - $c_containerSTR = str_replace("\\", "", $c_container); + $customerContainers = $customerContainerMap[$c_uid] ?? []; + ob_start(); + include $_SERVER['DOCUMENT_ROOT'].'/lib/customer_container_chips.php'; + $containerChipsHtml = ob_get_clean(); $c_lastpickupdateSTR = $func -> convertFormat ($c_lastpickupdate, 3); $c_fullquantitydateSTR = $func -> convertFormat ($c_fullquantitydate, 3); $c_fullquantityActual = round(floatval($c_fullquantity + ($dDiff * $c_fullquantitydaily))); @@ -430,8 +450,7 @@ while($list=mysqli_fetch_array($result, MYSQLI_ASSOC)) { $list_numberSTR $c_nameSTR   $driverPopup $c_accountno - $c_maincontainer - $c_containerSTR + $containerChipsHtml $c_paymenttype $c_rate $c_addressSTR @@ -518,7 +537,6 @@ $('#c_fullcycle_".$c_uid."').on('change', function () { // changed $strList .= " - $c_mainvolume $c_lastpickupdateSTR $c_fullquantitySTR @@ -614,8 +632,7 @@ if ($c_type_r == 'R') { R $c_nameSTR   $driverPopup $c_accountno - $c_maincontainer - $c_containerSTR + $containerChipsHtml $c_paymenttype $c_rate $c_addressSTR @@ -623,7 +640,6 @@ if ($c_type_r == 'R') { $getLastYearQ $getThisYearQ $c_fullcycle - $c_mainvolume $c_lastpickupdateSTR $c_fullquantitySTR @@ -715,8 +731,7 @@ if ($c_type_p == 'P') { S $c_nameSTR   $driverPopup $c_accountno - $c_maincontainer - $c_containerSTR + $containerChipsHtml $c_paymenttype $c_rate $c_addressSTR @@ -724,7 +739,6 @@ if ($c_type_p == 'P') { $getLastYearQ $getThisYearQ $c_fullcycle - $c_mainvolume $c_lastpickupdateSTR $c_fullquantitySTR @@ -964,49 +978,6 @@ $(document).ready(function(){ - - -
@@ -1019,8 +990,7 @@ $(document).ready(function(){ No Restaurant Name Account - Container - Detail + Container Payment Rate Address @@ -1028,7 +998,6 @@ $(document).ready(function(){ Cycle - Main Vol. Last PU Liter @@ -1039,59 +1008,6 @@ $(document).ready(function(){ - - - @@ -1105,22 +1021,6 @@ $(document).ready(function(){

ESTIMATE : 0 L



- - -
@@ -1143,36 +1043,6 @@ $(document).ready(function(){ - - - - - + +
+ + + + +
+ +
+ + +
+ + +
+ + + + + +
+
+
Load Before Start
+
+
+
+ + +
+
+
Expected Return
+
+
+
+ + + + +
+ + +
+
+
+ + +
+
+
+ +
+
+
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public_html/doc/install_wait_list.php b/public_html/doc/install_wait_list.php new file mode 100644 index 0000000..f393fbd --- /dev/null +++ b/public_html/doc/install_wait_list.php @@ -0,0 +1,2144 @@ +checkLevelModal(5); +// ============================ +// Search filters (LEFT ONLY) +// ============================ +$where = []; +if (!empty($kw_customer)) $where[] = "(iw_customer_name LIKE '%{$kw_customer}%' OR iw_address LIKE '%{$kw_customer}%' OR iw_accountno LIKE '%{$kw_customer}%' OR iw_city LIKE '%{$kw_customer}%')"; +if (!empty($kw_work)) $where[] = "(iw_work_type = '{$kw_work}')"; +if (!empty($_GET['kw_two_person'])) $where[] = "(iw_required_person_cnt IS NOT NULL AND iw_required_person_cnt > 0)"; +if (!empty($_GET['kw_draft'])) $where[] = "(iw_status = 'DRAFT')"; +if (!empty($where)) { + $add_where = ' AND ' . implode(' AND ', $where); +} +function db_escape($jdb, $str) { + return mysqli_real_escape_string($jdb->DBConn, $str); +} + +// ============================ +// Total count +// ============================ +$sql_cnt = " + SELECT COUNT(*) + FROM tbl_install_waitlist + WHERE iw_status in ('WAITING','DRAFT') + $add_where +"; +$total_count = $jdb->rQuery($sql_cnt, "Install wait list count error"); + +// ============================ +// 1) Load waitlist +// ============================ +$sql = " + SELECT * + FROM tbl_install_waitlist + WHERE iw_status in ('WAITING','DRAFT') + $add_where + ORDER BY iw_requested_due_date ASC, iw_request_at ASC +"; +$rs = $jdb->nQuery($sql, "Load waitlist error"); + +$rows = []; +$iw_uids = []; +$customer_uids = []; + +while ($r = mysqli_fetch_assoc($rs)) { + $rows[] = $r; + $iw_uids[] = (int)$r['iw_uid']; + if (!empty($r['iw_customer_uid'])) { + $customer_uids[] = (int)$r['iw_customer_uid']; + } +} +$iw_uids = array_values(array_unique($iw_uids)); +$customer_uids = array_values(array_unique($customer_uids)); + +/* ============================ + * 2) Customer containers (cc_status='A') + * ============================ */ +$customerContainerMap = []; // [customer_uid => [rows...]] +if ($customer_uids) { + $in = implode(',', $customer_uids); + $sql_cc = " + SELECT cc_customer_uid, cc_type, cc_owner_type, cc_is_used, cc_has_lock, cc_has_wheel, cc_has_woodframe + FROM tbl_customer_container + WHERE cc_status = 'A' + AND cc_customer_uid IN ($in) + ORDER BY cc_customer_uid, cc_type, cc_uid + "; + + $rc = $jdb->nQuery($sql_cc, "Load customer containers error"); + + while ($c = mysqli_fetch_assoc($rc)) { + $cu = (int)$c['cc_customer_uid']; + if (!isset($customerContainerMap[$cu])) $customerContainerMap[$cu] = []; + $customerContainerMap[$cu][] = $c; + } +} + +/* ============================ + * 3) Task containers (waitlist) + * ============================ */ +$taskMap = []; // [iw_uid => rows] +if ($iw_uids) { + $in = implode(',', $iw_uids); + $sql_task = " + SELECT * + FROM tbl_install_waitlist_container + WHERE iwc_wait_uid IN ($in) + ORDER BY iwc_wait_uid, iwc_uid + "; + $rt = $jdb->nQuery($sql_task, "Load task containers error"); + + while ($t = mysqli_fetch_assoc($rt)) { + $taskMap[$t['iwc_wait_uid']][] = $t; + } +} + +/* ============================ + * 4) Customer images (install_order) + * ============================ */ +$imageMap = []; // [customer_uid => true] +if ($customer_uids) { + $in = implode(',', $customer_uids); + + $sql_img = " + SELECT DISTINCT i_customeruid + FROM tbl_customer_image + WHERE i_status = 'A' + AND i_type = 'install_order' + AND i_customeruid IN ($in) + "; + + $ri = $jdb->nQuery($sql_img, "Load customer images error"); + + while ($img = mysqli_fetch_assoc($ri)) { + $imageMap[(int)$img['i_customeruid']] = true; + } +} + +// ============================ +// 5) customer driver +// ============================ +$customerDriverMap = []; // [customer_uid => driver_uid] +if ($customer_uids) { + $in = implode(',', $customer_uids); + + $sql_cd = " + SELECT + c.c_uid, + d.dr_initial + FROM tbl_customer c + LEFT JOIN tbl_member m + ON c.c_regionuid = m.m_regionuid + LEFT JOIN tbl_driver d + ON m.m_defaultdriver = d.dr_uid + WHERE c.c_uid IN ($in) + AND d.dr_type = 'UCO' + "; + + $rc = $jdb->nQuery($sql_cd, "Load customer driver error"); + + while ($c = mysqli_fetch_assoc($rc)) { + $cu = (int)$c['c_uid']; + $customerDriverMap[$cu] = $c['dr_initial'] ?? ''; + } +} + +// ============================ +// 6) install drivers +// ============================ +$drivers = []; + +$sql_install = " + SELECT dr_uid, dr_initial + FROM tbl_driver + WHERE dr_status = 'A' + AND dr_type = 'INSTALL' + ORDER BY dr_initial +"; +$ris = $jdb->nQuery($sql_install, "Load install drivers error"); + +while ($r = mysqli_fetch_assoc($ris)) { + $drivers[] = $r; +} + +// ============================ +// 7) driver initials (modal) +// ============================ +$driverInitialMap = []; // [dr_uid => dr_initial] +$sql_driver = " + SELECT dr_uid, dr_initial + FROM tbl_driver + WHERE dr_status = 'A' +"; +$res = $jdb->nQuery($sql_driver, "Load driver map error"); + +while ($r = mysqli_fetch_assoc($res)) { + $driverInitialMap[(int)$r['dr_uid']] = $r['dr_initial']; +} + +// ============================ +// 전화번호 +// ============================ +function formatPhone($phone) { + $digits = preg_replace('/\D+/', '', (string)$phone); + + // 10 digits => (xxx) xxx-xxxx + if (strlen($digits) === 10) { + return sprintf( + '(%s) %s - %s', + substr($digits, 0, 3), + substr($digits, 3, 3), + substr($digits, 6, 4) + ); + } + + return $phone; +} +?> + + + + + + +
+ + +
+
+ +
+
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
No waiting items.
+ + + + + + +
+ + +
+ + +
+ + + + + Draft + + + + 1): ?> + + + x + + + + + + + + + +
+ + + Every days + + + + + · Last + + +
+ +
+ + +
+ + Due: + + + + + + + + + +
+ +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ +
+ + +
+ , , · + +
+ + + +
+ +
+ + + + +
+ Request + + (by ) + + +
+ + +
+ CS + +
+ + + +
+ Work + +
+ + + + + + + +
+ + + + + + + +
+ +
+ + Pickup + + Install + + + +
+ +
+ + '; + if ($t['iwc_has_wheel'] === 'Y') $optIcons .= ' '; + if ($t['iwc_has_woodframe'] === 'Y') $optIcons .= ' '; + ?> + + + + + + + +
+
+ +
+ +
+ +
+
+
+ + + + +
+ +
+ +
+ +
+ +
+ + + + + + + +
+ + +
+
+ + +
+ + +
+ $d): ?> + + +
+
+ + +
+ + + +
+
+ +
+ + + + +
+ + +
+ + +
+ Stops: 0   + + +
+
+ + +
+
+
+ +
+ + + + + + + + + + + + + + + + + +
#CustomerUCOAddressTypePickupInstall
+ +
+
+
+
+
+ +
+
+ +
+ + + + + + +Customer"; + +$sql_driver = " + SELECT dr_uid, dr_initial + FROM tbl_driver + WHERE dr_status = 'A' + ORDER BY dr_initial +"; + +$rmem = $jdb->nQuery($sql_driver, "Load driver list error"); + +while ($row = mysqli_fetch_assoc($rmem)) { + $driveruid = $row['dr_uid']; + $ini = htmlspecialchars(trim($row['dr_initial'])); + if ($ini === '') continue; + + $iwRequestByOptions .= ""; +} +?> + + + + + + + + + + + + + + + + diff --git a/public_html/doc/map.php b/public_html/doc/map.php index bb549de..e784c30 100644 --- a/public_html/doc/map.php +++ b/public_html/doc/map.php @@ -109,6 +109,7 @@ +
Loading...
@@ -646,7 +647,7 @@ function popup(){ - +
@@ -724,6 +725,105 @@ function popup(){ + + + + + + +

ACTUAL : L

+

- - - diff --git a/public_html/include/arrayinfo.php b/public_html/include/arrayinfo.php index 3ec2667..8bc848b 100644 --- a/public_html/include/arrayinfo.php +++ b/public_html/include/arrayinfo.php @@ -8,7 +8,7 @@ $arrPaymenttype = array ('CA' => 'Cash', 'CHQ' => 'Cheque', 'DIRECT' => 'Direct $arrBin = array ('D' => '200L Drum', '400B' => '400L Bin', '500B' => '500L Bin', '600B' => '600L Bin', '700B' => '700L Bin', '800B' => '800L Bin', '1000B' => '1000L Bin', - 'OB' => 'Owner\'s Bin' ,'OD' => 'Owner\'s Drum' , 'P' => 'Bucket', 'CB' => 'Customized Bin'); + 'P' => 'Bucket', 'CB' => 'Customized Bin'); $arrForm = array ('Not Yet' => 'Not Yet', 'Paper' => 'Paper', 'Electronic' => 'Electronic' ); $arrSchedule = array ('None' => 'None', '1W' => '1W', '2W' => '2W', '3W' => '3W', '4W' => '4W', '5W' => '5W', '6W' => '6W', 'Will Call' => 'Will Call' ); diff --git a/public_html/include/top.php b/public_html/include/top.php index 4be8127..4e20f06 100644 --- a/public_html/include/top.php +++ b/public_html/include/top.php @@ -5,10 +5,13 @@ $userName = $_SESSION['ss_NAME'] ?? ''; // ---- Level Groups ---- $isAdmin = in_array($level, [1, 5]); -$isManager = in_array($level, [1, 5, 9]); -$canExport = in_array($level, [1, 5, 6, 7]); -$canOrder = ($level != 7); +$isManager = in_array($level, [1, 5]); +$isAccounting = ($level == 6); +$isMarketing = ($level == 7); // cannot order +$isInstallDriver= ($level == 8); +$isUcoDriver = ($level == 9); $isCustomerOnly = ($level == 10); +$canExport = in_array($level, [1, 5, 6, 7]); ?> @@ -29,14 +32,38 @@ $isCustomerOnly = ($level == 10); + + + +
  • FORECAST
  • - + + + + +
  • ORDER
  • - + +
  • ORDER
  • + + +
  • MAP
  • diff --git a/public_html/js/customer_image.js b/public_html/js/customer_image.js new file mode 100644 index 0000000..58db11b --- /dev/null +++ b/public_html/js/customer_image.js @@ -0,0 +1,104 @@ +function openCustomerImages(customerUid) { + const modalEl = document.getElementById('customerImageModal'); + const modal = new bootstrap.Modal(modalEl); + + const body = document.getElementById('customerImageModalBody'); + body.innerHTML = '
    Loading images...
    '; + + modal.show(); + + const params = new URLSearchParams({ + customer_uid: customerUid, + i_type: 'install_order' + }); + + fetch('/lib/customer_image.php?' + params.toString()) + .then(res => res.text()) + .then(html => { + body.innerHTML = html; + + // HTML 주입 후, 슬라이더 초기화 (스크립트 실행 기대 X) + initCustomerImageSlider(body); + }) + .catch(err => { + console.error(err); + body.innerHTML = '
    Failed to load images.
    '; + }); +} + +function initCustomerImageSlider(scopeEl) { + const slider = scopeEl.querySelector('#customerImageSlider'); + if (!slider) return; + + let images = []; + let meta = []; + + try { + images = JSON.parse(slider.dataset.images || '[]'); + meta = JSON.parse(slider.dataset.meta || '[]'); + } catch (e) { + console.error('slider json parse error', e); + return; + } + + if (!images.length) return; + + let currentIndex = 0; + + const imgEl = scopeEl.querySelector('#sliderImage'); + const idxEl = scopeEl.querySelector('#sliderIndex'); + const metaEl = scopeEl.querySelector('#sliderMeta'); + const btnPrev = scopeEl.querySelector('#btnPrevImage'); + const btnNext = scopeEl.querySelector('#btnNextImage'); + + function formatCreated(str) { + // 기대 포맷: 20260205121137 + if (!str) return ''; + if (/^\d{14}$/.test(str)) { + return str.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/, '$1-$2-$3 $4:$5:$6'); + } + return str; // 이미 datetime 이면 그대로 + } + + function getTypeLabel(type) { + switch (type) { + case 'install': + return 'Install Image'; + case 'install_order': + return 'Container Request Image'; + default: + return type || ''; + } + } + + function update() { + imgEl.src = images[currentIndex]; + idxEl.textContent = (currentIndex + 1); + + const m = meta[currentIndex] || {}; + metaEl.innerHTML = + 'Type: ' + getTypeLabel(m.type || '') + '
    ' + + 'Note: ' + ((m.note || '').replace(/\n/g, '
    ')) + '
    ' + + 'Created: ' + formatCreated(m.created || '') + '
    ' + + 'By: ' + (m.by || ''); + } + + // 이벤트 중복 방지: 버튼을 클론으로 교체해서 기존 리스너 제거 + const prevClone = btnPrev.cloneNode(true); + const nextClone = btnNext.cloneNode(true); + btnPrev.parentNode.replaceChild(prevClone, btnPrev); + btnNext.parentNode.replaceChild(nextClone, btnNext); + + prevClone.addEventListener('click', () => { + currentIndex = (currentIndex - 1 + images.length) % images.length; + update(); + }); + + nextClone.addEventListener('click', () => { + currentIndex = (currentIndex + 1) % images.length; + update(); + }); + + // 초기 렌더 + update(); +} diff --git a/public_html/js/install_wait_modal.js b/public_html/js/install_wait_modal.js new file mode 100644 index 0000000..2556f0a --- /dev/null +++ b/public_html/js/install_wait_modal.js @@ -0,0 +1,1010 @@ +window.InstallWait = window.InstallWait || { + step: 1, + customer: null, + job: null, + pickup: [], + schedule: { date:'', note:'' }, + photos: [] +}; + +/* 슬라이드 이동 (100vw 기준) */ +function goInstallWaitStep(step) { + const wrapper = document.querySelector('.install-wait-steps-wrapper'); + if (!wrapper) return; // DOM 아직 없으면 그냥 나감 + wrapper.style.transform = `translateX(-${(step - 1) * 100}%)`; +} + +/* ============================ + STEP 1 +============================ */ +function initAddInstallWaitModal() { + + const input = document.getElementById('customerSearchInput'); + const result = document.getElementById('customerSearchResult'); + const btnNext = document.getElementById('btnStep1Next'); + + $('#addInstallWaitModal') + .one('shown.bs.modal', function () { + const input = document.getElementById('customerSearchInput'); + if (input) input.focus(); + }); + + btnNext.onclick = () => { + if (!InstallWait.customer) return; + goInstallWaitStep(2); + initInstallWaitStep2(); + }; + + let timer = null; + + input.addEventListener('input', function () { + const keyword = this.value.trim(); + clearTimeout(timer); + + if (keyword.length < 2) { + result.innerHTML = ''; + btnNext.disabled = true; + return; + } + + timer = setTimeout(() => { + api('search_customer', { keyword }, rows => { + + if (!rows || !rows.length) { + result.innerHTML = `
    No customer found
    The customer may already be in the wait list or already assigned.
    `; + return; + } + + result.innerHTML = rows.map(r => ` +
    +
    ${r.c_accountno} - ${r.c_name}
    +
    ${r.c_address}, ${r.c_city}
    +
    + `).join(''); + + bindCustomerRowEvents(); + }); + }, 300); + }); + + +} + +function bindCustomerRowEvents() { + + const btnNext = document.getElementById('btnStep1Next'); + let selected = null; + + document.querySelectorAll('.customer-row').forEach(row => { + row.onclick = function () { + + if (selected === this) { + this.classList.remove('selected'); + selected = null; + InstallWait.customer = null; + btnNext.disabled = true; + btnNext.classList.remove('active-step'); + return; + } + + document.querySelectorAll('.customer-row').forEach(r => r.classList.remove('selected')); + + this.classList.add('selected'); + selected = this; + + InstallWait.customer = { + uid: this.dataset.uid, + account: this.dataset.account, + name: this.dataset.name, + address: this.dataset.address, + city: this.dataset.city, + postal: this.dataset.postal, + phone: this.dataset.phone + }; + + btnNext.disabled = false; + btnNext.classList.add('active-step'); + }; + }); +} + +/* ============================ + STEP 2 +============================ */ +function syncCustomerFromStep2() { + const map = { + account: 'iwCustomerAccount', + name: 'iwCustomerName', + address: 'iwCustomerAddress', + city: 'iwCustomerCity', + postal: 'iwCustomerPostal', + phone: 'iwCustomerPhone' + }; + + Object.keys(map).forEach(k => { + InstallWait.customer[k] = + document.getElementById(map[k]).value.trim(); + }); +} + +function syncCustomerToInputs() { + const c = InstallWait.customer || {}; + iwCustomerAccount.value = c.account || ''; + iwCustomerName.value = c.name || ''; + iwCustomerAddress.value = c.address || ''; + iwCustomerCity.value = c.city || ''; + iwCustomerPostal.value = c.postal || ''; + iwCustomerPhone.value = c.phone || ''; +} + +function initInstallWaitStep2() { + + syncCustomerToInputs(); + + document.getElementById('btnStep2Back').onclick = () => { + goInstallWaitStep(1); + }; + + bindJobSelection(); + bindStep2Next(); +} + +function bindJobSelection() { + + const cards = document.querySelectorAll('.iw-job-card'); + const btnNext = btnStep2Next; + let selected = null; + + cards.forEach(card => { + card.onclick = () => { + + if (selected === card) { + card.classList.remove('active'); + selected = null; + InstallWait.job = null; + btnNext.disabled = true; + return; + } + + cards.forEach(c => c.classList.remove('active')); + card.classList.add('active'); + + selected = card; + InstallWait.job = { type: card.dataset.job }; + + btnNext.disabled = false; + btnNext.classList.add('active-step'); + }; + }); +} + +function bindStep2Next() { + + btnStep2Next.onclick = () => { + if (!InstallWait.job) return; + + syncCustomerFromStep2(); + const memberUid = document.getElementById('installWaitContext').dataset.memberUid || ''; + api('install_wait_create_draft', { + c_uid: InstallWait.customer.uid, + job_type: InstallWait.job.type, + account: InstallWait.customer.account, + name: InstallWait.customer.name, + address: InstallWait.customer.address, + city: InstallWait.customer.city, + postal: InstallWait.customer.postal, + phone: InstallWait.customer.phone, + member_uid: memberUid + }, res => { + + if (!res || !res.ok || !res.iw_uid) return; + + InstallWait.iw_uid = res.iw_uid; + + goInstallWaitStep(3); + initInstallWaitStep3(); + }); + }; +} + +/* ============================ + STEP 3 +============================ */ +function initInstallWaitStep3() { + + btnStep3Back.onclick = () => { + // id 없으면 그냥 back + if (!InstallWait.iw_uid) { + goInstallWaitStep(2); + return; + } + + const iw_uid = InstallWait.iw_uid; + if (!confirm('Delete this install request?')) return; + + btnStep3Back.disabled = true; + + api('install_wait_delete', { iw_uid }, (res) => { + btnStep3Back.disabled = false; + + if (!res || !res.ok) { + // alert(res?.msg || 'Delete failed'); + showPopupMessage(res?.msg || 'Delete failed', 'error', 1500); + return; + } else { + showPopupMessage('Successfully Deleted', 'success', 1500); + } + + const rowEl = btnStep3Back.closest('.install-wait-row'); + if (rowEl) rowEl.remove(); + + // 로컬 상태 초기화 + InstallWait.iw_uid = null; + InstallWait.pickup = []; + InstallWait.schedule = { date:'', note:'' }; + InstallWait.photos = []; + + // Step3 UI 잔상 제거 + const area = document.getElementById('iwStep3ContainerArea'); + if (area) area.innerHTML = ''; + + goInstallWaitStep(2); + initInstallWaitStep2(); + return; + }); + }; + + // Complete 버튼 + btnStep3Complete.onclick = () => { + + if (!InstallWait.iw_uid) { + // alert('Invalid install wait'); + showPopupMessage('Invalid install wait', 'error', 1500); + return; + } + + // 컨테이너 선택여부 + if (!validateContainersBeforeComplete()) { + return; + } + + // set parameter + const twoOn = document.getElementById('btnTwoPerson').classList.contains('iw-toggle-on'); + const recOn = document.getElementById('btnRecurring').classList.contains('iw-toggle-on'); + const payload = { + iw_uid: InstallWait.iw_uid, + request_at: document.getElementById('iwRequestDate').value || null, + due_date: document.getElementById('iwScheduleDate').value || null, + request_by: document.getElementById('iwRequestBy').value || null, + request_note: document.getElementById('iwRequestNote').value || null, + cs_note: document.getElementById('iwCsNote').value || null, + work_note: document.getElementById('iwWorkNote').value || null, + person_cnt: twoOn ? 2 : null, + person_reason: twoOn ? document.getElementById('iwTwoPersonReason').value : null, + scheduled: recOn ? 1 : 0, + scheduled_cycle: recOn ? document.getElementById('iwRecurringDays').value: 0 + }; + btnStep3Complete.disabled = true; + + // 1단계: install_wait complete + api('install_wait_complete', payload, (res) => { + + if (!res || !res.ok) { + btnStep3Complete.disabled = false; + //alert('Failed to complete install request'); + showPopupMessage('Save failed', 'error', 1500); + return; + } + + // 2단계: 사진 업로드 있을 때 + const files = document.getElementById('iwPhotos').files; + if (!files || files.length === 0) { + showPopupMessage('Successfully Saved', 'success', 1000); + finishInstallWaitComplete(); + return; + } + const memberUid = document.getElementById('installWaitContext').dataset.memberUid || ''; + + const fd = new FormData(); + fd.append('actionStr', 'ADDIMAGE'); + fd.append('i_type', 'install_order'); + fd.append('i_customeruid', InstallWait.customer.uid); + fd.append('i_memberuid', memberUid); + fd.append('i_createdby', memberUid); + fd.append('i_note', 'Before Install'); + fd.append('i_sourceuid', InstallWait.iw_uid); + [...files].forEach(file => { + fd.append('upload_file[]', file); + }); + + fetch('/lib/user_process.php', { + method: 'POST', + body: fd + }) + .then(() => { + showPopupMessage('Successfully Saved', 'success', 1000); + finishInstallWaitComplete(); + }) + .catch(() => { + //alert('Install completed, but image upload failed'); + showPopupMessage('Saved (photo failed)', 'error', 1500); + finishInstallWaitComplete(); + }); + }); + }; + + InstallWait.pickup = InstallWait.pickup || []; + + setStep3Title(); + renderStep3ActionBoxes(); + loadCurrentContainersIfNeeded(); + bindStep3Toggles(); + fillRequestBySelect('Customer'); + initInstallWaitDatePickers(); +} + +function setStep3Title() { + const jobLabel = InstallWait.job?.type || ''; + const acct = InstallWait.customer?.account ? ` of ${InstallWait.customer.account}` : ''; + const titleEl = document.getElementById('iwStep3Title'); + if (titleEl) titleEl.innerText = `Step 3 of 3 - ${jobLabel}${acct} Detail`; +} + +function renderStep3ActionBoxes() { + + const area = document.getElementById('iwStep3ContainerArea'); + area.innerHTML = ''; + + const jobType = InstallWait.job?.type; + if (!jobType) return; + + // Row 1 : CURRENT | PICKUP + if (['PICKUP','EXCHANGE','INSTALL','RELOCATE','CLEAN'].includes(jobType)) { + area.insertAdjacentHTML('beforeend', ` +
    + ${renderActionBox('CURRENT')} +
    + ${renderActionBox('PICKUP')} +
    + `); + } + + // Row 2 : SELECT | INSTALL + if (['INSTALL','EXCHANGE'].includes(jobType)) { + area.insertAdjacentHTML('beforeend', ` +
    + ${renderActionBox('SELECT')} +
    + ${renderActionBox('INSTALL')} +
    + `); + } + bindSelectInstallEvents(); +} + +function renderActionBox(action) { + + const icon = { + CURRENT: 'bi-archive', + PICKUP: 'bi-dash-circle', + SELECT: 'bi-plus-square', + INSTALL: 'bi-plus-circle', + RELOCATE: 'bi-arrow-left-right' + }[action] || 'bi-box'; + + const colorClass = + action === 'PICKUP' ? 'pickup' + : action === 'INSTALL' ? 'install' + : ''; + + let content = ''; + + // CURRENT + if (action === 'CURRENT') { + content = `
    Loading...
    `; + } + + // SELECT 전용 UI + if (action === 'SELECT') { + content = renderSelectInstallUI(); + } + + return ` +
    +
    + ${action} +
    +
    + ${content} +
    +
    + `; +} + +function countTaskChips(action) { + // action: 'PICKUP' | 'INSTALL' + const wrap = document.querySelector(`.container-chips[data-action="${action}"]`); + if (!wrap) return 0; + + // 프로젝트에서 chip 클래스가 여러 개일 수 있어서 span.chip 기준으로 카운트 + return wrap.querySelectorAll('span.chip').length; +} + +function validateContainersBeforeComplete() { + const jobType = (InstallWait.job?.type || '').toUpperCase(); + + const pickupCnt = countTaskChips('PICKUP'); + const installCnt = countTaskChips('INSTALL'); + + // EXCHANGE: 둘 다 있어야 함 + if (jobType === 'EXCHANGE') { + if (pickupCnt < 1 || installCnt < 1) { + //alert('EXCHANGE requires containers in BOTH Pickup and Install. Please select containers.'); + showPopupMessage('Please select containers.', 'error', 1500); + return false; + } + return true; + } + + // INSTALL: install에 있어야 함 + if (jobType === 'INSTALL') { + if (installCnt < 1) { + //alert('Please select at least 1 container in Install.'); + showPopupMessage('Please select containers.', 'error', 1500); + return false; + } + return true; + } + + // PICKUP: pickup에 있어야 함 + if (jobType === 'PICKUP') { + if (pickupCnt < 1) { + //alert('Please select at least 1 container in Pickup.'); + showPopupMessage('Please select containers.', 'error', 1500); + return false; + } + return true; + } + + // 다른 타입(혹시 RELOCATE/CLEAN 등 Step3에서도 쓰면 정책 정해서 추가) + return true; +} + +function loadCurrentContainersIfNeeded(onDone) { + const jobType = InstallWait.job?.type; + if (!['PICKUP','EXCHANGE','INSTALL','RELOCATE','CLEAN'].includes(jobType)) { + if (typeof onDone === 'function') onDone(); + return; + } + + const wrap = document.querySelector('.container-chips[data-action="CURRENT"]'); + if (!wrap) { + if (typeof onDone === 'function') onDone(); + return; + } + + api('get_customer_containers_active', + { c_uid: InstallWait.customer.uid }, + rows => { + if (!rows || rows.length === 0) { + wrap.innerHTML = `
    No active containers
    `; + if (typeof onDone === 'function') onDone(); + return; + } + wrap.innerHTML = rows.map(renderContainerChip).join(''); + bindCurrentContainerChipEvents(); + + if (typeof onDone === 'function') onDone(); + } + ); +} + +function renderContainerChip(c) { + + const type = (c.cc_type || '').toUpperCase(); + const icon = getContainerIconByType(type); + const isRestaurantOwned = c.cc_owner_type === 'O'; + const chipClass = isRestaurantOwned + ? 'chip chip-restaurant iw-current-chip' + : 'chip chip-company iw-current-chip'; + + const ownerBadge = isRestaurantOwned + ? 'O' + : ''; + + const flags = + (c.cc_has_lock === 'Y' ? ' ' : '') + + (c.cc_has_wheel === 'Y' ? ' ' : '') + + (c.cc_has_woodframe === 'Y' + ? ' ' + : ''); + + const used = c.cc_is_used === 'Y' ? 'Used ' : ''; + + return ` + + + ${ownerBadge} + + ${used}${c.cc_type} + ${flags} + + `; +} + +function bindCurrentContainerChipEvents() { + + const jobType = InstallWait.job?.type; + const disableClick = ['INSTALL','RELOCATE','CLEAN'].includes(jobType); + + const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]'); + const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]'); + if (!currentWrap || !pickupWrap) return; + + currentWrap.querySelectorAll('.iw-current-chip').forEach(chip => { + + chip.onclick = () => { + + if (disableClick) { + // 클릭은 막고, 시각적 피드백만 줄 수도 있음 + chip.classList.add('disabled'); + return; + } + + pickupWrap.appendChild(chip); + upsertPickupContainers(); + bindPickupContainerChipEvents(); + }; + }); +} + +function bindPickupContainerChipEvents() { + + const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]'); + const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]'); + if (!currentWrap || !pickupWrap) return; + + pickupWrap.querySelectorAll('.iw-current-chip').forEach(chip => { + + chip.onclick = () => { + + // UI 이동 + currentWrap.appendChild(chip); + + // PICKUP 기준으로 다시 업설트 + upsertPickupContainers(); + + // 이벤트 재바인딩 + bindCurrentContainerChipEvents(); + }; + }); +} + +function chipKeyFromDataset(ds){ + const norm = v => (v ?? '').toString(); + return [ + norm(ds.type).toUpperCase(), + norm(ds.capacity), + norm(ds.is_used), + norm(ds.owner_type), + norm(ds.has_lock), + norm(ds.has_wheel), + norm(ds.has_frame), + ].join('|'); +} + +function chipKeyFromIwcRow(r){ + const norm = v => (v ?? '').toString(); + return [ + norm(r.iwc_type).toUpperCase(), + norm(r.iwc_capacity), + norm(r.iwc_is_used || 'N'), + norm(r.iwc_owner_type || 'C'), + norm(r.iwc_has_lock || 'N'), + norm(r.iwc_has_wheel || 'N'), + norm(r.iwc_has_woodframe || 'N'), + ].join('|'); +} + +function applyPickupFromDBToCurrent(pickupRows) { + const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]'); + const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]'); + if (!currentWrap || !pickupWrap) return; + + // key → remaining count + const pickupCountMap = {}; + (pickupRows || []).forEach(r => { + const key = chipKeyFromIwcRow(r); + pickupCountMap[key] = (pickupCountMap[key] || 0) + 1; + }); + + // CURRENT에서 하나씩 매칭 + [...currentWrap.querySelectorAll('.iw-current-chip')].forEach(chip => { + const key = chipKeyFromDataset(chip.dataset); + if (pickupCountMap[key] > 0) { + pickupWrap.appendChild(chip); + pickupCountMap[key]--; // ⭐ 하나 소비 + } + }); + + bindCurrentContainerChipEvents(); + bindPickupContainerChipEvents(); +} + +function upsertPickupContainers() { + + const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]'); + if (!pickupWrap) return; + + const items = [...pickupWrap.querySelectorAll('.iw-current-chip')].map(el => ({ + action: 'PICKUP', + type: el.dataset.type, + capacity: el.dataset.capacity || null, + is_used: el.dataset.is_used || 'N', + owner_type: el.dataset.owner_type || 'C', + has_lock: el.dataset.has_lock || 'N', + has_wheel: el.dataset.has_wheel || 'N', + has_frame: el.dataset.has_frame || 'N' + })); + + api('install_wait_upsert_containers', { + iw_uid: InstallWait.iw_uid, + action: 'PICKUP', + items + }); +} + +function renderSelectInstallUI() { + + const binOptions = Object.entries(window.IW_BINS || {}) + .map(([key, label]) => + `` + ).join(''); + + return ` +
    +
    + + +
    + + + + + + + + + + + + + +
    + + + + +
    + + +
    + `; +} + +function bindSelectInstallEvents() { + + const usedBtn = document.getElementById('iwSelectUsed'); + const typeSel = document.getElementById('iwSelectType'); + const capInp = document.getElementById('iwSelectCapacity'); + + if (!usedBtn || !typeSel) return; + + usedBtn.onclick = () => { + usedBtn.classList.toggle('active'); + }; + + // 타입 선택 시 용량 자동 표시 + typeSel.onchange = () => { + + const val = typeSel.value; + + // CB 는 직접 입력 + if (val === 'CB') { + capInp.readOnly = false; + capInp.value = ''; + capInp.focus(); + return; + } else if (val === 'D') { + capInp.value = '200'; + return; + } + + capInp.readOnly = true; + + const m = val.match(/^(\d+)/); + capInp.value = m ? m[1] : ''; + }; + + // 악세사리 토글 + document.querySelectorAll('.iw-opt').forEach(btn => { + btn.onclick = () => btn.classList.toggle('active'); + }); + + // Add → INSTALL 이동 + document.getElementById('iwAddInstall').onclick = addInstallFromSelect; +} + +function addInstallFromSelect() { + + const type = document.getElementById('iwSelectType').value; + if (!type) return showPopupMessage('Select container type.', 'error', 1500); //alert('Select container type'); + + const capacity = document.getElementById('iwSelectCapacity').value || null; + const is_used = document.getElementById('iwSelectUsed').classList.contains('active') ? 'Y' : 'N'; + + const has_lock = document.querySelector('.iw-opt[data-opt="L"]').classList.contains('active') ? 'Y' : 'N'; + const has_wheel = document.querySelector('.iw-opt[data-opt="W"]').classList.contains('active') ? 'Y' : 'N'; + const has_frame = document.querySelector('.iw-opt[data-opt="F"]').classList.contains('active') ? 'Y' : 'N'; + + const installWrap = document.querySelector('.container-chips[data-action="INSTALL"]'); + + const icon = getContainerIconByType(type); + const chip = ` + + + + ${is_used === 'Y' ? 'Used ' : ''}${type} + ${has_lock === 'Y' ? ' ' : ''} + ${has_wheel === 'Y' ? ' ' : ''} + ${has_frame === 'Y' ? ' ' : ''} + + `; + + installWrap.insertAdjacentHTML('beforeend', chip); + bindInstallChipEvents(); + upsertInstallContainers(); +} + +function upsertInstallContainers() { + + const wrap = document.querySelector('.container-chips[data-action="INSTALL"]'); + + const items = [...wrap.querySelectorAll('.iw-install-chip')].map(el => ({ + action: 'INSTALL', + type: el.dataset.type, + capacity: el.dataset.capacity || null, + is_used: el.dataset.is_used || 'N', + has_lock: el.dataset.has_lock || 'N', + has_wheel: el.dataset.has_wheel || 'N', + has_frame: el.dataset.has_frame || 'N', + owner_type: 'C' + })); + + api('install_wait_upsert_containers', { + iw_uid: InstallWait.iw_uid, + action: 'INSTALL', + items + }); +} + +function bindInstallChipEvents() { + + const installWrap = document.querySelector('.container-chips[data-action="INSTALL"]'); + if (!installWrap) return; + + installWrap.querySelectorAll('.iw-install-chip').forEach(chip => { + + chip.onclick = () => { + chip.remove(); + upsertInstallContainers(); + }; + }); +} + +function initInstallWaitDatePickers() { + + if (!$.fn.datepicker) return; + + $('.date-picker-form').datepicker({ + dateFormat: 'yy-mm-dd', + changeMonth: true, + changeYear: true + }); +} + +function fillRequestBySelect(selectedValue = 'Customer') { + const sel = document.getElementById('iwRequestBy'); + if (!sel || !window.IW_REQUEST_BY_OPTIONS) return; + + sel.innerHTML = window.IW_REQUEST_BY_OPTIONS; + + if (!sel.querySelector(`option[value="${selectedValue}"]`)) { + selectedValue = 'Customer'; + } + + sel.value = selectedValue; +} + +// 이미지 여부 체크 +function checkCustomerHasImages(customerUid) { + if (!customerUid) return; + + const btn = document.getElementById('btnViewPhotos'); + if (!btn) return; + + const params = new URLSearchParams({ + customer_uid: customerUid, + i_type: 'install_order' + }); + + fetch('/lib/customer_image.php?' + params.toString()) + .then(res => res.text()) + .then(html => { + + const temp = document.createElement('div'); + temp.innerHTML = html; + + const hasImage = temp.querySelector('img') !== null; + + console.log('hasImage:', hasImage); + + if (hasImage) { + btn.classList.remove('d-none'); + btn.onclick = () => openCustomerImages(customerUid); + } else { + btn.classList.add('d-none'); + } + }) + .catch(() => { + btn.classList.add('d-none'); + }); +} + +function bindStep3Toggles() { + + const btnTwo = document.getElementById('btnTwoPerson'); + const reason = document.getElementById('iwTwoPersonReason'); + + btnTwo.onclick = () => { + const on = btnTwo.classList.toggle('iw-toggle-on'); + + reason.disabled = !on; + if (!on) reason.value = ''; + }; + + const btnRec = document.getElementById('btnRecurring'); + const days = document.getElementById('iwRecurringDays'); + + btnRec.onclick = () => { + const on = btnRec.classList.toggle('iw-toggle-on'); + + days.disabled = !on; + if (!on) days.value = ''; + }; +} + +function getContainerIconByType(type) { + + type = (type || '').toUpperCase(); + + if (type.includes('D')) return 'bi-database-fill'; // Drum + if (type.includes('B') && !type.includes('BUCKET')) + return 'bi-archive-fill'; // Bin + + return 'bi-box2'; // Fallback +} + +function renderDbChipCommon(r, cls) { + const type = (r.iwc_type || '').toUpperCase(); + const icon = getContainerIconByType(type); + + const isRestaurantOwned = (r.iwc_owner_type || 'C') === 'O'; + const chipClass = isRestaurantOwned + ? `chip chip-restaurant ${cls}` + : `chip chip-company ${cls}`; + + const ownerBadge = isRestaurantOwned ? 'O' : ''; + + const flags = + (r.iwc_has_lock === 'Y' ? ' ' : '') + + (r.iwc_has_wheel === 'Y' ? ' ' : '') + + ((r.iwc_has_woodframe || 'N') === 'Y' + ? ' ' + : ''); + + const used = r.iwc_is_used === 'Y' ? 'Used ' : ''; + + return ` + + ${ownerBadge} + + ${used}${r.iwc_type} + ${flags} + + `; +} + +function renderPickupChipFromDB(r) { + return renderDbChipCommon(r, 'iw-current-chip'); // PICKUP는 current-chip 클래스 재사용 +} + +function renderInstallChipFromDB(r) { + return renderDbChipCommon(r, 'iw-install-chip'); +} + + +// Complete 액션 종료 후 +function finishInstallWaitComplete() { + + const modalEl = document.getElementById('addInstallWaitModal'); + + // 모달 완전히 닫힌 뒤 리로드 + modalEl.addEventListener('hidden.bs.modal', function onHidden() { + modalEl.removeEventListener('hidden.bs.modal', onHidden); + + // 상태 정리 + InstallWait = { step: 1 }; + + // 이 시점에 리로드 + location.reload(); + }); + + // 모달 닫기 (애니메이션 시작) + const modal = bootstrap.Modal.getInstance(modalEl); + if (modal) modal.hide(); +} diff --git a/public_html/lib/add_install_wait_modal.php b/public_html/lib/add_install_wait_modal.php new file mode 100644 index 0000000..380ff98 --- /dev/null +++ b/public_html/lib/add_install_wait_modal.php @@ -0,0 +1,249 @@ +
    + +
    + + +
    + +
    + +
    +
    Step 1 of 3 - Select customer
    +
    + +
    + +
    + + +
    + +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    +
    Step 2 of 3 - Job Type
    +
    + +
    + +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + +
    + + + + + + + + + + +
    +
    + +
    + + +
    + +
    + +
    + +
    + +
    +
    +
    + +
    + + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    + +
    + +
    +
    + +
    +
    +
    + + +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + + +
    + + +
    + + + +
    + +
    + + + +
    +
    diff --git a/public_html/lib/customer_container_chips.php b/public_html/lib/customer_container_chips.php new file mode 100644 index 0000000..072a6de --- /dev/null +++ b/public_html/lib/customer_container_chips.php @@ -0,0 +1,66 @@ + $cc, + 'cnt' => 0 + ]; + } + $groupedContainers[$key]['cnt']++; +} +?> + +
    + + '; + if ($cc['cc_has_wheel'] === 'Y') $optIcons .= ' '; + if ($cc['cc_has_woodframe'] === 'Y') $optIcons .= ' '; + ?> + + + + + + + + O + + + 1): ?> + x + + + +
    diff --git a/public_html/lib/customer_image.php b/public_html/lib/customer_image.php new file mode 100644 index 0000000..19859b9 --- /dev/null +++ b/public_html/lib/customer_image.php @@ -0,0 +1,136 @@ +Invalid request.
    '; + exit; +} + +$i_type = mysqli_real_escape_string($jdb->DBConn, $i_type); + +// type +$whereType = ""; +if (strtoupper($i_type) !== 'ALL') { + $whereType = "AND i.i_type = '{$i_type}'"; +} + +// status +$statusWhere = "AND i.i_status = 'A'"; +if ($showInactive) { + $statusWhere = "AND i.i_status IN ('A','I')"; +} + +$sql = " + SELECT i.*, + m.m_firstname, + m.m_lastname, + m.m_initial + FROM tbl_customer_image i + LEFT JOIN tbl_member m ON m.m_uid = i.i_createdby + WHERE i.i_customeruid = '{$c_uid}' + {$whereType} + {$statusWhere} + ORDER BY i.i_createddate DESC +"; + +// +$rlt = mysqli_query($jdb->DBConn, $sql); +if (!$rlt) { + echo '
    Query error
    '; + exit; +} + +// 결과 없을 때 체크 +if (mysqli_num_rows($rlt) === 0) { + echo '
    No images.
    '; + exit; +} + +// +$images = []; +$meta = []; + +while ($row = mysqli_fetch_assoc($rlt)) { + + $files = explode(',', $row['i_filename']); + + foreach ($files as $f) { + $f = trim($f); + if (!$f) continue; // 빈값 + + $images[] = $row['i_filepath'] . $f; + + $meta[] = [ + 'note' => $row['i_note'] ?? '', + 'created' => $row['i_createddate'] ?? '', + 'by' => trim(($row['m_firstname'] ?? '').' '.($row['m_lastname'] ?? '')), + 'type' => $row['i_type'] ?? '' + ]; + } +} + +$total = count($images); +if ($total === 0) { + echo '
    No images.
    '; + exit; +} + +// JSON을 data-attribute로 심기 +$imagesJson = htmlspecialchars(json_encode($images), ENT_QUOTES); +$metaJson = htmlspecialchars(json_encode($meta), ENT_QUOTES); +?> + + + +
    + + Image + +
    + + + +
    + +
    + 1 / +
    + +
    + Type:
    + Note:
    + Created:
    + By: +
    + +
    diff --git a/public_html/lib/user_process.php b/public_html/lib/user_process.php index ed55ec6..7f621cc 100644 --- a/public_html/lib/user_process.php +++ b/public_html/lib/user_process.php @@ -546,15 +546,18 @@ if ($actionStr == "ADDIMAGE") { if ($fileError != 0) continue; // 확장자 - $ext = pathinfo($fileName, PATHINFO_EXTENSION); + $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + $allowedExt = ['jpg', 'jpeg', 'png', 'webp']; + if (!in_array($ext, $allowedExt)) continue; - // 새 파일명 생성 - $newFileName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_".$i.".".$ext; + // 새 파일명 생성 (jpg로 통일) + $newFileName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_".$i.".jpg"; // 저장 경로 $savePath = $upload_folder . "/" . $newFileName; - if (!move_uploaded_file($fileTmp, $savePath)) { + // 이미지 압축 + 리사이즈 저장 + if (!compressAndResizeImage($fileTmp, $savePath, $ext, 1200, 75)) { continue; } @@ -698,14 +701,20 @@ if ($actionStr == "UPDATEIMAGEFULL") { if ($_FILES['upload_file']['error'][$i] != 0) continue; - $ext = pathinfo($_FILES['upload_file']['name'][$i], PATHINFO_EXTENSION); - $newName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_".$i.".".$ext; + $ext = strtolower(pathinfo($_FILES['upload_file']['name'][$i], PATHINFO_EXTENSION)); - $savePath = $upload_folder . $newName; + $allowedExt = ['jpg', 'jpeg', 'png', 'webp']; + if (!in_array($ext, $allowedExt)) continue; - if (move_uploaded_file($_FILES['upload_file']['tmp_name'][$i], $savePath)) { - $newList[] = $newName; - } + // 파일명 jpg로 통일 + $newName = "IMG_".$i_customeruid."_".time().rand(1000,9999)."_".$i.".jpg"; + + $savePath = $upload_folder . $newName; + + // 압축 + 리사이즈 + if (compressAndResizeImage($_FILES['upload_file']['tmp_name'][$i], $savePath, $ext, 1200, 75)) { + $newList[] = $newName; + } } } @@ -1206,6 +1215,60 @@ if ($actionStr == "SIGNATURE") { exit(); } +function compressAndResizeImage($srcPath, $destPath, $ext, $maxWidth = 1200, $quality = 75) { + list($width, $height, $imageType) = getimagesize($srcPath); + + if (!$width || !$height) return false; + + switch ($imageType) { + case IMAGETYPE_JPEG: + $srcImg = imagecreatefromjpeg($srcPath); + break; + case IMAGETYPE_PNG: + $srcImg = imagecreatefrompng($srcPath); + break; + case IMAGETYPE_WEBP: + if (!function_exists('imagecreatefromwebp')) return false; + $srcImg = imagecreatefromwebp($srcPath); + break; + default: + return false; + } + + if (!$srcImg) return false; + + if ($width > $maxWidth) { + $newWidth = $maxWidth; + $newHeight = intval(($height / $width) * $newWidth); + } else { + $newWidth = $width; + $newHeight = $height; + } + + $dstImg = imagecreatetruecolor($newWidth, $newHeight); + + if ($imageType == IMAGETYPE_PNG || $imageType == IMAGETYPE_WEBP) { + imagealphablending($dstImg, false); + imagesavealpha($dstImg, true); + $transparent = imagecolorallocatealpha($dstImg, 0, 0, 0, 127); + imagefilledrectangle($dstImg, 0, 0, $newWidth, $newHeight, $transparent); + } + + imagecopyresampled( + $dstImg, $srcImg, + 0, 0, 0, 0, + $newWidth, $newHeight, + $width, $height + ); + + $result = imagejpeg($dstImg, $destPath, $quality); + + imagedestroy($srcImg); + imagedestroy($dstImg); + + return $result; +} + $msg = "Invalid data. Please try again."; $func -> modalMsg ($msg, "/index_intranet.php");