broadcast_from * (pluck JSON 路径被 Query Builder 当作结果列名读取 stdClass 属性) * (门店即用户:通知按 store_id 归属,store_id=0 为全员广播) */ class MiniNoticeTest extends ProcurementTestCase { /** 造一家门店 */ private function makeStore(): StoreModel { return StoreModel::factory()->create(); } /** 通知列表:本店通知 + 未读广播,unread_count 正确 */ public function test_notice_list_includes_personal_and_broadcast(): void { $store = $this->makeStore(); $this->actingAsMiniStore($store); NoticeModel::create([ 'store_id' => NoticeModel::BROADCAST_STORE_ID, 'type' => NoticeModel::TYPE_SYSTEM, 'title' => '系统公告', 'content' => '全员可见', 'is_read' => NoticeModel::UNREAD, ]); NoticeModel::create([ 'store_id' => $store->id, 'type' => NoticeModel::TYPE_ORDER, 'title' => '订单状态更新', 'content' => '您的订单已接单', 'is_read' => NoticeModel::UNREAD, ]); $data = $this->getJson('/mini/notice')->assertOk()->json('data'); $this->assertSame(2, $data['total']); $this->assertSame(2, $data['unread_count']); } /** 广播标记已读:复制本店已读副本,列表排除原广播(回归:列表不再报错) */ public function test_broadcast_read_creates_personal_copy(): void { $store = $this->makeStore(); $this->actingAsMiniStore($store); $broadcast = NoticeModel::create([ 'store_id' => NoticeModel::BROADCAST_STORE_ID, 'type' => NoticeModel::TYPE_SYSTEM, 'title' => '系统公告', 'content' => '全员可见', 'data' => ['foo' => 'bar'], 'is_read' => NoticeModel::UNREAD, ]); $this->putJson("/mini/notice/{$broadcast->id}/read")->assertJsonPath('success', true); // 已读副本:本店记录、已读、broadcast_from 记来源且保留原 data $copy = NoticeModel::where('store_id', $store->id)->first(); $this->assertNotNull($copy); $this->assertSame(NoticeModel::READ, $copy->is_read); $this->assertSame($broadcast->id, $copy->data['broadcast_from']); $this->assertSame('bar', $copy->data['foo']); // 列表:原广播被排除,仅剩已读副本,未读数归零 $data = $this->getJson('/mini/notice')->assertOk()->json('data'); $this->assertSame(1, $data['total']); $this->assertSame(0, $data['unread_count']); $this->assertSame($copy->id, $data['data'][0]['id']); // 重复标记不重复复制 $this->putJson("/mini/notice/{$broadcast->id}/read")->assertJsonPath('success', true); $this->assertSame(1, NoticeModel::where('store_id', $store->id)->count()); } /** 定向通知标记已读:直接更新原记录,不产生副本 */ public function test_personal_notice_read_marks_directly(): void { $store = $this->makeStore(); $this->actingAsMiniStore($store); $notice = NoticeModel::create([ 'store_id' => $store->id, 'type' => NoticeModel::TYPE_ORDER, 'title' => '订单状态更新', 'content' => '您的订单已接单', 'is_read' => NoticeModel::UNREAD, ]); $this->putJson("/mini/notice/{$notice->id}/read")->assertJsonPath('success', true); $this->assertSame(NoticeModel::READ, $notice->refresh()->is_read); $this->assertSame(1, NoticeModel::count(), '定向通知直接更新,不产生副本'); } /** 他店通知不可标记(仅本店通知与广播可读) */ public function test_other_stores_notice_not_found(): void { $store = $this->makeStore(); $this->actingAsMiniStore($store); $other = $this->makeStore(); $notice = NoticeModel::create([ 'store_id' => $other->id, 'type' => NoticeModel::TYPE_ORDER, 'title' => '订单状态更新', 'content' => '他店通知', 'is_read' => NoticeModel::UNREAD, ]); $this->putJson("/mini/notice/{$notice->id}/read")->assertJsonPath('success', false); $this->assertSame(NoticeModel::UNREAD, $notice->refresh()->is_read); } }