174 lines
7.9 KiB
PHP
174 lines
7.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use App\Services\Auth\FinanceRoster;
|
|
use Illuminate\Support\Carbon;
|
|
use Mockery;
|
|
use Modules\EcommerceFinance\Repositories\DingAttendanceMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\DingWorklogMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\DingWorklogPenaltyCalculator;
|
|
use Tests\TestCase;
|
|
|
|
class DingWorklogPenaltyCalculatorTest extends TestCase
|
|
{
|
|
public function test_penalties_double_for_each_person_and_accumulate(): void
|
|
{
|
|
config([
|
|
'dingtalk.worklog.penalty_start_date' => '2026-10-10',
|
|
'dingtalk.worklog.penalty_excluded_user_ids' => [],
|
|
'app.timezone' => 'Asia/Shanghai',
|
|
]);
|
|
$submitted = [];
|
|
$misses = [
|
|
'jiangling' => ['2026-10-10', '2026-10-12', '2026-10-18'],
|
|
'qiuxiang' => ['2026-10-12', '2026-10-18'],
|
|
];
|
|
foreach (['jiangling' => '江陵', 'qiuxiang' => '秋香'] as $userId => $name) {
|
|
for ($day = 10; $day <= 18; $day++) {
|
|
$date = sprintf('2026-10-%02d', $day);
|
|
if (! in_array($date, $misses[$userId], true)) {
|
|
$submitted[] = [
|
|
'creator_userid' => $userId,
|
|
'creator_name' => $name,
|
|
'statistics_date' => $date,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$repository = Mockery::mock(DingWorklogMirrorRepository::class);
|
|
$repository->shouldReceive('earliestStatisticsDate')->once()->andReturn('2026-10-01');
|
|
$repository->shouldReceive('submittedDatesByUser')
|
|
->once()->with('2026-10-10', '2026-10-18')->andReturn($submitted);
|
|
$roster = Mockery::mock(FinanceRoster::class);
|
|
$roster->shouldReceive('cachedFinanceMembers')->once()->andReturn([
|
|
['userid' => 'jiangling', 'name' => '江陵'],
|
|
['userid' => 'qiuxiang', 'name' => '秋香'],
|
|
]);
|
|
$attendance = Mockery::mock(DingAttendanceMirrorRepository::class);
|
|
$attendance->shouldReceive('ready')->once()->andReturnTrue();
|
|
$attendance->shouldReceive('exemptDatesForUsers')->once()->andReturn([]);
|
|
|
|
$result = (new DingWorklogPenaltyCalculator($repository, $roster, $attendance))->summary(
|
|
'2026-10-10',
|
|
'2026-10-18',
|
|
Carbon::parse('2026-10-20 10:00:00', 'Asia/Shanghai'),
|
|
);
|
|
|
|
$this->assertSame('2026-10-10', $result['start_date']);
|
|
$this->assertSame('2026-10-18', $result['end_date']);
|
|
$this->assertSame('江陵', $result['members'][0]['name']);
|
|
$this->assertSame(['2026-10-10', '2026-10-12', '2026-10-18'], $result['members'][0]['missed_dates']);
|
|
$this->assertSame(3, $result['members'][0]['missed_count']);
|
|
$this->assertSame('10 + 20 + 40', $result['members'][0]['penalty_formula']);
|
|
$this->assertSame('70', $result['members'][0]['penalty_points']);
|
|
$this->assertSame('秋香', $result['members'][1]['name']);
|
|
$this->assertSame('10 + 20', $result['members'][1]['penalty_formula']);
|
|
$this->assertSame('30', $result['members'][1]['penalty_points']);
|
|
$this->assertSame('100', $result['total_points']);
|
|
$this->assertSame('100', $result['total_points_formatted']);
|
|
}
|
|
|
|
public function test_dates_before_november_fourth_are_not_charged(): void
|
|
{
|
|
config([
|
|
'dingtalk.worklog.penalty_start_date' => '2026-11-04',
|
|
'dingtalk.worklog.penalty_excluded_user_ids' => [],
|
|
'app.timezone' => 'Asia/Shanghai',
|
|
]);
|
|
$repository = Mockery::mock(DingWorklogMirrorRepository::class);
|
|
$repository->shouldReceive('earliestStatisticsDate')->once()->andReturn('2026-10-21');
|
|
$repository->shouldReceive('submittedDatesByUser')
|
|
->once()->with('2026-11-04', '2026-11-04')->andReturn([]);
|
|
$roster = Mockery::mock(FinanceRoster::class);
|
|
$roster->shouldReceive('cachedFinanceMembers')->once()->andReturn([
|
|
['userid' => 'jiangling', 'name' => '江陵'],
|
|
]);
|
|
$attendance = Mockery::mock(DingAttendanceMirrorRepository::class);
|
|
$attendance->shouldReceive('ready')->once()->andReturnTrue();
|
|
$attendance->shouldReceive('exemptDatesForUsers')->once()->andReturn([]);
|
|
|
|
$result = (new DingWorklogPenaltyCalculator($repository, $roster, $attendance))->summary(
|
|
'2026-10-22',
|
|
'2026-11-04',
|
|
Carbon::parse('2026-11-05 13:00:00', 'Asia/Shanghai'),
|
|
);
|
|
|
|
$this->assertSame('2026-11-04', $result['start_date']);
|
|
$this->assertSame('2026-11-04', $result['end_date']);
|
|
$this->assertSame(['2026-11-04'], $result['members'][0]['missed_dates']);
|
|
$this->assertSame('10', $result['total_points']);
|
|
}
|
|
|
|
public function test_excluded_members_do_not_receive_penalty_points(): void
|
|
{
|
|
config([
|
|
'dingtalk.worklog.penalty_start_date' => '2026-11-04',
|
|
'dingtalk.worklog.penalty_excluded_user_ids' => [
|
|
'02591267217229',
|
|
'17097754648162977',
|
|
],
|
|
'app.timezone' => 'Asia/Shanghai',
|
|
]);
|
|
$repository = Mockery::mock(DingWorklogMirrorRepository::class);
|
|
$repository->shouldReceive('earliestStatisticsDate')->once()->andReturn('2026-11-04');
|
|
$repository->shouldReceive('submittedDatesByUser')
|
|
->once()->with('2026-11-04', '2026-11-04')->andReturn([]);
|
|
$roster = Mockery::mock(FinanceRoster::class);
|
|
$roster->shouldReceive('cachedFinanceMembers')->once()->andReturn([
|
|
['userid' => '02591267217229', 'name' => '江陵'],
|
|
['userid' => '17097754648162977', 'name' => '周生辰'],
|
|
['userid' => 'qiuxiang', 'name' => '秋香'],
|
|
]);
|
|
$attendance = Mockery::mock(DingAttendanceMirrorRepository::class);
|
|
$attendance->shouldReceive('ready')->once()->andReturnTrue();
|
|
$attendance->shouldReceive('exemptDatesForUsers')->once()->andReturn([]);
|
|
|
|
$result = (new DingWorklogPenaltyCalculator($repository, $roster, $attendance))->summary(
|
|
'2026-11-04',
|
|
'2026-11-04',
|
|
Carbon::parse('2026-11-05 13:00:00', 'Asia/Shanghai'),
|
|
);
|
|
|
|
$this->assertSame(['秋香'], array_column($result['members'], 'name'));
|
|
$this->assertSame('10', $result['total_points']);
|
|
}
|
|
|
|
public function test_approved_leave_or_rest_days_are_not_charged(): void
|
|
{
|
|
config([
|
|
'dingtalk.worklog.penalty_start_date' => '2026-11-04',
|
|
'dingtalk.worklog.penalty_excluded_user_ids' => [],
|
|
'app.timezone' => 'Asia/Shanghai',
|
|
]);
|
|
$repository = Mockery::mock(DingWorklogMirrorRepository::class);
|
|
$repository->shouldReceive('earliestStatisticsDate')->once()->andReturn('2026-11-04');
|
|
$repository->shouldReceive('submittedDatesByUser')
|
|
->once()->with('2026-11-04', '2026-11-04')->andReturn([]);
|
|
$roster = Mockery::mock(FinanceRoster::class);
|
|
$roster->shouldReceive('cachedFinanceMembers')->once()->andReturn([
|
|
['userid' => 'changyi', 'name' => '常仪'],
|
|
['userid' => 'yaoyao', 'name' => '杳杳'],
|
|
]);
|
|
$attendance = Mockery::mock(DingAttendanceMirrorRepository::class);
|
|
$attendance->shouldReceive('ready')->once()->andReturnTrue();
|
|
$attendance->shouldReceive('exemptDatesForUsers')
|
|
->once()
|
|
->with(['changyi', 'yaoyao'], '2026-11-04', '2026-11-04')
|
|
->andReturn([
|
|
'changyi' => ['2026-11-04' => true],
|
|
'yaoyao' => ['2026-11-04' => true],
|
|
]);
|
|
|
|
$result = (new DingWorklogPenaltyCalculator($repository, $roster, $attendance))->summary(
|
|
'2026-11-04',
|
|
'2026-11-04',
|
|
Carbon::parse('2026-11-05 13:00:00', 'Asia/Shanghai'),
|
|
);
|
|
|
|
$this->assertSame([], $result['members']);
|
|
$this->assertSame('0', $result['total_points']);
|
|
}
|
|
}
|