Files
HeuJung 60cccbf62d feat(ecommerce): 마일리지 적립 절사 기준 신설 및 주문 시점 스냅샷
적립 포인트 절사가 코드에 1점 버림으로 고정돼 있어 운영자가 조정할 수 없었다.
언어/통화 설정의 환율 절사를 따라가는 방안을 먼저 검토했으나, 그쪽은 외화 환산
분기에서만 적용되어 기본 통화 상점에는 따라갈 값 자체가 없다. 적립은 기본 통화
원장에 정수로 확정 기록되는 값이라 성격이 다르므로 마일리지 통화별 규칙에 신설했다.

- earn_rounding_unit(1/10/100) + earn_rounding_method(floor/round/ceil)
- 기본값은 종전 하드코딩과 정확히 같은 금액을 산출 — 기존 쇼핑몰 적립액 무변화
- 주문 계산·옵션 분할·부분취소 안분 전 경로에 동일 기준 적용
- 주문 시점 값을 스냅샷으로 보관 — 이후 기준을 바꿔도 과거 주문이 소급 변경되지 않는다
- 설정 UI·검증·다국어(ko/en/ja)·API 문서 동반, 기존 사이트는 업그레이드 스텝이 명시 기입

함께 처리: 마일리지 내역 목록 테스트가 첫 컬럼 field 를 `no` 로 단언해 실패하던 것을
현재 레이아웃 값(`number`)에 맞춰 정정했다. 이 브랜치와 무관한 stale 케이스다.
2026-08-04 09:56:00 +09:00

118 lines
4.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Support;
use Modules\Sirsoft\Ecommerce\Support\MileageRounding;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
use PHPUnit\Framework\Attributes\Test;
/**
* 적립 마일리지 절사 규칙 SSoT 단위 테스트
*
* 이 클래스가 규칙 해석과 적용을 단독으로 책임진다. 계산·안분·스냅샷 세 지점이 모두
* 여기를 거치므로, 폴백이 종전 하드코딩(`(int) floor`)과 어긋나면 기존 사이트의 적립액이
* 아무도 설정을 바꾸지 않았는데 달라진다.
*/
class MileageRoundingTest extends ModuleTestCase
{
#[Test]
public function 규칙이_없으면_종전_동작인_1점_버림으로_폴백한다(): void
{
$this->assertSame(['unit' => '1', 'method' => 'floor'], MileageRounding::normalize(null));
$this->assertSame(['unit' => '1', 'method' => 'floor'], MileageRounding::normalize([]));
// 폴백 등가성: 절사 전 값이 무엇이든 (int) floor 와 같은 결과여야 한다
foreach ([0, 0.4, 1.0, 781.2, 781.6, 1234.999] as $raw) {
$this->assertSame(
(int) floor($raw),
MileageRounding::apply($raw),
"폴백이 종전 (int) floor 동작과 어긋난다 (입력: {$raw})"
);
}
}
#[Test]
public function 허용되지_않은_값은_기본값으로_접는다(): void
{
$folded = MileageRounding::normalize([
MileageRounding::UNIT_KEY => '7', // 허용 목록 밖
MileageRounding::METHOD_KEY => 'trunc', // 허용 목록 밖
]);
// 해석 불가한 값에서 임의 방식을 고르면 지급액이 조용히 달라진다 — 기본값으로 접는다.
$this->assertSame(['unit' => '1', 'method' => 'floor'], $folded);
}
/**
* @return array<string, array{0: string, 1: string, 2: float, 3: int}>
*/
public static function 절사_조합(): array
{
return [
'1점 버림' => ['1', 'floor', 781.6, 781],
'1점 반올림' => ['1', 'round', 781.6, 782],
'1점 올림' => ['1', 'ceil', 781.2, 782],
'10점 버림' => ['10', 'floor', 781.6, 780],
'10점 반올림' => ['10', 'round', 785.0, 790],
'10점 올림' => ['10', 'ceil', 781.0, 790],
'100점 버림' => ['100', 'floor', 781.6, 700],
'100점 반올림' => ['100', 'round', 781.6, 800],
'100점 올림' => ['100', 'ceil', 701.0, 800],
'0점은 어떤 조합에서도 0' => ['100', 'ceil', 0.0, 0],
];
}
#[Test]
#[\PHPUnit\Framework\Attributes\DataProvider('절사_조합')]
public function 단위와_방식_조합이_그대로_적용된다(string $unit, string $method, float $raw, int $expected): void
{
$result = MileageRounding::applyRule($raw, [
MileageRounding::UNIT_KEY => $unit,
MileageRounding::METHOD_KEY => $method,
]);
$this->assertSame($expected, $result);
}
#[Test]
public function 부동소수_오차가_한_단위를_깎지_않는다(): void
{
// 7810 × 0.1 은 780.9999999999999 로 계산돼 버림에서 780 이 된다.
// 절사 기준 자체와 무관한 계산 잔여물이 지급액을 1점 깎으면 안 된다.
$this->assertSame(781, MileageRounding::apply(7810 * 0.1));
}
#[Test]
public function 주문_스냅샷에서_절사_기준을_읽는다(): void
{
$snapshot = [
'usable' => true,
'currency' => 'KRW',
'rule' => [
'use_unit' => 10,
MileageRounding::UNIT_KEY => '100',
MileageRounding::METHOD_KEY => 'ceil',
],
];
$this->assertSame(
['unit' => '100', 'method' => 'ceil'],
MileageRounding::fromOrderSnapshot($snapshot)
);
}
#[Test]
public function 이_기능_이전_주문의_스냅샷은_종전_동작으로_해석된다(): void
{
// 절사 키가 없던 시절의 스냅샷 — 그때의 동작이 곧 기본값이므로 재계산 결과가 보존된다.
$legacy = [
'usable' => true,
'currency' => 'KRW',
'rule' => ['point_value' => 1.0, 'use_unit' => 10, 'max_use_type' => 'percent'],
];
$this->assertSame(['unit' => '1', 'method' => 'floor'], MileageRounding::fromOrderSnapshot($legacy));
$this->assertSame(['unit' => '1', 'method' => 'floor'], MileageRounding::fromOrderSnapshot(null));
}
}