237 lines
10 KiB
PHP
237 lines
10 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Business\SystemizationTodoWorkbookImporter;
|
||
use App\Models\Business\SystemizationTodo;
|
||
use App\Models\Business\SystemizationTodoAttachment;
|
||
use Illuminate\Http\UploadedFile;
|
||
use Illuminate\Support\Facades\Storage;
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class SystemizationTodosTest extends FeatureTestCase
|
||
{
|
||
public function test_page_supports_daily_items_completion_progress_and_screenshots(): void
|
||
{
|
||
Storage::fake('uploads');
|
||
|
||
$this->loginAs()
|
||
->get(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
]))
|
||
->assertOk()
|
||
->assertSeeText('财务系统化待办')
|
||
->assertSeeText('新增待办')
|
||
->assertSeeText('从 Excel 导入现有待办')
|
||
->assertSeeText('总负责人')
|
||
->assertSeeText('责任人')
|
||
->assertSeeText('开始时间')
|
||
->assertSeeText('完成时间')
|
||
->assertSeeText('在列表底部新增一条')
|
||
->assertSee('data-sys-todo-create', false)
|
||
->assertSee('data-sys-todo-choose-files', false)
|
||
->assertSee('id="sysTodoImageViewer"', false);
|
||
|
||
$this->post(route('business.systemization-todos.store'), [
|
||
'category' => '收入',
|
||
'section' => '平台收入核对',
|
||
'title' => '核对淘系收入与平台账单差异',
|
||
'owner' => '晨曦',
|
||
'assignee' => '栎阳',
|
||
'start_date' => '2026-10-25',
|
||
'due_date' => '2026-10-28',
|
||
'status' => SystemizationTodo::STATUS_IN_PROGRESS,
|
||
'progress' => '已拉取账单,待核对订单',
|
||
'notes' => '周会跟进',
|
||
'screenshots' => [
|
||
$this->pngUpload('first.png'),
|
||
$this->pngUpload('existing.png'),
|
||
],
|
||
])->assertRedirect(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
]));
|
||
|
||
$todo = SystemizationTodo::query()->firstOrFail();
|
||
$this->assertSame('收入', $todo->category);
|
||
$this->assertSame(SystemizationTodo::STATUS_IN_PROGRESS, $todo->status);
|
||
$this->assertSame(2, $todo->attachments()->count());
|
||
$this->get(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
]))
|
||
->assertOk()
|
||
->assertSee('data-sys-todo-row-edit', false)
|
||
->assertSee('data-sys-todo-image-url', false);
|
||
$firstAttachment = $todo->attachments()->firstOrFail();
|
||
$existingAttachment = $todo->attachments()->reorder()->orderByDesc('id')->firstOrFail();
|
||
Storage::disk('uploads')->assertExists($firstAttachment->path);
|
||
Storage::disk('uploads')->assertExists($existingAttachment->path);
|
||
$this->get(route('business.systemization-todos.attachments.show', [
|
||
'todo' => $todo->id,
|
||
'attachment' => $firstAttachment->id,
|
||
]))->assertOk()->assertHeader('content-type', 'image/png');
|
||
|
||
$this->patch(route('business.systemization-todos.update', ['todo' => $todo->id]), [
|
||
'category' => '收入',
|
||
'section' => '平台收入核对',
|
||
'title' => '核对淘系收入与平台账单差异',
|
||
'owner' => '晨曦',
|
||
'assignee' => '栎阳',
|
||
'start_date' => '2026-10-25',
|
||
'due_date' => '2026-10-27',
|
||
'status' => SystemizationTodo::STATUS_COMPLETED,
|
||
'progress' => '已完成核对并留档',
|
||
'notes' => '周会确认关闭',
|
||
'remove_attachments' => [$firstAttachment->id],
|
||
'screenshots' => [$this->pngUpload('completed.png')],
|
||
])->assertRedirect();
|
||
|
||
$todo->refresh();
|
||
$this->assertSame(SystemizationTodo::STATUS_COMPLETED, $todo->status);
|
||
$this->assertSame('2026-10-27', $todo->completed_at?->format('Y-m-d'));
|
||
Storage::disk('uploads')->assertMissing($firstAttachment->path);
|
||
$this->assertTrue(
|
||
Storage::disk('uploads')->exists($existingAttachment->path),
|
||
'保留截图丢失:first=' . $firstAttachment->path . ' existing=' . $existingAttachment->path,
|
||
);
|
||
$this->assertSame(2, $todo->attachments()->count());
|
||
$this->assertTrue(
|
||
$todo->attachments()->pluck('original_name')->contains('completed.png')
|
||
);
|
||
|
||
$this->delete(route('business.systemization-todos.destroy', ['todo' => $todo->id]))
|
||
->assertRedirect();
|
||
$this->assertSoftDeleted('business_systemization_todos', ['id' => $todo->id]);
|
||
}
|
||
|
||
public function test_excel_import_preserves_hierarchy_and_does_not_overwrite_existing_rows(): void
|
||
{
|
||
$path = sys_get_temp_dir() . '/systemization-todos-' . uniqid() . '.xlsx';
|
||
$spreadsheet = new Spreadsheet();
|
||
$sheet = $spreadsheet->getActiveSheet();
|
||
$sheet->fromArray([
|
||
['类型', '内容', '待办事项(具体工作计划)', '总负责人', '责任人', '开始时间', '完成时间', '完成情况(放截图)', '备注'],
|
||
['收入', '平台收入核对', '核对淘系账单', '晨曦', '栎阳', '2026-10-20', '2026-10-21', '已完成', '首轮导入'],
|
||
[null, null, '核对拼多多账单', '晨曦', '秋香', '待定', '11月', '第一步已开始', null],
|
||
]);
|
||
(new Xlsx($spreadsheet))->save($path);
|
||
$spreadsheet->disconnectWorksheets();
|
||
|
||
try {
|
||
$this->loginAs()->post(route('business.systemization-todos.import'), [
|
||
'source' => new UploadedFile(
|
||
$path,
|
||
'10.19工作梳理.xlsx',
|
||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
null,
|
||
true,
|
||
),
|
||
])->assertRedirect();
|
||
|
||
$this->assertSame(2, SystemizationTodo::query()->count());
|
||
$first = SystemizationTodo::query()->orderBy('sort_order')->firstOrFail();
|
||
$second = SystemizationTodo::query()->orderByDesc('sort_order')->firstOrFail();
|
||
$this->assertSame('收入', $first->category);
|
||
$this->assertSame('平台收入核对', $first->section);
|
||
$this->assertSame(SystemizationTodo::STATUS_COMPLETED, $first->status);
|
||
$this->assertSame('收入', $second->category);
|
||
$this->assertSame('平台收入核对', $second->section);
|
||
$this->assertSame('待定', $second->start_note);
|
||
$this->assertSame('11月', $second->due_note);
|
||
$this->assertSame(SystemizationTodo::STATUS_IN_PROGRESS, $second->status);
|
||
|
||
$result = app(SystemizationTodoWorkbookImporter::class)
|
||
->import($path, (int) company_id(), user_id());
|
||
$this->assertSame(0, $result['created']);
|
||
$this->assertSame(2, $result['skipped']);
|
||
|
||
$this->assertSame(2, SystemizationTodo::query()->count());
|
||
$this->assertSame(0, SystemizationTodoAttachment::query()->count());
|
||
} finally {
|
||
@unlink($path);
|
||
}
|
||
}
|
||
|
||
public function test_page_filters_by_date_and_merges_adjacent_category_and_section_cells(): void
|
||
{
|
||
$this->loginAs();
|
||
|
||
$this->createTodo('收入', '1、平台收入核对', '收入事项 A', '2026-10-26', 1, '晨曦', '栎阳、秋香');
|
||
$this->createTodo('收入', '平台收入核对', '收入事项 B', '2026-10-27', 2);
|
||
$this->createTodo('成本', '3、退款核销', '成本事项', '2026-10-26', 3, '常仪', '山时');
|
||
|
||
$this->get(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
]))
|
||
->assertOk()
|
||
->assertSee('rowspan="2"', false)
|
||
->assertSee('<th scope="col">内容</th><th scope="col">序号</th><th scope="col">待办事项</th>', false)
|
||
->assertSee('title="当前列表连续序号">1</span>', false)
|
||
->assertSee('title="当前列表连续序号">2</span>', false)
|
||
->assertDontSee('1、平台收入核对')
|
||
->assertDontSee('3、退款核销')
|
||
->assertSee('收入事项 A')
|
||
->assertSee('收入事项 B');
|
||
|
||
$response = $this->get(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
'due_date' => '2026-10-26',
|
||
]));
|
||
|
||
$response->assertOk()->assertSee('收入事项 A')->assertSee('成本事项');
|
||
$content = $response->viewData('content');
|
||
$this->assertNotContains('收入事项 B', $content['items']->pluck('title')->all());
|
||
$this->assertSame([
|
||
'due_date' => '2026-10-26',
|
||
], array_intersect_key($content['todoFilters'], array_flip(['due_date'])));
|
||
|
||
$peopleResponse = $this->get(route('business.pages.show', [
|
||
'section' => 'other',
|
||
'page' => 'systemization-todos',
|
||
'owner' => '晨曦',
|
||
'assignee' => '秋香',
|
||
]))->assertOk();
|
||
$peopleContent = $peopleResponse->viewData('content');
|
||
$this->assertSame(['收入事项 A'], $peopleContent['items']->pluck('title')->all());
|
||
}
|
||
|
||
private function createTodo(
|
||
string $category,
|
||
string $section,
|
||
string $title,
|
||
string $dueDate,
|
||
int $sortOrder,
|
||
string $owner = '晨曦',
|
||
string $assignee = '栎阳',
|
||
): SystemizationTodo {
|
||
return SystemizationTodo::query()->create([
|
||
'company_id' => (int) company_id(),
|
||
'category' => $category,
|
||
'section' => $section,
|
||
'title' => $title,
|
||
'owner' => $owner,
|
||
'assignee' => $assignee,
|
||
'start_date' => '2026-10-25',
|
||
'due_date' => $dueDate,
|
||
'status' => SystemizationTodo::STATUS_PENDING,
|
||
'sort_order' => $sortOrder,
|
||
'created_by' => user_id(),
|
||
'updated_by' => user_id(),
|
||
]);
|
||
}
|
||
|
||
private function pngUpload(string $name): UploadedFile
|
||
{
|
||
return UploadedFile::fake()->createWithContent(
|
||
$name,
|
||
base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='),
|
||
);
|
||
}
|
||
}
|