주문 상태가 배송완료인데 아직 안 받았다는 문의가 있었다. 확인해 보니 주문에 상품이 세 개고 그중 둘만 배송됐다.
Table of contents
Open Table of contents
상태가 세 단위에 있었다
grep -rn status 로 상태를 담는 자리를 찾아보니 한 곳이 아니었다.
SELECT status FROM orders WHERE order_no = ?; -- 70
SELECT status FROM order_item WHERE order_no = ?; -- 70, 70, 30
SELECT status FROM order_item_option WHERE order_no = ?; -- 70, 70, 30, 30
주문과 주문 상품과 주문 상품 옵션 세 단위에 각각 상태가 있다. 주문 상태 70이 배송완료인데 항목 하나는 30 준비중이다.
세 표의 컬럼 이름이 전부 status 라 어느 것을 말하는지가 문맥에 따라 갈렸다. 화면은 orders.status 만 보여 주고 문의한 사람은 order_item.status 를 묻고 있었으니 어느 단위인지를 먼저 정하지 않으면 이 대화가 계속 어긋난다.
상위 상태가 어떻게 정해지는지 봤다
주문 상태를 누가 어떻게 정하는지 찾았다.
// 항목 중 하나라도 배송완료면 주문도 배송완료
if ($this->hasItemWithStatus($orderNo, 70)) {
$this->db->update('orders', ['status' => 70], ['order_no' => $orderNo]);
}
하나라도다. 셋 중 하나만 배송돼도 주문은 배송완료가 된다.
전부 배송돼야 완료로 볼 수도 있다. 어느 쪽이든 규칙이므로 나쁜 것은 아닌데 그 규칙이 어디에도 안 적혀 있었고 이것이 맞는지가 먼저 정할 문제였다.
규칙을 다시 정했다
물어보니 전부 배송완료면 배송완료이고 일부만 배송완료면 부분배송완료이며 전부 준비중이면 준비중이었다. 부분 상태가 없어서 하나라도 되면 완료로 처리한 것이었다.
상태를 하나 추가했다.
30 준비중
50 배송중
70 배송완료
71 부분배송완료 (새로 추가)
$counts = $this->countItemStatus($orderNo); // [30 => 1, 70 => 2]
if (count($counts) === 1) {
$status = array_key_first($counts); // 전부 같은 상태
} else if (isset($counts[70])) {
$status = 71; // 일부 배송완료
} else {
$status = min(array_keys($counts)); // 가장 앞선 상태
}
countItemStatus 로 항목 상태를 세어서 종류가 하나면 그대로 쓰고 70이 섞여 있으면 71로 둔다.
화면과 함수 이름의 단위
상위 상태만 보여 주면 여전히 헷갈려서 항목별 상태를 같이 표시했다.
주문 20160318-0412 부분배송완료
티셔츠 (L) 배송완료 송장 1234...
바지 (32) 배송완료 송장 1234...
모자 준비중
무엇이 안 왔는지가 화면에 있으니 문의 자체가 줄었다.
코드에서도 어느 단위인지 헷갈려서 함수 이름에 단위를 넣었다.
getOrderStatus($orderNo); // 주문 단위
getItemStatuses($orderNo); // 항목별
getOptionStatuses($itemNo); // 옵션별
이전에는 getStatus() 하나였고 무엇을 돌려주는지 안에 들어가 봐야 알았다.
상태 숫자도 코드 여기저기에 흩어져 있었다.
if ($order->status == 70) { ... }
if ($item->status >= 50) { ... }
상수로 뺐다.
class OrderStatus {
const READY = 30;
const SHIPPING = 50;
const DONE = 70;
const PARTIAL = 71;
public static function label($s) { ... }
public static function isShipped($s) { return $s >= self::SHIPPING; }
}
isShipped 처럼 판단하는 것도 여기 뒀다. >= 50 이 여러 곳에 있으면 새 상태를 넣을 때 전부 확인해야 한다.
실제로 71을 넣을 때 이것이 문제였다. >= 50 이 71도 참으로 만드는데 부분배송을 배송된 것으로 볼지가 자리마다 달랐다.
검증 — 단위별 개수 세기
정리하고 나서 자료가 규칙대로인지 확인했다.
SELECT o.status AS order_status,
GROUP_CONCAT(DISTINCT i.status ORDER BY i.status) AS item_statuses,
COUNT(*) AS cnt
FROM orders o
JOIN order_item i ON i.order_no = o.order_no
GROUP BY o.order_no
HAVING COUNT(DISTINCT i.status) > 1
LIMIT 20;
항목 상태가 여러 가지인데 주문 상태가 70인 것이 214건 나왔다. 규칙을 바꾸기 전에 만들어진 것들이라 일괄로 다시 계산했다.
규칙을 바꾸면 이전 자료가 새 규칙과 안 맞는다는 것을 그때 확인한 셈이다.
정리
- 같은 이름의 상태가 여러 단위에 있으면 어느 것인지 먼저 정한다
orders·order_item·order_item_option이 전부status를 쓰고 있었다- 단위를 안 정하면 대화가 계속 어긋난다
- 상위 상태를 하위에서 어떻게 정하는지 규칙을 확인한다
- 하나라도인지 전부인지가 첫 질문이다
- 중간을 나타낼 값이 없으면 부분 상태를 만든다
- 화면에 단위별 상태를 같이 보여 주면 문의가 준다
getOrderStatus처럼 함수 이름에 단위를 넣는다- 상태 값과 판단 함수를 한 곳에 모은다
>= 50이 흩어져 있으면 새 상태를 넣을 때 전부 확인해야 한다- 규칙을 바꾸면 세어 보고 이전 자료를 다시 계산한다