fix: 상품별 배송완료 시각을 기준으로 주문 포인트 지급
개별·일괄 완료 처리에서 완료 시각을 기록하고 해당 시각부터 지급 대기 기간을 계산한다. 기존 완료 상품은 종전 기준을 유지하며 0일 설정은 완료 처리 직후 지급한다. 신규 설치 스키마와 DB 마이그레이션을 추가했다. 임시 MySQL 테이블 회귀 테스트, PHP 문법 검사와 스키마 일치 검증을 통과했다.
This commit is contained in:
@@ -88,7 +88,8 @@ function change_status($od_id, $current_status, $change_status)
|
|||||||
$sql = " update {$g5['g5_shop_order_table']} set od_status = '{$change_status}' where od_id = '{$od_id}' and od_status = '{$current_status}' ";
|
$sql = " update {$g5['g5_shop_order_table']} set od_status = '{$change_status}' where od_id = '{$od_id}' and od_status = '{$current_status}' ";
|
||||||
sql_query($sql, true);
|
sql_query($sql, true);
|
||||||
|
|
||||||
$sql = " update {$g5['g5_shop_cart_table']} set ct_status = '{$change_status}' where od_id = '{$od_id}' and ct_status = '{$current_status}' ";
|
$complete_time_sql = get_cart_complete_time_sql($change_status);
|
||||||
|
$sql = " update {$g5['g5_shop_cart_table']} set ct_complete_time = $complete_time_sql, ct_status = '{$change_status}' where od_id = '{$od_id}' and ct_status = '{$current_status}' ";
|
||||||
sql_query($sql, true);
|
sql_query($sql, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -587,8 +587,8 @@ if( function_exists('pg_setting_check') ){
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><label for="de_point_days">주문완료 포인트</label></th>
|
<th scope="row"><label for="de_point_days">주문완료 포인트</label></th>
|
||||||
<td>
|
<td>
|
||||||
<?php echo help("주문자가 회원일 경우에만 주문완료시 포인트를 지급합니다. 주문취소, 반품 등을 고려하여 포인트를 지급할 적당한 기간을 입력하십시오. (기본값은 7일)\n0일로 설정하는 경우에는 주문완료와 동시에 포인트를 지급합니다."); ?>
|
<?php echo help("주문자가 회원일 경우에만 상품별 배송완료 시각부터 설정한 기간이 지난 후 포인트를 지급합니다. 주문취소, 반품 등을 고려하여 포인트를 지급할 적당한 기간을 입력하십시오. (기본값은 7일)\n0일로 설정하면 배송완료 처리 시 지급합니다. 그 외에는 기간 경과 후 관리자 주문 상세 조회 또는 완료 처리 시 지급합니다."); ?>
|
||||||
주문 완료 <input type="text" name="de_point_days" value="<?php echo get_sanitize_input($default['de_point_days']); ?>" id="de_point_days" class="frm_input" size="2"> 일 이후에 포인트를 지급
|
배송 완료 <input type="text" name="de_point_days" value="<?php echo get_sanitize_input($default['de_point_days']); ?>" id="de_point_days" class="frm_input" size="2"> 일 이후에 포인트를 지급
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -322,8 +322,10 @@ for ($i=0; $i<$cnt; $i++)
|
|||||||
$now = G5_TIME_YMDHIS;
|
$now = G5_TIME_YMDHIS;
|
||||||
$ct_history="\n$ct_status|{$member['mb_id']}|$now|$REMOTE_ADDR";
|
$ct_history="\n$ct_status|{$member['mb_id']}|$now|$REMOTE_ADDR";
|
||||||
|
|
||||||
|
$complete_time_sql = get_cart_complete_time_sql($ct_status);
|
||||||
$sql = " update {$g5['g5_shop_cart_table']}
|
$sql = " update {$g5['g5_shop_cart_table']}
|
||||||
set ct_point_use = '$point_use',
|
set ct_complete_time = $complete_time_sql,
|
||||||
|
ct_point_use = '$point_use',
|
||||||
ct_stock_use = '$stock_use',
|
ct_stock_use = '$stock_use',
|
||||||
ct_status = '$ct_status',
|
ct_status = '$ct_status',
|
||||||
ct_history = CONCAT(ct_history,'$ct_history')
|
ct_history = CONCAT(ct_history,'$ct_history')
|
||||||
@@ -336,6 +338,11 @@ for ($i=0; $i<$cnt; $i++)
|
|||||||
$arr_it_id[] = $ct['it_id'];
|
$arr_it_id[] = $ct['it_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 완료 처리 직후 실행하여 0일 설정도 즉시 적용한다.
|
||||||
|
if ($ct_status == '완료') {
|
||||||
|
save_order_point();
|
||||||
|
}
|
||||||
|
|
||||||
// 상품 판매수량 반영
|
// 상품 판매수량 반영
|
||||||
if(is_array($arr_it_id) && !empty($arr_it_id)) {
|
if(is_array($arr_it_id) && !empty($arr_it_id)) {
|
||||||
$unq_it_id = array_unique($arr_it_id);
|
$unq_it_id = array_unique($arr_it_id);
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ for ($i=0; $i<$count_post_chk; $i++)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 일괄 완료 처리도 0일 설정이면 즉시 적립한다.
|
||||||
|
if ($od_status == '완료') {
|
||||||
|
save_order_point();
|
||||||
|
}
|
||||||
|
|
||||||
// SMS
|
// SMS
|
||||||
$sms_count = count($sms_messages);
|
$sms_count = count($sms_messages);
|
||||||
if($sms_count > 0) {
|
if($sms_count > 0) {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 배송완료 기준 주문 포인트
|
||||||
|
|
||||||
|
주문 포인트 지급 기간은 장바구니 상품·옵션 레코드(`ct_id`)별 배송완료 시각(`ct_complete_time`)부터 계산한다. 주문 접수 시각, 장바구니 생성 시각, 송장 등록 시각과는 구분한다.
|
||||||
|
|
||||||
|
## 상태 변경과 지급
|
||||||
|
|
||||||
|
- 관리자 주문 상세의 선택 상품 완료 처리와 주문 목록의 일괄 완료 처리에서 완료 시각을 기록한다.
|
||||||
|
- 이미 완료된 상품을 다시 완료로 저장하면 기존 완료 시각을 유지한다.
|
||||||
|
- 완료를 해제하면 완료 시각을 `NULL`로 초기화한다. 이후 다시 완료하면 새 시각부터 지급 기간을 계산한다.
|
||||||
|
- 회원의 포인트가 있는 상품 중 완료 상태이고 아직 적립되지 않은 상품에 지급한다.
|
||||||
|
- 설정이 0일이면 완료 처리 시 지급한다. 그 외에는 설정 기간 경과 후 관리자 주문 상세 조회 또는 완료 처리 시 지급한다. 별도 예약 작업으로 정시에 지급하는 방식은 아니다.
|
||||||
|
|
||||||
|
## 기존 설치 업그레이드
|
||||||
|
|
||||||
|
변경된 코드로 주문을 처리하기 전에 **환경설정 → DB업그레이드** 또는 CLI에서 `20260909_002_cart_complete_time` 마이그레이션을 적용한다. 선행 마이그레이션이 미적용 상태이면 먼저 적용해야 한다.
|
||||||
|
|
||||||
|
기존 레코드는 완료 시각을 `NULL`로 유지한다. 기존 배송완료·미지급 상품은 지급을 추가로 늦추지 않도록 종전의 `ct_time` 기준을 적용한다. 완료 시각을 추정하거나 업그레이드 시각으로 덮어쓰지 않는다. 기존 상품도 완료를 해제한 후 다시 완료하면 새 기준을 적용한다. 이미 지급한 포인트는 마이그레이션으로 변경하지 않는다.
|
||||||
|
|
||||||
|
신규 설치 스키마에도 같은 컬럼을 포함한다. 별도 확장 기능에서 상품 상태를 변경한다면 완료 시각 기록과 초기화도 함께 구현해야 한다. `get_cart_complete_time_sql()`의 반환식을 `ct_status` 대입보다 먼저 사용하면 기존 상태를 기준으로 완료 시각을 결정할 수 있다.
|
||||||
|
|
||||||
|
## 검증
|
||||||
|
|
||||||
|
`php tests/order_completion_point.php`는 `data/dbconfig.php`의 DB 연결로 세션 전용 임시 테이블을 만들어 상태 변경, 기존 상품 전환 및 지급 조건을 검증한다. 실제 주문·포인트 테이블은 변경하지 않으며 포인트 등록 함수는 테스트 대역을 사용한다. DB 계정에 임시 테이블 생성 권한이 필요하다.
|
||||||
@@ -58,6 +58,7 @@ CREATE TABLE IF NOT EXISTS `g5_shop_cart` (
|
|||||||
`ct_direct` tinyint(4) NOT NULL DEFAULT '0',
|
`ct_direct` tinyint(4) NOT NULL DEFAULT '0',
|
||||||
`ct_select` tinyint(4) NOT NULL DEFAULT '0',
|
`ct_select` tinyint(4) NOT NULL DEFAULT '0',
|
||||||
`ct_select_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
`ct_select_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
`ct_complete_time` datetime DEFAULT NULL,
|
||||||
PRIMARY KEY (`ct_id`),
|
PRIMARY KEY (`ct_id`),
|
||||||
KEY `od_id` (`od_id`),
|
KEY `od_id` (`od_id`),
|
||||||
KEY `it_id` (`it_id`),
|
KEY `it_id` (`it_id`),
|
||||||
|
|||||||
+19
-3
@@ -2500,17 +2500,33 @@ function exists_inicis_shop_order($oid, $pp=array(), $od_time='', $od_ip='')
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 상태 변경 UPDATE에서 ct_status를 대입하기 전에 사용한다.
|
||||||
|
// 완료 상태에서 다시 저장하면 최초 완료 시각을 유지하고, 완료 해제 시 초기화한다.
|
||||||
|
function get_cart_complete_time_sql($ct_status)
|
||||||
|
{
|
||||||
|
if ($ct_status != '완료') {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "CASE WHEN ct_status = '완료' THEN ct_complete_time ELSE '".G5_TIME_YMDHIS."' END";
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// 주문포인트를 적립한다.
|
// 주문포인트를 적립한다.
|
||||||
// 설정일이 지난 포인트 부여되지 않은 배송완료된 장바구니 자료에 포인트 부여
|
// 배송완료 시각(ct_complete_time)부터 설정일이 지난 미적립 상품에 포인트 부여
|
||||||
// 설정일이 0 이면 주문서 완료 설정 시점에서 포인트를 바로 부여합니다.
|
// 설정일이 0 이면 배송완료 처리 시 포인트를 바로 부여합니다.
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
function save_order_point($ct_status="완료")
|
function save_order_point($ct_status="완료")
|
||||||
{
|
{
|
||||||
global $g5, $default;
|
global $g5, $default;
|
||||||
|
|
||||||
|
// 완료 시각이 없는 기존 상품에만 종전의 장바구니 생성 시각 기준을 유지한다.
|
||||||
$beforedays = date("Y-m-d H:i:s", ( time() - (86400 * (int)$default['de_point_days']) ) ); // 86400초는 하루
|
$beforedays = date("Y-m-d H:i:s", ( time() - (86400 * (int)$default['de_point_days']) ) ); // 86400초는 하루
|
||||||
$sql = " select * from {$g5['g5_shop_cart_table']} where ct_status = '$ct_status' and ct_point_use = '0' and ct_time <= '$beforedays' ";
|
$sql = " select * from {$g5['g5_shop_cart_table']}
|
||||||
|
where ct_status = '$ct_status'
|
||||||
|
and ct_point_use = '0'
|
||||||
|
and ((ct_complete_time is not null and ct_complete_time <= '$beforedays')
|
||||||
|
or (ct_complete_time is null and ct_time <= '$beforedays')) ";
|
||||||
$result = sql_query($sql);
|
$result = sql_query($sql);
|
||||||
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
||||||
// 회원 ID 를 얻는다.
|
// 회원 ID 를 얻는다.
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- @description 상품별 배송완료 시각 추가 (기존 완료 상품은 종전 포인트 지급 기준 유지)
|
||||||
|
-- @if-table-exists {{g5_shop_cart_table}}
|
||||||
|
-- @if-column-missing {{g5_shop_cart_table}} ct_complete_time
|
||||||
|
ALTER TABLE `{{g5_shop_cart_table}}` ADD `ct_complete_time` datetime DEFAULT NULL AFTER `ct_select_time`;
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
// 실행: php tests/order_completion_point.php
|
||||||
|
// data/dbconfig.php의 DB 연결을 사용하며 세션 전용 TEMPORARY TABLE만 변경한다.
|
||||||
|
if (PHP_SAPI !== 'cli') exit;
|
||||||
|
define('_GNUBOARD_', true);
|
||||||
|
define('G5_TIME_YMDHIS', date('Y-m-d H:i:s'));
|
||||||
|
$root = dirname(dirname(__FILE__));
|
||||||
|
require $root.'/data/dbconfig.php';
|
||||||
|
require $root.'/lib/shop.lib.php';
|
||||||
|
require $root.'/adm/shop_admin/admin.shop.lib.php';
|
||||||
|
require $root.'/lib/migration.lib.php';
|
||||||
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||||
|
$db = new mysqli(G5_MYSQL_HOST, G5_MYSQL_USER, G5_MYSQL_PASSWORD, G5_MYSQL_DB);
|
||||||
|
$db->set_charset('utf8mb4');
|
||||||
|
$g5['g5_shop_cart_table'] = 'test_completion_cart_'.getmypid();
|
||||||
|
$g5['g5_shop_order_table'] = 'test_completion_order_'.getmypid();
|
||||||
|
$cart = $g5['g5_shop_cart_table'];
|
||||||
|
$order = $g5['g5_shop_order_table'];
|
||||||
|
$points = array();
|
||||||
|
function sql_query($sql, $error = true) { global $db; return $db->query($sql); }
|
||||||
|
function sql_fetch_array($result) { return $result->fetch_assoc(); }
|
||||||
|
function sql_fetch($sql) { return sql_fetch_array(sql_query($sql)); }
|
||||||
|
function insert_point($member, $amount, $content, $table, $id, $action) {
|
||||||
|
global $points;
|
||||||
|
$points[] = array($member, $amount, $action);
|
||||||
|
}
|
||||||
|
function expect_completion($condition, $message) {
|
||||||
|
if (!$condition) throw new Exception($message);
|
||||||
|
}
|
||||||
|
function completion_row($id) {
|
||||||
|
global $cart;
|
||||||
|
return sql_fetch("SELECT * FROM $cart WHERE ct_id = $id");
|
||||||
|
}
|
||||||
|
function complete_item($id, $status) {
|
||||||
|
// 관리자 개별 처리의 실제 UPDATE 구문을 실행한다.
|
||||||
|
global $cart, $root, $g5;
|
||||||
|
$source = file_get_contents($root.'/adm/shop_admin/orderformcartupdate.php');
|
||||||
|
$start = strpos($source, ' $complete_time_sql = get_cart_complete_time_sql($ct_status);');
|
||||||
|
$end = strpos($source, ' sql_query($sql);', $start) + strlen(' sql_query($sql);');
|
||||||
|
$ct_status = $status;
|
||||||
|
$ct_id = $id;
|
||||||
|
$ct = completion_row($id);
|
||||||
|
$od_id = $ct['od_id'];
|
||||||
|
$point_use = $ct['ct_point_use'];
|
||||||
|
$stock_use = 0;
|
||||||
|
$ct_history = '';
|
||||||
|
eval(substr($source, $start, $end - $start));
|
||||||
|
}
|
||||||
|
sql_query("CREATE TEMPORARY TABLE $order (od_id bigint PRIMARY KEY, mb_id varchar(255), od_status varchar(255))");
|
||||||
|
sql_query("CREATE TEMPORARY TABLE $cart (ct_id int PRIMARY KEY, od_id bigint, ct_status varchar(255), ct_point_use int DEFAULT 0, ct_point int DEFAULT 10, ct_qty int DEFAULT 2, ct_stock_use int DEFAULT 0, ct_history text, ct_time datetime, ct_select_time datetime)");
|
||||||
|
$old = date('Y-m-d H:i:s', time() - 86400 * 30);
|
||||||
|
$now = G5_TIME_YMDHIS;
|
||||||
|
sql_query("INSERT INTO $order VALUES (1, 'member', '배송'), (2, '', '완료')");
|
||||||
|
sql_query("INSERT INTO $cart (ct_id, od_id, ct_status, ct_time, ct_select_time, ct_history) VALUES (1, 1, '완료', '$old', '$old', ''), (2, 1, '배송', '$old', '$old', '')");
|
||||||
|
$migration = g5_migration_parse_file($root.'/migrations/20260909_002_cart_complete_time.sql');
|
||||||
|
expect_completion(!isset($migration['error']), '마이그레이션 파싱');
|
||||||
|
foreach ($migration['statements'] as $statement) {
|
||||||
|
sql_query(g5_migration_replace_placeholders($statement['sql']));
|
||||||
|
}
|
||||||
|
expect_completion(completion_row(1)['ct_complete_time'] === null, '기존 완료 시각은 NULL 유지');
|
||||||
|
complete_item(1, '완료');
|
||||||
|
expect_completion(completion_row(1)['ct_complete_time'] === null, '기존 완료 재저장 시 종전 정책 유지');
|
||||||
|
change_status(1, '배송', '완료');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === $now, '일괄 완료 시각 기록');
|
||||||
|
expect_completion(completion_row(1)['ct_complete_time'] === null, '일괄 처리에서 기존 완료 상품 유지');
|
||||||
|
complete_item(2, '배송');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === null, '개별 완료 해제');
|
||||||
|
complete_item(2, '완료');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === $now, '개별 재완료 시각 기록');
|
||||||
|
sql_query("UPDATE $cart SET ct_complete_time = '$old' WHERE ct_id = 2");
|
||||||
|
complete_item(2, '완료');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === $old, '완료 재저장 시 최초 시각 유지');
|
||||||
|
change_status(1, '완료', '배송');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === null, '일괄 완료 해제');
|
||||||
|
change_status(1, '배송', '완료');
|
||||||
|
expect_completion(completion_row(2)['ct_complete_time'] === $now, '일괄 재완료');
|
||||||
|
|
||||||
|
// 날짜 경계와 적립 루프를 실제 SQL로 검증한다.
|
||||||
|
sql_query("DELETE FROM $cart");
|
||||||
|
$cutoff = date('Y-m-d H:i:s', time() - 86400 * 7);
|
||||||
|
$future = date('Y-m-d H:i:s', time() + 86400);
|
||||||
|
$rows = array(
|
||||||
|
array(1, 1, '완료', 0, $old, $now), // 오래된 장바구니, 오늘 완료
|
||||||
|
array(2, 1, '완료', 0, $old, $cutoff), // 정확히 7일 경과
|
||||||
|
array(3, 1, '완료', 0, $old, $old),
|
||||||
|
array(4, 1, '배송', 0, $old, $old),
|
||||||
|
array(5, 1, '취소', 0, $old, $old),
|
||||||
|
array(6, 1, '완료', 1, $old, $old),
|
||||||
|
array(7, 1, '완료', 0, $old, null), // 기존 완료
|
||||||
|
array(8, 1, '완료', 0, $now, null), // 기존 기준에서도 대기
|
||||||
|
array(9, 2, '완료', 0, $old, $old), // 비회원
|
||||||
|
array(10, 1, '완료', 0, $old, $future)
|
||||||
|
);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
list($id, $od_id, $status, $used, $created, $completed) = $row;
|
||||||
|
$completed_sql = $completed === null ? 'NULL' : "'$completed'";
|
||||||
|
sql_query("INSERT INTO $cart (ct_id, od_id, ct_status, ct_point_use, ct_time, ct_complete_time) VALUES ($id, $od_id, '$status', $used, '$created', $completed_sql)");
|
||||||
|
}
|
||||||
|
$default = array('de_point_days' => 7);
|
||||||
|
save_order_point();
|
||||||
|
expect_completion($points === array(array('member', 20, '1,2'), array('member', 20, '1,3'), array('member', 20, '1,7')), '배송완료 기간, 기존 정책, 상태, 회원, 수량 검증');
|
||||||
|
save_order_point();
|
||||||
|
expect_completion(count($points) === 3, '중복 지급 방지');
|
||||||
|
$default['de_point_days'] = 0;
|
||||||
|
save_order_point();
|
||||||
|
expect_completion(count($points) === 5, '0일이면 오늘 완료 및 기존 당일 상품 지급');
|
||||||
|
expect_completion(completion_row(10)['ct_point_use'] == 0, '미래 완료 시각 제외');
|
||||||
|
foreach (array('orderformcartupdate.php', 'orderlistupdate.php') as $file) {
|
||||||
|
$source = file_get_contents($root.'/adm/shop_admin/'.$file);
|
||||||
|
expect_completion(strpos($source, 'save_order_point();') !== false, $file.' 완료 후 적립 호출');
|
||||||
|
}
|
||||||
|
echo "배송완료 시각 기록·유지·초기화, 부분 완료, 기존 상품 전환, 7일·0일 지급, 중복 지급 방지 통과\n";
|
||||||
Reference in New Issue
Block a user