goiintra/public_html/assets/api/daily.php

1487 lines
45 KiB
PHP

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