Integrate feature/limeng-stock-quote-billing-rules at bca3c074 and port the snapshot purpose of feature/jushuitan-cost-price-snapshot at 4c2c422c onto current main. Keep authoritative operation history and accounting readers separate from observations; retain deployment guards and existing source data.
560 lines
24 KiB
PHP
560 lines
24 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Models\Auth\Permission;
|
||
use Modules\EcommerceFinance\Repositories\ProductCatalogMirrorRepository;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class ProductCatalogPageTest extends FeatureTestCase
|
||
{
|
||
protected function grantAdminPermission(): void
|
||
{
|
||
$this->user->attachPermission(
|
||
Permission::where('name', 'read-admin-panel')->firstOrFail()
|
||
);
|
||
}
|
||
|
||
public function test_normal_products_page_renders_mirror_rows_and_filters(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$state = [
|
||
'status' => 'succeeded',
|
||
'last_succeeded_at' => '2026-08-15 17:51:03',
|
||
'last_full_succeeded_at' => '2026-08-10 03:15:00',
|
||
'mirror_row_count' => 547217,
|
||
];
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('state')->once()->andReturn($state);
|
||
$repository->shouldReceive('syncStatus')->once()->with($state)->andReturn([
|
||
'code' => 'healthy',
|
||
'lag_minutes' => 4,
|
||
]);
|
||
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
||
$repository->shouldReceive('paginate')
|
||
->once()
|
||
->withArgs(static function (array $filters, int $page, int $limit): bool {
|
||
return $filters['i_id'] === 'STYLE-001'
|
||
&& $filters['sku_id'] === 'SKU-001'
|
||
&& $filters['keyword'] === '胡桃'
|
||
&& $filters['enabled'] === '1'
|
||
&& $filters['stock_disabled'] === '0'
|
||
&& $filters['sale_price_min'] === '800'
|
||
&& $filters['sale_price_max'] === '900'
|
||
&& $page === 1
|
||
&& $limit <= 50;
|
||
})
|
||
->andReturn([
|
||
'data' => [(object) [
|
||
'autoid' => 1001,
|
||
'i_id' => 'STYLE-001',
|
||
'sku_id' => 'SKU-001',
|
||
'name' => '岩板餐桌',
|
||
'short_name' => '餐桌',
|
||
'properties_value' => '1.4米',
|
||
'color' => '胡桃色',
|
||
'sale_price' => 899,
|
||
'cost_price' => 500,
|
||
'purchase_price' => 480,
|
||
'work_platform_price' => 475,
|
||
'work_platform_price_effect_time' => '2026-08-11 00:00:00',
|
||
'pricing_status' => '已定价',
|
||
'price_change_before_price' => 490,
|
||
'price_change_after_price' => 475,
|
||
'price_change_diff_amount' => -15,
|
||
'price_change_effect_time' => '2026-08-11 00:00:00',
|
||
'price_change_history' => [
|
||
[
|
||
'id' => 12,
|
||
'record_type' => '1',
|
||
'record_type_label' => '变价',
|
||
'before_price' => 490,
|
||
'after_price' => 475,
|
||
'diff_amount' => -15,
|
||
'effect_start_time' => '2026-08-11 00:00:00',
|
||
'effect_end_time' => null,
|
||
'created_at' => '2026-08-10 09:30:00',
|
||
],
|
||
[
|
||
'id' => 3,
|
||
'record_type' => '0',
|
||
'record_type_label' => '定价',
|
||
'before_price' => null,
|
||
'after_price' => 490,
|
||
'diff_amount' => null,
|
||
'effect_start_time' => '2026-01-01 00:00:00',
|
||
'effect_end_time' => '2026-08-10 00:00:00',
|
||
'created_at' => '2025-12-31 08:00:00',
|
||
],
|
||
],
|
||
'price_change_history_count' => 2,
|
||
'work_platform_price_history_count' => 2,
|
||
'jushuitan_cost_price_history' => [[
|
||
'source' => '聚水潭商品及库存管理',
|
||
'record_type_label' => '成本价变更',
|
||
'cost_price' => 500,
|
||
'before_price' => 480,
|
||
'after_price' => 500,
|
||
'diff_amount' => 20,
|
||
'effect_start_time' => '2026-08-12',
|
||
'effect_end_time' => null,
|
||
'remark' => '采购价:480->500',
|
||
'operator' => '简尚',
|
||
'operation' => '商品与库存管理导入',
|
||
]],
|
||
'jushuitan_cost_price_history_count' => 1,
|
||
'jushuitan_cost_price_observations' => [[
|
||
'record_type_label' => '观测到成本价变化', 'before_price' => 499, 'after_price' => 500,
|
||
'effect_start_time' => '2026-10-20 12:00:00', 'remark' => '发现时间,不是真实改价时间',
|
||
]],
|
||
'purchase_order_unit_price' => 65,
|
||
'purchase_order_finished_at' => '2026-08-20 11:03:05',
|
||
'purchase_order_id' => 827380,
|
||
'data_sources' => ['商品及库存管理(普通商品资料)', '工作平台价格', '1688代发单价'],
|
||
'market_price' => 1099,
|
||
'brand' => '北木',
|
||
'category' => '住宅家具',
|
||
'supplier_name' => '新北供应商',
|
||
'weight' => 4.32,
|
||
'l' => 49,
|
||
'w' => 18.5,
|
||
'h' => 10.5,
|
||
'volume' => 9518.25,
|
||
'unit' => '个',
|
||
'board_material' => '正常板材',
|
||
'product_type' => '成品',
|
||
'creator_id' => 19203883,
|
||
'enabled' => 1,
|
||
'stock_disabled' => 0,
|
||
'modified' => '2026-08-15 10:00:00',
|
||
]],
|
||
'total' => 1,
|
||
'purchase_price_sync_status' => [
|
||
'code' => 'ready',
|
||
'initialized' => true,
|
||
'last_succeeded_at' => '2026-08-21 12:00:00',
|
||
],
|
||
'work_platform_price_sync_status' => [
|
||
'code' => 'ready',
|
||
'initialized' => true,
|
||
'last_succeeded_at' => '2026-09-01 03:02:00',
|
||
],
|
||
'price_change_sync_status' => [
|
||
'code' => 'ready',
|
||
'initialized' => true,
|
||
'last_succeeded_at' => '2026-09-01 03:02:00',
|
||
],
|
||
'jushuitan_cost_price_history_sync_status' => [
|
||
'code' => 'failed',
|
||
'initialized' => true,
|
||
'last_succeeded_at' => '2026-09-15 00:05:00',
|
||
],
|
||
]);
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$response = $this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
'i_id' => 'STYLE-001',
|
||
'sku_id' => 'SKU-001',
|
||
'keyword' => '胡桃',
|
||
'enabled' => '1',
|
||
'stock_disabled' => '0',
|
||
'sale_price_min' => '900',
|
||
'sale_price_max' => '800',
|
||
]));
|
||
|
||
$response
|
||
->assertOk()
|
||
->assertSee('data-product-catalog-page', false)
|
||
->assertSeeText('普通商品资料')
|
||
->assertSeeText('聚水潭成本价历史最近同步失败')
|
||
->assertSeeText('观测 1 条(最近20条)')
|
||
->assertSee('data-sync-state="healthy"', false)
|
||
->assertSeeText('同步正常')
|
||
->assertSeeText('最近同步 2026-08-15 17:51:03')
|
||
->assertSeeText('数据延迟 4 分钟')
|
||
->assertSeeText('最近校准 2026-08-10 03:15:00')
|
||
->assertSeeText('STYLE-001')
|
||
->assertSeeText('SKU-001')
|
||
->assertSeeText('岩板餐桌')
|
||
->assertSeeText('899.00')
|
||
->assertSeeText('普通商品采购价')
|
||
->assertSeeText('工作平台价格')
|
||
->assertSeeText('475.00')
|
||
->assertSeeText('2026-08-11')
|
||
->assertSeeText('已定价')
|
||
->assertSeeText('工作平台变价前价格')
|
||
->assertSeeText('490.00')
|
||
->assertSeeText('工作平台变价后价格')
|
||
->assertSeeText('工作平台差额')
|
||
->assertSeeText('-15.00')
|
||
->assertSeeText('聚水潭成本价记录')
|
||
->assertSeeText('工作平台价格记录')
|
||
->assertSeeText('变价前价格')
|
||
->assertSeeText('变价后价格')
|
||
->assertSeeText('2 条')
|
||
->assertSeeText('1 条')
|
||
->assertSee('data-price-history-trigger', false)
|
||
->assertSeeText('价格记录')
|
||
->assertSeeText('当前成本价')
|
||
->assertSeeText('当前工作平台价格')
|
||
->assertSeeText('导出价格记录')
|
||
->assertSeeText('生效结束日期')
|
||
->assertSeeText('记录时间')
|
||
->assertSeeText('操作方式')
|
||
->assertSeeText('操作人')
|
||
->assertSee('"remark":"采购价:480-\\u003E500"', false)
|
||
->assertSee('简尚', false)
|
||
->assertSee('商品与库存管理导入', false)
|
||
->assertSee('"record_type_label":"变价"', false)
|
||
->assertSee('"record_type_label":"定价"', false)
|
||
->assertSeeText('数据来源')
|
||
->assertSeeText('商品及库存管理(普通商品资料)')
|
||
->assertSeeText('1688代发单价')
|
||
->assertDontSee('<th class="is-numeric">1688代发单价</th>', false)
|
||
->assertDontSeeText('65.00')
|
||
->assertSeeText('导出')
|
||
->assertDontSeeText('工作平台价格尚未完成首次同步')
|
||
->assertDontSeeText('商品价格变价记录尚未完成首次同步')
|
||
->assertSeeText('正常同步')
|
||
->assertSeeText('重量(kg)')
|
||
->assertSeeText('4.32')
|
||
->assertSeeText('长(cm)')
|
||
->assertSeeText('49.00')
|
||
->assertSeeText('体积(cm³)')
|
||
->assertSeeText('9,518.25')
|
||
->assertSeeText('正常板材')
|
||
->assertSeeText('成品')
|
||
->assertSeeText('19203883')
|
||
->assertDontSee('pic_url');
|
||
}
|
||
|
||
public function test_normal_products_page_does_not_report_zero_jushuitan_history_before_initial_sync(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$state = [
|
||
'status' => 'succeeded',
|
||
'initial_completed_at' => '2026-08-14 21:15:36',
|
||
'last_succeeded_at' => '2026-09-15 10:00:00',
|
||
'mirror_row_count' => 558035,
|
||
];
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('state')->once()->andReturn($state);
|
||
$repository->shouldReceive('syncStatus')->once()->with($state)->andReturn([
|
||
'code' => 'healthy',
|
||
'lag_minutes' => 2,
|
||
]);
|
||
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
||
$repository->shouldReceive('paginate')->once()->andReturn([
|
||
'data' => [(object) [
|
||
'autoid' => 15961,
|
||
'sku_id' => '15961G',
|
||
'name' => '测试商品',
|
||
'cost_price' => 65,
|
||
'jushuitan_cost_price_history' => [],
|
||
'jushuitan_cost_price_history_count' => 0,
|
||
]],
|
||
'total' => 1,
|
||
'jushuitan_cost_price_history_sync_status' => [
|
||
'code' => 'initial_failed',
|
||
'initialized' => false,
|
||
'last_succeeded_at' => null,
|
||
],
|
||
]);
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
'sku_id' => '15961G',
|
||
]))
|
||
->assertOk()
|
||
->assertSee('data-jushuitan-cost-price-history-sync-state="initial_failed"', false)
|
||
->assertSeeText('聚水潭成本价记录首次同步失败')
|
||
->assertSeeText('当前记录数量不代表聚水潭真实历史')
|
||
->assertSeeText('待同步')
|
||
->assertSee('title="聚水潭成本价记录尚未完成首次同步"', false);
|
||
}
|
||
|
||
public function test_normal_products_export_uses_all_filters_and_includes_data_source_column(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
||
$repository->shouldReceive('exportRows')
|
||
->once()
|
||
->withArgs(static function (array $filters): bool {
|
||
return $filters['sku_id'] === 'SKU-001'
|
||
&& $filters['supplier_name'] === '新北供应商'
|
||
&& $filters['sale_price_min'] === '800'
|
||
&& $filters['sale_price_max'] === '900';
|
||
})
|
||
->andReturn((static function (): \Generator {
|
||
yield (object) [
|
||
'autoid' => 1001,
|
||
'i_id' => 'STYLE-001',
|
||
'sku_id' => 'SKU-001',
|
||
'name' => '岩板餐桌',
|
||
'short_name' => '餐桌',
|
||
'properties_value' => '1.4米',
|
||
'color' => '胡桃色',
|
||
'brand' => '北木',
|
||
'category' => '住宅家具',
|
||
'supplier_name' => '新北供应商',
|
||
'sale_price' => 899,
|
||
'cost_price' => 500,
|
||
'purchase_price' => 480,
|
||
'work_platform_price' => 475,
|
||
'work_platform_price_effect_time' => '2026-08-11 00:00:00',
|
||
'pricing_status' => '已定价',
|
||
'price_change_before_price' => 490,
|
||
'price_change_after_price' => 475,
|
||
'price_change_diff_amount' => -15,
|
||
'price_change_effect_time' => '2026-08-11 00:00:00',
|
||
'purchase_order_unit_price' => 65,
|
||
'data_sources' => ['商品及库存管理(普通商品资料)', '工作平台价格', '1688代发单价'],
|
||
'market_price' => 1099,
|
||
'enabled' => 1,
|
||
'stock_disabled' => 0,
|
||
'modified' => '2026-08-15 10:00:00',
|
||
];
|
||
})());
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$response = $this->loginAs()->get(route('business.product-inventory.normal-products.export', [
|
||
'sku_id' => 'SKU-001',
|
||
'supplier_name' => '新北供应商',
|
||
'sale_price_min' => '900',
|
||
'sale_price_max' => '800',
|
||
]));
|
||
|
||
$response->assertOk();
|
||
$this->assertStringContainsString('attachment;', (string) $response->headers->get('content-disposition'));
|
||
$content = $response->streamedContent();
|
||
$this->assertStringStartsWith(chr(0xEF) . chr(0xBB) . chr(0xBF), $content);
|
||
$this->assertStringContainsString('数据来源', $content);
|
||
$this->assertStringContainsString('工作平台价格', $content);
|
||
$this->assertStringContainsString('475', $content);
|
||
$this->assertStringContainsString('定价状态', $content);
|
||
$this->assertStringContainsString('已定价', $content);
|
||
$this->assertStringContainsString('变价前价格', $content);
|
||
$this->assertStringContainsString('490', $content);
|
||
$this->assertStringContainsString('变价后价格', $content);
|
||
$stream = fopen('php://memory', 'r+');
|
||
fwrite($stream, $content);
|
||
rewind($stream);
|
||
$header = fgetcsv($stream);
|
||
fclose($stream);
|
||
$this->assertIsArray($header);
|
||
$this->assertNotContains('1688代发单价', $header);
|
||
$this->assertStringContainsString('商品及库存管理(普通商品资料);工作平台价格;1688代发单价', $content);
|
||
$this->assertStringContainsString('SKU-001', $content);
|
||
}
|
||
|
||
public function test_normal_products_page_marks_work_platform_prices_as_not_yet_synchronized(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$state = [
|
||
'status' => 'succeeded',
|
||
'initial_completed_at' => '2026-08-14 21:15:36',
|
||
'last_succeeded_at' => '2026-08-21 12:00:00',
|
||
'mirror_row_count' => 547217,
|
||
];
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('state')->once()->andReturn($state);
|
||
$repository->shouldReceive('syncStatus')->once()->with($state)->andReturn([
|
||
'code' => 'healthy',
|
||
'lag_minutes' => 1,
|
||
]);
|
||
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
||
$repository->shouldReceive('paginate')->once()->andReturn([
|
||
'data' => [],
|
||
'total' => 0,
|
||
'work_platform_price_sync_status' => [
|
||
'code' => 'pending',
|
||
'initialized' => false,
|
||
'last_succeeded_at' => null,
|
||
],
|
||
'price_change_sync_status' => [
|
||
'code' => 'ready',
|
||
'initialized' => true,
|
||
'last_succeeded_at' => '2026-09-01 03:02:00',
|
||
],
|
||
]);
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
]))
|
||
->assertOk()
|
||
->assertSee('data-work-platform-price-sync-state="pending"', false)
|
||
->assertSeeText('工作平台价格尚未完成首次同步')
|
||
->assertSeeText('当前仅显示普通商品采购价');
|
||
}
|
||
|
||
public function test_normal_products_page_does_not_treat_initial_sync_as_empty_data(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$state = ['status' => 'running'];
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('state')->once()->andReturn($state);
|
||
$repository->shouldReceive('syncStatus')->once()->with($state)->andReturn([
|
||
'code' => 'initializing',
|
||
'lag_minutes' => null,
|
||
]);
|
||
$repository->shouldReceive('ready')->once()->andReturnFalse();
|
||
$repository->shouldNotReceive('paginate');
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
]))
|
||
->assertOk()
|
||
->assertSeeText('普通商品资料正在执行首次全量同步')
|
||
->assertSeeText('首次同步中')
|
||
->assertDontSeeText('当前镜像中没有普通商品资料。');
|
||
}
|
||
|
||
public function test_normal_products_page_reports_an_unavailable_mirror(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnFalse();
|
||
$repository->shouldNotReceive('state');
|
||
$repository->shouldNotReceive('syncStatus');
|
||
$repository->shouldNotReceive('ready');
|
||
$repository->shouldNotReceive('paginate');
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
]))
|
||
->assertOk()
|
||
->assertSeeText('共享业务镜像尚未建立')
|
||
->assertDontSeeText('当前镜像中没有普通商品资料。');
|
||
}
|
||
|
||
public function test_normal_products_page_keeps_old_rows_visible_after_a_sync_failure(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$state = [
|
||
'status' => 'failed',
|
||
'run_mode' => 'incremental',
|
||
'initial_completed_at' => '2026-08-14 21:15:36',
|
||
'last_succeeded_at' => '2026-08-15 17:51:03',
|
||
'mirror_row_count' => 547217,
|
||
];
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnTrue();
|
||
$repository->shouldReceive('state')->once()->andReturn($state);
|
||
$repository->shouldReceive('syncStatus')->once()->with($state)->andReturn([
|
||
'code' => 'failed',
|
||
'lag_minutes' => 18,
|
||
]);
|
||
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
||
$repository->shouldReceive('paginate')->once()->andReturn([
|
||
'data' => [(object) [
|
||
'autoid' => 1001,
|
||
'i_id' => 'STYLE-OLD',
|
||
'sku_id' => 'SKU-OLD',
|
||
'name' => '最近成功同步的商品',
|
||
'short_name' => null,
|
||
'properties_value' => null,
|
||
'color' => null,
|
||
'sale_price' => 100,
|
||
'cost_price' => null,
|
||
'purchase_price' => null,
|
||
'market_price' => null,
|
||
'brand' => null,
|
||
'category' => null,
|
||
'supplier_name' => null,
|
||
'enabled' => 1,
|
||
'stock_disabled' => 0,
|
||
'modified' => '2026-08-15 17:45:00',
|
||
]],
|
||
'total' => 1,
|
||
]);
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->loginAs()->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
]))
|
||
->assertOk()
|
||
->assertSee('data-sync-state="failed"', false)
|
||
->assertSeeText('同步失败,显示最近数据')
|
||
->assertSeeText('最近成功同步的商品');
|
||
}
|
||
|
||
public function test_normal_products_export_reports_an_unavailable_mirror(): void
|
||
{
|
||
$this->grantAdminPermission();
|
||
|
||
$repository = \Mockery::mock(ProductCatalogMirrorRepository::class);
|
||
$repository->shouldReceive('available')->once()->andReturnFalse();
|
||
$repository->shouldNotReceive('ready');
|
||
$repository->shouldNotReceive('exportRows');
|
||
$this->app->instance(ProductCatalogMirrorRepository::class, $repository);
|
||
|
||
$this->withExceptionHandling()
|
||
->loginAs()
|
||
->get(route('business.product-inventory.normal-products.export'))
|
||
->assertServiceUnavailable();
|
||
}
|
||
|
||
public function test_normal_products_export_rejects_users_without_sidebar_permission(): void
|
||
{
|
||
$subject = 'user_' . $this->user->id;
|
||
|
||
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]);
|
||
setting()->set('system.sidebar_permissions', json_encode([
|
||
'configured' => [$subject],
|
||
'subjects' => [
|
||
$subject => ['company-management/basic-information'],
|
||
],
|
||
], JSON_UNESCAPED_UNICODE));
|
||
|
||
$this->withExceptionHandling()
|
||
->loginAs()
|
||
->get(route('business.product-inventory.normal-products.export'))
|
||
->assertForbidden();
|
||
}
|
||
|
||
public function test_normal_products_page_rejects_users_without_sidebar_permission(): void
|
||
{
|
||
$subject = 'user_' . $this->user->id;
|
||
|
||
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]);
|
||
setting()->set('system.sidebar_permissions', json_encode([
|
||
'configured' => [$subject],
|
||
'subjects' => [
|
||
$subject => ['company-management/basic-information'],
|
||
],
|
||
], JSON_UNESCAPED_UNICODE));
|
||
|
||
$this->withExceptionHandling()
|
||
->loginAs()
|
||
->get(route('business.pages.show', [
|
||
'section' => 'product-inventory',
|
||
'page' => 'normal-products',
|
||
]))
|
||
->assertForbidden();
|
||
}
|
||
}
|